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