001package org.hl7.fhir.r4.utils;
002
003// remember group resolution
004// trace - account for which wasn't transformed in the source
005
006import java.io.IOException;
007import java.util.ArrayList;
008import java.util.EnumSet;
009import java.util.HashMap;
010import java.util.HashSet;
011import java.util.List;
012import java.util.Map;
013import java.util.Set;
014import java.util.UUID;
015
016import org.hl7.fhir.r4.conformance.ProfileUtilities;
017import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider;
018import org.hl7.fhir.r4.context.IWorkerContext;
019import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult;
020import org.hl7.fhir.r4.elementmodel.Element;
021import org.hl7.fhir.r4.elementmodel.Property;
022import org.hl7.fhir.r4.model.Base;
023import org.hl7.fhir.r4.model.BooleanType;
024import org.hl7.fhir.r4.model.CanonicalType;
025import org.hl7.fhir.r4.model.CodeType;
026import org.hl7.fhir.r4.model.CodeableConcept;
027import org.hl7.fhir.r4.model.Coding;
028import org.hl7.fhir.r4.model.ConceptMap;
029import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent;
030import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupUnmappedMode;
031import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent;
032import org.hl7.fhir.r4.model.ConceptMap.TargetElementComponent;
033import org.hl7.fhir.r4.model.Constants;
034import org.hl7.fhir.r4.model.ContactDetail;
035import org.hl7.fhir.r4.model.ContactPoint;
036import org.hl7.fhir.r4.model.DecimalType;
037import org.hl7.fhir.r4.model.ElementDefinition;
038import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionMappingComponent;
039import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
040import org.hl7.fhir.r4.model.Enumeration;
041import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence;
042import org.hl7.fhir.r4.model.Enumerations.PublicationStatus;
043import org.hl7.fhir.r4.model.ExpressionNode;
044import org.hl7.fhir.r4.model.ExpressionNode.CollectionStatus;
045import org.hl7.fhir.r4.model.IdType;
046import org.hl7.fhir.r4.model.IntegerType;
047import org.hl7.fhir.r4.model.Narrative.NarrativeStatus;
048import org.hl7.fhir.r4.model.PrimitiveType;
049import org.hl7.fhir.r4.model.Reference;
050import org.hl7.fhir.r4.model.Resource;
051import org.hl7.fhir.r4.model.ResourceFactory;
052import org.hl7.fhir.r4.model.StringType;
053import org.hl7.fhir.r4.model.StructureDefinition;
054import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionMappingComponent;
055import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule;
056import org.hl7.fhir.r4.model.StructureMap;
057import org.hl7.fhir.r4.model.StructureMap.StructureMapContextType;
058import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupComponent;
059import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupInputComponent;
060import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleComponent;
061import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleDependentComponent;
062import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleSourceComponent;
063import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleTargetComponent;
064import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleTargetParameterComponent;
065import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupTypeMode;
066import org.hl7.fhir.r4.model.StructureMap.StructureMapInputMode;
067import org.hl7.fhir.r4.model.StructureMap.StructureMapSourceListMode;
068import org.hl7.fhir.r4.model.StructureMap.StructureMapTargetListMode;
069import org.hl7.fhir.r4.model.StructureMap.StructureMapModelMode;
070import org.hl7.fhir.r4.model.StructureMap.StructureMapStructureComponent;
071import org.hl7.fhir.r4.model.StructureMap.StructureMapTransform;
072import org.hl7.fhir.r4.model.Type;
073import org.hl7.fhir.r4.model.TypeDetails;
074import org.hl7.fhir.r4.model.TypeDetails.ProfiledType;
075import org.hl7.fhir.r4.model.UriType;
076import org.hl7.fhir.r4.model.ValueSet;
077import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
078import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
079import org.hl7.fhir.r4.utils.FHIRLexer.FHIRLexerException;
080import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext;
081import org.apache.commons.lang3.NotImplementedException;
082import org.hl7.fhir.exceptions.DefinitionException;
083import org.hl7.fhir.exceptions.FHIRException;
084import org.hl7.fhir.exceptions.FHIRFormatError;
085import org.hl7.fhir.exceptions.PathEngineException;
086import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
087import org.hl7.fhir.utilities.TextFile;
088import org.hl7.fhir.utilities.Utilities;
089import org.hl7.fhir.utilities.validation.ValidationMessage;
090import org.hl7.fhir.utilities.xhtml.NodeType;
091import org.hl7.fhir.utilities.xhtml.XhtmlNode;
092
093/**
094 * Services in this class:
095 * 
096 * string render(map) - take a structure and convert it to text
097 * map parse(text) - take a text representation and parse it 
098 * getTargetType(map) - return the definition for the type to create to hand in 
099 * transform(appInfo, source, map, target) - transform from source to target following the map
100 * analyse(appInfo, map) - generate profiles and other analysis artifacts for the targets of the transform
101 * map generateMapFromMappings(StructureDefinition) - build a mapping from a structure definition with loigcal mappings
102 *  
103 * @author Grahame Grieve
104 *
105 */
106public class StructureMapUtilities {
107
108        public class ResolvedGroup {
109    public StructureMapGroupComponent target;
110    public StructureMap targetMap;
111  }
112  public static final String MAP_WHERE_CHECK = "map.where.check";
113  public static final String MAP_WHERE_LOG = "map.where.log";
114        public static final String MAP_WHERE_EXPRESSION = "map.where.expression";
115        public static final String MAP_SEARCH_EXPRESSION = "map.search.expression";
116        public static final String MAP_EXPRESSION = "map.transform.expression";
117  private static final boolean RENDER_MULTIPLE_TARGETS_ONELINE = true;
118  private static final String AUTO_VAR_NAME = "vvv";
119
120        public interface ITransformerServices {
121                //    public boolean validateByValueSet(Coding code, String valuesetId);
122          public void log(String message); // log internal progress
123          public Base createType(Object appInfo, String name) throws FHIRException;
124    public Base createResource(Object appInfo, Base res, boolean atRootofTransform); // an already created resource is provided; this is to identify/store it
125                public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException;
126                //    public Coding translate(Coding code)
127                //    ValueSet validation operation
128                //    Translation operation
129                //    Lookup another tree of data
130                //    Create an instance tree
131                //    Return the correct string format to refer to a tree (input or output)
132    public Base resolveReference(Object appContext, String url) throws FHIRException;
133    public List<Base> performSearch(Object appContext, String url) throws FHIRException;
134        }
135
136        private class FFHIRPathHostServices implements IEvaluationContext{
137
138    public Base resolveConstant(Object appContext, String name) throws PathEngineException {
139      Variables vars = (Variables) appContext;
140      Base res = vars.get(VariableMode.INPUT, name);
141      if (res == null)
142        res = vars.get(VariableMode.OUTPUT, name);
143      return res;
144    }
145
146    @Override
147    public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException {
148      if (!(appContext instanceof VariablesForProfiling)) 
149        throw new Error("Internal Logic Error (wrong type '"+appContext.getClass().getName()+"' in resolveConstantType)");
150      VariablesForProfiling vars = (VariablesForProfiling) appContext;
151      VariableForProfiling v = vars.get(null, name);
152      if (v == null)
153        throw new PathEngineException("Unknown variable '"+name+"' from variables "+vars.summary());
154      return v.property.types;
155    }
156
157    @Override
158    public boolean log(String argument, List<Base> focus) {
159      throw new Error("Not Implemented Yet");
160    }
161
162    @Override
163    public FunctionDetails resolveFunction(String functionName) {
164      return null; // throw new Error("Not Implemented Yet");
165    }
166
167    @Override
168    public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException {
169      throw new Error("Not Implemented Yet");
170    }
171
172    @Override
173    public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
174      throw new Error("Not Implemented Yet");
175    }
176
177    @Override
178    public Base resolveReference(Object appContext, String url) throws FHIRException {
179      if (services == null)
180        return null;
181      return services.resolveReference(appContext, url);
182    }
183
184    @Override
185    public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException {
186      IResourceValidator val = worker.newValidator();
187      List<ValidationMessage> valerrors = new ArrayList<ValidationMessage>();
188      if (item instanceof Resource) {
189        val.validate(appContext, valerrors, (Resource) item, url);
190        boolean ok = true;
191        for (ValidationMessage v : valerrors)
192          ok = ok && v.getLevel().isError();
193        return ok;
194      }
195      throw new NotImplementedException("Not done yet (FFHIRPathHostServices.conformsToProfile), when item is element");
196    }
197          
198        }
199        private IWorkerContext worker;
200        private FHIRPathEngine fpe;
201        private ITransformerServices services;
202  private ProfileKnowledgeProvider pkp;
203  private Map<String, Integer> ids = new HashMap<String, Integer>(); 
204
205        public StructureMapUtilities(IWorkerContext worker, ITransformerServices services, ProfileKnowledgeProvider pkp) {
206                super();
207                this.worker = worker;
208                this.services = services;
209                this.pkp = pkp;
210                fpe = new FHIRPathEngine(worker);
211                fpe.setHostServices(new FFHIRPathHostServices());
212        }
213
214        public StructureMapUtilities(IWorkerContext worker, ITransformerServices services) {
215                super();
216                this.worker = worker;
217                this.services = services;
218                fpe = new FHIRPathEngine(worker);
219    fpe.setHostServices(new FFHIRPathHostServices());
220        }
221
222  public StructureMapUtilities(IWorkerContext worker) {
223    super();
224    this.worker = worker;
225    fpe = new FHIRPathEngine(worker);
226    fpe.setHostServices(new FFHIRPathHostServices());
227  }
228
229        public static String render(StructureMap map) {
230                StringBuilder b = new StringBuilder();
231                b.append("map \"");
232                b.append(map.getUrl());
233                b.append("\" = \"");
234                b.append(Utilities.escapeJava(map.getName()));
235                b.append("\"\r\n\r\n");
236
237                renderConceptMaps(b, map);
238                renderUses(b, map);
239                renderImports(b, map);
240                for (StructureMapGroupComponent g : map.getGroup())
241                        renderGroup(b, g);
242                return b.toString();
243        }
244
245        private static void renderConceptMaps(StringBuilder b, StructureMap map) {
246    for (Resource r : map.getContained()) {
247      if (r instanceof ConceptMap) {
248        produceConceptMap(b, (ConceptMap) r);
249      }
250    }
251  }
252
253  private static void produceConceptMap(StringBuilder b, ConceptMap cm) {
254    b.append("conceptmap \"");
255    b.append(cm.getId());
256    b.append("\" {\r\n");
257    Map<String, String> prefixesSrc = new HashMap<String, String>();
258    Map<String, String> prefixesTgt = new HashMap<String, String>();
259    char prefix = 's';
260    for (ConceptMapGroupComponent cg : cm.getGroup()) {
261      if (!prefixesSrc.containsKey(cg.getSource())) {
262        prefixesSrc.put(cg.getSource(), String.valueOf(prefix));
263        b.append("  prefix ");
264        b.append(prefix);
265        b.append(" = \"");
266        b.append(cg.getSource());
267        b.append("\"\r\n");
268        prefix++;
269      }
270      if (!prefixesTgt.containsKey(cg.getTarget())) {
271        prefixesTgt.put(cg.getTarget(), String.valueOf(prefix));
272        b.append("  prefix ");
273        b.append(prefix);
274        b.append(" = \"");
275        b.append(cg.getTarget());
276        b.append("\"\r\n");
277        prefix++;
278      }
279    }
280    b.append("\r\n");
281    for (ConceptMapGroupComponent cg : cm.getGroup()) {
282      if (cg.hasUnmapped()) {
283        b.append("  unmapped for ");
284        b.append(prefix);
285        b.append(" = ");
286        b.append(cg.getUnmapped().getMode());
287        b.append("\r\n"); 
288      }   
289    }
290    
291    for (ConceptMapGroupComponent cg : cm.getGroup()) {
292      for (SourceElementComponent ce : cg.getElement()) {
293        b.append("  ");
294        b.append(prefixesSrc.get(cg.getSource()));
295        b.append(":");
296        b.append(ce.getCode());
297        b.append(" ");
298        b.append(getChar(ce.getTargetFirstRep().getEquivalence()));
299        b.append(" ");
300        b.append(prefixesTgt.get(cg.getTarget()));
301        b.append(":");
302        b.append(ce.getTargetFirstRep().getCode());
303        b.append("\r\n");
304      }
305    }
306    b.append("}\r\n\r\n");
307  }
308
309  private static Object getChar(ConceptMapEquivalence equivalence) {
310    switch (equivalence) {
311    case RELATEDTO: return "-";
312    case EQUAL: return "=";
313    case EQUIVALENT: return "==";
314    case DISJOINT: return "!=";
315    case UNMATCHED: return "--";
316    case WIDER: return "<=";
317    case SUBSUMES: return "<-";
318    case NARROWER: return ">=";
319    case SPECIALIZES: return ">-";
320    case INEXACT: return "~";
321    default: return "??";
322    }
323  }
324
325  private static void renderUses(StringBuilder b, StructureMap map) {
326                for (StructureMapStructureComponent s : map.getStructure()) {
327                        b.append("uses \"");
328                        b.append(s.getUrl());
329      b.append("\" ");
330      if (s.hasAlias()) {
331        b.append("alias ");
332        b.append(s.getAlias());
333        b.append(" ");
334      }
335      b.append("as ");
336                        b.append(s.getMode().toCode());
337                        b.append("\r\n");
338                        renderDoco(b, s.getDocumentation());
339                }
340                if (map.hasStructure())
341                        b.append("\r\n");
342        }
343
344        private static void renderImports(StringBuilder b, StructureMap map) {
345                for (UriType s : map.getImport()) {
346                        b.append("imports \"");
347                        b.append(s.getValue());
348                        b.append("\"\r\n");
349                }
350                if (map.hasImport())
351                        b.append("\r\n");
352        }
353
354  public static String groupToString(StructureMapGroupComponent g) {
355    StringBuilder b = new StringBuilder();
356    renderGroup(b, g);
357    return b.toString();
358  }
359  
360  private static void renderGroup(StringBuilder b, StructureMapGroupComponent g) {
361                b.append("group ");
362    switch (g.getTypeMode()) {
363    case TYPES: 
364                 b.append("for types");
365                 break;
366    case TYPEANDTYPES: 
367                 b.append("for type+types ");
368                 break;
369    default: // NONE, NULL
370    }
371                b.append(g.getName());
372                if (g.hasExtends()) {
373                        b.append(" extends ");
374                        b.append(g.getExtends());
375                }
376                if (g.hasDocumentation()) 
377                        renderDoco(b, g.getDocumentation());
378                b.append("\r\n");
379                for (StructureMapGroupInputComponent gi : g.getInput()) {
380                        b.append("  input ");
381                        b.append(gi.getName());
382                        if (gi.hasType()) {
383                                b.append(" : ");
384                                b.append(gi.getType());
385                        }
386                        b.append(" as ");
387                        b.append(gi.getMode().toCode());
388                        b.append("\r\n");
389                }
390                if (g.hasInput())
391                        b.append("\r\n");
392                for (StructureMapGroupRuleComponent r : g.getRule()) {
393                        renderRule(b, r, 2);
394                }
395                b.append("\r\nendgroup\r\n");
396        }
397
398  public static String ruleToString(StructureMapGroupRuleComponent r) {
399    StringBuilder b = new StringBuilder();
400    renderRule(b, r, 0);
401    return b.toString();
402  }
403  
404        private static void renderRule(StringBuilder b, StructureMapGroupRuleComponent r, int indent) {
405                for (int i = 0; i < indent; i++)
406                        b.append(' ');
407                b.append(r.getName());
408                b.append(" : for ");
409                boolean canBeAbbreviated = checkisSimple(r);
410                
411                boolean first = true;
412                for (StructureMapGroupRuleSourceComponent rs : r.getSource()) {
413                        if (first)
414                                first = false;
415                        else
416                                b.append(", ");
417                        renderSource(b, rs, canBeAbbreviated);
418                }
419                if (r.getTarget().size() > 1) {
420                        b.append(" make ");
421                        first = true;
422                        for (StructureMapGroupRuleTargetComponent rt : r.getTarget()) {
423                                if (first)
424                                        first = false;
425                                else
426                                        b.append(", ");
427                                if (RENDER_MULTIPLE_TARGETS_ONELINE)
428                b.append(' ');
429                                else {
430                                b.append("\r\n");
431                                for (int i = 0; i < indent+4; i++)
432                                        b.append(' ');
433                                }
434                                renderTarget(b, rt, false);
435                        }
436                } else if (r.hasTarget()) { 
437                        b.append(" make ");
438                        renderTarget(b, r.getTarget().get(0), canBeAbbreviated);
439                }
440                if (!canBeAbbreviated) {
441                  if (r.hasRule()) {
442                    b.append(" then {\r\n");
443                    renderDoco(b, r.getDocumentation());
444                    for (StructureMapGroupRuleComponent ir : r.getRule()) {
445                      renderRule(b, ir, indent+2);
446                    }
447                    for (int i = 0; i < indent; i++)
448                      b.append(' ');
449                    b.append("}\r\n");
450                  } else {
451                    if (r.hasDependent()) {
452                      b.append(" then ");
453                      first = true;
454                      for (StructureMapGroupRuleDependentComponent rd : r.getDependent()) {
455                        if (first)
456                          first = false;
457                        else
458                          b.append(", ");
459                        b.append(rd.getName());
460                        b.append("(");
461                        boolean ifirst = true;
462                        for (StringType rdp : rd.getVariable()) {
463                          if (ifirst)
464                            ifirst = false;
465                          else
466                            b.append(", ");
467                          b.append(rdp.asStringValue());
468                        }
469                        b.append(")");
470                      }
471                    }
472                  }
473                }
474    renderDoco(b, r.getDocumentation());
475    b.append("\r\n");
476        }
477
478  private static boolean checkisSimple(StructureMapGroupRuleComponent r) {
479    return 
480          (r.getSource().size() == 1 && r.getSourceFirstRep().hasElement() && r.getSourceFirstRep().hasVariable()) && 
481          (r.getTarget().size() == 1 && r.getTargetFirstRep().hasVariable() && (r.getTargetFirstRep().getTransform() == null || r.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE) && r.getTargetFirstRep().getParameter().size() == 0) &&
482          (r.getDependent().size() == 0);
483  }
484
485  public static String sourceToString(StructureMapGroupRuleSourceComponent r) {
486    StringBuilder b = new StringBuilder();
487    renderSource(b, r, false);
488    return b.toString();
489  }
490  
491        private static void renderSource(StringBuilder b, StructureMapGroupRuleSourceComponent rs, boolean abbreviate) {
492                b.append(rs.getContext());
493                if (rs.getContext().equals("@search")) {
494      b.append('(');
495      b.append(rs.getElement());
496      b.append(')');
497                } else if (rs.hasElement()) {
498                        b.append('.');
499                        b.append(rs.getElement());
500                }
501                if (rs.hasType()) {
502      b.append(" : ");
503      b.append(rs.getType());
504      if (rs.hasMin()) {
505        b.append(" ");
506        b.append(rs.getMin());
507        b.append("..");
508        b.append(rs.getMax());
509      }
510                }
511                
512                if (rs.hasListMode()) {
513                        b.append(" ");
514                                b.append(rs.getListMode().toCode());
515                }
516                if (rs.hasDefaultValue()) {
517                  b.append(" default ");
518                  assert rs.getDefaultValue() instanceof StringType;
519                  b.append("\""+Utilities.escapeJson(((StringType) rs.getDefaultValue()).asStringValue())+"\"");
520                }
521                if (!abbreviate && rs.hasVariable()) {
522                        b.append(" as ");
523                        b.append(rs.getVariable());
524                }
525                if (rs.hasCondition())  {
526                        b.append(" where ");
527                        b.append(rs.getCondition());
528                }
529                if (rs.hasCheck())  {
530                        b.append(" check ");
531                        b.append(rs.getCheck());
532                }
533    if (rs.hasLogMessage()) {
534      b.append(" log ");
535      b.append(rs.getLogMessage());
536    }
537        }
538
539  public static String targetToString(StructureMapGroupRuleTargetComponent rt) {
540    StringBuilder b = new StringBuilder();
541    renderTarget(b, rt, false);
542    return b.toString();
543  }
544        
545        private static void renderTarget(StringBuilder b, StructureMapGroupRuleTargetComponent rt, boolean abbreviate) {
546          if (rt.hasContext()) {
547            if (rt.getContextType() == StructureMapContextType.TYPE)
548              b.append("@");
549                b.append(rt.getContext());
550                if (rt.hasElement())  {
551                        b.append('.');
552                        b.append(rt.getElement());
553                }
554          }
555                if (!abbreviate && rt.hasTransform()) {
556            if (rt.hasContext()) 
557                        b.append(" = ");
558                        if (rt.getTransform() == StructureMapTransform.COPY && rt.getParameter().size() == 1) {
559                                renderTransformParam(b, rt.getParameter().get(0));
560      } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 1) {
561        b.append("(");
562        b.append("\""+((StringType) rt.getParameter().get(0).getValue()).asStringValue()+"\"");
563        b.append(")");
564                        } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 2) {
565                                b.append(rt.getTransform().toCode());
566                                b.append("(");
567                                b.append(((IdType) rt.getParameter().get(0).getValue()).asStringValue());
568                                b.append("\""+((StringType) rt.getParameter().get(1).getValue()).asStringValue()+"\"");
569                                b.append(")");
570                        } else {
571                                b.append(rt.getTransform().toCode());
572                                b.append("(");
573                                boolean first = true;
574                                for (StructureMapGroupRuleTargetParameterComponent rtp : rt.getParameter()) {
575                                        if (first)
576                                                first = false;
577                                        else
578                                                b.append(", ");
579                                        renderTransformParam(b, rtp);
580                                }
581                                b.append(")");
582                        }
583                }
584                if (!abbreviate && rt.hasVariable()) {
585                        b.append(" as ");
586                        b.append(rt.getVariable());
587                }
588                for (Enumeration<StructureMapTargetListMode> lm : rt.getListMode()) {
589                        b.append(" ");
590                        b.append(lm.getValue().toCode());
591                        if (lm.getValue() == StructureMapTargetListMode.SHARE) {
592                                b.append(" ");
593                                b.append(rt.getListRuleId());
594                        }
595                }
596        }
597
598  public static String paramToString(StructureMapGroupRuleTargetParameterComponent rtp) {
599    StringBuilder b = new StringBuilder();
600    renderTransformParam(b, rtp);
601    return b.toString();
602  }
603        
604        private static void renderTransformParam(StringBuilder b, StructureMapGroupRuleTargetParameterComponent rtp) {
605          try {
606                if (rtp.hasValueBooleanType())
607                        b.append(rtp.getValueBooleanType().asStringValue());
608                else if (rtp.hasValueDecimalType())
609                        b.append(rtp.getValueDecimalType().asStringValue());
610                else if (rtp.hasValueIdType())
611                        b.append(rtp.getValueIdType().asStringValue());
612                else if (rtp.hasValueDecimalType())
613                        b.append(rtp.getValueDecimalType().asStringValue());
614                else if (rtp.hasValueIntegerType())
615                        b.append(rtp.getValueIntegerType().asStringValue());
616                else 
617              b.append("\""+Utilities.escapeJava(rtp.getValueStringType().asStringValue())+"\"");
618          } catch (FHIRException e) {
619            e.printStackTrace();
620            b.append("error!");
621          }
622        }
623
624        private static void renderDoco(StringBuilder b, String doco) {
625                if (Utilities.noString(doco))
626                        return;
627                b.append(" // ");
628                b.append(doco.replace("\r\n", " ").replace("\r", " ").replace("\n", " "));
629        }
630
631        public StructureMap parse(String text) throws FHIRException {
632                FHIRLexer lexer = new FHIRLexer(text);
633                if (lexer.done())
634                        throw lexer.error("Map Input cannot be empty");
635                lexer.skipComments();
636                lexer.token("map");
637                StructureMap result = new StructureMap();
638                result.setUrl(lexer.readConstant("url"));
639                lexer.token("=");
640                result.setName(lexer.readConstant("name"));
641                lexer.skipComments();
642
643                while (lexer.hasToken("conceptmap"))
644                        parseConceptMap(result, lexer);
645
646                while (lexer.hasToken("uses"))
647                        parseUses(result, lexer);
648                while (lexer.hasToken("imports"))
649                        parseImports(result, lexer);
650
651                parseGroup(result, lexer);
652
653                while (!lexer.done()) {
654                        parseGroup(result, lexer);    
655                }
656
657                return result;
658        }
659
660        private void parseConceptMap(StructureMap result, FHIRLexer lexer) throws FHIRLexerException {
661                lexer.token("conceptmap");
662                ConceptMap map = new ConceptMap();
663                String id = lexer.readConstant("map id");
664                if (!id.startsWith("#"))
665                        lexer.error("Concept Map identifier must start with #");
666                map.setId(id);
667                map.setStatus(PublicationStatus.DRAFT); // todo: how to add this to the text format
668                result.getContained().add(map);
669                lexer.token("{");
670                lexer.skipComments();
671                //        lexer.token("source");
672                //        map.setSource(new UriType(lexer.readConstant("source")));
673                //        lexer.token("target");
674                //        map.setSource(new UriType(lexer.readConstant("target")));
675                Map<String, String> prefixes = new HashMap<String, String>();
676                while (lexer.hasToken("prefix")) {
677                        lexer.token("prefix");
678                        String n = lexer.take();
679                        lexer.token("=");
680                        String v = lexer.readConstant("prefix url");
681                        prefixes.put(n, v);
682                }
683                while (lexer.hasToken("unmapped")) {
684                  lexer.token("unmapped");
685      lexer.token("for");
686      String n = readPrefix(prefixes, lexer);
687      ConceptMapGroupComponent g = getGroup(map, n, null);
688                  lexer.token("=");
689      String v = lexer.take();
690      if (v.equals("provided")) {
691        g.getUnmapped().setMode(ConceptMapGroupUnmappedMode.PROVIDED);
692      } else
693        lexer.error("Only unmapped mode PROVIDED is supported at this time");
694                }
695                while (!lexer.hasToken("}")) {
696                  String srcs = readPrefix(prefixes, lexer);
697                        lexer.token(":");
698      String sc = lexer.getCurrent().startsWith("\"") ? lexer.readConstant("code") : lexer.take();
699                  ConceptMapEquivalence eq = readEquivalence(lexer);
700                  String tgts = (eq != ConceptMapEquivalence.UNMATCHED) ? readPrefix(prefixes, lexer) : "";
701                  ConceptMapGroupComponent g = getGroup(map, srcs, tgts);
702                        SourceElementComponent e = g.addElement();
703                        e.setCode(sc);
704      if (e.getCode().startsWith("\""))
705        e.setCode(lexer.processConstant(e.getCode()));
706                        TargetElementComponent tgt = e.addTarget();
707                        if (eq != ConceptMapEquivalence.EQUIVALENT)
708                          tgt.setEquivalence(eq);
709                        if (tgt.getEquivalence() != ConceptMapEquivalence.UNMATCHED) {
710                                lexer.token(":");
711                                tgt.setCode(lexer.take());
712                                if (tgt.getCode().startsWith("\""))
713                                  tgt.setCode(lexer.processConstant(tgt.getCode()));
714                        }
715                        if (lexer.hasComment())
716                                tgt.setComment(lexer.take().substring(2).trim());
717                }
718                lexer.token("}");
719        }
720
721
722        private ConceptMapGroupComponent getGroup(ConceptMap map, String srcs, String tgts) {
723          for (ConceptMapGroupComponent grp : map.getGroup()) {
724            if (grp.getSource().equals(srcs)) 
725              if ((tgts == null && !grp.hasTarget()) || (tgts != null && tgts.equals(grp.getTarget())))
726              return grp;
727          }
728          ConceptMapGroupComponent grp = map.addGroup(); 
729    grp.setSource(srcs);
730    grp.setTarget(tgts);
731    return grp;
732  }
733
734
735        private String readPrefix(Map<String, String> prefixes, FHIRLexer lexer) throws FHIRLexerException {
736                String prefix = lexer.take();
737                if (!prefixes.containsKey(prefix))
738                        throw lexer.error("Unknown prefix '"+prefix+"'");
739                return prefixes.get(prefix);
740        }
741
742
743        private ConceptMapEquivalence readEquivalence(FHIRLexer lexer) throws FHIRLexerException {
744                String token = lexer.take();
745    if (token.equals("-"))
746      return ConceptMapEquivalence.RELATEDTO;
747    if (token.equals("="))
748      return ConceptMapEquivalence.EQUAL;
749                if (token.equals("=="))
750                        return ConceptMapEquivalence.EQUIVALENT;
751                if (token.equals("!="))
752                        return ConceptMapEquivalence.DISJOINT;
753                if (token.equals("--"))
754                        return ConceptMapEquivalence.UNMATCHED;
755                if (token.equals("<="))
756                        return ConceptMapEquivalence.WIDER;
757                if (token.equals("<-"))
758                        return ConceptMapEquivalence.SUBSUMES;
759                if (token.equals(">="))
760                        return ConceptMapEquivalence.NARROWER;
761                if (token.equals(">-"))
762                        return ConceptMapEquivalence.SPECIALIZES;
763                if (token.equals("~"))
764                        return ConceptMapEquivalence.INEXACT;
765                throw lexer.error("Unknown equivalence token '"+token+"'");
766        }
767
768
769        private void parseUses(StructureMap result, FHIRLexer lexer) throws FHIRException {
770                lexer.token("uses");
771                StructureMapStructureComponent st = result.addStructure();
772                st.setUrl(lexer.readConstant("url"));
773                if (lexer.hasToken("alias")) {
774            lexer.token("alias");
775                  st.setAlias(lexer.take());
776                }
777                lexer.token("as");
778                st.setMode(StructureMapModelMode.fromCode(lexer.take()));
779                lexer.skipToken(";");
780                if (lexer.hasComment()) {
781                        st.setDocumentation(lexer.take().substring(2).trim());
782                }
783                lexer.skipComments();
784        }
785
786        private void parseImports(StructureMap result, FHIRLexer lexer) throws FHIRException {
787                lexer.token("imports");
788                result.addImport(lexer.readConstant("url"));
789                lexer.skipToken(";");
790                if (lexer.hasComment()) {
791                        lexer.next();
792                }
793                lexer.skipComments();
794        }
795
796        private void parseGroup(StructureMap result, FHIRLexer lexer) throws FHIRException {
797                lexer.token("group");
798                StructureMapGroupComponent group = result.addGroup();
799                if (lexer.hasToken("for")) {
800                  lexer.token("for");
801                  if ("type".equals(lexer.getCurrent())) {
802        lexer.token("type");
803        lexer.token("+");
804        lexer.token("types");
805        group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES);
806                  } else {
807                    lexer.token("types");
808        group.setTypeMode(StructureMapGroupTypeMode.TYPES);
809                  }
810                } else
811                  group.setTypeMode(StructureMapGroupTypeMode.NONE);
812                group.setName(lexer.take());
813                if (lexer.hasToken("extends")) {
814                        lexer.next();
815                        group.setExtends(lexer.take());
816                }
817                lexer.skipComments();
818                while (lexer.hasToken("input")) 
819                        parseInput(group, lexer);
820                while (!lexer.hasToken("endgroup")) {
821                        if (lexer.done())
822                                throw lexer.error("premature termination expecting 'endgroup'");
823                        parseRule(result, group.getRule(), lexer);
824                }
825                lexer.next();
826                lexer.skipComments();
827        }
828
829        private void parseInput(StructureMapGroupComponent group, FHIRLexer lexer) throws FHIRException {
830                lexer.token("input");
831                StructureMapGroupInputComponent input = group.addInput();
832                input.setName(lexer.take());
833                if (lexer.hasToken(":")) {
834                        lexer.token(":");
835                        input.setType(lexer.take());
836                }
837                lexer.token("as");
838                input.setMode(StructureMapInputMode.fromCode(lexer.take()));
839                if (lexer.hasComment()) {
840                        input.setDocumentation(lexer.take().substring(2).trim());
841                }
842                lexer.skipToken(";");
843                lexer.skipComments();
844        }
845
846        private void parseRule(StructureMap map, List<StructureMapGroupRuleComponent> list, FHIRLexer lexer) throws FHIRException {
847                StructureMapGroupRuleComponent rule = new StructureMapGroupRuleComponent(); 
848                list.add(rule);
849                rule.setName(lexer.takeDottedToken());
850                lexer.token(":");
851                lexer.token("for");
852                boolean done = false;
853                while (!done) {
854                        parseSource(rule, lexer);
855                        done = !lexer.hasToken(",");
856                        if (!done)
857                                lexer.next();
858                }
859                if (lexer.hasToken("make")) {
860                        lexer.token("make");
861                        done = false;
862                        while (!done) {
863                                parseTarget(rule, lexer);
864                                done = !lexer.hasToken(",");
865                                if (!done)
866                                        lexer.next();
867                        }
868                }
869                if (lexer.hasToken("then")) {
870                        lexer.token("then");
871                        if (lexer.hasToken("{")) {
872                                lexer.token("{");
873                                if (lexer.hasComment()) {
874                                        rule.setDocumentation(lexer.take().substring(2).trim());
875                                }
876                                lexer.skipComments();
877                                while (!lexer.hasToken("}")) {
878                                        if (lexer.done())
879                                                throw lexer.error("premature termination expecting '}' in nested group");
880                                        parseRule(map, rule.getRule(), lexer);
881                                }      
882                                lexer.token("}");
883                        } else {
884                                done = false;
885                                while (!done) {
886                                        parseRuleReference(rule, lexer);
887                                        done = !lexer.hasToken(",");
888                                        if (!done)
889                                                lexer.next();
890                                }
891                        }
892                } else if (lexer.hasComment()) {
893                        rule.setDocumentation(lexer.take().substring(2).trim());
894                }
895                if (isSimpleSyntax(rule)) {
896                  rule.getSourceFirstRep().setVariable(AUTO_VAR_NAME);
897                  rule.getTargetFirstRep().setVariable(AUTO_VAR_NAME);
898                  rule.getTargetFirstRep().setTransform(StructureMapTransform.CREATE); // with no parameter - e.g. imply what is to be created
899                  // no dependencies - imply what is to be done based on types
900                }
901                lexer.skipComments();
902        }
903
904        private boolean isSimpleSyntax(StructureMapGroupRuleComponent rule) {
905    return 
906        (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasContext() && rule.getSourceFirstRep().hasElement() && !rule.getSourceFirstRep().hasVariable()) &&
907        (rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasContext() && rule.getTargetFirstRep().hasElement() && !rule.getTargetFirstRep().hasVariable() && !rule.getTargetFirstRep().hasParameter()) &&
908        (rule.getDependent().size() == 0 && rule.getRule().size() == 0);
909  }
910
911  private void parseRuleReference(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRLexerException {
912                StructureMapGroupRuleDependentComponent ref = rule.addDependent();
913                ref.setName(lexer.take());
914                lexer.token("(");
915                boolean done = false;
916                while (!done) {
917                        ref.addVariable(lexer.take());
918                        done = !lexer.hasToken(",");
919                        if (!done)
920                                lexer.next();
921                }
922                lexer.token(")");
923        }
924
925        private void parseSource(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException {
926                StructureMapGroupRuleSourceComponent source = rule.addSource();
927                source.setContext(lexer.take());
928                if (source.getContext().equals("search") && lexer.hasToken("(")) {
929            source.setContext("@search");
930      lexer.take();
931      ExpressionNode node = fpe.parse(lexer);
932      source.setUserData(MAP_SEARCH_EXPRESSION, node);
933      source.setElement(node.toString());
934      lexer.token(")");
935                } else if (lexer.hasToken(".")) {
936                        lexer.token(".");
937                        source.setElement(lexer.take());
938                }
939                if (lexer.hasToken(":")) {
940                  // type and cardinality
941                  lexer.token(":");
942                  source.setType(lexer.takeDottedToken());
943                  if (!lexer.hasToken("as", "first", "last", "not_first", "not_last", "only_one", "default")) {
944                    source.setMin(lexer.takeInt());
945                    lexer.token("..");
946                    source.setMax(lexer.take());
947                  }
948                }
949                if (lexer.hasToken("default")) {
950                  lexer.token("default");
951                  source.setDefaultValue(new StringType(lexer.readConstant("default value")));
952                }
953                if (Utilities.existsInList(lexer.getCurrent(), "first", "last", "not_first", "not_last", "only_one"))
954                        source.setListMode(StructureMapSourceListMode.fromCode(lexer.take()));
955
956                if (lexer.hasToken("as")) {
957                        lexer.take();
958                        source.setVariable(lexer.take());
959                }
960                if (lexer.hasToken("where")) {
961                        lexer.take();
962                        ExpressionNode node = fpe.parse(lexer);
963                        source.setUserData(MAP_WHERE_EXPRESSION, node);
964                        source.setCondition(node.toString());
965                }
966                if (lexer.hasToken("check")) {
967                        lexer.take();
968                        ExpressionNode node = fpe.parse(lexer);
969                        source.setUserData(MAP_WHERE_CHECK, node);
970                        source.setCheck(node.toString());
971                }
972    if (lexer.hasToken("log")) {
973      lexer.take();
974      ExpressionNode node = fpe.parse(lexer);
975      source.setUserData(MAP_WHERE_CHECK, node);
976      source.setLogMessage(node.toString());
977    }
978        }
979
980        private void parseTarget(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException {
981                StructureMapGroupRuleTargetComponent target = rule.addTarget();
982                String start = lexer.take();
983                if (lexer.hasToken(".")) {
984            target.setContext(start);
985            target.setContextType(StructureMapContextType.VARIABLE);
986            start = null;
987                        lexer.token(".");
988                        target.setElement(lexer.take());
989                }
990                String name;
991                boolean isConstant = false;
992                if (lexer.hasToken("=")) {
993                  if (start != null)
994              target.setContext(start);
995                        lexer.token("=");
996                        isConstant = lexer.isConstant(true);
997                        name = lexer.take();
998                } else 
999                  name = start;
1000                
1001                if ("(".equals(name)) {
1002                  // inline fluentpath expression
1003      target.setTransform(StructureMapTransform.EVALUATE);
1004      ExpressionNode node = fpe.parse(lexer);
1005      target.setUserData(MAP_EXPRESSION, node);
1006      target.addParameter().setValue(new StringType(node.toString()));
1007      lexer.token(")");
1008                } else if (lexer.hasToken("(")) {
1009                                target.setTransform(StructureMapTransform.fromCode(name));
1010                                lexer.token("(");
1011                                if (target.getTransform() == StructureMapTransform.EVALUATE) {
1012                                        parseParameter(target, lexer);
1013                                        lexer.token(",");
1014                                        ExpressionNode node = fpe.parse(lexer);
1015                                        target.setUserData(MAP_EXPRESSION, node);
1016                                        target.addParameter().setValue(new StringType(node.toString()));
1017                                } else { 
1018                                        while (!lexer.hasToken(")")) {
1019                                                parseParameter(target, lexer);
1020                                                if (!lexer.hasToken(")"))
1021                                                        lexer.token(",");
1022                                        }       
1023                                }
1024                                lexer.token(")");
1025                } else if (name != null) {
1026                                target.setTransform(StructureMapTransform.COPY);
1027                                if (!isConstant) {
1028                                        String id = name;
1029                                        while (lexer.hasToken(".")) {
1030                                                id = id + lexer.take() + lexer.take();
1031                                        }
1032                                        target.addParameter().setValue(new IdType(id));
1033                                }
1034                                else 
1035                                        target.addParameter().setValue(readConstant(name, lexer));
1036                        }
1037                if (lexer.hasToken("as")) {
1038                        lexer.take();
1039                        target.setVariable(lexer.take());
1040                }
1041                while (Utilities.existsInList(lexer.getCurrent(), "first", "last", "share", "collate")) {
1042                        if (lexer.getCurrent().equals("share")) {
1043                                target.addListMode(StructureMapTargetListMode.SHARE);
1044                                lexer.next();
1045                                target.setListRuleId(lexer.take());
1046                        } else if (lexer.getCurrent().equals("first")) 
1047                                target.addListMode(StructureMapTargetListMode.FIRST);
1048                        else
1049                                target.addListMode(StructureMapTargetListMode.LAST);
1050                        lexer.next();
1051                }
1052        }
1053
1054
1055        private void parseParameter(StructureMapGroupRuleTargetComponent target, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError {
1056                if (!lexer.isConstant(true)) {
1057                        target.addParameter().setValue(new IdType(lexer.take()));
1058                } else if (lexer.isStringConstant())
1059                        target.addParameter().setValue(new StringType(lexer.readConstant("??")));
1060                else {
1061                        target.addParameter().setValue(readConstant(lexer.take(), lexer));
1062                }
1063        }
1064
1065        private Type readConstant(String s, FHIRLexer lexer) throws FHIRLexerException {
1066                if (Utilities.isInteger(s))
1067                        return new IntegerType(s);
1068                else if (Utilities.isDecimal(s))
1069                        return new DecimalType(s);
1070                else if (Utilities.existsInList(s, "true", "false"))
1071                        return new BooleanType(s.equals("true"));
1072                else 
1073                        return new StringType(lexer.processConstant(s));        
1074        }
1075
1076        public StructureDefinition getTargetType(StructureMap map) throws FHIRException {
1077          boolean found = false;
1078          StructureDefinition res = null;
1079          for (StructureMapStructureComponent uses : map.getStructure()) {
1080            if (uses.getMode() == StructureMapModelMode.TARGET) {
1081              if (found)
1082                throw new FHIRException("Multiple targets found in map "+map.getUrl());
1083              found = true;
1084              res = worker.fetchResource(StructureDefinition.class, uses.getUrl());
1085              if (res == null)
1086                throw new FHIRException("Unable to find "+uses.getUrl()+" referenced from map "+map.getUrl());      
1087            }
1088          }
1089          if (res == null)
1090      throw new FHIRException("No targets found in map "+map.getUrl());
1091          return res;
1092        }
1093
1094        public enum VariableMode {
1095                INPUT, OUTPUT
1096        }
1097
1098        public class Variable {
1099                private VariableMode mode;
1100                private String name;
1101                private Base object;
1102                public Variable(VariableMode mode, String name, Base object) {
1103                        super();
1104                        this.mode = mode;
1105                        this.name = name;
1106                        this.object = object;
1107                }
1108                public VariableMode getMode() {
1109                        return mode;
1110                }
1111                public String getName() {
1112                        return name;
1113                }
1114                public Base getObject() {
1115                        return object;
1116                }
1117    public String summary() {
1118      return name+": "+object.fhirType();
1119    }
1120        }
1121
1122        public class Variables {
1123                private List<Variable> list = new ArrayList<Variable>();
1124
1125                public void add(VariableMode mode, String name, Base object) {
1126                        Variable vv = null;
1127                        for (Variable v : list) 
1128                                if ((v.mode == mode) && v.getName().equals(name))
1129                                        vv = v;
1130                        if (vv != null)
1131                                list.remove(vv);
1132                        list.add(new Variable(mode, name, object));
1133                }
1134
1135                public Variables copy() {
1136                        Variables result = new Variables();
1137                        result.list.addAll(list);
1138                        return result;
1139                }
1140
1141                public Base get(VariableMode mode, String name) {
1142                        for (Variable v : list) 
1143                                if ((v.mode == mode) && v.getName().equals(name))
1144                                        return v.getObject();
1145                        return null;
1146                }
1147
1148    public String summary() {
1149      CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder();
1150      CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder();
1151      for (Variable v : list)
1152        if (v.mode == VariableMode.INPUT)
1153          s.append(v.summary());
1154        else
1155          t.append(v.summary());
1156      return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]";
1157    }
1158        }
1159
1160        public class TransformContext {
1161                private Object appInfo;
1162
1163                public TransformContext(Object appInfo) {
1164                        super();
1165                        this.appInfo = appInfo;
1166                }
1167
1168                public Object getAppInfo() {
1169                        return appInfo;
1170                }
1171
1172        }
1173
1174        private void log(String cnt) {
1175          if (services != null)
1176            services.log(cnt);
1177        }
1178
1179        /**
1180         * Given an item, return all the children that conform to the pattern described in name
1181         * 
1182         * Possible patterns:
1183         *  - a simple name (which may be the base of a name with [] e.g. value[x])
1184         *  - a name with a type replacement e.g. valueCodeableConcept
1185         *  - * which means all children
1186         *  - ** which means all descendents
1187         *  
1188         * @param item
1189         * @param name
1190         * @param result
1191         * @throws FHIRException 
1192         */
1193        protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException {
1194                for (Base v : item.listChildrenByName(name, true))
1195                        if (v != null)
1196                                result.add(v);
1197        }
1198
1199        public void transform(Object appInfo, Base source, StructureMap map, Base target) throws FHIRException {
1200                TransformContext context = new TransformContext(appInfo);
1201    log("Start Transform "+map.getUrl());
1202    StructureMapGroupComponent g = map.getGroup().get(0);
1203
1204                Variables vars = new Variables();
1205                vars.add(VariableMode.INPUT, getInputName(g, StructureMapInputMode.SOURCE, "source"), source);
1206                vars.add(VariableMode.OUTPUT, getInputName(g, StructureMapInputMode.TARGET, "target"), target);
1207
1208    executeGroup("", context, map, vars, g, true);
1209    if (target instanceof Element)
1210      ((Element) target).sort();
1211        }
1212
1213        private String getInputName(StructureMapGroupComponent g, StructureMapInputMode mode, String def) throws DefinitionException {
1214          String name = null;
1215    for (StructureMapGroupInputComponent inp : g.getInput()) {
1216      if (inp.getMode() == mode)
1217        if (name != null)
1218          throw new DefinitionException("This engine does not support multiple source inputs");
1219        else
1220          name = inp.getName();
1221    }
1222    return name == null ? def : name;
1223        }
1224
1225        private void executeGroup(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, boolean atRoot) throws FHIRException {
1226                log(indent+"Group : "+group.getName());
1227    // todo: check inputs
1228                if (group.hasExtends()) {
1229                  ResolvedGroup rg = resolveGroupReference(map, group, group.getExtends());
1230                  executeGroup(indent+" ", context, rg.targetMap, vars, rg.target, false); 
1231                }
1232                  
1233                for (StructureMapGroupRuleComponent r : group.getRule()) {
1234                        executeRule(indent+"  ", context, map, vars, group, r, atRoot);
1235                }
1236        }
1237
1238        private void executeRule(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, boolean atRoot) throws FHIRException {
1239                log(indent+"rule : "+rule.getName());
1240                if (rule.getName().contains("CarePlan.participant-unlink"))
1241                  System.out.println("debug");
1242                Variables srcVars = vars.copy();
1243                if (rule.getSource().size() != 1)
1244                        throw new FHIRException("Rule \""+rule.getName()+"\": not handled yet");
1245                List<Variables> source = processSource(rule.getName(), context, srcVars, rule.getSource().get(0), map.getUrl());
1246                if (source != null) {
1247                        for (Variables v : source) {
1248                                for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) {
1249                                        processTarget(rule.getName(), context, v, map, group, t, rule.getSource().size() == 1 ? rule.getSourceFirstRep().getVariable() : null, atRoot);
1250                                }
1251                                if (rule.hasRule()) {
1252                                        for (StructureMapGroupRuleComponent childrule : rule.getRule()) {
1253                                                executeRule(indent +"  ", context, map, v, group, childrule, false);
1254                                        }
1255                                } else if (rule.hasDependent()) {
1256                                        for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) {
1257                                                executeDependency(indent+"  ", context, map, v, group, dependent);
1258                                        }
1259                                } else if (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasVariable() && rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasVariable() && rule.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE && !rule.getTargetFirstRep().hasParameter()) {
1260                                  // simple inferred, map by type
1261                                  Base src = v.get(VariableMode.INPUT, rule.getSourceFirstRep().getVariable());
1262                                  Base tgt = v.get(VariableMode.OUTPUT, rule.getTargetFirstRep().getVariable());
1263                                  String srcType = src.fhirType();
1264                                  String tgtType = tgt.fhirType();
1265                                  ResolvedGroup defGroup = resolveGroupByTypes(map, rule.getName(), group, srcType, tgtType);
1266                            Variables vdef = new Variables();
1267          vdef.add(VariableMode.INPUT, defGroup.target.getInput().get(0).getName(), src);
1268          vdef.add(VariableMode.OUTPUT, defGroup.target.getInput().get(1).getName(), tgt);
1269                                  executeGroup(indent+"  ", context, defGroup.targetMap, vdef, defGroup.target, false);
1270                                }
1271                        }
1272                }
1273        }
1274
1275        private void executeDependency(String indent, TransformContext context, StructureMap map, Variables vin, StructureMapGroupComponent group, StructureMapGroupRuleDependentComponent dependent) throws FHIRException {
1276          ResolvedGroup rg = resolveGroupReference(map, group, dependent.getName());
1277
1278                if (rg.target.getInput().size() != dependent.getVariable().size()) {
1279                        throw new FHIRException("Rule '"+dependent.getName()+"' has "+Integer.toString(rg.target.getInput().size())+" but the invocation has "+Integer.toString(dependent.getVariable().size())+" variables");
1280                }
1281                Variables v = new Variables();
1282                for (int i = 0; i < rg.target.getInput().size(); i++) {
1283                        StructureMapGroupInputComponent input = rg.target.getInput().get(i);
1284                        StringType rdp = dependent.getVariable().get(i);
1285      String var = rdp.asStringValue();
1286                        VariableMode mode = input.getMode() == StructureMapInputMode.SOURCE ? VariableMode.INPUT :   VariableMode.OUTPUT; 
1287                        Base vv = vin.get(mode, var);
1288      if (vv == null && mode == VariableMode.INPUT) //* once source, always source. but target can be treated as source at user convenient
1289        vv = vin.get(VariableMode.OUTPUT, var);
1290                        if (vv == null)
1291                                throw new FHIRException("Rule '"+dependent.getName()+"' "+mode.toString()+" variable '"+input.getName()+"' named as '"+var+"' has no value");
1292                        v.add(mode, input.getName(), vv);       
1293                }
1294                executeGroup(indent+"  ", context, rg.targetMap, v, rg.target, false);
1295        }
1296
1297  private String determineTypeFromSourceType(StructureMap map, StructureMapGroupComponent source, Base base, String[] types) throws FHIRException {
1298    String type = base.fhirType();
1299    String kn = "type^"+type;
1300    if (source.hasUserData(kn))
1301      return source.getUserString(kn);
1302    
1303    ResolvedGroup res = new ResolvedGroup();
1304    res.targetMap = null;
1305    res.target = null;
1306    for (StructureMapGroupComponent grp : map.getGroup()) {
1307      if (matchesByType(map, grp, type)) {
1308        if (res.targetMap == null) {
1309          res.targetMap = map;
1310          res.target = grp;
1311        } else 
1312          throw new FHIRException("Multiple possible matches looking for default rule for '"+type+"'");
1313      }
1314    }
1315    if (res.targetMap != null) {
1316      String result = getActualType(res.targetMap, res.target.getInput().get(1).getType());
1317      source.setUserData(kn, result);
1318      return result;
1319    }
1320
1321    for (UriType imp : map.getImport()) {
1322      List<StructureMap> impMapList = findMatchingMaps(imp.getValue());
1323      if (impMapList.size() == 0)
1324        throw new FHIRException("Unable to find map(s) for "+imp.getValue());
1325      for (StructureMap impMap : impMapList) {
1326        if (!impMap.getUrl().equals(map.getUrl())) {
1327          for (StructureMapGroupComponent grp : impMap.getGroup()) {
1328            if (matchesByType(impMap, grp, type)) {
1329              if (res.targetMap == null) {
1330                res.targetMap = impMap;
1331                res.target = grp;
1332              } else 
1333                throw new FHIRException("Multiple possible matches for default rule for '"+type+"' in "+res.targetMap.getUrl()+" ("+res.target.getName()+") and "+impMap.getUrl()+" ("+grp.getName()+")");
1334            }
1335          }
1336        }
1337      }
1338    }
1339    if (res.target == null)
1340      throw new FHIRException("No matches found for default rule for '"+type+"' from "+map.getUrl());
1341    String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); // should be .getType, but R2...
1342    source.setUserData(kn, result);
1343    return result;
1344  }
1345
1346  private List<StructureMap> findMatchingMaps(String value) {
1347    List<StructureMap> res = new ArrayList<StructureMap>();
1348    if (value.contains("*")) {
1349      for (StructureMap sm : worker.listTransforms()) {
1350        if (urlMatches(value, sm.getUrl())) {
1351          res.add(sm); 
1352        }
1353      }
1354    } else {
1355      StructureMap sm = worker.getTransform(value);
1356      if (sm != null)
1357        res.add(sm); 
1358    }
1359    Set<String> check = new HashSet<String>();
1360    for (StructureMap sm : res) {
1361      if (check.contains(sm.getUrl()))
1362        throw new Error("duplicate");
1363      else
1364        check.add(sm.getUrl());
1365    }
1366    return res;
1367  }
1368
1369  private boolean urlMatches(String mask, String url) {
1370    return url.length() > mask.length() && url.startsWith(mask.substring(0, mask.indexOf("*"))) && url.endsWith(mask.substring(mask.indexOf("*")+1)) ;
1371  }
1372
1373  private ResolvedGroup resolveGroupByTypes(StructureMap map, String ruleid, StructureMapGroupComponent source, String srcType, String tgtType) throws FHIRException {
1374    String kn = "types^"+srcType+":"+tgtType;
1375    if (source.hasUserData(kn))
1376      return (ResolvedGroup) source.getUserData(kn);
1377    
1378    ResolvedGroup res = new ResolvedGroup();
1379    res.targetMap = null;
1380    res.target = null;
1381    for (StructureMapGroupComponent grp : map.getGroup()) {
1382      if (matchesByType(map, grp, srcType, tgtType)) {
1383        if (res.targetMap == null) {
1384          res.targetMap = map;
1385          res.target = grp;
1386        } else 
1387          throw new FHIRException("Multiple possible matches looking for rule for '"+srcType+"/"+tgtType+"', from rule '"+ruleid+"'");
1388      }
1389    }
1390    if (res.targetMap != null) {
1391      source.setUserData(kn, res);
1392      return res;
1393    }
1394
1395    for (UriType imp : map.getImport()) {
1396      List<StructureMap> impMapList = findMatchingMaps(imp.getValue());
1397      if (impMapList.size() == 0)
1398        throw new FHIRException("Unable to find map(s) for "+imp.getValue());
1399      for (StructureMap impMap : impMapList) {
1400        if (!impMap.getUrl().equals(map.getUrl())) {
1401          for (StructureMapGroupComponent grp : impMap.getGroup()) {
1402            if (matchesByType(impMap, grp, srcType, tgtType)) {
1403              if (res.targetMap == null) {
1404                res.targetMap = impMap;
1405                res.target = grp;
1406              } else 
1407                throw new FHIRException("Multiple possible matches for rule for '"+srcType+"/"+tgtType+"' in "+res.targetMap.getUrl()+" and "+impMap.getUrl()+", from rule '"+ruleid+"'");
1408            }
1409          }
1410        }
1411      }
1412    }
1413    if (res.target == null)
1414      throw new FHIRException("No matches found for rule for '"+srcType+" to "+tgtType+"' from "+map.getUrl()+", from rule '"+ruleid+"'");
1415    source.setUserData(kn, res);
1416    return res;
1417  }
1418
1419
1420  private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String type) throws FHIRException {
1421    if (grp.getTypeMode() != StructureMapGroupTypeMode.TYPEANDTYPES)
1422      return false;
1423    if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET)
1424      return false;
1425    return matchesType(map, type, grp.getInput().get(0).getType());
1426  }
1427
1428  private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String srcType, String tgtType) throws FHIRException {
1429    if (grp.getTypeMode() == StructureMapGroupTypeMode.NONE)
1430      return false;
1431    if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET)
1432      return false;
1433    if (!grp.getInput().get(0).hasType() || !grp.getInput().get(1).hasType())
1434      return false;
1435    return matchesType(map, srcType, grp.getInput().get(0).getType()) && matchesType(map, tgtType, grp.getInput().get(1).getType());
1436  }
1437
1438  private boolean matchesType(StructureMap map, String actualType, String statedType) throws FHIRException {
1439    // check the aliases
1440    for (StructureMapStructureComponent imp : map.getStructure()) {
1441      if (imp.hasAlias() && statedType.equals(imp.getAlias())) {
1442        StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl());
1443        if (sd != null)
1444          statedType = sd.getType();
1445        break;
1446      }
1447    }
1448    
1449    if (Utilities.isAbsoluteUrl(actualType)) {
1450      StructureDefinition sd = worker.fetchResource(StructureDefinition.class, actualType);
1451      if (sd != null)
1452        actualType = sd.getType();
1453    }
1454    if (Utilities.isAbsoluteUrl(statedType)) {
1455      StructureDefinition sd = worker.fetchResource(StructureDefinition.class, statedType);
1456      if (sd != null)
1457        statedType = sd.getType();
1458    }
1459    return actualType.equals(statedType);
1460  }
1461
1462  private String getActualType(StructureMap map, String statedType) throws FHIRException {
1463    // check the aliases
1464    for (StructureMapStructureComponent imp : map.getStructure()) {
1465      if (imp.hasAlias() && statedType.equals(imp.getAlias())) {
1466        StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl());
1467        if (sd == null)
1468          throw new FHIRException("Unable to resolve structure "+imp.getUrl());
1469        return sd.getId(); // should be sd.getType(), but R2...
1470      }
1471    }
1472    return statedType;
1473  }
1474
1475
1476  private ResolvedGroup resolveGroupReference(StructureMap map, StructureMapGroupComponent source, String name) throws FHIRException {
1477    String kn = "ref^"+name;
1478    if (source.hasUserData(kn))
1479      return (ResolvedGroup) source.getUserData(kn);
1480    
1481          ResolvedGroup res = new ResolvedGroup();
1482    res.targetMap = null;
1483    res.target = null;
1484    for (StructureMapGroupComponent grp : map.getGroup()) {
1485      if (grp.getName().equals(name)) {
1486        if (res.targetMap == null) {
1487          res.targetMap = map;
1488          res.target = grp;
1489        } else 
1490          throw new FHIRException("Multiple possible matches for rule '"+name+"'");
1491      }
1492    }
1493    if (res.targetMap != null) {
1494      source.setUserData(kn, res);
1495      return res;
1496    }
1497
1498    for (UriType imp : map.getImport()) {
1499      List<StructureMap> impMapList = findMatchingMaps(imp.getValue());
1500      if (impMapList.size() == 0)
1501        throw new FHIRException("Unable to find map(s) for "+imp.getValue());
1502      for (StructureMap impMap : impMapList) {
1503        if (!impMap.getUrl().equals(map.getUrl())) {
1504          for (StructureMapGroupComponent grp : impMap.getGroup()) {
1505            if (grp.getName().equals(name)) {
1506              if (res.targetMap == null) {
1507                res.targetMap = impMap;
1508                res.target = grp;
1509              } else 
1510                throw new FHIRException("Multiple possible matches for rule '"+name+"' in "+res.targetMap.getUrl()+" and "+impMap.getUrl());
1511            }
1512          }
1513        }
1514      }
1515    }
1516    if (res.target == null)
1517      throw new FHIRException("No matches found for rule '"+name+"'. Reference found in "+map.getUrl());
1518    source.setUserData(kn, res);
1519    return res;
1520  }
1521
1522  private List<Variables> processSource(String ruleId, TransformContext context, Variables vars, StructureMapGroupRuleSourceComponent src, String pathForErrors) throws FHIRException {
1523    List<Base> items;
1524    if (src.getContext().equals("@search")) {
1525      ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_SEARCH_EXPRESSION);
1526      if (expr == null) {
1527        expr = fpe.parse(src.getElement());
1528        src.setUserData(MAP_SEARCH_EXPRESSION, expr);
1529      }
1530      String search = fpe.evaluateToString(vars, null, new StringType(), expr); // string is a holder of nothing to ensure that variables are processed correctly 
1531      items = services.performSearch(context.appInfo, search);
1532    } else {
1533      items = new ArrayList<Base>();
1534      Base b = vars.get(VariableMode.INPUT, src.getContext());
1535      if (b == null)
1536        throw new FHIRException("Unknown input variable "+src.getContext()+" in "+pathForErrors+" rule "+ruleId+" (vars = "+vars.summary()+")");
1537
1538      if (!src.hasElement()) 
1539        items.add(b);
1540      else { 
1541        getChildrenByName(b, src.getElement(), items);
1542        if (items.size() == 0 && src.hasDefaultValue())
1543          items.add(src.getDefaultValue());
1544      }
1545    }
1546    
1547                if (src.hasType()) {
1548            List<Base> remove = new ArrayList<Base>();
1549            for (Base item : items) {
1550              if (item != null && !isType(item, src.getType())) {
1551                remove.add(item);
1552              }
1553            }
1554            items.removeAll(remove);
1555                }
1556
1557    if (src.hasCondition()) {
1558      ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_EXPRESSION);
1559      if (expr == null) {
1560        expr = fpe.parse(src.getCondition());
1561        //        fpe.check(context.appInfo, ??, ??, expr)
1562        src.setUserData(MAP_WHERE_EXPRESSION, expr);
1563      }
1564      List<Base> remove = new ArrayList<Base>();
1565      for (Base item : items) {
1566        if (!fpe.evaluateToBoolean(vars, null, item, expr))
1567          remove.add(item);
1568      }
1569      items.removeAll(remove);
1570    }
1571
1572    if (src.hasCheck()) {
1573      ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_CHECK);
1574      if (expr == null) {
1575        expr = fpe.parse(src.getCheck());
1576        //        fpe.check(context.appInfo, ??, ??, expr)
1577        src.setUserData(MAP_WHERE_CHECK, expr);
1578      }
1579      List<Base> remove = new ArrayList<Base>();
1580      for (Base item : items) {
1581        if (!fpe.evaluateToBoolean(vars, null, item, expr))
1582          throw new FHIRException("Rule \""+ruleId+"\": Check condition failed");
1583      }
1584    } 
1585
1586    if (src.hasLogMessage()) {
1587      ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_LOG);
1588      if (expr == null) {
1589        expr = fpe.parse(src.getLogMessage());
1590        //        fpe.check(context.appInfo, ??, ??, expr)
1591        src.setUserData(MAP_WHERE_LOG, expr);
1592      }
1593      CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1594      for (Base item : items) 
1595        b.appendIfNotNull(fpe.evaluateToString(vars, null, item, expr));
1596      if (b.length() > 0)
1597        services.log(b.toString());
1598    } 
1599
1600                
1601                if (src.hasListMode() && !items.isEmpty()) {
1602                  switch (src.getListMode()) {
1603                  case FIRST:
1604                    Base bt = items.get(0);
1605        items.clear();
1606        items.add(bt);
1607                    break;
1608                  case NOTFIRST: 
1609        if (items.size() > 0)
1610          items.remove(0);
1611        break;
1612                  case LAST:
1613        bt = items.get(items.size()-1);
1614        items.clear();
1615        items.add(bt);
1616        break;
1617                  case NOTLAST: 
1618        if (items.size() > 0)
1619          items.remove(items.size()-1);
1620        break;
1621                  case ONLYONE:
1622                    if (items.size() > 1)
1623          throw new FHIRException("Rule \""+ruleId+"\": Check condition failed: the collection has more than one item");
1624        break;
1625      case NULL:
1626                  }
1627                }
1628                List<Variables> result = new ArrayList<Variables>();
1629                for (Base r : items) {
1630                        Variables v = vars.copy();
1631                        if (src.hasVariable())
1632                                v.add(VariableMode.INPUT, src.getVariable(), r);
1633                        result.add(v); 
1634                }
1635                return result;
1636        }
1637
1638
1639        private boolean isType(Base item, String type) {
1640    if (type.equals(item.fhirType()))
1641      return true;
1642    return false;
1643  }
1644
1645  private void processTarget(String ruleId, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar, boolean atRoot) throws FHIRException {
1646          Base dest = null;
1647          if (tgt.hasContext()) {
1648                dest = vars.get(VariableMode.OUTPUT, tgt.getContext());
1649                if (dest == null)
1650                        throw new FHIRException("Rule \""+ruleId+"\": target context not known: "+tgt.getContext());
1651                if (!tgt.hasElement())
1652                        throw new FHIRException("Rule \""+ruleId+"\": Not supported yet");
1653          }
1654                Base v = null;
1655                if (tgt.hasTransform()) {
1656                        v = runTransform(ruleId, context, map, group, tgt, vars, dest, tgt.getElement(), srcVar, atRoot);
1657                        if (v != null && dest != null)
1658                                v = dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); // reset v because some implementations may have to rewrite v when setting the value
1659                } else if (dest != null) 
1660                        v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement());
1661                if (tgt.hasVariable() && v != null)
1662                        vars.add(VariableMode.OUTPUT, tgt.getVariable(), v);
1663        }
1664
1665        private Base runTransform(String ruleId, TransformContext context, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, Variables vars, Base dest, String element, String srcVar, boolean root) throws FHIRException {
1666          try {
1667            switch (tgt.getTransform()) {
1668            case CREATE :
1669              String tn;
1670              if (tgt.getParameter().isEmpty()) {
1671                // we have to work out the type. First, we see if there is a single type for the target. If there is, we use that
1672                String[] types = dest.getTypesForProperty(element.hashCode(), element);
1673                if (types.length == 1 && !"*".equals(types[0]) && !types[0].equals("Resource"))
1674                  tn = types[0];
1675                else if (srcVar != null) {
1676                  tn = determineTypeFromSourceType(map, group, vars.get(VariableMode.INPUT, srcVar), types);
1677                } else
1678                  throw new Error("Cannot determine type implicitly because there is no single input variable");
1679              } else {
1680                tn = getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString());
1681                // ok, now we resolve the type name against the import statements 
1682                for (StructureMapStructureComponent uses : map.getStructure()) {
1683                  if (uses.getMode() == StructureMapModelMode.TARGET && uses.hasAlias() && tn.equals(uses.getAlias())) {
1684                    tn = uses.getUrl();
1685                    break;
1686                  }
1687                }
1688              }
1689              Base res = services != null ? services.createType(context.getAppInfo(), tn) : ResourceFactory.createResourceOrType(tn);
1690              if (res.isResource() && !res.fhirType().equals("Parameters")) {
1691//              res.setIdBase(tgt.getParameter().size() > 1 ? getParamString(vars, tgt.getParameter().get(0)) : UUID.randomUUID().toString().toLowerCase());
1692                if (services != null) 
1693                  res = services.createResource(context.getAppInfo(), res, root);
1694              }
1695              if (tgt.hasUserData("profile"))
1696                res.setUserData("profile", tgt.getUserData("profile"));
1697              return res;
1698            case COPY : 
1699              return getParam(vars, tgt.getParameter().get(0));
1700            case EVALUATE :
1701              ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION);
1702              if (expr == null) {
1703                expr = fpe.parse(getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()));
1704                tgt.setUserData(MAP_WHERE_EXPRESSION, expr);
1705              }
1706              List<Base> v = fpe.evaluate(vars, null, tgt.getParameter().size() == 2 ? getParam(vars, tgt.getParameter().get(0)) : new BooleanType(false), expr);
1707              if (v.size() == 0)
1708                return null;
1709              else if (v.size() != 1)
1710                throw new FHIRException("Rule \""+ruleId+"\": Evaluation of "+expr.toString()+" returned "+Integer.toString(v.size())+" objects");
1711              else
1712                return v.get(0);
1713
1714            case TRUNCATE : 
1715              String src = getParamString(vars, tgt.getParameter().get(0));
1716              String len = getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString());
1717              if (Utilities.isInteger(len)) {
1718                int l = Integer.parseInt(len);
1719                if (src.length() > l)
1720                  src = src.substring(0, l);
1721              }
1722              return new StringType(src);
1723            case ESCAPE : 
1724              throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet");
1725            case CAST :
1726        src = getParamString(vars, tgt.getParameter().get(0));
1727        if (tgt.getParameter().size() == 1)
1728          throw new FHIRException("Implicit type parameters on cast not yet supported");
1729        String t = getParamString(vars, tgt.getParameter().get(1));
1730        if (t.equals("string"))
1731          return new StringType(src);
1732        else
1733          throw new FHIRException("cast to "+t+" not yet supported");
1734            case APPEND : 
1735        StringBuilder sb = new StringBuilder(getParamString(vars, tgt.getParameter().get(0)));
1736        for (int i = 1; i < tgt.getParameter().size(); i++)
1737          sb.append(getParamString(vars, tgt.getParameter().get(1)));
1738        return new StringType(sb.toString());
1739            case TRANSLATE : 
1740              return translate(context, map, vars, tgt.getParameter());
1741            case REFERENCE :
1742              Base b = getParam(vars, tgt.getParameter().get(0));
1743              if (b == null)
1744                throw new FHIRException("Rule \""+ruleId+"\": Unable to find parameter "+((IdType) tgt.getParameter().get(0).getValue()).asStringValue());
1745              if (!b.isResource())
1746                throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType());
1747              else {
1748                String id = b.getIdBase();
1749                if (id == null) {
1750                  id = UUID.randomUUID().toString().toLowerCase();
1751                  b.setIdBase(id);
1752                }
1753                return new Reference().setReference(b.fhirType()+"/"+id);
1754              }
1755            case DATEOP :
1756              throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet");
1757            case UUID :
1758              return new IdType(UUID.randomUUID().toString());
1759            case POINTER :
1760              b = getParam(vars, tgt.getParameter().get(0));
1761              if (b instanceof Resource)
1762                return new UriType("urn:uuid:"+((Resource) b).getId());
1763              else
1764                throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType());
1765            case CC:
1766              CodeableConcept cc = new CodeableConcept();
1767              cc.addCoding(buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())));
1768              return cc;
1769            case C: 
1770              Coding c = buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()));
1771              return c;
1772            default:
1773              throw new Error("Rule \""+ruleId+"\": Transform Unknown: "+tgt.getTransform().toCode());
1774            }
1775          } catch (Exception e) {
1776            throw new FHIRException("Exception executing transform "+tgt.toString()+" on Rule \""+ruleId+"\": "+e.getMessage(), e);
1777          }
1778        }
1779
1780
1781  private Coding buildCoding(String uri, String code) throws FHIRException {
1782          // if we can get this as a valueSet, we will
1783          String system = null;
1784          String display = null;
1785          ValueSet vs = Utilities.noString(uri) ? null : worker.fetchResourceWithException(ValueSet.class, uri);
1786          if (vs != null) {
1787            ValueSetExpansionOutcome vse = worker.expandVS(vs, true, false);
1788            if (vse.getError() != null)
1789              throw new FHIRException(vse.getError());
1790            CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1791            for (ValueSetExpansionContainsComponent t : vse.getValueset().getExpansion().getContains()) {
1792              if (t.hasCode())
1793                b.append(t.getCode());
1794              if (code.equals(t.getCode()) && t.hasSystem()) {
1795                system = t.getSystem();
1796          display = t.getDisplay();
1797                break;
1798              }
1799        if (code.equalsIgnoreCase(t.getDisplay()) && t.hasSystem()) {
1800          system = t.getSystem();
1801          display = t.getDisplay();
1802          break;
1803        }
1804            }
1805            if (system == null)
1806              throw new FHIRException("The code '"+code+"' is not in the value set '"+uri+"' (valid codes: "+b.toString()+"; also checked displays)");
1807          } else
1808            system = uri;
1809          ValidationResult vr = worker.validateCode(system, code, null);
1810          if (vr != null && vr.getDisplay() != null)
1811            display = vr.getDisplay();
1812   return new Coding().setSystem(system).setCode(code).setDisplay(display);
1813  }
1814
1815
1816  private String getParamStringNoNull(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter, String message) throws FHIRException {
1817    Base b = getParam(vars, parameter);
1818    if (b == null)
1819      throw new FHIRException("Unable to find a value for "+parameter.toString()+". Context: "+message);
1820    if (!b.hasPrimitiveValue())
1821      throw new FHIRException("Found a value for "+parameter.toString()+", but it has a type of "+b.fhirType()+" and cannot be treated as a string. Context: "+message);
1822    return b.primitiveValue();
1823  }
1824
1825  private String getParamString(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException {
1826                Base b = getParam(vars, parameter);
1827                if (b == null || !b.hasPrimitiveValue())
1828                        return null;
1829                return b.primitiveValue();
1830        }
1831
1832
1833        private Base getParam(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException {
1834                Type p = parameter.getValue();
1835                if (!(p instanceof IdType))
1836                        return p;
1837                else { 
1838                        String n = ((IdType) p).asStringValue();
1839      Base b = vars.get(VariableMode.INPUT, n);
1840                        if (b == null)
1841                                b = vars.get(VariableMode.OUTPUT, n);
1842                        if (b == null)
1843        throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")");
1844                        return b;
1845                }
1846        }
1847
1848
1849        private Base translate(TransformContext context, StructureMap map, Variables vars, List<StructureMapGroupRuleTargetParameterComponent> parameter) throws FHIRException {
1850                Base src = getParam(vars, parameter.get(0));
1851                String id = getParamString(vars, parameter.get(1));
1852                String fld = parameter.size() > 2 ? getParamString(vars, parameter.get(2)) : null;
1853                return translate(context, map, src, id, fld);
1854        }
1855
1856        private class SourceElementComponentWrapper {
1857          private ConceptMapGroupComponent group;
1858    private SourceElementComponent comp;
1859    public SourceElementComponentWrapper(ConceptMapGroupComponent group, SourceElementComponent comp) {
1860      super();
1861      this.group = group;
1862      this.comp = comp;
1863    }
1864        }
1865        public Base translate(TransformContext context, StructureMap map, Base source, String conceptMapUrl, String fieldToReturn) throws FHIRException {
1866                Coding src = new Coding();
1867                if (source.isPrimitive()) {
1868                        src.setCode(source.primitiveValue());
1869                } else if ("Coding".equals(source.fhirType())) {
1870                        Base[] b = source.getProperty("system".hashCode(), "system", true);
1871                        if (b.length == 1)
1872                                src.setSystem(b[0].primitiveValue());
1873                        b = source.getProperty("code".hashCode(), "code", true);
1874                        if (b.length == 1)
1875                                src.setCode(b[0].primitiveValue());
1876                } else if ("CE".equals(source.fhirType())) {
1877                        Base[] b = source.getProperty("codeSystem".hashCode(), "codeSystem", true);
1878                        if (b.length == 1)
1879                                src.setSystem(b[0].primitiveValue());
1880                        b = source.getProperty("code".hashCode(), "code", true);
1881                        if (b.length == 1)
1882                                src.setCode(b[0].primitiveValue());
1883                } else
1884                        throw new FHIRException("Unable to translate source "+source.fhirType());
1885
1886                String su = conceptMapUrl;
1887                if (conceptMapUrl.equals("http://hl7.org/fhir/ConceptMap/special-oid2uri")) {
1888                        String uri = worker.oid2Uri(src.getCode());
1889                        if (uri == null)
1890                                uri = "urn:oid:"+src.getCode();
1891                        if ("uri".equals(fieldToReturn))
1892                                return new UriType(uri);
1893                        else
1894                                throw new FHIRException("Error in return code");
1895                } else {
1896                        ConceptMap cmap = null;
1897                        if (conceptMapUrl.startsWith("#")) {
1898                                for (Resource r : map.getContained()) {
1899                                        if (r instanceof ConceptMap && ((ConceptMap) r).getId().equals(conceptMapUrl.substring(1))) {
1900                                                cmap = (ConceptMap) r;
1901                                                su = map.getUrl()+conceptMapUrl;
1902                                        }
1903                                }
1904                                if (cmap == null)
1905                      throw new FHIRException("Unable to translate - cannot find map "+conceptMapUrl);
1906                        } else
1907                                cmap = worker.fetchResource(ConceptMap.class, conceptMapUrl);
1908                        Coding outcome = null;
1909                        boolean done = false;
1910                        String message = null;
1911                        if (cmap == null) {
1912                                if (services == null) 
1913                                        message = "No map found for "+conceptMapUrl;
1914                                else {
1915                                        outcome = services.translate(context.appInfo, src, conceptMapUrl);
1916                                        done = true;
1917                                }
1918                        } else {
1919                          List<SourceElementComponentWrapper> list = new ArrayList<SourceElementComponentWrapper>();
1920                          for (ConceptMapGroupComponent g : cmap.getGroup()) {
1921                            for (SourceElementComponent e : g.getElement()) {
1922                                                if (!src.hasSystem() && src.getCode().equals(e.getCode())) 
1923                                list.add(new SourceElementComponentWrapper(g, e));
1924                              else if (src.hasSystem() && src.getSystem().equals(g.getSource()) && src.getCode().equals(e.getCode()))
1925                                list.add(new SourceElementComponentWrapper(g, e));
1926                            }
1927                                }
1928                                if (list.size() == 0)
1929                                        done = true;
1930                                else if (list.get(0).comp.getTarget().size() == 0)
1931                                        message = "Concept map "+su+" found no translation for "+src.getCode();
1932                                else {
1933                                        for (TargetElementComponent tgt : list.get(0).comp.getTarget()) {
1934                                                if (tgt.getEquivalence() == null || EnumSet.of( ConceptMapEquivalence.EQUAL , ConceptMapEquivalence.RELATEDTO , ConceptMapEquivalence.EQUIVALENT, ConceptMapEquivalence.WIDER).contains(tgt.getEquivalence())) {
1935                                                        if (done) {
1936                                                                message = "Concept map "+su+" found multiple matches for "+src.getCode();
1937                                                                done = false;
1938                                                        } else {
1939                                                                done = true;
1940                                                                outcome = new Coding().setCode(tgt.getCode()).setSystem(list.get(0).group.getTarget());
1941                                                        }
1942                                                } else if (tgt.getEquivalence() == ConceptMapEquivalence.UNMATCHED) {
1943                                                        done = true;
1944                                                }
1945                                        }
1946                                        if (!done)
1947                                                message = "Concept map "+su+" found no usable translation for "+src.getCode();
1948                                }
1949                        }
1950                        if (!done) 
1951                                throw new FHIRException(message);
1952                        if (outcome == null)
1953                                return null;
1954                        if ("code".equals(fieldToReturn))
1955                                return new CodeType(outcome.getCode());
1956                        else
1957                                return outcome; 
1958                }
1959        }
1960
1961
1962        public class PropertyWithType {
1963    private String path;
1964    private Property baseProperty;
1965    private Property profileProperty;
1966          private TypeDetails types;
1967    public PropertyWithType(String path, Property baseProperty, Property profileProperty, TypeDetails types) {
1968      super();
1969      this.baseProperty = baseProperty;
1970      this.profileProperty = profileProperty;
1971      this.path = path;
1972      this.types = types;
1973    }
1974
1975    public TypeDetails getTypes() {
1976      return types;
1977    }
1978    public String getPath() {
1979      return path;
1980    }
1981
1982    public Property getBaseProperty() {
1983      return baseProperty;
1984    }
1985
1986    public void setBaseProperty(Property baseProperty) {
1987      this.baseProperty = baseProperty;
1988    }
1989
1990    public Property getProfileProperty() {
1991      return profileProperty;
1992    }
1993
1994    public void setProfileProperty(Property profileProperty) {
1995      this.profileProperty = profileProperty;
1996    }
1997
1998    public String summary() {
1999      return path;
2000    }
2001    
2002        }
2003        
2004        public class VariableForProfiling {
2005            private VariableMode mode;
2006            private String name;
2007            private PropertyWithType property;
2008            
2009            public VariableForProfiling(VariableMode mode, String name, PropertyWithType property) {
2010              super();
2011              this.mode = mode;
2012              this.name = name;
2013              this.property = property;
2014            }
2015            public VariableMode getMode() {
2016              return mode;
2017            }
2018            public String getName() {
2019              return name;
2020            }
2021      public PropertyWithType getProperty() {
2022        return property;
2023      }
2024      public String summary() {
2025        return name+": "+property.summary();
2026      }      
2027          }
2028
2029  public class VariablesForProfiling {
2030    private List<VariableForProfiling> list = new ArrayList<VariableForProfiling>();
2031    private boolean optional;
2032    private boolean repeating;
2033
2034    public VariablesForProfiling(boolean optional, boolean repeating) {
2035      this.optional = optional;
2036      this.repeating = repeating;
2037    }
2038
2039    public void add(VariableMode mode, String name, String path, Property property, TypeDetails types) {
2040      add(mode, name, new PropertyWithType(path, property, null, types));
2041    }
2042    
2043    public void add(VariableMode mode, String name, String path, Property baseProperty, Property profileProperty, TypeDetails types) {
2044      add(mode, name, new PropertyWithType(path, baseProperty, profileProperty, types));
2045    }
2046    
2047    public void add(VariableMode mode, String name, PropertyWithType property) {
2048      VariableForProfiling vv = null;
2049      for (VariableForProfiling v : list) 
2050        if ((v.mode == mode) && v.getName().equals(name))
2051          vv = v;
2052      if (vv != null)
2053        list.remove(vv);
2054      list.add(new VariableForProfiling(mode, name, property));
2055    }
2056
2057    public VariablesForProfiling copy(boolean optional, boolean repeating) {
2058      VariablesForProfiling result = new VariablesForProfiling(optional, repeating);
2059      result.list.addAll(list);
2060      return result;
2061    }
2062
2063    public VariablesForProfiling copy() {
2064      VariablesForProfiling result = new VariablesForProfiling(optional, repeating);
2065      result.list.addAll(list);
2066      return result;
2067    }
2068
2069    public VariableForProfiling get(VariableMode mode, String name) {
2070      if (mode == null) {
2071        for (VariableForProfiling v : list) 
2072          if ((v.mode == VariableMode.OUTPUT) && v.getName().equals(name))
2073            return v;
2074        for (VariableForProfiling v : list) 
2075          if ((v.mode == VariableMode.INPUT) && v.getName().equals(name))
2076            return v;
2077      }
2078      for (VariableForProfiling v : list) 
2079        if ((v.mode == mode) && v.getName().equals(name))
2080          return v;
2081      return null;
2082    }
2083
2084    public String summary() {
2085      CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder();
2086      CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder();
2087      for (VariableForProfiling v : list)
2088        if (v.mode == VariableMode.INPUT)
2089          s.append(v.summary());
2090        else
2091          t.append(v.summary());
2092      return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]";
2093    }
2094  }
2095
2096  public class StructureMapAnalysis {
2097    private List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
2098    private XhtmlNode summary;
2099    public List<StructureDefinition> getProfiles() {
2100      return profiles;
2101    }
2102    public XhtmlNode getSummary() {
2103      return summary;
2104    }
2105    
2106  }
2107
2108        /**
2109         * Given a structure map, return a set of analyses on it. 
2110         * 
2111         * Returned:
2112         *   - a list or profiles for what it will create. First profile is the target
2113         *   - a table with a summary (in xhtml) for easy human undertanding of the mapping
2114         *   
2115         * 
2116         * @param appInfo
2117         * @param map
2118         * @return
2119         * @throws Exception
2120         */
2121  public StructureMapAnalysis analyse(Object appInfo, StructureMap map) throws Exception {
2122    ids.clear();
2123    StructureMapAnalysis result = new StructureMapAnalysis(); 
2124    TransformContext context = new TransformContext(appInfo);
2125    VariablesForProfiling vars = new VariablesForProfiling(false, false);
2126    StructureMapGroupComponent start = map.getGroup().get(0);
2127    for (StructureMapGroupInputComponent t : start.getInput()) {
2128      PropertyWithType ti = resolveType(map, t.getType(), t.getMode());
2129      if (t.getMode() == StructureMapInputMode.SOURCE)
2130       vars.add(VariableMode.INPUT, t.getName(), ti);
2131      else 
2132        vars.add(VariableMode.OUTPUT, t.getName(), createProfile(map, result.profiles, ti, start.getName(), start));
2133    }
2134
2135    result.summary = new XhtmlNode(NodeType.Element, "table").setAttribute("class", "grid");
2136    XhtmlNode tr = result.summary.addTag("tr");
2137    tr.addTag("td").addTag("b").addText("Source");
2138    tr.addTag("td").addTag("b").addText("Target");
2139    
2140    log("Start Profiling Transform "+map.getUrl());
2141    analyseGroup("", context, map, vars, start, result);
2142    ProfileUtilities pu = new ProfileUtilities(worker, null, pkp);
2143    for (StructureDefinition sd : result.getProfiles())
2144      pu.cleanUpDifferential(sd);
2145    return result;
2146  }
2147
2148
2149  private void analyseGroup(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapAnalysis result) throws Exception {
2150    log(indent+"Analyse Group : "+group.getName());
2151    // todo: extends
2152    // todo: check inputs
2153    XhtmlNode tr = result.summary.addTag("tr").setAttribute("class", "diff-title");
2154    XhtmlNode xs = tr.addTag("td");
2155    XhtmlNode xt = tr.addTag("td");
2156    for (StructureMapGroupInputComponent inp : group.getInput()) {
2157      if (inp.getMode() == StructureMapInputMode.SOURCE) 
2158        noteInput(vars, inp, VariableMode.INPUT, xs);
2159      if (inp.getMode() == StructureMapInputMode.TARGET) 
2160        noteInput(vars, inp, VariableMode.OUTPUT, xt);
2161    }
2162    for (StructureMapGroupRuleComponent r : group.getRule()) {
2163      analyseRule(indent+"  ", context, map, vars, group, r, result);
2164    }    
2165  }
2166
2167
2168  private void noteInput(VariablesForProfiling vars, StructureMapGroupInputComponent inp, VariableMode mode, XhtmlNode xs) {
2169    VariableForProfiling v = vars.get(mode, inp.getName());
2170    if (v != null)
2171      xs.addText("Input: "+v.property.getPath());
2172  }
2173
2174  private void analyseRule(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, StructureMapAnalysis result) throws Exception {
2175    log(indent+"Analyse rule : "+rule.getName());
2176    XhtmlNode tr = result.summary.addTag("tr");
2177    XhtmlNode xs = tr.addTag("td");
2178    XhtmlNode xt = tr.addTag("td");
2179
2180    VariablesForProfiling srcVars = vars.copy();
2181    if (rule.getSource().size() != 1)
2182      throw new Exception("Rule \""+rule.getName()+"\": not handled yet");
2183    VariablesForProfiling source = analyseSource(rule.getName(), context, srcVars, rule.getSourceFirstRep(), xs);
2184
2185    TargetWriter tw = new TargetWriter();
2186      for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) {
2187      analyseTarget(rule.getName(), context, source, map, t, rule.getSourceFirstRep().getVariable(), tw, result.profiles, rule.getName());
2188      }
2189    tw.commit(xt);
2190
2191          for (StructureMapGroupRuleComponent childrule : rule.getRule()) {
2192      analyseRule(indent+"  ", context, map, source, group, childrule, result);
2193          }
2194//    for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) {
2195//      executeDependency(indent+"  ", context, map, v, group, dependent); // do we need group here?
2196//    }
2197          }
2198
2199  public class StringPair {
2200    private String var;
2201    private String desc;
2202    public StringPair(String var, String desc) {
2203      super();
2204      this.var = var;
2205      this.desc = desc;
2206        }
2207    public String getVar() {
2208      return var;
2209      }
2210    public String getDesc() {
2211      return desc;
2212    }
2213  }
2214  public class TargetWriter {
2215    private Map<String, String> newResources = new HashMap<String, String>();
2216    private List<StringPair> assignments = new ArrayList<StringPair>();
2217    private List<StringPair> keyProps = new ArrayList<StringPair>();
2218    private CommaSeparatedStringBuilder txt = new CommaSeparatedStringBuilder();
2219
2220    public void newResource(String var, String name) {
2221      newResources.put(var, name);
2222      txt.append("new "+name);
2223    }
2224
2225    public void valueAssignment(String context, String desc) {
2226      assignments.add(new StringPair(context, desc));      
2227      txt.append(desc);
2228        }
2229
2230    public void keyAssignment(String context, String desc) {
2231      keyProps.add(new StringPair(context, desc));      
2232      txt.append(desc);
2233    }
2234    public void commit(XhtmlNode xt) {
2235      if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 1 && newResources.containsKey(keyProps.get(0).getVar()) ) {
2236        xt.addText("new "+assignments.get(0).desc+" ("+keyProps.get(0).desc.substring(keyProps.get(0).desc.indexOf(".")+1)+")");
2237      } else if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 0) {
2238        xt.addText("new "+assignments.get(0).desc);
2239      } else {
2240        xt.addText(txt.toString());        
2241    }
2242    }
2243  }
2244
2245  private VariablesForProfiling analyseSource(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMapGroupRuleSourceComponent src, XhtmlNode td) throws Exception {
2246    VariableForProfiling var = vars.get(VariableMode.INPUT, src.getContext());
2247    if (var == null)
2248      throw new FHIRException("Rule \""+ruleId+"\": Unknown input variable "+src.getContext());
2249    PropertyWithType prop = var.getProperty();
2250
2251    boolean optional = false;
2252    boolean repeating = false;
2253
2254    if (src.hasCondition()) {
2255      optional = true;
2256    }
2257
2258    if (src.hasElement()) {
2259      Property element = prop.getBaseProperty().getChild(prop.types.getType(), src.getElement());
2260      if (element == null)
2261        throw new Exception("Rule \""+ruleId+"\": Unknown element name "+src.getElement());
2262      if (element.getDefinition().getMin() == 0)
2263        optional = true;
2264      if (element.getDefinition().getMax().equals("*"))
2265        repeating = true;
2266      VariablesForProfiling result = vars.copy(optional, repeating);
2267      TypeDetails type = new TypeDetails(CollectionStatus.SINGLETON);
2268      for (TypeRefComponent tr : element.getDefinition().getType()) {
2269        if (!tr.hasCode())
2270          throw new Error("Rule \""+ruleId+"\": Element has no type");
2271        ProfiledType pt = new ProfiledType(tr.getCode());
2272        if (tr.hasProfile())
2273          pt.addProfiles(tr.getProfile());
2274        if (element.getDefinition().hasBinding())
2275          pt.addBinding(element.getDefinition().getBinding());
2276        type.addType(pt);
2277    } 
2278      td.addText(prop.getPath()+"."+src.getElement()); 
2279      if (src.hasVariable())
2280        result.add(VariableMode.INPUT, src.getVariable(), new PropertyWithType(prop.getPath()+"."+src.getElement(), element, null, type));
2281    return result;
2282    } else {
2283      td.addText(prop.getPath()); // ditto!
2284      return vars.copy(optional, repeating);
2285    }
2286  }
2287
2288
2289  private void analyseTarget(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMap map, StructureMapGroupRuleTargetComponent tgt, String tv, TargetWriter tw, List<StructureDefinition> profiles, String sliceName) throws Exception {
2290    VariableForProfiling var = null;
2291    if (tgt.hasContext()) {
2292      var = vars.get(VariableMode.OUTPUT, tgt.getContext());
2293      if (var == null)
2294        throw new Exception("Rule \""+ruleId+"\": target context not known: "+tgt.getContext());
2295      if (!tgt.hasElement())
2296        throw new Exception("Rule \""+ruleId+"\": Not supported yet");
2297    }
2298
2299    
2300    TypeDetails type = null;
2301    if (tgt.hasTransform()) {
2302      type = analyseTransform(context, map, tgt, var, vars);
2303        // profiling: dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v);
2304    } else {
2305      Property vp = var.property.baseProperty.getChild(tgt.getElement(),  tgt.getElement());
2306      if (vp == null)
2307        throw new Exception("Unknown Property "+tgt.getElement()+" on "+var.property.path);
2308      
2309      type = new TypeDetails(CollectionStatus.SINGLETON, vp.getType(tgt.getElement()));
2310    }
2311
2312    if (tgt.getTransform() == StructureMapTransform.CREATE) {
2313      String s = getParamString(vars, tgt.getParameter().get(0));
2314      if (worker.getResourceNames().contains(s))
2315        tw.newResource(tgt.getVariable(), s);
2316    } else { 
2317      boolean mapsSrc = false;
2318      for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) {
2319        Type pr = p.getValue();
2320        if (pr instanceof IdType && ((IdType) pr).asStringValue().equals(tv)) 
2321          mapsSrc = true;
2322      }
2323      if (mapsSrc) { 
2324        if (var == null)
2325          throw new Error("Rule \""+ruleId+"\": Attempt to assign with no context");
2326        tw.valueAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+getTransformSuffix(tgt.getTransform()));
2327      } else if (tgt.hasContext()) {
2328        if (isSignificantElement(var.property, tgt.getElement())) {
2329          String td = describeTransform(tgt);
2330          if (td != null)
2331            tw.keyAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+" = "+td);
2332        }
2333      }
2334    }
2335    Type fixed = generateFixedValue(tgt);
2336    
2337    PropertyWithType prop = updateProfile(var, tgt.getElement(), type, map, profiles, sliceName, fixed, tgt);
2338    if (tgt.hasVariable())
2339      if (tgt.hasElement())
2340        vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); 
2341      else
2342        vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); 
2343  }
2344  
2345  private Type generateFixedValue(StructureMapGroupRuleTargetComponent tgt) {
2346    if (!allParametersFixed(tgt))
2347      return null;
2348    if (!tgt.hasTransform())
2349      return null;
2350    switch (tgt.getTransform()) {
2351    case COPY: return tgt.getParameter().get(0).getValue(); 
2352    case TRUNCATE: return null; 
2353    //case ESCAPE: 
2354    //case CAST: 
2355    //case APPEND: 
2356    case TRANSLATE: return null; 
2357  //case DATEOP, 
2358  //case UUID, 
2359  //case POINTER, 
2360  //case EVALUATE, 
2361    case CC: 
2362      CodeableConcept cc = new CodeableConcept();
2363      cc.addCoding(buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue()));
2364      return cc;
2365    case C: 
2366      return buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue());
2367    case QTY: return null; 
2368  //case ID, 
2369  //case CP, 
2370    default:
2371      return null;
2372    }
2373  }
2374
2375  @SuppressWarnings("rawtypes")
2376  private Coding buildCoding(Type value1, Type value2) {
2377    return new Coding().setSystem(((PrimitiveType) value1).asStringValue()).setCode(((PrimitiveType) value2).asStringValue()) ;
2378  }
2379
2380  private boolean allParametersFixed(StructureMapGroupRuleTargetComponent tgt) {
2381    for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) {
2382      Type pr = p.getValue();
2383      if (pr instanceof IdType)
2384        return false;
2385    }
2386    return true;
2387  }
2388
2389  private String describeTransform(StructureMapGroupRuleTargetComponent tgt) throws FHIRException {
2390    switch (tgt.getTransform()) {
2391    case COPY: return null; 
2392    case TRUNCATE: return null; 
2393    //case ESCAPE: 
2394    //case CAST: 
2395    //case APPEND: 
2396    case TRANSLATE: return null; 
2397  //case DATEOP, 
2398  //case UUID, 
2399  //case POINTER, 
2400  //case EVALUATE, 
2401    case CC: return describeTransformCCorC(tgt); 
2402    case C: return describeTransformCCorC(tgt); 
2403    case QTY: return null; 
2404  //case ID, 
2405  //case CP, 
2406    default:
2407      return null;
2408    }
2409  }
2410
2411  @SuppressWarnings("rawtypes")
2412  private String describeTransformCCorC(StructureMapGroupRuleTargetComponent tgt) throws FHIRException {
2413    if (tgt.getParameter().size() < 2)
2414      return null;
2415    Type p1 = tgt.getParameter().get(0).getValue();
2416    Type p2 = tgt.getParameter().get(1).getValue();
2417    if (p1 instanceof IdType || p2 instanceof IdType)
2418      return null;
2419    if (!(p1 instanceof PrimitiveType) || !(p2 instanceof PrimitiveType))
2420      return null;
2421    String uri = ((PrimitiveType) p1).asStringValue();
2422    String code = ((PrimitiveType) p2).asStringValue();
2423    if (Utilities.noString(uri))
2424      throw new FHIRException("Describe Transform, but the uri is blank");
2425    if (Utilities.noString(code))
2426      throw new FHIRException("Describe Transform, but the code is blank");
2427    Coding c = buildCoding(uri, code);
2428    return NarrativeGenerator.describeSystem(c.getSystem())+"#"+c.getCode()+(c.hasDisplay() ? "("+c.getDisplay()+")" : "");
2429  }
2430
2431
2432  private boolean isSignificantElement(PropertyWithType property, String element) {
2433    if ("Observation".equals(property.getPath()))
2434      return "code".equals(element);
2435    else if ("Bundle".equals(property.getPath()))
2436      return "type".equals(element);
2437    else
2438      return false;
2439  }
2440
2441  private String getTransformSuffix(StructureMapTransform transform) {
2442    switch (transform) {
2443    case COPY: return ""; 
2444    case TRUNCATE: return " (truncated)"; 
2445    //case ESCAPE: 
2446    //case CAST: 
2447    //case APPEND: 
2448    case TRANSLATE: return " (translated)"; 
2449  //case DATEOP, 
2450  //case UUID, 
2451  //case POINTER, 
2452  //case EVALUATE, 
2453    case CC: return " (--> CodeableConcept)"; 
2454    case C: return " (--> Coding)"; 
2455    case QTY: return " (--> Quantity)"; 
2456  //case ID, 
2457  //case CP, 
2458    default:
2459      return " {??)";
2460    }
2461  }
2462
2463  private PropertyWithType updateProfile(VariableForProfiling var, String element, TypeDetails type, StructureMap map, List<StructureDefinition> profiles, String sliceName, Type fixed, StructureMapGroupRuleTargetComponent tgt) throws FHIRException {
2464    if (var == null) {
2465      assert (Utilities.noString(element));
2466      // 1. start the new structure definition
2467      StructureDefinition sdn = worker.fetchResource(StructureDefinition.class, type.getType());
2468      if (sdn == null)
2469        throw new FHIRException("Unable to find definition for "+type.getType());
2470      ElementDefinition edn = sdn.getSnapshot().getElementFirstRep();
2471      PropertyWithType pn = createProfile(map, profiles, new PropertyWithType(sdn.getId(), new Property(worker, edn, sdn), null, type), sliceName, tgt);
2472
2473//      // 2. hook it into the base bundle
2474//      if (type.getType().startsWith("http://hl7.org/fhir/StructureDefinition/") && worker.getResourceNames().contains(type.getType().substring(40))) {
2475//        StructureDefinition sd = var.getProperty().profileProperty.getStructure();
2476//        ElementDefinition ed = sd.getDifferential().addElement();
2477//        ed.setPath("Bundle.entry");
2478//        ed.setName(sliceName);
2479//        ed.setMax("1"); // well, it is for now...
2480//        ed = sd.getDifferential().addElement();
2481//        ed.setPath("Bundle.entry.fullUrl");
2482//        ed.setMin(1);
2483//        ed = sd.getDifferential().addElement();
2484//        ed.setPath("Bundle.entry.resource");
2485//        ed.setMin(1);
2486//        ed.addType().setCode(pn.getProfileProperty().getStructure().getType()).setProfile(pn.getProfileProperty().getStructure().getUrl());
2487//      }
2488      return pn; 
2489    } else {
2490      assert (!Utilities.noString(element));
2491      Property pvb = var.getProperty().getBaseProperty();
2492      Property pvd = var.getProperty().getProfileProperty();
2493      Property pc = pvb.getChild(element, var.property.types);
2494      if (pc == null)
2495        throw new DefinitionException("Unable to find a definition for "+pvb.getDefinition().getPath()+"."+element);
2496      
2497      // the profile structure definition (derived)
2498      StructureDefinition sd = var.getProperty().profileProperty.getStructure();
2499      ElementDefinition ednew = sd.getDifferential().addElement();
2500      ednew.setPath(var.getProperty().profileProperty.getDefinition().getPath()+"."+pc.getName());
2501      ednew.setUserData("slice-name", sliceName);
2502      ednew.setFixed(fixed);
2503      for (ProfiledType pt : type.getProfiledTypes()) {
2504        if (pt.hasBindings())
2505          ednew.setBinding(pt.getBindings().get(0));
2506        if (pt.getUri().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
2507          String t = pt.getUri().substring(40);
2508          t = checkType(t, pc, pt.getProfiles());
2509          if (t != null) {
2510            if (pt.hasProfiles()) {
2511              for (String p : pt.getProfiles())
2512                if (t.equals("Reference"))
2513                  ednew.getType(t).addTargetProfile(p);
2514                else
2515                  ednew.getType(t).addProfile(p);
2516            } else 
2517            ednew.getType(t);
2518      }
2519        }
2520      }
2521      
2522      return new PropertyWithType(var.property.path+"."+element, pc, new Property(worker, ednew, sd), type);
2523    }
2524  }
2525  
2526
2527
2528  private String checkType(String t, Property pvb, List<String> profiles) throws FHIRException {
2529    if (pvb.getDefinition().getType().size() == 1 && isCompatibleType(t, pvb.getDefinition().getType().get(0).getCode()) && profilesMatch(profiles, pvb.getDefinition().getType().get(0).getProfile())) 
2530      return null;
2531    for (TypeRefComponent tr : pvb.getDefinition().getType()) {
2532      if (isCompatibleType(t, tr.getCode()))
2533        return tr.getCode(); // note what is returned - the base type, not the inferred mapping type
2534    }
2535    throw new FHIRException("The type "+t+" is not compatible with the allowed types for "+pvb.getDefinition().getPath());
2536  }
2537
2538  private boolean profilesMatch(List<String> profiles, List<CanonicalType> profile) {
2539    return profiles == null || profiles.size() == 0 || profile.size() == 0 || (profiles.size() == 1 && profiles.get(0).equals(profile.get(0).getValue()));
2540  }
2541
2542  private boolean isCompatibleType(String t, String code) {
2543    if (t.equals(code))
2544      return true;
2545    if (t.equals("string")) {
2546      StructureDefinition sd = worker.fetchTypeDefinition(code);
2547      if (sd != null && sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/string"))
2548        return true;
2549    }
2550    return false;
2551  }
2552
2553  private TypeDetails analyseTransform(TransformContext context, StructureMap map, StructureMapGroupRuleTargetComponent tgt, VariableForProfiling var, VariablesForProfiling vars) throws FHIRException {
2554    switch (tgt.getTransform()) {
2555    case CREATE :
2556      String p = getParamString(vars, tgt.getParameter().get(0));
2557      return new TypeDetails(CollectionStatus.SINGLETON, p);
2558    case COPY : 
2559      return getParam(vars, tgt.getParameter().get(0));
2560    case EVALUATE :
2561      ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION);
2562      if (expr == null) {
2563        expr = fpe.parse(getParamString(vars, tgt.getParameter().get(tgt.getParameter().size()-1)));
2564        tgt.setUserData(MAP_WHERE_EXPRESSION, expr);
2565      }
2566      return fpe.check(vars, null, expr);
2567
2568////case TRUNCATE : 
2569////  String src = getParamString(vars, tgt.getParameter().get(0));
2570////  String len = getParamString(vars, tgt.getParameter().get(1));
2571////  if (Utilities.isInteger(len)) {
2572////    int l = Integer.parseInt(len);
2573////    if (src.length() > l)
2574////      src = src.substring(0, l);
2575////  }
2576////  return new StringType(src);
2577////case ESCAPE : 
2578////  throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
2579////case CAST :
2580////  throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
2581////case APPEND : 
2582////  throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
2583    case TRANSLATE : 
2584      return new TypeDetails(CollectionStatus.SINGLETON, "CodeableConcept");
2585   case CC:
2586     ProfiledType res = new ProfiledType("CodeableConcept");
2587     if (tgt.getParameter().size() >= 2  && isParamId(vars, tgt.getParameter().get(1))) {
2588       TypeDetails td = vars.get(null, getParamId(vars, tgt.getParameter().get(1))).property.types;
2589       if (td != null && td.hasBinding())
2590         // todo: do we need to check that there's no implicit translation her? I don't think we do...
2591         res.addBinding(td.getBinding());
2592     }
2593     return new TypeDetails(CollectionStatus.SINGLETON, res);
2594   case C:
2595     return new TypeDetails(CollectionStatus.SINGLETON, "Coding");
2596   case QTY:
2597     return new TypeDetails(CollectionStatus.SINGLETON, "Quantity");
2598   case REFERENCE :
2599      VariableForProfiling vrs = vars.get(VariableMode.OUTPUT, getParamId(vars, tgt.getParameterFirstRep()));
2600      if (vrs == null)
2601        throw new FHIRException("Unable to resolve variable \""+getParamId(vars, tgt.getParameterFirstRep())+"\"");
2602      String profile = vrs.property.getProfileProperty().getStructure().getUrl();
2603     TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON);
2604     td.addType("Reference", profile);
2605     return td;  
2606////case DATEOP :
2607////  throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
2608////case UUID :
2609////  return new IdType(UUID.randomUUID().toString());
2610////case POINTER :
2611////  Base b = getParam(vars, tgt.getParameter().get(0));
2612////  if (b instanceof Resource)
2613////    return new UriType("urn:uuid:"+((Resource) b).getId());
2614////  else
2615////    throw new FHIRException("Transform engine cannot point at an element of type "+b.fhirType());
2616    default:
2617      throw new Error("Transform Unknown or not handled yet: "+tgt.getTransform().toCode());
2618    }
2619  }
2620  private String getParamString(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) {
2621    Type p = parameter.getValue();
2622    if (p == null || p instanceof IdType)
2623      return null;
2624    if (!p.hasPrimitiveValue())
2625      return null;
2626    return p.primitiveValue();
2627  }
2628
2629  private String getParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) {
2630    Type p = parameter.getValue();
2631    if (p == null || !(p instanceof IdType))
2632      return null;
2633    return p.primitiveValue();
2634  }
2635
2636  private boolean isParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) {
2637    Type p = parameter.getValue();
2638    if (p == null || !(p instanceof IdType))
2639      return false;
2640    return vars.get(null, p.primitiveValue()) != null;
2641  }
2642
2643  private TypeDetails getParam(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException {
2644    Type p = parameter.getValue();
2645    if (!(p instanceof IdType))
2646      return new TypeDetails(CollectionStatus.SINGLETON, ProfileUtilities.sdNs(p.fhirType(), worker.getOverrideVersionNs()));
2647    else { 
2648      String n = ((IdType) p).asStringValue();
2649      VariableForProfiling b = vars.get(VariableMode.INPUT, n);
2650      if (b == null)
2651        b = vars.get(VariableMode.OUTPUT, n);
2652      if (b == null)
2653        throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")");
2654      return b.getProperty().getTypes();
2655    }
2656  }
2657
2658  private PropertyWithType createProfile(StructureMap map, List<StructureDefinition> profiles, PropertyWithType prop, String sliceName, Base ctxt) throws DefinitionException {
2659    if (prop.getBaseProperty().getDefinition().getPath().contains(".")) 
2660      throw new DefinitionException("Unable to process entry point");
2661
2662    String type = prop.getBaseProperty().getDefinition().getPath();
2663    String suffix = "";
2664    if (ids.containsKey(type)) {
2665      int id = ids.get(type);
2666      id++;
2667      ids.put(type, id);
2668      suffix = "-"+Integer.toString(id);
2669    } else
2670      ids.put(type, 0);
2671    
2672    StructureDefinition profile = new StructureDefinition();
2673    profiles.add(profile);
2674    profile.setDerivation(TypeDerivationRule.CONSTRAINT);
2675    profile.setType(type);
2676    profile.setBaseDefinition(prop.getBaseProperty().getStructure().getUrl());
2677    profile.setName("Profile for "+profile.getType()+" for "+sliceName);
2678    profile.setUrl(map.getUrl().replace("StructureMap", "StructureDefinition")+"-"+profile.getType()+suffix);
2679    ctxt.setUserData("profile", profile.getUrl()); // then we can easily assign this profile url for validation later when we actually transform
2680    profile.setId(map.getId()+"-"+profile.getType()+suffix);
2681    profile.setStatus(map.getStatus());
2682    profile.setExperimental(map.getExperimental());
2683    profile.setDescription("Generated automatically from the mapping by the Java Reference Implementation");
2684    for (ContactDetail c : map.getContact()) {
2685      ContactDetail p = profile.addContact();
2686      p.setName(c.getName());
2687      for (ContactPoint cc : c.getTelecom()) 
2688        p.addTelecom(cc);
2689    }
2690    profile.setDate(map.getDate());
2691    profile.setCopyright(map.getCopyright());
2692    profile.setFhirVersion(Constants.VERSION);
2693    profile.setKind(prop.getBaseProperty().getStructure().getKind());
2694    profile.setAbstract(false);
2695    ElementDefinition ed = profile.getDifferential().addElement();
2696    ed.setPath(profile.getType());
2697    prop.profileProperty = new Property(worker, ed, profile);
2698    return prop;
2699  }
2700
2701  private PropertyWithType resolveType(StructureMap map, String type, StructureMapInputMode mode) throws Exception {
2702    for (StructureMapStructureComponent imp : map.getStructure()) {
2703      if ((imp.getMode() == StructureMapModelMode.SOURCE && mode == StructureMapInputMode.SOURCE) || 
2704          (imp.getMode() == StructureMapModelMode.TARGET && mode == StructureMapInputMode.TARGET)) {
2705        StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl());
2706        if (sd == null)
2707          throw new Exception("Import "+imp.getUrl()+" cannot be resolved");
2708        if (sd.getId().equals(type)) {
2709          return new PropertyWithType(sd.getType(), new Property(worker, sd.getSnapshot().getElement().get(0), sd), null, new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()));
2710        }
2711      }
2712    }
2713    throw new Exception("Unable to find structure definition for "+type+" in imports");
2714  }
2715
2716
2717  public StructureMap generateMapFromMappings(StructureDefinition sd) throws IOException, FHIRException {
2718    String id = getLogicalMappingId(sd);
2719    if (id == null) 
2720        return null;
2721    String prefix = ToolingExtensions.readStringExtension(sd,  ToolingExtensions.EXT_MAPPING_PREFIX);
2722    String suffix = ToolingExtensions.readStringExtension(sd,  ToolingExtensions.EXT_MAPPING_SUFFIX);
2723    if (prefix == null || suffix == null)
2724      return null;
2725    // we build this by text. Any element that has a mapping, we put it's mappings inside it....
2726    StringBuilder b = new StringBuilder();
2727    b.append(prefix);
2728
2729    ElementDefinition root = sd.getSnapshot().getElementFirstRep();
2730    String m = getMapping(root, id);
2731    if (m != null)
2732      b.append(m+"\r\n");
2733    addChildMappings(b, id, "", sd, root, false);
2734    b.append("\r\n");
2735    b.append(suffix);
2736    b.append("\r\n");
2737    StructureMap map = parse(b.toString());
2738    map.setId(tail(map.getUrl()));
2739    if (!map.hasStatus())
2740      map.setStatus(PublicationStatus.DRAFT);
2741    map.getText().setStatus(NarrativeStatus.GENERATED);
2742    map.getText().setDiv(new XhtmlNode(NodeType.Element, "div"));
2743    map.getText().getDiv().addTag("pre").addText(render(map));
2744    return map;
2745  }
2746
2747
2748  private String tail(String url) {
2749    return url.substring(url.lastIndexOf("/")+1);
2750  }
2751
2752
2753  private void addChildMappings(StringBuilder b, String id, String indent, StructureDefinition sd, ElementDefinition ed, boolean inner) throws DefinitionException {
2754    boolean first = true;
2755    List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed);
2756    for (ElementDefinition child : children) {
2757      if (first && inner) {
2758        b.append(" then {\r\n");
2759        first = false;
2760      }
2761      String map = getMapping(child, id);
2762      if (map != null) {
2763        b.append(indent+"  "+child.getPath()+": "+map);
2764        addChildMappings(b, id, indent+"  ", sd, child, true);
2765        b.append("\r\n");
2766      }
2767    }
2768    if (!first && inner)
2769      b.append(indent+"}");
2770    
2771  }
2772
2773
2774  private String getMapping(ElementDefinition ed, String id) {
2775    for (ElementDefinitionMappingComponent map : ed.getMapping())
2776      if (id.equals(map.getIdentity()))
2777        return map.getMap();
2778    return null;
2779  }
2780
2781
2782  private String getLogicalMappingId(StructureDefinition sd) {
2783    String id = null;
2784    for (StructureDefinitionMappingComponent map : sd.getMapping()) {
2785      if ("http://hl7.org/fhir/logical".equals(map.getUri()))
2786        return map.getIdentity();
2787    }
2788    return null;
2789  }
2790        
2791}