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