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