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