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