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