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_CHECK, 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        //        fpe.check(context.appInfo, ??, ??, expr)
1638        src.setUserData(MAP_WHERE_EXPRESSION, expr);
1639      }
1640      List<Base> remove = new ArrayList<Base>();
1641      for (Base item : items) {
1642        if (!fpe.evaluateToBoolean(vars, null, null, item, expr)) {
1643          log(indent + "  condition [" + src.getCondition() + "] for " + item.toString() + " : false");
1644          remove.add(item);
1645        } else
1646          log(indent + "  condition [" + src.getCondition() + "] for " + item.toString() + " : true");
1647      }
1648      items.removeAll(remove);
1649    }
1650
1651    if (src.hasCheck()) {
1652      ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_CHECK);
1653      if (expr == null) {
1654        expr = fpe.parse(src.getCheck());
1655        //        fpe.check(context.appInfo, ??, ??, expr)
1656        src.setUserData(MAP_WHERE_CHECK, expr);
1657      }
1658      List<Base> remove = new ArrayList<Base>();
1659      for (Base item : items) {
1660        if (!fpe.evaluateToBoolean(vars, null, null, item, expr))
1661          throw new FHIRException("Rule \"" + ruleId + "\": Check condition failed");
1662      }
1663    }
1664
1665    if (src.hasLogMessage()) {
1666      ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_LOG);
1667      if (expr == null) {
1668        expr = fpe.parse(src.getLogMessage());
1669        //        fpe.check(context.appInfo, ??, ??, expr)
1670        src.setUserData(MAP_WHERE_LOG, expr);
1671      }
1672      CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1673      for (Base item : items)
1674        b.appendIfNotNull(fpe.evaluateToString(vars, null, null, item, expr));
1675      if (b.length() > 0)
1676        services.log(b.toString());
1677    }
1678
1679
1680    if (src.hasListMode() && !items.isEmpty()) {
1681      switch (src.getListMode()) {
1682        case FIRST:
1683          Base bt = items.get(0);
1684          items.clear();
1685          items.add(bt);
1686          break;
1687        case NOTFIRST:
1688          if (items.size() > 0)
1689            items.remove(0);
1690          break;
1691        case LAST:
1692          bt = items.get(items.size() - 1);
1693          items.clear();
1694          items.add(bt);
1695          break;
1696        case NOTLAST:
1697          if (items.size() > 0)
1698            items.remove(items.size() - 1);
1699          break;
1700        case ONLYONE:
1701          if (items.size() > 1)
1702            throw new FHIRException("Rule \"" + ruleId + "\": Check condition failed: the collection has more than one item");
1703          break;
1704        case NULL:
1705      }
1706    }
1707    List<Variables> result = new ArrayList<Variables>();
1708    for (Base r : items) {
1709      Variables v = vars.copy();
1710      if (src.hasVariable())
1711        v.add(VariableMode.INPUT, src.getVariable(), r);
1712      result.add(v);
1713    }
1714    return result;
1715  }
1716
1717
1718  private boolean isType(Base item, String type) {
1719    return type.equals(item.fhirType());
1720  }
1721
1722  private void processTarget(String rulePath, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar, boolean atRoot, Variables sharedVars) throws FHIRException {
1723    Base dest = null;
1724    if (tgt.hasContext()) {
1725      dest = vars.get(VariableMode.OUTPUT, tgt.getContext());
1726      if (dest == null) {
1727        throw new FHIRException("Rul \"" + rulePath + "\": target context not known: " + tgt.getContext());
1728      }
1729    }
1730    Base v = null;
1731    if (tgt.hasTransform()) {
1732      v = runTransform(rulePath, context, map, group, tgt, vars, dest, tgt.getElement(), srcVar, atRoot);
1733      if (v != null && dest != null) {
1734        try {
1735          v = dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); // reset v because some implementations may have to rewrite v when setting the value
1736        } catch (Exception e) {
1737          throw new FHIRException("Error setting "+tgt.getElement()+" on "+dest.fhirType()+" for rule "+rulePath+" to value "+v.toString()+": "+e.getMessage(), e);
1738        }
1739      }
1740    } else if (dest != null) {
1741      if (tgt.hasListMode(StructureMapTargetListMode.SHARE)) {
1742        v = sharedVars.get(VariableMode.SHARED, tgt.getListRuleId());
1743        if (v == null) {
1744          v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement());
1745          sharedVars.add(VariableMode.SHARED, tgt.getListRuleId(), v);
1746        }
1747      } else if (tgt.hasElement()) {
1748        v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement());
1749      } else {
1750        v = dest;
1751      }
1752    }
1753    if (tgt.hasVariable() && v != null)
1754      vars.add(VariableMode.OUTPUT, tgt.getVariable(), v);
1755  }
1756
1757  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 {
1758    try {
1759      switch (tgt.getTransform()) {
1760        case CREATE:
1761          String tn;
1762          if (tgt.getParameter().isEmpty()) {
1763            // 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
1764            String[] types = dest.getTypesForProperty(element.hashCode(), element);
1765            if (types.length == 1 && !"*".equals(types[0]) && !types[0].equals("Resource"))
1766              tn = types[0];
1767            else if (srcVar != null) {
1768              tn = determineTypeFromSourceType(map, group, vars.get(VariableMode.INPUT, srcVar), types);
1769            } else
1770              throw new FHIRException("Cannot determine type implicitly because there is no single input variable");
1771          } else {
1772            tn = getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString());
1773            // ok, now we resolve the type name against the import statements
1774            for (StructureMapStructureComponent uses : map.getStructure()) {
1775              if (uses.getMode() == StructureMapModelMode.TARGET && uses.hasAlias() && tn.equals(uses.getAlias())) {
1776                tn = uses.getUrl();
1777                break;
1778              }
1779            }
1780          }
1781          Base res = services != null ? services.createType(context.getAppInfo(), tn) : typeFactory(tn);
1782          if (res.isResource() && !res.fhirType().equals("Parameters")) {
1783//              res.setIdBase(tgt.getParameter().size() > 1 ? getParamString(vars, tgt.getParameter().get(0)) : UUID.randomUUID().toString().toLowerCase());
1784            if (services != null)
1785              res = services.createResource(context.getAppInfo(), res, root);
1786          }
1787          if (tgt.hasUserData("profile"))
1788            res.setUserData("profile", tgt.getUserData("profile"));
1789          return res;
1790        case COPY:
1791          return getParam(vars, tgt.getParameter().get(0));
1792        case EVALUATE:
1793          ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION);
1794          if (expr == null) {
1795            expr = fpe.parse(getParamStringNoNull(vars, tgt.getParameter().get(tgt.getParameter().size() - 1), tgt.toString()));
1796            tgt.setUserData(MAP_EXPRESSION, expr);
1797          }
1798          List<Base> v = fpe.evaluate(vars, null, null, tgt.getParameter().size() == 2 ? getParam(vars, tgt.getParameter().get(0)) : new BooleanType(false), expr);
1799          if (v.size() == 0)
1800            return null;
1801          else if (v.size() != 1)
1802            throw new FHIRException("Rule \"" + rulePath+ "\": Evaluation of " + expr.toString() + " returned " + v.size() + " objects");
1803          else
1804            return v.get(0);
1805
1806        case TRUNCATE:
1807          String src = getParamString(vars, tgt.getParameter().get(0));
1808          String len = getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString());
1809          if (Utilities.isInteger(len)) {
1810            int l = Integer.parseInt(len);
1811            if (src.length() > l)
1812              src = src.substring(0, l);
1813          }
1814          return new StringType(src);
1815        case ESCAPE:
1816          throw new FHIRException("Rule \"" + rulePath + "\": Transform " + tgt.getTransform().toCode() + " not supported yet");
1817        case CAST:
1818          src = getParamString(vars, tgt.getParameter().get(0));
1819          if (tgt.getParameter().size() == 1)
1820            throw new FHIRException("Implicit type parameters on cast not yet supported");
1821          String t = getParamString(vars, tgt.getParameter().get(1));
1822          switch(t) {
1823            case "boolean":
1824              return new BooleanType(src);
1825            case "integer":
1826              return new IntegerType(src);
1827            case  "integer64":
1828              return new Integer64Type(src);
1829            case  "string":
1830              return new StringType(src);
1831            case  "decimal":
1832              return new DecimalType(src);
1833            case  "uri":
1834              return new UriType(src);
1835            case  "base64Binary":
1836              return new Base64BinaryType(src);
1837            case  "instant":
1838              return new InstantType(src);
1839            case  "date":
1840              return new DateType(src);
1841            case  "dateTime":
1842              return new DateTimeType(src);
1843            case  "time":
1844              return new TimeType(src);
1845            case  "code":
1846              return new CodeType(src);
1847            case  "oid":
1848              return new OidType(src);
1849            case  "id":
1850              return new IdType(src);
1851            case  "markdown":
1852              return new MarkdownType(src);
1853            case  "unsignedInt":
1854              return new UnsignedIntType(src);
1855            case  "positiveInt":
1856              return new PositiveIntType(src);
1857            case  "uuid":
1858              return new UuidType(src);
1859            case  "url":
1860              return new UrlType(src);
1861            case  "canonical":
1862              return new CanonicalType(src);
1863          }
1864          throw new FHIRException("cast to " + t + " not yet supported");
1865        case APPEND:
1866          StringBuilder sb = new StringBuilder(getParamString(vars, tgt.getParameter().get(0)));
1867          for (int i = 1; i < tgt.getParameter().size(); i++)
1868            sb.append(getParamString(vars, tgt.getParameter().get(i)));
1869          return new StringType(sb.toString());
1870        case TRANSLATE:
1871          return translate(context, map, vars, tgt.getParameter());
1872        case REFERENCE:
1873          Base b = getParam(vars, tgt.getParameter().get(0));
1874          if (b == null)
1875            throw new FHIRException("Rule \"" + rulePath + "\": Unable to find parameter " + ((IdType) tgt.getParameter().get(0).getValue()).asStringValue());
1876          if (!b.isResource())
1877            throw new FHIRException("Rule \"" + rulePath + "\": Transform engine cannot point at an element of type " + b.fhirType());
1878          else {
1879            String id = b.getIdBase();
1880            if (id == null) {
1881              id = UUID.randomUUID().toString().toLowerCase();
1882              b.setIdBase(id);
1883            }
1884            return new StringType(b.fhirType() + "/" + id);
1885          }
1886        case DATEOP:
1887          throw new FHIRException("Rule \"" + rulePath + "\": Transform " + tgt.getTransform().toCode() + " not supported yet");
1888        case UUID:
1889          return new IdType(UUID.randomUUID().toString());
1890        case POINTER:
1891          b = getParam(vars, tgt.getParameter().get(0));
1892          if (b instanceof Resource)
1893            return new UriType("urn:uuid:" + ((Resource) b).getId());
1894          else
1895            throw new FHIRException("Rule \"" + rulePath + "\": Transform engine cannot point at an element of type " + b.fhirType());
1896        case CC:
1897          CodeableConcept cc = new CodeableConcept();
1898          cc.addCoding(buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())));
1899          return cc;
1900        case C:
1901          Coding c = buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()));
1902          return c;
1903        default:
1904          throw new FHIRException("Rule \"" + rulePath + "\": Transform Unknown: " + tgt.getTransform().toCode());
1905      }
1906    } catch (Exception e) {
1907      throw new FHIRException("Exception executing transform " + tgt.toString() + " on Rule \"" + rulePath + "\": " + e.getMessage(), e);
1908    }
1909  }
1910
1911  private Base typeFactory(String tn) {
1912    if (Utilities.isAbsoluteUrl(tn) && !tn.startsWith("http://hl7.org/fhir/StructureDefinition")) {
1913      StructureDefinition sd = worker.fetchTypeDefinition(tn);
1914      if (sd == null) {
1915        if (Utilities.existsInList(tn, "http://hl7.org/fhirpath/System.String")) {
1916          sd = worker.fetchTypeDefinition("string"); 
1917        }
1918      }
1919      if (sd == null) {
1920        throw new FHIRException("Unable to create type "+tn);
1921      } else {
1922        return Manager.build(worker, sd);
1923      }
1924    } else {
1925      return ResourceFactory.createResourceOrType(tn);
1926    }
1927  }
1928
1929
1930  private Coding buildCoding(String uri, String code) throws FHIRException {
1931    // if we can get this as a valueSet, we will
1932    String system = null;
1933    String display = null;
1934    String version = null;
1935    ValueSet vs = Utilities.noString(uri) ? null : worker.fetchResourceWithException(ValueSet.class, uri);
1936    if (vs != null) {
1937      ValueSetExpansionOutcome vse = worker.expandVS(vs, true, false);
1938      if (vse.getError() != null)
1939        throw new FHIRException(vse.getError());
1940      CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1941      for (ValueSetExpansionContainsComponent t : vse.getValueset().getExpansion().getContains()) {
1942        if (t.hasCode())
1943          b.append(t.getCode());
1944        if (code.equals(t.getCode()) && t.hasSystem()) {
1945          system = t.getSystem();
1946          version = t.getVersion();
1947          display = t.getDisplay();
1948          break;
1949        }
1950        if (code.equalsIgnoreCase(t.getDisplay()) && t.hasSystem()) {
1951          system = t.getSystem();
1952          version = t.getVersion();
1953          display = t.getDisplay();
1954          break;
1955        }
1956      }
1957      if (system == null)
1958        throw new FHIRException("The code '" + code + "' is not in the value set '" + uri + "' (valid codes: " + b.toString() + "; also checked displays)");
1959    } else {
1960      system = uri;
1961    }
1962    ValidationResult vr = worker.validateCode(terminologyServiceOptions.withVersionFlexible(true), system, version, code, null);
1963    if (vr != null && vr.getDisplay() != null)
1964      display = vr.getDisplay();
1965    return new Coding().setSystem(system).setCode(code).setDisplay(display);
1966  }
1967
1968
1969  private String getParamStringNoNull(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter, String message) throws FHIRException {
1970    Base b = getParam(vars, parameter);
1971    if (b == null)
1972      throw new FHIRException("Unable to find a value for " + parameter.toString() + ". Context: " + message);
1973    if (!b.hasPrimitiveValue())
1974      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);
1975    return b.primitiveValue();
1976  }
1977
1978  private String getParamString(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException {
1979    Base b = getParam(vars, parameter);
1980    if (b == null || !b.hasPrimitiveValue())
1981      return null;
1982    return b.primitiveValue();
1983  }
1984
1985
1986  private Base getParam(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException {
1987    DataType p = parameter.getValue();
1988    if (!(p instanceof IdType))
1989      return p;
1990    else {
1991      String n = ((IdType) p).asStringValue();
1992      Base b = vars.get(VariableMode.INPUT, n);
1993      if (b == null)
1994        b = vars.get(VariableMode.OUTPUT, n);
1995      if (b == null)
1996        throw new DefinitionException("Variable " + n + " not found (" + vars.summary() + ")");
1997      return b;
1998    }
1999  }
2000
2001
2002  private Base translate(TransformContext context, StructureMap map, Variables vars, List<StructureMapGroupRuleTargetParameterComponent> parameter) throws FHIRException {
2003    Base src = getParam(vars, parameter.get(0));
2004    String id = getParamString(vars, parameter.get(1));
2005    String fld = parameter.size() > 2 ? getParamString(vars, parameter.get(2)) : null;
2006    return translate(context, map, src, id, fld);
2007  }
2008
2009  public Base translate(TransformContext context, StructureMap map, Base source, String conceptMapUrl, String fieldToReturn) throws FHIRException {
2010    Coding src = new Coding();
2011    if (source.isPrimitive()) {
2012      src.setCode(source.primitiveValue());
2013    } else if ("Coding".equals(source.fhirType())) {
2014      Base[] b = source.getProperty("system".hashCode(), "system", true);
2015      if (b.length == 1)
2016        src.setSystem(b[0].primitiveValue());
2017      b = source.getProperty("code".hashCode(), "code", true);
2018      if (b.length == 1)
2019        src.setCode(b[0].primitiveValue());
2020    } else if ("CE".equals(source.fhirType())) {
2021      Base[] b = source.getProperty("codeSystem".hashCode(), "codeSystem", true);
2022      if (b.length == 1)
2023        src.setSystem(b[0].primitiveValue());
2024      b = source.getProperty("code".hashCode(), "code", true);
2025      if (b.length == 1)
2026        src.setCode(b[0].primitiveValue());
2027    } else
2028      throw new FHIRException("Unable to translate source " + source.fhirType());
2029
2030    String su = conceptMapUrl;
2031    if (conceptMapUrl.equals("http://hl7.org/fhir/ConceptMap/special-oid2uri")) {
2032      String uri = new ContextUtilities(worker).oid2Uri(src.getCode());
2033      if (uri == null)
2034        uri = "urn:oid:" + src.getCode();
2035      if ("uri".equals(fieldToReturn))
2036        return new UriType(uri);
2037      else
2038        throw new FHIRException("Error in return code");
2039    } else {
2040      ConceptMap cmap = null;
2041      if (conceptMapUrl.startsWith("#")) {
2042        for (Resource r : map.getContained()) {
2043          if (r instanceof ConceptMap && r.getId().equals(conceptMapUrl.substring(1))) {
2044            cmap = (ConceptMap) r;
2045            su = map.getUrl() + "#" + conceptMapUrl;
2046          }
2047        }
2048        if (cmap == null)
2049          throw new FHIRException("Unable to translate - cannot find map " + conceptMapUrl);
2050      } else {
2051        if (conceptMapUrl.contains("#")) {
2052          String[] p = conceptMapUrl.split("\\#");
2053          StructureMap mapU = worker.fetchResource(StructureMap.class, p[0]);
2054          for (Resource r : mapU.getContained()) {
2055            if (r instanceof ConceptMap && r.getId().equals(p[1])) {
2056              cmap = (ConceptMap) r;
2057              su = conceptMapUrl;
2058            }
2059          }
2060        }
2061        if (cmap == null)
2062          cmap = worker.fetchResource(ConceptMap.class, conceptMapUrl);
2063      }
2064      Coding outcome = null;
2065      boolean done = false;
2066      String message = null;
2067      if (cmap == null) {
2068        if (services == null)
2069          message = "No map found for " + conceptMapUrl;
2070        else {
2071          outcome = services.translate(context.getAppInfo(), src, conceptMapUrl);
2072          done = true;
2073        }
2074      } else {
2075        List<SourceElementComponentWrapper> list = new ArrayList<SourceElementComponentWrapper>();
2076        for (ConceptMapGroupComponent g : cmap.getGroup()) {
2077          for (SourceElementComponent e : g.getElement()) {
2078            if (!src.hasSystem() && src.getCode().equals(e.getCode()))
2079              list.add(new SourceElementComponentWrapper(g, e));
2080            else if (src.hasSystem() && src.getSystem().equals(g.getSource()) && src.getCode().equals(e.getCode()))
2081              list.add(new SourceElementComponentWrapper(g, e));
2082          }
2083        }
2084        if (list.size() == 0)
2085          done = true;
2086        else if (list.get(0).getComp().getTarget().size() == 0)
2087          message = "Concept map " + su + " found no translation for " + src.getCode();
2088        else {
2089          for (TargetElementComponent tgt : list.get(0).getComp().getTarget()) {
2090            if (tgt.getRelationship() == null || EnumSet.of(ConceptMapRelationship.RELATEDTO, ConceptMapRelationship.EQUIVALENT, ConceptMapRelationship.SOURCEISNARROWERTHANTARGET).contains(tgt.getRelationship())) {
2091              if (done) {
2092                message = "Concept map " + su + " found multiple matches for " + src.getCode();
2093                done = false;
2094              } else {
2095                done = true;
2096                outcome = new Coding().setCode(tgt.getCode()).setSystem(list.get(0).getGroup().getTarget());
2097              }
2098            }
2099          }
2100          if (!done)
2101            message = "Concept map " + su + " found no usable translation for " + src.getCode();
2102        }
2103      }
2104      if (!done)
2105        throw new FHIRException(message);
2106      if (outcome == null)
2107        return null;
2108      if ("code".equals(fieldToReturn))
2109        return new CodeType(outcome.getCode());
2110      else
2111        return outcome;
2112    }
2113  }
2114
2115
2116  /**
2117   * Given a structure map, return a set of analyses on it.
2118   * <p>
2119   * Returned:
2120   * - a list or profiles for what it will create. First profile is the target
2121   * - a table with a summary (in xhtml) for easy human undertanding of the mapping
2122   *
2123   * @param appInfo
2124   * @param map
2125   * @return
2126   * @throws Exception
2127   */
2128  public StructureMapAnalysis analyse(Object appInfo, StructureMap map) throws FHIRException {
2129    ids.clear();
2130    StructureMapAnalysis result = new StructureMapAnalysis();
2131    TransformContext context = new TransformContext(appInfo);
2132    VariablesForProfiling vars = new VariablesForProfiling(this, false, false);
2133    if (map.hasGroup()) {
2134      StructureMapGroupComponent start = map.getGroup().get(0);
2135      for (StructureMapGroupInputComponent t : start.getInput()) {
2136        PropertyWithType ti = resolveType(map, t.getType(), t.getMode());
2137        if (t.getMode() == StructureMapInputMode.SOURCE)
2138          vars.add(VariableMode.INPUT, t.getName(), ti);
2139        else
2140          vars.add(VariableMode.OUTPUT, t.getName(), createProfile(map, result.profiles, ti, start.getName(), start));
2141      }
2142      result.summary = new XhtmlNode(NodeType.Element, "table").setAttribute("class", "grid");
2143      XhtmlNode tr = result.summary.addTag("tr");
2144      tr.addTag("td").addTag("b").addText("Source");
2145      tr.addTag("td").addTag("b").addText("Target");
2146
2147      log("Start Profiling Transform " + map.getUrl());
2148      analyseGroup("", context, map, vars, start, result);
2149    }
2150    ProfileUtilities pu = new ProfileUtilities(worker, null, pkp);
2151    for (StructureDefinition sd : result.getProfiles())
2152      pu.cleanUpDifferential(sd);
2153    return result;
2154  }
2155
2156
2157  private void analyseGroup(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapAnalysis result) throws FHIRException {
2158    log(indent + "Analyse Group : " + group.getName());
2159    // todo: extends
2160    // todo: check inputs
2161    XhtmlNode tr = result.summary.addTag("tr").setAttribute("class", "diff-title");
2162    XhtmlNode xs = tr.addTag("td");
2163    XhtmlNode xt = tr.addTag("td");
2164    for (StructureMapGroupInputComponent inp : group.getInput()) {
2165      if (inp.getMode() == StructureMapInputMode.SOURCE)
2166        noteInput(vars, inp, VariableMode.INPUT, xs);
2167      if (inp.getMode() == StructureMapInputMode.TARGET)
2168        noteInput(vars, inp, VariableMode.OUTPUT, xt);
2169    }
2170    for (StructureMapGroupRuleComponent r : group.getRule()) {
2171      analyseRule(indent + "  ", context, map, vars, group, r, result);
2172    }
2173  }
2174
2175
2176  private void noteInput(VariablesForProfiling vars, StructureMapGroupInputComponent inp, VariableMode mode, XhtmlNode xs) {
2177    VariableForProfiling v = vars.get(mode, inp.getName());
2178    if (v != null)
2179      xs.addText("Input: " + v.getProperty().getPath());
2180  }
2181
2182  private void analyseRule(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, StructureMapAnalysis result) throws FHIRException {
2183    log(indent + "Analyse rule : " + rule.getName());
2184    XhtmlNode tr = result.summary.addTag("tr");
2185    XhtmlNode xs = tr.addTag("td");
2186    XhtmlNode xt = tr.addTag("td");
2187
2188    VariablesForProfiling srcVars = vars.copy();
2189    if (rule.getSource().size() != 1)
2190      throw new FHIRException("Rule \"" + rule.getName() + "\": not handled yet");
2191    VariablesForProfiling source = analyseSource(rule.getName(), context, srcVars, rule.getSourceFirstRep(), xs);
2192
2193    TargetWriter tw = new TargetWriter();
2194    for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) {
2195      analyseTarget(rule.getName(), context, source, map, t, rule.getSourceFirstRep().getVariable(), tw, result.profiles, rule.getName());
2196    }
2197    tw.commit(xt);
2198
2199    for (StructureMapGroupRuleComponent childrule : rule.getRule()) {
2200      analyseRule(indent + "  ", context, map, source, group, childrule, result);
2201    }
2202//    for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) {
2203//      executeDependency(indent+"  ", context, map, v, group, dependent); // do we need group here?
2204//    }
2205  }
2206
2207  private VariablesForProfiling analyseSource(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMapGroupRuleSourceComponent src, XhtmlNode td) throws FHIRException {
2208    VariableForProfiling var = vars.get(VariableMode.INPUT, src.getContext());
2209    if (var == null)
2210      throw new FHIRException("Rule \"" + ruleId + "\": Unknown input variable " + src.getContext());
2211    PropertyWithType prop = var.getProperty();
2212
2213    boolean optional = false;
2214    boolean repeating = false;
2215
2216    if (src.hasCondition()) {
2217      optional = true;
2218    }
2219
2220    if (src.hasElement()) {
2221      Property element = prop.getBaseProperty().getChild(prop.getTypes().getType(), src.getElement());
2222      if (element == null)
2223        throw new FHIRException("Rule \"" + ruleId + "\": Unknown element name " + src.getElement());
2224      if (element.getDefinition().getMin() == 0)
2225        optional = true;
2226      if (element.getDefinition().getMax().equals("*"))
2227        repeating = true;
2228      VariablesForProfiling result = vars.copy(optional, repeating);
2229      TypeDetails type = new TypeDetails(CollectionStatus.SINGLETON);
2230      for (TypeRefComponent tr : element.getDefinition().getType()) {
2231        if (!tr.hasCode())
2232          throw new FHIRException("Rule \"" + ruleId + "\": Element has no type");
2233        ProfiledType pt = new ProfiledType(tr.getWorkingCode());
2234        if (tr.hasProfile())
2235          pt.addProfiles(tr.getProfile());
2236        if (element.getDefinition().hasBinding())
2237          pt.addBinding(element.getDefinition().getBinding());
2238        type.addType(pt);
2239      }
2240      td.addText(prop.getPath() + "." + src.getElement());
2241      if (src.hasVariable())
2242        result.add(VariableMode.INPUT, src.getVariable(), new PropertyWithType(prop.getPath() + "." + src.getElement(), element, null, type));
2243      return result;
2244    } else {
2245      td.addText(prop.getPath()); // ditto!
2246      return vars.copy(optional, repeating);
2247    }
2248  }
2249
2250
2251  private void analyseTarget(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMap map, StructureMapGroupRuleTargetComponent tgt, String tv, TargetWriter tw, List<StructureDefinition> profiles, String sliceName) throws FHIRException {
2252    VariableForProfiling var = null;
2253    if (tgt.hasContext()) {
2254      var = vars.get(VariableMode.OUTPUT, tgt.getContext());
2255      if (var == null)
2256        throw new FHIRException("Rule \"" + ruleId + "\": target context not known: " + tgt.getContext());
2257      if (!tgt.hasElement())
2258        throw new FHIRException("Rule \"" + ruleId + "\": Not supported yet");
2259    }
2260
2261
2262    TypeDetails type = null;
2263    if (tgt.hasTransform()) {
2264      type = analyseTransform(context, map, tgt, var, vars);
2265    } else {
2266      Property vp = var.getProperty().getBaseProperty().getChild(tgt.getElement(), tgt.getElement());
2267      if (vp == null)
2268        throw new FHIRException("Unknown Property " + tgt.getElement() + " on " + var.getProperty().getPath());
2269
2270      type = new TypeDetails(CollectionStatus.SINGLETON, vp.getType(tgt.getElement()));
2271    }
2272
2273    if (tgt.getTransform() == StructureMapTransform.CREATE) {
2274      String s = getParamString(vars, tgt.getParameter().get(0));
2275      if (worker.getResourceNames().contains(s))
2276        tw.newResource(tgt.getVariable(), s);
2277    } else {
2278      boolean mapsSrc = false;
2279      for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) {
2280        DataType pr = p.getValue();
2281        if (pr instanceof IdType && ((IdType) pr).asStringValue().equals(tv))
2282          mapsSrc = true;
2283      }
2284      if (mapsSrc) {
2285        if (var == null)
2286          throw new FHIRException("Rule \"" + ruleId + "\": Attempt to assign with no context");
2287        tw.valueAssignment(tgt.getContext(), var.getProperty().getPath() + "." + tgt.getElement() + getTransformSuffix(tgt.getTransform()));
2288      } else if (tgt.hasContext()) {
2289        if (isSignificantElement(var.getProperty(), tgt.getElement())) {
2290          String td = describeTransform(tgt);
2291          if (td != null)
2292            tw.keyAssignment(tgt.getContext(), var.getProperty().getPath() + "." + tgt.getElement() + " = " + td);
2293        }
2294      }
2295    }
2296    DataType fixed = generateFixedValue(tgt);
2297
2298    PropertyWithType prop = updateProfile(var, tgt.getElement(), type, map, profiles, sliceName, fixed, tgt);
2299    if (tgt.hasVariable())
2300      if (tgt.hasElement())
2301        vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop);
2302      else
2303        vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop);
2304  }
2305
2306  private DataType generateFixedValue(StructureMapGroupRuleTargetComponent tgt) {
2307    if (!allParametersFixed(tgt))
2308      return null;
2309    if (!tgt.hasTransform())
2310      return null;
2311    switch (tgt.getTransform()) {
2312      case COPY:
2313        return tgt.getParameter().get(0).getValue();
2314      case TRUNCATE:
2315        return null;
2316      //case ESCAPE:
2317      //case CAST:
2318      //case APPEND:
2319      case TRANSLATE:
2320        return null;
2321      //case DATEOP,
2322      //case UUID,
2323      //case POINTER,
2324      //case EVALUATE,
2325      case CC:
2326        CodeableConcept cc = new CodeableConcept();
2327        cc.addCoding(buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue()));
2328        return cc;
2329      case C:
2330        return buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue());
2331      case QTY:
2332        return null;
2333      //case ID,
2334      //case CP,
2335      default:
2336        return null;
2337    }
2338  }
2339
2340  @SuppressWarnings("rawtypes")
2341  private Coding buildCoding(DataType value1, DataType value2) {
2342    return new Coding().setSystem(((PrimitiveType) value1).asStringValue()).setCode(((PrimitiveType) value2).asStringValue());
2343  }
2344
2345  private boolean allParametersFixed(StructureMapGroupRuleTargetComponent tgt) {
2346    for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) {
2347      DataType pr = p.getValue();
2348      if (pr instanceof IdType)
2349        return false;
2350    }
2351    return true;
2352  }
2353
2354  private String describeTransform(StructureMapGroupRuleTargetComponent tgt) throws FHIRException {
2355    switch (tgt.getTransform()) {
2356      case COPY:
2357        return null;
2358      case TRUNCATE:
2359        return null;
2360      //case ESCAPE:
2361      //case CAST:
2362      //case APPEND:
2363      case TRANSLATE:
2364        return null;
2365      //case DATEOP,
2366      //case UUID,
2367      //case POINTER,
2368      //case EVALUATE,
2369      case CC:
2370        return describeTransformCCorC(tgt);
2371      case C:
2372        return describeTransformCCorC(tgt);
2373      case QTY:
2374        return null;
2375      //case ID,
2376      //case CP,
2377      default:
2378        return null;
2379    }
2380  }
2381
2382  @SuppressWarnings("rawtypes")
2383  private String describeTransformCCorC(StructureMapGroupRuleTargetComponent tgt) throws FHIRException {
2384    if (tgt.getParameter().size() < 2)
2385      return null;
2386    DataType p1 = tgt.getParameter().get(0).getValue();
2387    DataType p2 = tgt.getParameter().get(1).getValue();
2388    if (p1 instanceof IdType || p2 instanceof IdType)
2389      return null;
2390    if (!(p1 instanceof PrimitiveType) || !(p2 instanceof PrimitiveType))
2391      return null;
2392    String uri = ((PrimitiveType) p1).asStringValue();
2393    String code = ((PrimitiveType) p2).asStringValue();
2394    if (Utilities.noString(uri))
2395      throw new FHIRException("Describe Transform, but the uri is blank");
2396    if (Utilities.noString(code))
2397      throw new FHIRException("Describe Transform, but the code is blank");
2398    Coding c = buildCoding(uri, code);
2399    return c.getSystem() + "#" + c.getCode() + (c.hasDisplay() ? "(" + c.getDisplay() + ")" : "");
2400  }
2401
2402
2403  private boolean isSignificantElement(PropertyWithType property, String element) {
2404    if ("Observation".equals(property.getPath()))
2405      return "code".equals(element);
2406    else if ("Bundle".equals(property.getPath()))
2407      return "type".equals(element);
2408    else
2409      return false;
2410  }
2411
2412  private String getTransformSuffix(StructureMapTransform transform) {
2413    switch (transform) {
2414      case COPY:
2415        return "";
2416      case TRUNCATE:
2417        return " (truncated)";
2418      //case ESCAPE:
2419      //case CAST:
2420      //case APPEND:
2421      case TRANSLATE:
2422        return " (translated)";
2423      //case DATEOP,
2424      //case UUID,
2425      //case POINTER,
2426      //case EVALUATE,
2427      case CC:
2428        return " (--> CodeableConcept)";
2429      case C:
2430        return " (--> Coding)";
2431      case QTY:
2432        return " (--> Quantity)";
2433      //case ID,
2434      //case CP,
2435      default:
2436        return " {??)";
2437    }
2438  }
2439
2440  private PropertyWithType updateProfile(VariableForProfiling var, String element, TypeDetails type, StructureMap map, List<StructureDefinition> profiles, String sliceName, DataType fixed, StructureMapGroupRuleTargetComponent tgt) throws FHIRException {
2441    if (var == null) {
2442      assert (Utilities.noString(element));
2443      // 1. start the new structure definition
2444      StructureDefinition sdn = worker.fetchResource(StructureDefinition.class, type.getType());
2445      if (sdn == null)
2446        throw new FHIRException("Unable to find definition for " + type.getType());
2447      ElementDefinition edn = sdn.getSnapshot().getElementFirstRep();
2448      PropertyWithType pn = createProfile(map, profiles, new PropertyWithType(sdn.getId(), new Property(worker, edn, sdn), null, type), sliceName, tgt);
2449      return pn;
2450    } else {
2451      assert (!Utilities.noString(element));
2452      Property pvb = var.getProperty().getBaseProperty();
2453      Property pvd = var.getProperty().getProfileProperty();
2454      Property pc = pvb.getChild(element, var.getProperty().getTypes());
2455      if (pc == null)
2456        throw new DefinitionException("Unable to find a definition for " + pvb.getDefinition().getPath() + "." + element);
2457
2458      // the profile structure definition (derived)
2459      StructureDefinition sd = var.getProperty().getProfileProperty().getStructure();
2460      ElementDefinition ednew = sd.getDifferential().addElement();
2461      ednew.setPath(var.getProperty().getProfileProperty().getDefinition().getPath() + "." + pc.getName());
2462      ednew.setUserData("slice-name", sliceName);
2463      ednew.setFixed(fixed);
2464      for (ProfiledType pt : type.getProfiledTypes()) {
2465        if (pt.hasBindings())
2466          ednew.setBinding(pt.getBindings().get(0));
2467        if (pt.getUri().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
2468          String t = pt.getUri().substring(40);
2469          t = checkType(t, pc, pt.getProfiles());
2470          if (t != null) {
2471            if (pt.hasProfiles()) {
2472              for (String p : pt.getProfiles())
2473                if (t.equals("Reference"))
2474                  ednew.getType(t).addTargetProfile(p);
2475                else
2476                  ednew.getType(t).addProfile(p);
2477            } else
2478              ednew.getType(t);
2479          }
2480        }
2481      }
2482
2483      return new PropertyWithType(var.getProperty().getPath() + "." + element, pc, new Property(worker, ednew, sd), type);
2484    }
2485  }
2486
2487
2488  private String checkType(String t, Property pvb, List<String> profiles) throws FHIRException {
2489    if (pvb.getDefinition().getType().size() == 1 && isCompatibleType(t, pvb.getDefinition().getType().get(0).getWorkingCode()) && profilesMatch(profiles, pvb.getDefinition().getType().get(0).getProfile()))
2490      return null;
2491    for (TypeRefComponent tr : pvb.getDefinition().getType()) {
2492      if (isCompatibleType(t, tr.getWorkingCode()))
2493        return tr.getWorkingCode(); // note what is returned - the base type, not the inferred mapping type
2494    }
2495    throw new FHIRException("The type " + t + " is not compatible with the allowed types for " + pvb.getDefinition().getPath());
2496  }
2497
2498  private boolean profilesMatch(List<String> profiles, List<CanonicalType> profile) {
2499    return profiles == null || profiles.size() == 0 || profile.size() == 0 || (profiles.size() == 1 && profiles.get(0).equals(profile.get(0).getValue()));
2500  }
2501
2502  private boolean isCompatibleType(String t, String code) {
2503    if (t.equals(code))
2504      return true;
2505    if (t.equals("string")) {
2506      StructureDefinition sd = worker.fetchTypeDefinition(code);
2507      return sd != null && sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/string");
2508    }
2509    return false;
2510  }
2511
2512  private TypeDetails analyseTransform(TransformContext context, StructureMap map, StructureMapGroupRuleTargetComponent tgt, VariableForProfiling var, VariablesForProfiling vars) throws FHIRException {
2513    switch (tgt.getTransform()) {
2514      case CREATE:
2515        String p = getParamString(vars, tgt.getParameter().get(0));
2516        return new TypeDetails(CollectionStatus.SINGLETON, p);
2517      case COPY:
2518        return getParam(vars, tgt.getParameter().get(0));
2519      case EVALUATE:
2520        ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION);
2521        if (expr == null) {
2522          expr = fpe.parse(getParamString(vars, tgt.getParameter().get(tgt.getParameter().size() - 1)));
2523          tgt.setUserData(MAP_WHERE_EXPRESSION, expr);
2524        }
2525        return fpe.check(vars, null, expr);
2526      case TRANSLATE:
2527        return new TypeDetails(CollectionStatus.SINGLETON, "CodeableConcept");
2528      case CC:
2529        ProfiledType res = new ProfiledType("CodeableConcept");
2530        if (tgt.getParameter().size() >= 2 && isParamId(vars, tgt.getParameter().get(1))) {
2531          TypeDetails td = vars.get(null, getParamId(vars, tgt.getParameter().get(1))).getProperty().getTypes();
2532          if (td != null && td.hasBinding())
2533            // todo: do we need to check that there's no implicit translation her? I don't think we do...
2534            res.addBinding(td.getBinding());
2535        }
2536        return new TypeDetails(CollectionStatus.SINGLETON, res);
2537      case C:
2538        return new TypeDetails(CollectionStatus.SINGLETON, "Coding");
2539      case QTY:
2540        return new TypeDetails(CollectionStatus.SINGLETON, "Quantity");
2541      case REFERENCE:
2542        VariableForProfiling vrs = vars.get(VariableMode.OUTPUT, getParamId(vars, tgt.getParameterFirstRep()));
2543        if (vrs == null)
2544          throw new FHIRException("Unable to resolve variable \"" + getParamId(vars, tgt.getParameterFirstRep()) + "\"");
2545        String profile = vrs.getProperty().getProfileProperty().getStructure().getUrl();
2546        TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON);
2547        td.addType("Reference", profile);
2548        return td;
2549      case UUID:
2550        return new TypeDetails(CollectionStatus.SINGLETON, "id");
2551      default:
2552        throw new FHIRException("Transform Unknown or not handled yet: " + tgt.getTransform().toCode());
2553    }
2554  }
2555
2556  private String getParamString(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) {
2557    DataType p = parameter.getValue();
2558    if (p == null || p instanceof IdType)
2559      return null;
2560    if (!p.hasPrimitiveValue())
2561      return null;
2562    return p.primitiveValue();
2563  }
2564
2565  private String getParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) {
2566    DataType p = parameter.getValue();
2567    if (p == null || !(p instanceof IdType))
2568      return null;
2569    return p.primitiveValue();
2570  }
2571
2572  private boolean isParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) {
2573    DataType p = parameter.getValue();
2574    if (p == null || !(p instanceof IdType))
2575      return false;
2576    return vars.get(null, p.primitiveValue()) != null;
2577  }
2578
2579  private TypeDetails getParam(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException {
2580    DataType p = parameter.getValue();
2581    if (!(p instanceof IdType))
2582      return new TypeDetails(CollectionStatus.SINGLETON, ProfileUtilities.sdNs(p.fhirType(), null));
2583    else {
2584      String n = ((IdType) p).asStringValue();
2585      VariableForProfiling b = vars.get(VariableMode.INPUT, n);
2586      if (b == null)
2587        b = vars.get(VariableMode.OUTPUT, n);
2588      if (b == null)
2589        throw new DefinitionException("Variable " + n + " not found (" + vars.summary() + ")");
2590      return b.getProperty().getTypes();
2591    }
2592  }
2593
2594  private PropertyWithType createProfile(StructureMap map, List<StructureDefinition> profiles, PropertyWithType prop, String sliceName, Base ctxt) throws FHIRException {
2595    if (prop.getBaseProperty().getDefinition().getPath().contains("."))
2596      throw new DefinitionException("Unable to process entry point");
2597
2598    String type = prop.getBaseProperty().getDefinition().getPath();
2599    String suffix = "";
2600    if (ids.containsKey(type)) {
2601      int id = ids.get(type);
2602      id++;
2603      ids.put(type, id);
2604      suffix = "-" + id;
2605    } else
2606      ids.put(type, 0);
2607
2608    StructureDefinition profile = new StructureDefinition();
2609    profiles.add(profile);
2610    profile.setDerivation(TypeDerivationRule.CONSTRAINT);
2611    profile.setType(type);
2612    profile.setBaseDefinition(prop.getBaseProperty().getStructure().getUrl());
2613    profile.setName("Profile for " + profile.getType() + " for " + sliceName);
2614    profile.setUrl(map.getUrl().replace("StructureMap", "StructureDefinition") + "-" + profile.getType() + suffix);
2615    ctxt.setUserData("profile", profile.getUrl()); // then we can easily assign this profile url for validation later when we actually transform
2616    profile.setId(map.getId() + "-" + profile.getType() + suffix);
2617    profile.setStatus(map.getStatus());
2618    profile.setExperimental(map.getExperimental());
2619    profile.setDescription("Generated automatically from the mapping by the Java Reference Implementation");
2620    for (ContactDetail c : map.getContact()) {
2621      ContactDetail p = profile.addContact();
2622      p.setName(c.getName());
2623      for (ContactPoint cc : c.getTelecom())
2624        p.addTelecom(cc);
2625    }
2626    profile.setDate(map.getDate());
2627    profile.setCopyright(map.getCopyright());
2628    profile.setFhirVersion(FHIRVersion.fromCode(Constants.VERSION));
2629    profile.setKind(prop.getBaseProperty().getStructure().getKind());
2630    profile.setAbstract(false);
2631    ElementDefinition ed = profile.getDifferential().addElement();
2632    ed.setPath(profile.getType());
2633    prop.setProfileProperty(new Property(worker, ed, profile));
2634    return prop;
2635  }
2636
2637  private PropertyWithType resolveType(StructureMap map, String type, StructureMapInputMode mode) throws FHIRException {
2638    for (StructureMapStructureComponent imp : map.getStructure()) {
2639      if ((imp.getMode() == StructureMapModelMode.SOURCE && mode == StructureMapInputMode.SOURCE) ||
2640        (imp.getMode() == StructureMapModelMode.TARGET && mode == StructureMapInputMode.TARGET)) {
2641        StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl());
2642        if (sd == null)
2643          throw new FHIRException("Import " + imp.getUrl() + " cannot be resolved");
2644        if (sd.getId().equals(type)) {
2645          return new PropertyWithType(sd.getType(), new Property(worker, sd.getSnapshot().getElement().get(0), sd), null, new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()));
2646        }
2647      }
2648    }
2649    throw new FHIRException("Unable to find structure definition for " + type + " in imports");
2650  }
2651
2652
2653  public StructureMap generateMapFromMappings(StructureDefinition sd) throws IOException, FHIRException {
2654    String id = getLogicalMappingId(sd);
2655    if (id == null)
2656      return null;
2657    String prefix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_PREFIX);
2658    String suffix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_SUFFIX);
2659    if (prefix == null || suffix == null)
2660      return null;
2661    // we build this by text. Any element that has a mapping, we put it's mappings inside it....
2662    StringBuilder b = new StringBuilder();
2663    b.append(prefix);
2664
2665    ElementDefinition root = sd.getSnapshot().getElementFirstRep();
2666    String m = getMapping(root, id);
2667    if (m != null)
2668      b.append(m + "\r\n");
2669    addChildMappings(b, id, "", sd, root, false);
2670    b.append("\r\n");
2671    b.append(suffix);
2672    b.append("\r\n");
2673    StructureMap map = parse(b.toString(), sd.getUrl());
2674    map.setId(tail(map.getUrl()));
2675    if (!map.hasStatus()) {
2676      map.setStatus(PublicationStatus.DRAFT);
2677    }
2678    if (!map.hasDescription() && map.hasTitle()) {
2679      map.setDescription(map.getTitle());
2680    }
2681    map.getText().setStatus(NarrativeStatus.GENERATED);
2682    map.getText().setDiv(new XhtmlNode(NodeType.Element, "div"));
2683    map.getText().getDiv().addTag("pre").addText(render(map));
2684    return map;
2685  }
2686
2687
2688  private String tail(String url) {
2689    return url.substring(url.lastIndexOf("/") + 1);
2690  }
2691
2692
2693  private void addChildMappings(StringBuilder b, String id, String indent, StructureDefinition sd, ElementDefinition ed, boolean inner) throws DefinitionException {
2694    boolean first = true;
2695    List<ElementDefinition> children = profileUtilities.getChildMap(sd, ed).getList();
2696    for (ElementDefinition child : children) {
2697      if (first && inner) {
2698        b.append(" then {\r\n");
2699        first = false;
2700      }
2701      String map = getMapping(child, id);
2702      if (map != null) {
2703        b.append(indent + "  " + child.getPath() + ": " + map);
2704        addChildMappings(b, id, indent + "  ", sd, child, true);
2705        b.append("\r\n");
2706      }
2707    }
2708    if (!first && inner)
2709      b.append(indent + "}");
2710
2711  }
2712
2713
2714  private String getMapping(ElementDefinition ed, String id) {
2715    for (ElementDefinitionMappingComponent map : ed.getMapping())
2716      if (id.equals(map.getIdentity()))
2717        return map.getMap();
2718    return null;
2719  }
2720
2721
2722  private String getLogicalMappingId(StructureDefinition sd) {
2723    String id = null;
2724    for (StructureDefinitionMappingComponent map : sd.getMapping()) {
2725      if ("http://hl7.org/fhir/logical".equals(map.getUri()))
2726        return map.getIdentity();
2727    }
2728    return null;
2729  }
2730
2731  public ValidationOptions getTerminologyServiceOptions() {
2732    return terminologyServiceOptions;
2733  }
2734
2735  public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) {
2736    this.terminologyServiceOptions = terminologyServiceOptions;
2737  }
2738
2739  public boolean isExceptionsForChecks() {
2740    return exceptionsForChecks;
2741  }
2742
2743  public void setExceptionsForChecks(boolean exceptionsForChecks) {
2744    this.exceptionsForChecks = exceptionsForChecks;
2745  }
2746
2747  public List<StructureMap> getMapsForUrl(List<StructureMap> maps, String url, StructureMapInputMode mode) {
2748    List<StructureMap> res = new ArrayList<>();
2749    for (StructureMap map : maps) {
2750      if (mapIsForUrl(map, url, mode)) {
2751        res.add(map);
2752      }
2753    }
2754    return res;
2755  }
2756
2757  private boolean mapIsForUrl(StructureMap map, String url, StructureMapInputMode mode) {
2758    for (StructureMapGroupComponent grp : map.getGroup()) {
2759      if (grp.getTypeMode() != StructureMapGroupTypeMode.NULL) {
2760        for (StructureMapGroupInputComponent p : grp.getInput()) {
2761          if (mode == null || mode == p.getMode()) { 
2762            String t = resolveInputType(p, map);
2763            if (url.equals(t)) {
2764              return true;
2765            }
2766          }
2767        }
2768      }
2769    }
2770    return false;
2771  }
2772
2773  public List<StructureMap> getMapsForUrlPrefix(List<StructureMap> maps, String url, StructureMapInputMode mode) {
2774    List<StructureMap> res = new ArrayList<>();
2775    for (StructureMap map : maps) {
2776      if (mapIsForUrlPrefix(map, url, mode)) {
2777        res.add(map);
2778      }
2779    }
2780    return res;
2781  }
2782
2783  private boolean mapIsForUrlPrefix(StructureMap map, String url, StructureMapInputMode mode) {
2784    for (StructureMapGroupComponent grp : map.getGroup()) {
2785      if (grp.getTypeMode() != StructureMapGroupTypeMode.NULL) {
2786        for (StructureMapGroupInputComponent p : grp.getInput()) {
2787          if (mode == null || mode == p.getMode()) { 
2788            String t = resolveInputType(p, map);
2789            if (t != null && t.startsWith(url)) {
2790              return true;
2791            }
2792          }
2793        }
2794      }
2795    }
2796    return false;
2797  }
2798
2799  private String resolveInputType(StructureMapGroupInputComponent p, StructureMap map) {
2800    for (StructureMapStructureComponent struc : map.getStructure()) {
2801      if (struc.hasAlias() && struc.getAlias().equals(p.getType())) {
2802        return struc.getUrl();
2803      }
2804    }
2805    return null;
2806  }
2807
2808  public ResolvedGroup getGroupForUrl(StructureMap map, String url, StructureMapInputMode mode) {
2809    for (StructureMapGroupComponent grp : map.getGroup()) {
2810      if (grp.getTypeMode() != StructureMapGroupTypeMode.NULL) {
2811        for (StructureMapGroupInputComponent p : grp.getInput()) {
2812          if (mode == null || mode == p.getMode()) { 
2813            String t = resolveInputType(p, map);
2814            if (url.equals(t)) {
2815              return new ResolvedGroup(map, grp);
2816            }
2817          }
2818        }
2819      }
2820    }
2821    return null;
2822 }
2823
2824  public String getInputType(ResolvedGroup grp, StructureMapInputMode mode) {
2825    if (grp.getTargetGroup().getInput().size() != 2 || grp.getTargetGroup().getInput().get(0).getMode() == grp.getTargetGroup().getInput().get(1).getMode()) {
2826      return null;      
2827    } else if (grp.getTargetGroup().getInput().get(0).getMode() == mode) {
2828      return resolveInputType(grp.getTargetGroup().getInput().get(0), grp.getTargetMap());
2829    } else {
2830      return resolveInputType(grp.getTargetGroup().getInput().get(1), grp.getTargetMap());
2831    }
2832  }
2833
2834  public boolean isDebug() {
2835    return debug;
2836  }
2837
2838  public void setDebug(boolean debug) {
2839    this.debug = debug;
2840  }
2841
2842}