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