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