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