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