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