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