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