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