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