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