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