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