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