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                        if (eq != ConceptMapEquivalence.EQUIVALENT)
793                          tgt.setEquivalence(eq);
794                        if (tgt.getEquivalence() != ConceptMapEquivalence.UNMATCHED) {
795                                lexer.token(":");
796                                tgt.setCode(lexer.take());
797                                if (tgt.getCode().startsWith("\""))
798                                  tgt.setCode(lexer.processConstant(tgt.getCode()));
799                        }
800                        if (lexer.hasComment())
801                                tgt.setComment(lexer.take().substring(2).trim());
802                }
803                lexer.token("}");
804        }
805
806
807        private ConceptMapGroupComponent getGroup(ConceptMap map, String srcs, String tgts) {
808          for (ConceptMapGroupComponent grp : map.getGroup()) {
809            if (grp.getSource().equals(srcs)) 
810              if (!grp.hasTarget() || tgts == null || tgts.equals(grp.getTarget())) {
811                if (!grp.hasTarget() && tgts != null)
812                  grp.setTarget(tgts);
813                return grp;
814              }
815          }
816          ConceptMapGroupComponent grp = map.addGroup(); 
817          grp.setSource(srcs);
818          grp.setTarget(tgts);
819          return grp;
820        }
821
822
823        private String readPrefix(Map<String, String> prefixes, FHIRLexer lexer) throws FHIRLexerException {
824                String prefix = lexer.take();
825                if (!prefixes.containsKey(prefix))
826                        throw lexer.error("Unknown prefix '"+prefix+"'");
827                return prefixes.get(prefix);
828        }
829
830
831        private ConceptMapEquivalence readEquivalence(FHIRLexer lexer) throws FHIRLexerException {
832                String token = lexer.take();
833    if (token.equals("-"))
834      return ConceptMapEquivalence.RELATEDTO;
835    if (token.equals("="))
836      return ConceptMapEquivalence.EQUAL;
837                if (token.equals("=="))
838                        return ConceptMapEquivalence.EQUIVALENT;
839                if (token.equals("!="))
840                        return ConceptMapEquivalence.DISJOINT;
841                if (token.equals("--"))
842                        return ConceptMapEquivalence.UNMATCHED;
843                if (token.equals("<="))
844                        return ConceptMapEquivalence.WIDER;
845                if (token.equals("<-"))
846                        return ConceptMapEquivalence.SUBSUMES;
847                if (token.equals(">="))
848                        return ConceptMapEquivalence.NARROWER;
849                if (token.equals(">-"))
850                        return ConceptMapEquivalence.SPECIALIZES;
851                if (token.equals("~"))
852                        return ConceptMapEquivalence.INEXACT;
853                throw lexer.error("Unknown equivalence token '"+token+"'");
854        }
855
856
857        private void parseUses(StructureMap result, FHIRLexer lexer) throws FHIRException {
858                lexer.token("uses");
859                StructureMapStructureComponent st = result.addStructure();
860                st.setUrl(lexer.readConstant("url"));
861                if (lexer.hasToken("alias")) {
862            lexer.token("alias");
863                  st.setAlias(lexer.take());
864                }
865                lexer.token("as");
866                st.setMode(StructureMapModelMode.fromCode(lexer.take()));
867                lexer.skipToken(";");
868                if (lexer.hasComment()) {
869                        st.setDocumentation(lexer.take().substring(2).trim());
870                }
871                lexer.skipComments();
872        }
873
874        private void parseImports(StructureMap result, FHIRLexer lexer) throws FHIRException {
875                lexer.token("imports");
876                result.addImport(lexer.readConstant("url"));
877                lexer.skipToken(";");
878                if (lexer.hasComment()) {
879                        lexer.next();
880                }
881                lexer.skipComments();
882        }
883
884        private void parseGroup(StructureMap result, FHIRLexer lexer) throws FHIRException {
885                lexer.token("group");
886                StructureMapGroupComponent group = result.addGroup();
887                boolean newFmt = false;
888                if (lexer.hasToken("for")) {
889                  lexer.token("for");
890                  if ("type".equals(lexer.getCurrent())) {
891        lexer.token("type");
892        lexer.token("+");
893        lexer.token("types");
894        group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES);
895                  } else {
896                    lexer.token("types");
897        group.setTypeMode(StructureMapGroupTypeMode.TYPES);
898                  }
899                } else
900                  group.setTypeMode(StructureMapGroupTypeMode.NONE);
901                group.setName(lexer.take());
902                if (lexer.hasToken("(")) {
903                  newFmt = true;
904                  lexer.take();
905                  while (!lexer.hasToken(")")) {
906                    parseInput(group, lexer, true);
907                    if (lexer.hasToken(","))
908                      lexer.token(",");
909                  }
910                  lexer.take();
911                }
912                if (lexer.hasToken("extends")) {
913                        lexer.next();
914                        group.setExtends(lexer.take());
915                }
916                if (newFmt) {
917      group.setTypeMode(StructureMapGroupTypeMode.NONE);
918                  if (lexer.hasToken("<")) {
919        lexer.token("<");
920        lexer.token("<");
921        if (lexer.hasToken("types")) {
922          group.setTypeMode(StructureMapGroupTypeMode.TYPES);          
923          lexer.token("types");
924        } else {
925          lexer.token("type");
926          lexer.token("+");
927          group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES);
928        }
929        lexer.token(">");
930        lexer.token(">");
931                  }
932                  lexer.token("{");
933                }
934                lexer.skipComments();
935                if (newFmt) {
936      while (!lexer.hasToken("}")) {
937        if (lexer.done())
938          throw lexer.error("premature termination expecting 'endgroup'");
939        parseRule(result, group.getRule(), lexer, true);
940      }
941                } else {
942                  while (lexer.hasToken("input")) 
943                    parseInput(group, lexer, false);
944                  while (!lexer.hasToken("endgroup")) {
945                    if (lexer.done())
946                      throw lexer.error("premature termination expecting 'endgroup'");
947                    parseRule(result, group.getRule(), lexer, false);
948                  }
949                }
950                lexer.next();
951                if (newFmt && lexer.hasToken(";"))
952            lexer.next();
953                lexer.skipComments();
954        }
955
956        private void parseInput(StructureMapGroupComponent group, FHIRLexer lexer, boolean newFmt) throws FHIRException {
957    StructureMapGroupInputComponent input = group.addInput();
958          if (newFmt) {
959            input.setMode(StructureMapInputMode.fromCode(lexer.take()));            
960          } else
961                lexer.token("input");
962                input.setName(lexer.take());
963                if (lexer.hasToken(":")) {
964                        lexer.token(":");
965                        input.setType(lexer.take());
966                }
967                if (!newFmt) {
968                lexer.token("as");
969                input.setMode(StructureMapInputMode.fromCode(lexer.take()));
970                  if (lexer.hasComment()) {
971                          input.setDocumentation(lexer.take().substring(2).trim());
972                }
973                lexer.skipToken(";");
974                  lexer.skipComments();
975                }
976        }
977
978        private void parseRule(StructureMap map, List<StructureMapGroupRuleComponent> list, FHIRLexer lexer, boolean newFmt) throws FHIRException {
979                StructureMapGroupRuleComponent rule = new StructureMapGroupRuleComponent(); 
980                list.add(rule);
981                if (!newFmt) {
982                  rule.setName(lexer.takeDottedToken());
983                  lexer.token(":");
984                  lexer.token("for");
985    }
986                boolean done = false;
987                while (!done) {
988                        parseSource(rule, lexer);
989                        done = !lexer.hasToken(",");
990                        if (!done)
991                                lexer.next();
992                }
993                if ((newFmt && lexer.hasToken("->")) || (!newFmt && lexer.hasToken("make"))) {
994                        lexer.token(newFmt ? "->" : "make");
995                        done = false;
996                        while (!done) {
997                                parseTarget(rule, lexer);
998                                done = !lexer.hasToken(",");
999                                if (!done)
1000                                        lexer.next();
1001                        }
1002                }
1003                if (lexer.hasToken("then")) {
1004                        lexer.token("then");
1005                        if (lexer.hasToken("{")) {
1006                                lexer.token("{");
1007                                if (lexer.hasComment()) {
1008                                        rule.setDocumentation(lexer.take().substring(2).trim());
1009                                }
1010                                lexer.skipComments();
1011                                while (!lexer.hasToken("}")) {
1012                                        if (lexer.done())
1013                                                throw lexer.error("premature termination expecting '}' in nested group");
1014                                        parseRule(map, rule.getRule(), lexer, newFmt);
1015                                }      
1016                                lexer.token("}");
1017                        } else {
1018                                done = false;
1019                                while (!done) {
1020                                        parseRuleReference(rule, lexer);
1021                                        done = !lexer.hasToken(",");
1022                                        if (!done)
1023                                                lexer.next();
1024                                }
1025                        }
1026                } else if (lexer.hasComment()) {
1027                        rule.setDocumentation(lexer.take().substring(2).trim());
1028                }
1029                if (isSimpleSyntax(rule)) {
1030                  rule.getSourceFirstRep().setVariable(AUTO_VAR_NAME);
1031                  rule.getTargetFirstRep().setVariable(AUTO_VAR_NAME);
1032                  rule.getTargetFirstRep().setTransform(StructureMapTransform.CREATE); // with no parameter - e.g. imply what is to be created
1033                  // no dependencies - imply what is to be done based on types
1034                }
1035                if (newFmt) {
1036                  if (lexer.isConstant()) {
1037                    if (lexer.isStringConstant()) {
1038                      rule.setName(lexer.readConstant("ruleName"));
1039                    } else {
1040                    rule.setName(lexer.take());
1041                    }
1042                  } else {
1043                    if (rule.getSource().size() != 1 || !rule.getSourceFirstRep().hasElement())
1044                      throw lexer.error("Complex rules must have an explicit name");
1045                    if (rule.getSourceFirstRep().hasType())
1046                      rule.setName(rule.getSourceFirstRep().getElement()+"-"+rule.getSourceFirstRep().getType());
1047                    else
1048          rule.setName(rule.getSourceFirstRep().getElement());
1049                  }
1050      lexer.token(";");
1051                }
1052                lexer.skipComments();
1053        }
1054
1055        private boolean isSimpleSyntax(StructureMapGroupRuleComponent rule) {
1056    return 
1057        (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasContext() && rule.getSourceFirstRep().hasElement() && !rule.getSourceFirstRep().hasVariable()) &&
1058        (rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasContext() && rule.getTargetFirstRep().hasElement() && !rule.getTargetFirstRep().hasVariable() && !rule.getTargetFirstRep().hasParameter()) &&
1059        (rule.getDependent().size() == 0 && rule.getRule().size() == 0);
1060  }
1061
1062  private void parseRuleReference(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRLexerException {
1063                StructureMapGroupRuleDependentComponent ref = rule.addDependent();
1064                ref.setName(lexer.take());
1065                lexer.token("(");
1066                boolean done = false;
1067                while (!done) {
1068                        ref.addVariable(lexer.take());
1069                        done = !lexer.hasToken(",");
1070                        if (!done)
1071                                lexer.next();
1072                }
1073                lexer.token(")");
1074        }
1075
1076        private void parseSource(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException {
1077                StructureMapGroupRuleSourceComponent source = rule.addSource();
1078                source.setContext(lexer.take());
1079                if (source.getContext().equals("search") && lexer.hasToken("(")) {
1080            source.setContext("@search");
1081      lexer.take();
1082      ExpressionNode node = fpe.parse(lexer);
1083      source.setUserData(MAP_SEARCH_EXPRESSION, node);
1084      source.setElement(node.toString());
1085      lexer.token(")");
1086                } else if (lexer.hasToken(".")) {
1087                        lexer.token(".");
1088                        source.setElement(lexer.take());
1089                }
1090                if (lexer.hasToken(":")) {
1091                  // type and cardinality
1092                  lexer.token(":");
1093                  source.setType(lexer.takeDottedToken());
1094                  if (!lexer.hasToken("as", "first", "last", "not_first", "not_last", "only_one", "default")) {
1095                    source.setMin(lexer.takeInt());
1096                    lexer.token("..");
1097                    source.setMax(lexer.take());
1098                  }
1099                }
1100                if (lexer.hasToken("default")) {
1101                  lexer.token("default");
1102                  source.setDefaultValue(new StringType(lexer.readConstant("default value")));
1103                }
1104                if (Utilities.existsInList(lexer.getCurrent(), "first", "last", "not_first", "not_last", "only_one"))
1105                        source.setListMode(StructureMapSourceListMode.fromCode(lexer.take()));
1106
1107                if (lexer.hasToken("as")) {
1108                        lexer.take();
1109                        source.setVariable(lexer.take());
1110                }
1111                if (lexer.hasToken("where")) {
1112                        lexer.take();
1113                        ExpressionNode node = fpe.parse(lexer);
1114                        source.setUserData(MAP_WHERE_EXPRESSION, node);
1115                        source.setCondition(node.toString());
1116                }
1117                if (lexer.hasToken("check")) {
1118                        lexer.take();
1119                        ExpressionNode node = fpe.parse(lexer);
1120                        source.setUserData(MAP_WHERE_CHECK, node);
1121                        source.setCheck(node.toString());
1122                }
1123    if (lexer.hasToken("log")) {
1124      lexer.take();
1125      ExpressionNode node = fpe.parse(lexer);
1126      source.setUserData(MAP_WHERE_CHECK, node);
1127      source.setLogMessage(node.toString());
1128    }
1129        }
1130
1131        private void parseTarget(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException {
1132                StructureMapGroupRuleTargetComponent target = rule.addTarget();
1133                String start = lexer.take();
1134                if (lexer.hasToken(".")) {
1135            target.setContext(start);
1136            target.setContextType(StructureMapContextType.VARIABLE);
1137            start = null;
1138                        lexer.token(".");
1139                        target.setElement(lexer.take());
1140                }
1141                String name;
1142                boolean isConstant = false;
1143                if (lexer.hasToken("=")) {
1144                  if (start != null)
1145              target.setContext(start);
1146                        lexer.token("=");
1147                        isConstant = lexer.isConstant();
1148                        name = lexer.take();
1149                } else 
1150                  name = start;
1151                
1152                if ("(".equals(name)) {
1153                  // inline fluentpath expression
1154      target.setTransform(StructureMapTransform.EVALUATE);
1155      ExpressionNode node = fpe.parse(lexer);
1156      target.setUserData(MAP_EXPRESSION, node);
1157      target.addParameter().setValue(new StringType(node.toString()));
1158      lexer.token(")");
1159                } else if (lexer.hasToken("(")) {
1160                                target.setTransform(StructureMapTransform.fromCode(name));
1161                                lexer.token("(");
1162                                if (target.getTransform() == StructureMapTransform.EVALUATE) {
1163                                        parseParameter(target, lexer);
1164                                        lexer.token(",");
1165                                        ExpressionNode node = fpe.parse(lexer);
1166                                        target.setUserData(MAP_EXPRESSION, node);
1167                                        target.addParameter().setValue(new StringType(node.toString()));
1168                                } else { 
1169                                        while (!lexer.hasToken(")")) {
1170                                                parseParameter(target, lexer);
1171                                                if (!lexer.hasToken(")"))
1172                                                        lexer.token(",");
1173                                        }       
1174                                }
1175                                lexer.token(")");
1176                } else if (name != null) {
1177                                target.setTransform(StructureMapTransform.COPY);
1178                                if (!isConstant) {
1179                                        String id = name;
1180                                        while (lexer.hasToken(".")) {
1181                                                id = id + lexer.take() + lexer.take();
1182                                        }
1183                                        target.addParameter().setValue(new IdType(id));
1184                                }
1185                                else 
1186                                        target.addParameter().setValue(readConstant(name, lexer));
1187                        }
1188                if (lexer.hasToken("as")) {
1189                        lexer.take();
1190                        target.setVariable(lexer.take());
1191                }
1192                while (Utilities.existsInList(lexer.getCurrent(), "first", "last", "share", "collate")) {
1193                        if (lexer.getCurrent().equals("share")) {
1194                                target.addListMode(StructureMapTargetListMode.SHARE);
1195                                lexer.next();
1196                                target.setListRuleId(lexer.take());
1197                        } else {
1198                                if (lexer.getCurrent().equals("first")) 
1199                                        target.addListMode(StructureMapTargetListMode.FIRST);
1200                                else
1201                                        target.addListMode(StructureMapTargetListMode.LAST);
1202                                lexer.next();
1203                        }
1204                }
1205        }
1206
1207
1208        private void parseParameter(StructureMapGroupRuleTargetComponent target, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError {
1209                if (!lexer.isConstant()) {
1210                        target.addParameter().setValue(new IdType(lexer.take()));
1211                } else if (lexer.isStringConstant())
1212                        target.addParameter().setValue(new StringType(lexer.readConstant("??")));
1213                else {
1214                        target.addParameter().setValue(readConstant(lexer.take(), lexer));
1215                }
1216        }
1217
1218        private Type readConstant(String s, FHIRLexer lexer) throws FHIRLexerException {
1219                if (Utilities.isInteger(s))
1220                        return new IntegerType(s);
1221                else if (Utilities.isDecimal(s, false))
1222                        return new DecimalType(s);
1223                else if (Utilities.existsInList(s, "true", "false"))
1224                        return new BooleanType(s.equals("true"));
1225                else 
1226                        return new StringType(lexer.processConstant(s));        
1227        }
1228
1229        public StructureDefinition getTargetType(StructureMap map) throws FHIRException {
1230          boolean found = false;
1231          StructureDefinition res = null;
1232          for (StructureMapStructureComponent uses : map.getStructure()) {
1233            if (uses.getMode() == StructureMapModelMode.TARGET) {
1234              if (found)
1235                throw new FHIRException("Multiple targets found in map "+map.getUrl());
1236              found = true;
1237              res = worker.fetchResource(StructureDefinition.class, uses.getUrl());
1238              if (res == null)
1239                throw new FHIRException("Unable to find "+uses.getUrl()+" referenced from map "+map.getUrl());      
1240            }
1241          }
1242          if (res == null)
1243      throw new FHIRException("No targets found in map "+map.getUrl());
1244          return res;
1245        }
1246
1247        public enum VariableMode {
1248                INPUT, OUTPUT, SHARED
1249        }
1250
1251        public class Variable {
1252                private VariableMode mode;
1253                private String name;
1254                private Base object;
1255                public Variable(VariableMode mode, String name, Base object) {
1256                        super();
1257                        this.mode = mode;
1258                        this.name = name;
1259                        this.object = object;
1260                }
1261                public VariableMode getMode() {
1262                        return mode;
1263                }
1264                public String getName() {
1265                        return name;
1266                }
1267                public Base getObject() {
1268                        return object;
1269                }
1270    public String summary() {
1271      if (object == null) 
1272        return null;
1273      else if (object instanceof PrimitiveType)
1274        return name+": \""+((PrimitiveType) object).asStringValue()+'"';
1275      else
1276        return name+": ("+object.fhirType()+")";
1277    }
1278        }
1279
1280        public class Variables {
1281                private List<Variable> list = new ArrayList<Variable>();
1282
1283                public void add(VariableMode mode, String name, Base object) {
1284                        Variable vv = null;
1285                        for (Variable v : list) 
1286                                if ((v.mode == mode) && v.getName().equals(name))
1287                                        vv = v;
1288                        if (vv != null)
1289                                list.remove(vv);
1290                        list.add(new Variable(mode, name, object));
1291                }
1292
1293                public Variables copy() {
1294                        Variables result = new Variables();
1295                        result.list.addAll(list);
1296                        return result;
1297                }
1298
1299                public Base get(VariableMode mode, String name) {
1300                        for (Variable v : list) 
1301                                if ((v.mode == mode) && v.getName().equals(name))
1302                                        return v.getObject();
1303                        return null;
1304                }
1305
1306            public String summary() {
1307              CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder();
1308              CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder();
1309              CommaSeparatedStringBuilder sh = new CommaSeparatedStringBuilder();
1310              for (Variable v : list)
1311                switch(v.mode) {
1312                case INPUT:
1313                  s.append(v.summary());
1314                  break;
1315                case OUTPUT:
1316                  t.append(v.summary());
1317                  break;
1318                case SHARED:
1319                  sh.append(v.summary());
1320                  break;
1321                }
1322              return "source variables ["+s.toString()+"], target variables ["+t.toString()+"], shared variables ["+sh.toString()+"]";
1323            }
1324            
1325        }
1326
1327        public class TransformContext {
1328                private Object appInfo;
1329
1330                public TransformContext(Object appInfo) {
1331                        super();
1332                        this.appInfo = appInfo;
1333                }
1334
1335                public Object getAppInfo() {
1336                        return appInfo;
1337                }
1338
1339        }
1340
1341        private void log(String cnt) {
1342          if (services != null)
1343            services.log(cnt);
1344          else
1345            System.out.println(cnt);
1346        }
1347
1348        /**
1349         * Given an item, return all the children that conform to the pattern described in name
1350         * 
1351         * Possible patterns:
1352         *  - a simple name (which may be the base of a name with [] e.g. value[x])
1353         *  - a name with a type replacement e.g. valueCodeableConcept
1354         *  - * which means all children
1355         *  - ** which means all descendents
1356         *  
1357         * @param item
1358         * @param name
1359         * @param result
1360         * @throws FHIRException 
1361         */
1362        protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException {
1363                for (Base v : item.listChildrenByName(name, true))
1364                        if (v != null)
1365                                result.add(v);
1366        }
1367
1368        public void transform(Object appInfo, Base source, StructureMap map, Base target) throws FHIRException {
1369                TransformContext context = new TransformContext(appInfo);
1370    log("Start Transform "+map.getUrl());
1371    StructureMapGroupComponent g = map.getGroup().get(0);
1372
1373                Variables vars = new Variables();
1374                vars.add(VariableMode.INPUT, getInputName(g, StructureMapInputMode.SOURCE, "source"), source);
1375                if (target != null)
1376                vars.add(VariableMode.OUTPUT, getInputName(g, StructureMapInputMode.TARGET, "target"), target);
1377
1378    executeGroup("", context, map, vars, g, true);
1379    if (target instanceof Element)
1380      ((Element) target).sort();
1381        }
1382
1383        private String getInputName(StructureMapGroupComponent g, StructureMapInputMode mode, String def) throws DefinitionException {
1384          String name = null;
1385    for (StructureMapGroupInputComponent inp : g.getInput()) {
1386      if (inp.getMode() == mode)
1387        if (name != null)
1388          throw new DefinitionException("This engine does not support multiple source inputs");
1389        else
1390          name = inp.getName();
1391    }
1392    return name == null ? def : name;
1393        }
1394
1395        private void executeGroup(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, boolean atRoot) throws FHIRException {
1396                log(indent+"Group : "+group.getName()+"; vars = "+vars.summary());
1397    // todo: check inputs
1398                if (group.hasExtends()) {
1399                  ResolvedGroup rg = resolveGroupReference(map, group, group.getExtends());
1400                  executeGroup(indent+" ", context, rg.targetMap, vars, rg.target, false); 
1401                }
1402                  
1403                for (StructureMapGroupRuleComponent r : group.getRule()) {
1404                        executeRule(indent+"  ", context, map, vars, group, r, atRoot);
1405                }
1406        }
1407
1408        private void executeRule(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, boolean atRoot) throws FHIRException {
1409                log(indent+"rule : "+rule.getName()+"; vars = "+vars.summary());
1410                Variables srcVars = vars.copy();
1411                if (rule.getSource().size() != 1)
1412                        throw new FHIRException("Rule \""+rule.getName()+"\": not handled yet");
1413                List<Variables> source = processSource(rule.getName(), context, srcVars, rule.getSource().get(0), map.getUrl(), indent);
1414                if (source != null) {
1415                        for (Variables v : source) {
1416                                for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) {
1417                                        processTarget(rule.getName(), context, v, map, group, t, rule.getSource().size() == 1 ? rule.getSourceFirstRep().getVariable() : null, atRoot, vars);
1418                                }
1419                                if (rule.hasRule()) {
1420                                        for (StructureMapGroupRuleComponent childrule : rule.getRule()) {
1421                                                executeRule(indent +"  ", context, map, v, group, childrule, false);
1422                                        }
1423                                } else if (rule.hasDependent()) {
1424                                        for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) {
1425                                                executeDependency(indent+"  ", context, map, v, group, dependent);
1426                                        }
1427                                } else if (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasVariable() && rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasVariable() && rule.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE && !rule.getTargetFirstRep().hasParameter()) {
1428                                  // simple inferred, map by type
1429                                  System.out.println(v.summary());
1430                                  Base src = v.get(VariableMode.INPUT, rule.getSourceFirstRep().getVariable());
1431                                  Base tgt = v.get(VariableMode.OUTPUT, rule.getTargetFirstRep().getVariable());
1432                                  String srcType = src.fhirType();
1433                                  String tgtType = tgt.fhirType();
1434                                  ResolvedGroup defGroup = resolveGroupByTypes(map, rule.getName(), group, srcType, tgtType);
1435                            Variables vdef = new Variables();
1436          vdef.add(VariableMode.INPUT, defGroup.target.getInput().get(0).getName(), src);
1437          vdef.add(VariableMode.OUTPUT, defGroup.target.getInput().get(1).getName(), tgt);
1438                                  executeGroup(indent+"  ", context, defGroup.targetMap, vdef, defGroup.target, false);
1439                                }
1440                        }
1441                }
1442        }
1443
1444  private void executeDependency(String indent, TransformContext context, StructureMap map, Variables vin, StructureMapGroupComponent group, StructureMapGroupRuleDependentComponent dependent) throws FHIRException {
1445          ResolvedGroup rg = resolveGroupReference(map, group, dependent.getName());
1446
1447                if (rg.target.getInput().size() != dependent.getVariable().size()) {
1448                        throw new FHIRException("Rule '"+dependent.getName()+"' has "+Integer.toString(rg.target.getInput().size())+" but the invocation has "+Integer.toString(dependent.getVariable().size())+" variables");
1449                }
1450                Variables v = new Variables();
1451                for (int i = 0; i < rg.target.getInput().size(); i++) {
1452                        StructureMapGroupInputComponent input = rg.target.getInput().get(i);
1453                        StringType rdp = dependent.getVariable().get(i);
1454      String var = rdp.asStringValue();
1455                        VariableMode mode = input.getMode() == StructureMapInputMode.SOURCE ? VariableMode.INPUT :   VariableMode.OUTPUT; 
1456                        Base vv = vin.get(mode, var);
1457      if (vv == null && mode == VariableMode.INPUT) //* once source, always source. but target can be treated as source at user convenient
1458        vv = vin.get(VariableMode.OUTPUT, var);
1459                        if (vv == null)
1460                                throw new FHIRException("Rule '"+dependent.getName()+"' "+mode.toString()+" variable '"+input.getName()+"' named as '"+var+"' has no value (vars = "+vin.summary()+")");
1461                        v.add(mode, input.getName(), vv);       
1462                }
1463                executeGroup(indent+"  ", context, rg.targetMap, v, rg.target, false);
1464        }
1465
1466  private String determineTypeFromSourceType(StructureMap map, StructureMapGroupComponent source, Base base, String[] types) throws FHIRException {
1467    String type = base.fhirType();
1468    String kn = "type^"+type;
1469    if (source.hasUserData(kn))
1470      return source.getUserString(kn);
1471    
1472    ResolvedGroup res = new ResolvedGroup();
1473    res.targetMap = null;
1474    res.target = null;
1475    for (StructureMapGroupComponent grp : map.getGroup()) {
1476      if (matchesByType(map, grp, type)) {
1477        if (res.targetMap == null) {
1478          res.targetMap = map;
1479          res.target = grp;
1480        } else 
1481          throw new FHIRException("Multiple possible matches looking for default rule for '"+type+"'");
1482      }
1483    }
1484    if (res.targetMap != null) {
1485      String result = getActualType(res.targetMap, res.target.getInput().get(1).getType());
1486      source.setUserData(kn, result);
1487      return result;
1488    }
1489
1490    for (UriType imp : map.getImport()) {
1491      List<StructureMap> impMapList = findMatchingMaps(imp.getValue());
1492      if (impMapList.size() == 0)
1493        throw new FHIRException("Unable to find map(s) for "+imp.getValue());
1494      for (StructureMap impMap : impMapList) {
1495        if (!impMap.getUrl().equals(map.getUrl())) {
1496          for (StructureMapGroupComponent grp : impMap.getGroup()) {
1497            if (matchesByType(impMap, grp, type)) {
1498              if (res.targetMap == null) {
1499                res.targetMap = impMap;
1500                res.target = grp;
1501              } else 
1502                throw new FHIRException("Multiple possible matches for default rule for '"+type+"' in "+res.targetMap.getUrl()+" ("+res.target.getName()+") and "+impMap.getUrl()+" ("+grp.getName()+")");
1503            }
1504          }
1505        }
1506      }
1507    }
1508    if (res.target == null)
1509      throw new FHIRException("No matches found for default rule for '"+type+"' from "+map.getUrl());
1510    String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); // should be .getType, but R2...
1511    source.setUserData(kn, result);
1512    return result;
1513  }
1514
1515  private List<StructureMap> findMatchingMaps(String value) {
1516    List<StructureMap> res = new ArrayList<StructureMap>();
1517    if (value.contains("*")) {
1518      for (StructureMap sm : worker.listTransforms()) {
1519        if (urlMatches(value, sm.getUrl())) {
1520            res.add(sm); 
1521          }
1522        }
1523    } else {
1524      StructureMap sm = worker.getTransform(value);
1525      if (sm != null)
1526        res.add(sm); 
1527    }
1528    Set<String> check = new HashSet<String>();
1529    for (StructureMap sm : res) {
1530      if (check.contains(sm.getUrl()))
1531        throw new Error("duplicate");
1532      else
1533        check.add(sm.getUrl());
1534    }
1535    return res;
1536  }
1537
1538  private boolean urlMatches(String mask, String url) {
1539    return url.length() > mask.length() && url.startsWith(mask.substring(0, mask.indexOf("*"))) && url.endsWith(mask.substring(mask.indexOf("*")+1)) ;
1540  }
1541
1542  private ResolvedGroup resolveGroupByTypes(StructureMap map, String ruleid, StructureMapGroupComponent source, String srcType, String tgtType) throws FHIRException {
1543    String kn = "types^"+srcType+":"+tgtType;
1544    if (source.hasUserData(kn))
1545      return (ResolvedGroup) source.getUserData(kn);
1546    
1547    ResolvedGroup res = new ResolvedGroup();
1548    res.targetMap = null;
1549    res.target = null;
1550    for (StructureMapGroupComponent grp : map.getGroup()) {
1551      if (matchesByType(map, grp, srcType, tgtType)) {
1552        if (res.targetMap == null) {
1553          res.targetMap = map;
1554          res.target = grp;
1555        } else 
1556          throw new FHIRException("Multiple possible matches looking for rule for '"+srcType+"/"+tgtType+"', from rule '"+ruleid+"'");
1557      }
1558    }
1559    if (res.targetMap != null) {
1560      source.setUserData(kn, res);
1561      return res;
1562    }
1563
1564    for (UriType imp : map.getImport()) {
1565      List<StructureMap> impMapList = findMatchingMaps(imp.getValue());
1566      if (impMapList.size() == 0)
1567        throw new FHIRException("Unable to find map(s) for "+imp.getValue());
1568      for (StructureMap impMap : impMapList) {
1569        if (!impMap.getUrl().equals(map.getUrl())) {
1570          for (StructureMapGroupComponent grp : impMap.getGroup()) {
1571            if (matchesByType(impMap, grp, srcType, tgtType)) {
1572              if (res.targetMap == null) {
1573                res.targetMap = impMap;
1574                res.target = grp;
1575              } else 
1576                throw new FHIRException("Multiple possible matches for rule for '"+srcType+"/"+tgtType+"' in "+res.targetMap.getUrl()+" and "+impMap.getUrl()+", from rule '"+ruleid+"'");
1577            }
1578          }
1579        }
1580      }
1581    }
1582    if (res.target == null)
1583      throw new FHIRException("No matches found for rule for '"+srcType+" to "+tgtType+"' from "+map.getUrl()+", from rule '"+ruleid+"'");
1584    source.setUserData(kn, res);
1585    return res;
1586  }
1587
1588
1589  private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String type) throws FHIRException {
1590    if (grp.getTypeMode() != StructureMapGroupTypeMode.TYPEANDTYPES)
1591      return false;
1592    if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET)
1593      return false;
1594    return matchesType(map, type, grp.getInput().get(0).getType());
1595  }
1596
1597  private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String srcType, String tgtType) throws FHIRException {
1598    if (grp.getTypeMode() == StructureMapGroupTypeMode.NONE)
1599      return false;
1600    if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET)
1601      return false;
1602    if (!grp.getInput().get(0).hasType() || !grp.getInput().get(1).hasType())
1603      return false;
1604    return matchesType(map, srcType, grp.getInput().get(0).getType()) && matchesType(map, tgtType, grp.getInput().get(1).getType());
1605  }
1606
1607  private boolean matchesType(StructureMap map, String actualType, String statedType) throws FHIRException {
1608    // check the aliases
1609    for (StructureMapStructureComponent imp : map.getStructure()) {
1610      if (imp.hasAlias() && statedType.equals(imp.getAlias())) {
1611        StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl());
1612        if (sd != null)
1613          statedType = sd.getType();
1614        break;
1615      }
1616    }
1617    
1618    if (Utilities.isAbsoluteUrl(actualType)) {
1619      StructureDefinition sd = worker.fetchResource(StructureDefinition.class, actualType);
1620      if (sd != null)
1621        actualType = sd.getType();
1622    }
1623    if (Utilities.isAbsoluteUrl(statedType)) {
1624      StructureDefinition sd = worker.fetchResource(StructureDefinition.class, statedType);
1625      if (sd != null)
1626        statedType = sd.getType();
1627    }
1628    return actualType.equals(statedType);
1629  }
1630
1631  private String getActualType(StructureMap map, String statedType) throws FHIRException {
1632    // check the aliases
1633    for (StructureMapStructureComponent imp : map.getStructure()) {
1634      if (imp.hasAlias() && statedType.equals(imp.getAlias())) {
1635        StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl());
1636        if (sd == null)
1637          throw new FHIRException("Unable to resolve structure "+imp.getUrl());
1638        return sd.getId(); // should be sd.getType(), but R2...
1639      }
1640    }
1641    return statedType;
1642  }
1643
1644
1645  private ResolvedGroup resolveGroupReference(StructureMap map, StructureMapGroupComponent source, String name) throws FHIRException {
1646    String kn = "ref^"+name;
1647    if (source.hasUserData(kn))
1648      return (ResolvedGroup) source.getUserData(kn);
1649    
1650          ResolvedGroup res = new ResolvedGroup();
1651    res.targetMap = null;
1652    res.target = null;
1653    for (StructureMapGroupComponent grp : map.getGroup()) {
1654      if (grp.getName().equals(name)) {
1655        if (res.targetMap == null) {
1656          res.targetMap = map;
1657          res.target = grp;
1658        } else 
1659          throw new FHIRException("Multiple possible matches for rule '"+name+"'");
1660      }
1661    }
1662    if (res.targetMap != null) {
1663      source.setUserData(kn, res);
1664      return res;
1665    }
1666
1667    for (UriType imp : map.getImport()) {
1668      List<StructureMap> impMapList = findMatchingMaps(imp.getValue());
1669      if (impMapList.size() == 0)
1670        throw new FHIRException("Unable to find map(s) for "+imp.getValue());
1671      for (StructureMap impMap : impMapList) {
1672        if (!impMap.getUrl().equals(map.getUrl())) {
1673          for (StructureMapGroupComponent grp : impMap.getGroup()) {
1674            if (grp.getName().equals(name)) {
1675              if (res.targetMap == null) {
1676                res.targetMap = impMap;
1677                res.target = grp;
1678              } else 
1679                throw new FHIRException("Multiple possible matches for rule group '"+name+"' in "+
1680                 res.targetMap.getUrl()+"#"+res.target.getName()+" and "+
1681                 impMap.getUrl()+"#"+grp.getName());
1682            }
1683          }
1684        }
1685      }
1686    }
1687    if (res.target == null)
1688      throw new FHIRException("No matches found for rule '"+name+"'. Reference found in "+map.getUrl());
1689    source.setUserData(kn, res);
1690    return res;
1691  }
1692
1693  private List<Variables> processSource(String ruleId, TransformContext context, Variables vars, StructureMapGroupRuleSourceComponent src, String pathForErrors, String indent) throws FHIRException {
1694    List<Base> items;
1695    if (src.getContext().equals("@search")) {
1696      ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_SEARCH_EXPRESSION);
1697      if (expr == null) {
1698        expr = fpe.parse(src.getElement());
1699        src.setUserData(MAP_SEARCH_EXPRESSION, expr);
1700      }
1701      String search = fpe.evaluateToString(vars, null, null, new StringType(), expr); // string is a holder of nothing to ensure that variables are processed correctly 
1702      items = services.performSearch(context.appInfo, search);
1703    } else {
1704      items = new ArrayList<Base>();
1705      Base b = vars.get(VariableMode.INPUT, src.getContext());
1706      if (b == null)
1707        throw new FHIRException("Unknown input variable "+src.getContext()+" in "+pathForErrors+" rule "+ruleId+" (vars = "+vars.summary()+")");
1708
1709      if (!src.hasElement()) 
1710        items.add(b);
1711      else { 
1712        getChildrenByName(b, src.getElement(), items);
1713        if (items.size() == 0 && src.hasDefaultValue())
1714          items.add(src.getDefaultValue());
1715      }
1716    }
1717    
1718                if (src.hasType()) {
1719            List<Base> remove = new ArrayList<Base>();
1720            for (Base item : items) {
1721              if (item != null && !isType(item, src.getType())) {
1722                remove.add(item);
1723              }
1724            }
1725            items.removeAll(remove);
1726                }
1727
1728    if (src.hasCondition()) {
1729      ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_EXPRESSION);
1730      if (expr == null) {
1731        expr = fpe.parse(src.getCondition());
1732        //        fpe.check(context.appInfo, ??, ??, expr)
1733        src.setUserData(MAP_WHERE_EXPRESSION, expr);
1734      }
1735      List<Base> remove = new ArrayList<Base>();
1736      for (Base item : items) {
1737        if (!fpe.evaluateToBoolean(vars, null, null, item, expr)) {
1738          log(indent+"  condition ["+src.getCondition()+"] for "+item.toString()+" : false");
1739          remove.add(item);
1740        } else
1741          log(indent+"  condition ["+src.getCondition()+"] for "+item.toString()+" : true");
1742      }
1743      items.removeAll(remove);
1744    }
1745
1746    if (src.hasCheck()) {
1747      ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_CHECK);
1748      if (expr == null) {
1749        expr = fpe.parse(src.getCheck());
1750        //        fpe.check(context.appInfo, ??, ??, expr)
1751        src.setUserData(MAP_WHERE_CHECK, expr);
1752      }
1753      List<Base> remove = new ArrayList<Base>();
1754      for (Base item : items) {
1755        if (!fpe.evaluateToBoolean(vars, null, null, item, expr))
1756          throw new FHIRException("Rule \""+ruleId+"\": Check condition failed");
1757      }
1758    } 
1759
1760    if (src.hasLogMessage()) {
1761      ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_LOG);
1762      if (expr == null) {
1763        expr = fpe.parse(src.getLogMessage());
1764        //        fpe.check(context.appInfo, ??, ??, expr)
1765        src.setUserData(MAP_WHERE_LOG, expr);
1766      }
1767      CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1768      for (Base item : items) 
1769        b.appendIfNotNull(fpe.evaluateToString(vars, null, null, item, expr));
1770      if (b.length() > 0)
1771        services.log(b.toString());
1772    } 
1773
1774                
1775                if (src.hasListMode() && !items.isEmpty()) {
1776                  switch (src.getListMode()) {
1777                  case FIRST:
1778                    Base bt = items.get(0);
1779        items.clear();
1780        items.add(bt);
1781                    break;
1782                  case NOTFIRST: 
1783        if (items.size() > 0)
1784          items.remove(0);
1785        break;
1786                  case LAST:
1787        bt = items.get(items.size()-1);
1788        items.clear();
1789        items.add(bt);
1790        break;
1791                  case NOTLAST: 
1792        if (items.size() > 0)
1793          items.remove(items.size()-1);
1794        break;
1795                  case ONLYONE:
1796                    if (items.size() > 1)
1797          throw new FHIRException("Rule \""+ruleId+"\": Check condition failed: the collection has more than one item");
1798        break;
1799      case NULL:
1800                  }
1801                }
1802                List<Variables> result = new ArrayList<Variables>();
1803                for (Base r : items) {
1804                        Variables v = vars.copy();
1805                        if (src.hasVariable())
1806                                v.add(VariableMode.INPUT, src.getVariable(), r);
1807                        result.add(v); 
1808                }
1809                return result;
1810        }
1811
1812
1813        private boolean isType(Base item, String type) {
1814    if (type.equals(item.fhirType()))
1815      return true;
1816    return false;
1817  }
1818
1819  private void processTarget(String ruleId, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar, boolean atRoot, Variables sharedVars) throws FHIRException {
1820          Base dest = null;
1821          if (tgt.hasContext()) {
1822                dest = vars.get(VariableMode.OUTPUT, tgt.getContext());
1823                if (dest == null)
1824                  throw new FHIRException("Rule \""+ruleId+"\": target context not known: "+tgt.getContext());
1825                if (!tgt.hasElement())
1826                  throw new FHIRException("Rule \""+ruleId+"\": Not supported yet");
1827          }
1828          Base v = null;
1829          if (tgt.hasTransform()) {
1830            v = runTransform(ruleId, context, map, group, tgt, vars, dest, tgt.getElement(), srcVar, atRoot);
1831            if (v != null && dest != null)
1832              v = dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); // reset v because some implementations may have to rewrite v when setting the value
1833          } else if (dest != null) { 
1834                if (tgt.hasListMode(StructureMapTargetListMode.SHARE)) {
1835                        v = sharedVars.get(VariableMode.SHARED, tgt.getListRuleId());
1836                        if (v == null) {
1837                                v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement());
1838                                sharedVars.add(VariableMode.SHARED, tgt.getListRuleId(), v);                            
1839                        }
1840                } else {
1841                        v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement());
1842                }
1843          }
1844          if (tgt.hasVariable() && v != null)
1845            vars.add(VariableMode.OUTPUT, tgt.getVariable(), v);
1846        }
1847
1848        private Base runTransform(String ruleId, TransformContext context, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, Variables vars, Base dest, String element, String srcVar, boolean root) throws FHIRException {
1849          try {
1850            switch (tgt.getTransform()) {
1851            case CREATE :
1852              String tn;
1853              if (tgt.getParameter().isEmpty()) {
1854                // we have to work out the type. First, we see if there is a single type for the target. If there is, we use that
1855                String[] types = dest.getTypesForProperty(element.hashCode(), element);
1856                if (types.length == 1 && !"*".equals(types[0]) && !types[0].equals("Resource"))
1857                  tn = types[0];
1858                else if (srcVar != null) {
1859                  tn = determineTypeFromSourceType(map, group, vars.get(VariableMode.INPUT, srcVar), types);
1860                } else
1861                  throw new Error("Cannot determine type implicitly because there is no single input variable");
1862              } else {
1863                tn = getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString());
1864                // ok, now we resolve the type name against the import statements 
1865                for (StructureMapStructureComponent uses : map.getStructure()) {
1866                  if (uses.getMode() == StructureMapModelMode.TARGET && uses.hasAlias() && tn.equals(uses.getAlias())) {
1867                    tn = uses.getUrl();
1868                    break;
1869                  }
1870                }
1871              }
1872              Base res = services != null ? services.createType(context.getAppInfo(), tn) : ResourceFactory.createResourceOrType(tn);
1873              if (res.isResource() && !res.fhirType().equals("Parameters")) {
1874//              res.setIdBase(tgt.getParameter().size() > 1 ? getParamString(vars, tgt.getParameter().get(0)) : UUID.randomUUID().toString().toLowerCase());
1875                if (services != null) 
1876                  res = services.createResource(context.getAppInfo(), res, root);
1877              }
1878              if (tgt.hasUserData("profile"))
1879                res.setUserData("profile", tgt.getUserData("profile"));
1880              return res;
1881            case COPY : 
1882              return getParam(vars, tgt.getParameter().get(0));
1883            case EVALUATE :
1884              ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION);
1885              if (expr == null) {
1886                expr = fpe.parse(getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()));
1887                tgt.setUserData(MAP_WHERE_EXPRESSION, expr);
1888              }
1889              List<Base> v = fpe.evaluate(vars, null, null, tgt.getParameter().size() == 2 ? getParam(vars, tgt.getParameter().get(0)) : new BooleanType(false), expr);
1890              if (v.size() == 0)
1891                return null;
1892              else if (v.size() != 1)
1893                throw new FHIRException("Rule \""+ruleId+"\": Evaluation of "+expr.toString()+" returned "+Integer.toString(v.size())+" objects");
1894              else
1895                return v.get(0);
1896
1897            case TRUNCATE : 
1898              String src = getParamString(vars, tgt.getParameter().get(0));
1899              String len = getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString());
1900              if (Utilities.isInteger(len)) {
1901                int l = Integer.parseInt(len);
1902                if (src.length() > l)
1903                  src = src.substring(0, l);
1904              }
1905              return new StringType(src);
1906            case ESCAPE : 
1907              throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet");
1908            case CAST :
1909        src = getParamString(vars, tgt.getParameter().get(0));
1910        if (tgt.getParameter().size() == 1)
1911          throw new FHIRException("Implicit type parameters on cast not yet supported");
1912        String t = getParamString(vars, tgt.getParameter().get(1));
1913        if (t.equals("string"))
1914          return new StringType(src);
1915        else
1916          throw new FHIRException("cast to "+t+" not yet supported");
1917            case APPEND : 
1918        StringBuilder sb = new StringBuilder(getParamString(vars, tgt.getParameter().get(0)));
1919        for (int i = 1; i < tgt.getParameter().size(); i++)
1920          sb.append(getParamString(vars, tgt.getParameter().get(i)));
1921        return new StringType(sb.toString());
1922            case TRANSLATE : 
1923              return translate(context, map, vars, tgt.getParameter());
1924            case REFERENCE :
1925              Base b = getParam(vars, tgt.getParameter().get(0));
1926              if (b == null)
1927                throw new FHIRException("Rule \""+ruleId+"\": Unable to find parameter "+((IdType) tgt.getParameter().get(0).getValue()).asStringValue());
1928              if (!b.isResource())
1929                throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType());
1930              else {
1931                String id = b.getIdBase();
1932                if (id == null) {
1933                  id = UUID.randomUUID().toString().toLowerCase();
1934                  b.setIdBase(id);
1935                }
1936                return new Reference().setReference(b.fhirType()+"/"+id);
1937              }
1938            case DATEOP :
1939              throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet");
1940            case UUID :
1941              return new IdType(UUID.randomUUID().toString());
1942            case POINTER :
1943              b = getParam(vars, tgt.getParameter().get(0));
1944              if (b instanceof Resource)
1945                return new UriType("urn:uuid:"+((Resource) b).getId());
1946              else
1947                throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType());
1948            case CC:
1949              CodeableConcept cc = new CodeableConcept();
1950              cc.addCoding(buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())));
1951              return cc;
1952            case C: 
1953              Coding c = buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()));
1954              return c;
1955            default:
1956              throw new Error("Rule \""+ruleId+"\": Transform Unknown: "+tgt.getTransform().toCode());
1957            }
1958          } catch (Exception e) {
1959            throw new FHIRException("Exception executing transform "+tgt.toString()+" on Rule \""+ruleId+"\": "+e.getMessage(), e);
1960          }
1961        }
1962
1963
1964  private Coding buildCoding(String uri, String code) throws FHIRException {
1965          // if we can get this as a valueSet, we will
1966          String system = null;
1967          String display = null;
1968          ValueSet vs = Utilities.noString(uri) ? null : worker.fetchResourceWithException(ValueSet.class, uri);
1969          if (vs != null) {
1970            ValueSetExpansionOutcome vse = worker.expandVS(vs, true, false);
1971            if (vse.getError() != null)
1972              throw new FHIRException(vse.getError());
1973            CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1974            for (ValueSetExpansionContainsComponent t : vse.getValueset().getExpansion().getContains()) {
1975              if (t.hasCode())
1976                b.append(t.getCode());
1977              if (code.equals(t.getCode()) && t.hasSystem()) {
1978                system = t.getSystem();
1979          display = t.getDisplay();
1980                break;
1981              }
1982        if (code.equalsIgnoreCase(t.getDisplay()) && t.hasSystem()) {
1983          system = t.getSystem();
1984          display = t.getDisplay();
1985          break;
1986        }
1987            }
1988            if (system == null)
1989              throw new FHIRException("The code '"+code+"' is not in the value set '"+uri+"' (valid codes: "+b.toString()+"; also checked displays)");
1990          } else
1991            system = uri;
1992          ValidationResult vr = worker.validateCode(terminologyServiceOptions, system, code, null);
1993          if (vr != null && vr.getDisplay() != null)
1994            display = vr.getDisplay();
1995   return new Coding().setSystem(system).setCode(code).setDisplay(display);
1996  }
1997
1998
1999  private String getParamStringNoNull(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter, String message) throws FHIRException {
2000    Base b = getParam(vars, parameter);
2001    if (b == null)
2002      throw new FHIRException("Unable to find a value for "+parameter.toString()+". Context: "+message);
2003    if (!b.hasPrimitiveValue())
2004      throw new FHIRException("Found a value for "+parameter.toString()+", but it has a type of "+b.fhirType()+" and cannot be treated as a string. Context: "+message);
2005    return b.primitiveValue();
2006  }
2007
2008  private String getParamString(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException {
2009                Base b = getParam(vars, parameter);
2010                if (b == null || !b.hasPrimitiveValue())
2011                        return null;
2012                return b.primitiveValue();
2013        }
2014
2015
2016        private Base getParam(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException {
2017                Type p = parameter.getValue();
2018                if (!(p instanceof IdType))
2019                        return p;
2020                else { 
2021                        String n = ((IdType) p).asStringValue();
2022      Base b = vars.get(VariableMode.INPUT, n);
2023                        if (b == null)
2024                                b = vars.get(VariableMode.OUTPUT, n);
2025                        if (b == null)
2026        throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")");
2027                        return b;
2028                }
2029        }
2030
2031
2032        private Base translate(TransformContext context, StructureMap map, Variables vars, List<StructureMapGroupRuleTargetParameterComponent> parameter) throws FHIRException {
2033                Base src = getParam(vars, parameter.get(0));
2034                String id = getParamString(vars, parameter.get(1));
2035                String fld = parameter.size() > 2 ? getParamString(vars, parameter.get(2)) : null;
2036                return translate(context, map, src, id, fld);
2037        }
2038
2039        private class SourceElementComponentWrapper {
2040          private ConceptMapGroupComponent group;
2041    private SourceElementComponent comp;
2042    public SourceElementComponentWrapper(ConceptMapGroupComponent group, SourceElementComponent comp) {
2043      super();
2044      this.group = group;
2045      this.comp = comp;
2046    }
2047        }
2048        public Base translate(TransformContext context, StructureMap map, Base source, String conceptMapUrl, String fieldToReturn) throws FHIRException {
2049                Coding src = new Coding();
2050                if (source.isPrimitive()) {
2051                        src.setCode(source.primitiveValue());
2052                } else if ("Coding".equals(source.fhirType())) {
2053                        Base[] b = source.getProperty("system".hashCode(), "system", true);
2054                        if (b.length == 1)
2055                                src.setSystem(b[0].primitiveValue());
2056                        b = source.getProperty("code".hashCode(), "code", true);
2057                        if (b.length == 1)
2058                                src.setCode(b[0].primitiveValue());
2059                } else if ("CE".equals(source.fhirType())) {
2060                        Base[] b = source.getProperty("codeSystem".hashCode(), "codeSystem", true);
2061                        if (b.length == 1)
2062                                src.setSystem(b[0].primitiveValue());
2063                        b = source.getProperty("code".hashCode(), "code", true);
2064                        if (b.length == 1)
2065                                src.setCode(b[0].primitiveValue());
2066                } else
2067                        throw new FHIRException("Unable to translate source "+source.fhirType());
2068
2069                String su = conceptMapUrl;
2070                if (conceptMapUrl.equals("http://hl7.org/fhir/ConceptMap/special-oid2uri")) {
2071                        String uri = worker.oid2Uri(src.getCode());
2072                        if (uri == null)
2073                                uri = "urn:oid:"+src.getCode();
2074                        if ("uri".equals(fieldToReturn))
2075                                return new UriType(uri);
2076                        else
2077                                throw new FHIRException("Error in return code");
2078                } else {
2079                        ConceptMap cmap = null;
2080                        if (conceptMapUrl.startsWith("#")) {
2081                                for (Resource r : map.getContained()) {
2082                                        if (r instanceof ConceptMap && ((ConceptMap) r).getId().equals(conceptMapUrl.substring(1))) {
2083                                                cmap = (ConceptMap) r;
2084                                                su = map.getUrl()+"#"+conceptMapUrl;
2085                                        }
2086                                }
2087                                if (cmap == null)
2088                      throw new FHIRException("Unable to translate - cannot find map "+conceptMapUrl);
2089                        } else {
2090                          if (conceptMapUrl.contains("#")) {
2091                            String[] p = conceptMapUrl.split("\\#");
2092                            StructureMap mapU = worker.fetchResource(StructureMap.class, p[0]);  
2093                for (Resource r : mapU.getContained()) {
2094                  if (r instanceof ConceptMap && ((ConceptMap) r).getId().equals(p[1])) {
2095                    cmap = (ConceptMap) r;
2096                    su = conceptMapUrl;
2097                  }
2098                }
2099                          }
2100                          if (cmap == null)
2101                                  cmap = worker.fetchResource(ConceptMap.class, conceptMapUrl);
2102                        }
2103                        Coding outcome = null;
2104                        boolean done = false;
2105                        String message = null;
2106                        if (cmap == null) {
2107                                if (services == null) 
2108                                        message = "No map found for "+conceptMapUrl;
2109                                else {
2110                                        outcome = services.translate(context.appInfo, src, conceptMapUrl);
2111                                        done = true;
2112                                }
2113                        } else {
2114                          List<SourceElementComponentWrapper> list = new ArrayList<SourceElementComponentWrapper>();
2115                          for (ConceptMapGroupComponent g : cmap.getGroup()) {
2116                            for (SourceElementComponent e : g.getElement()) {
2117                                                if (!src.hasSystem() && src.getCode().equals(e.getCode())) 
2118                                list.add(new SourceElementComponentWrapper(g, e));
2119                              else if (src.hasSystem() && src.getSystem().equals(g.getSource()) && src.getCode().equals(e.getCode()))
2120                                list.add(new SourceElementComponentWrapper(g, e));
2121                            }
2122                                }
2123                                if (list.size() == 0)
2124                                        done = true;
2125                                else if (list.get(0).comp.getTarget().size() == 0)
2126                                        message = "Concept map "+su+" found no translation for "+src.getCode();
2127                                else {
2128                                        for (TargetElementComponent tgt : list.get(0).comp.getTarget()) {
2129                                                if (tgt.getEquivalence() == null || EnumSet.of( ConceptMapEquivalence.EQUAL , ConceptMapEquivalence.RELATEDTO , ConceptMapEquivalence.EQUIVALENT, ConceptMapEquivalence.WIDER).contains(tgt.getEquivalence())) {
2130                                                        if (done) {
2131                                                                message = "Concept map "+su+" found multiple matches for "+src.getCode();
2132                                                                done = false;
2133                                                        } else {
2134                                                                done = true;
2135                                                                outcome = new Coding().setCode(tgt.getCode()).setSystem(list.get(0).group.getTarget());
2136                                                        }
2137                                                } else if (tgt.getEquivalence() == ConceptMapEquivalence.UNMATCHED) {
2138                                                        done = true;
2139                                                }
2140                                        }
2141                                        if (!done)
2142                                                message = "Concept map "+su+" found no usable translation for "+src.getCode();
2143                                }
2144                        }
2145                        if (!done) 
2146                                throw new FHIRException(message);
2147                        if (outcome == null)
2148                                return null;
2149                        if ("code".equals(fieldToReturn))
2150                                return new CodeType(outcome.getCode());
2151                        else
2152                                return outcome; 
2153                }
2154        }
2155
2156
2157        public class PropertyWithType {
2158    private String path;
2159    private Property baseProperty;
2160    private Property profileProperty;
2161          private TypeDetails types;
2162    public PropertyWithType(String path, Property baseProperty, Property profileProperty, TypeDetails types) {
2163      super();
2164      this.baseProperty = baseProperty;
2165      this.profileProperty = profileProperty;
2166      this.path = path;
2167      this.types = types;
2168    }
2169
2170    public TypeDetails getTypes() {
2171      return types;
2172    }
2173    public String getPath() {
2174      return path;
2175    }
2176
2177    public Property getBaseProperty() {
2178      return baseProperty;
2179    }
2180
2181    public void setBaseProperty(Property baseProperty) {
2182      this.baseProperty = baseProperty;
2183    }
2184
2185    public Property getProfileProperty() {
2186      return profileProperty;
2187    }
2188
2189    public void setProfileProperty(Property profileProperty) {
2190      this.profileProperty = profileProperty;
2191    }
2192
2193    public String summary() {
2194      return path;
2195    }
2196    
2197        }
2198        
2199        public class VariableForProfiling {
2200            private VariableMode mode;
2201            private String name;
2202            private PropertyWithType property;
2203            
2204            public VariableForProfiling(VariableMode mode, String name, PropertyWithType property) {
2205              super();
2206              this.mode = mode;
2207              this.name = name;
2208              this.property = property;
2209            }
2210            public VariableMode getMode() {
2211              return mode;
2212            }
2213            public String getName() {
2214              return name;
2215            }
2216      public PropertyWithType getProperty() {
2217        return property;
2218      }
2219      public String summary() {
2220        return name+": "+property.summary();
2221      }      
2222          }
2223
2224  public class VariablesForProfiling {
2225    private List<VariableForProfiling> list = new ArrayList<VariableForProfiling>();
2226    private boolean optional;
2227    private boolean repeating;
2228
2229    public VariablesForProfiling(boolean optional, boolean repeating) {
2230      this.optional = optional;
2231      this.repeating = repeating;
2232    }
2233
2234    public void add(VariableMode mode, String name, String path, Property property, TypeDetails types) {
2235      add(mode, name, new PropertyWithType(path, property, null, types));
2236    }
2237    
2238    public void add(VariableMode mode, String name, String path, Property baseProperty, Property profileProperty, TypeDetails types) {
2239      add(mode, name, new PropertyWithType(path, baseProperty, profileProperty, types));
2240    }
2241    
2242    public void add(VariableMode mode, String name, PropertyWithType property) {
2243      VariableForProfiling vv = null;
2244      for (VariableForProfiling v : list) 
2245        if ((v.mode == mode) && v.getName().equals(name))
2246          vv = v;
2247      if (vv != null)
2248        list.remove(vv);
2249      list.add(new VariableForProfiling(mode, name, property));
2250    }
2251
2252    public VariablesForProfiling copy(boolean optional, boolean repeating) {
2253      VariablesForProfiling result = new VariablesForProfiling(optional, repeating);
2254      result.list.addAll(list);
2255      return result;
2256    }
2257
2258    public VariablesForProfiling copy() {
2259      VariablesForProfiling result = new VariablesForProfiling(optional, repeating);
2260      result.list.addAll(list);
2261      return result;
2262    }
2263
2264    public VariableForProfiling get(VariableMode mode, String name) {
2265      if (mode == null) {
2266        for (VariableForProfiling v : list) 
2267          if ((v.mode == VariableMode.OUTPUT) && v.getName().equals(name))
2268            return v;
2269        for (VariableForProfiling v : list) 
2270          if ((v.mode == VariableMode.INPUT) && v.getName().equals(name))
2271            return v;
2272      }
2273      for (VariableForProfiling v : list) 
2274        if ((v.mode == mode) && v.getName().equals(name))
2275          return v;
2276      return null;
2277    }
2278
2279    public String summary() {
2280      CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder();
2281      CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder();
2282      for (VariableForProfiling v : list)
2283        if (v.mode == VariableMode.INPUT)
2284          s.append(v.summary());
2285        else
2286          t.append(v.summary());
2287      return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]";
2288    }
2289  }
2290
2291  public class StructureMapAnalysis {
2292    private List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
2293    private XhtmlNode summary;
2294    public List<StructureDefinition> getProfiles() {
2295      return profiles;
2296    }
2297    public XhtmlNode getSummary() {
2298      return summary;
2299    }
2300    
2301  }
2302
2303        /**
2304         * Given a structure map, return a set of analyses on it. 
2305         * 
2306         * Returned:
2307         *   - a list or profiles for what it will create. First profile is the target
2308         *   - a table with a summary (in xhtml) for easy human undertanding of the mapping
2309         *   
2310         * 
2311         * @param appInfo
2312         * @param map
2313         * @return
2314         * @throws Exception
2315         */
2316  public StructureMapAnalysis analyse(Object appInfo, StructureMap map) throws FHIRException {
2317    ids.clear();
2318    StructureMapAnalysis result = new StructureMapAnalysis(); 
2319    TransformContext context = new TransformContext(appInfo);
2320    VariablesForProfiling vars = new VariablesForProfiling(false, false);
2321    StructureMapGroupComponent start = map.getGroup().get(0);
2322    for (StructureMapGroupInputComponent t : start.getInput()) {
2323      PropertyWithType ti = resolveType(map, t.getType(), t.getMode());
2324      if (t.getMode() == StructureMapInputMode.SOURCE)
2325       vars.add(VariableMode.INPUT, t.getName(), ti);
2326      else 
2327        vars.add(VariableMode.OUTPUT, t.getName(), createProfile(map, result.profiles, ti, start.getName(), start));
2328    }
2329
2330    result.summary = new XhtmlNode(NodeType.Element, "table").setAttribute("class", "grid");
2331    XhtmlNode tr = result.summary.addTag("tr");
2332    tr.addTag("td").addTag("b").addText("Source");
2333    tr.addTag("td").addTag("b").addText("Target");
2334    
2335    log("Start Profiling Transform "+map.getUrl());
2336    analyseGroup("", context, map, vars, start, result);
2337    ProfileUtilities pu = new ProfileUtilities(worker, null, pkp);
2338    for (StructureDefinition sd : result.getProfiles())
2339      pu.cleanUpDifferential(sd);
2340    return result;
2341  }
2342
2343
2344  private void analyseGroup(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapAnalysis result) throws FHIRException {
2345    log(indent+"Analyse Group : "+group.getName());
2346    // todo: extends
2347    // todo: check inputs
2348    XhtmlNode tr = result.summary.addTag("tr").setAttribute("class", "diff-title");
2349    XhtmlNode xs = tr.addTag("td");
2350    XhtmlNode xt = tr.addTag("td");
2351    for (StructureMapGroupInputComponent inp : group.getInput()) {
2352      if (inp.getMode() == StructureMapInputMode.SOURCE) 
2353        noteInput(vars, inp, VariableMode.INPUT, xs);
2354      if (inp.getMode() == StructureMapInputMode.TARGET) 
2355        noteInput(vars, inp, VariableMode.OUTPUT, xt);
2356    }
2357    for (StructureMapGroupRuleComponent r : group.getRule()) {
2358      analyseRule(indent+"  ", context, map, vars, group, r, result);
2359    }    
2360  }
2361
2362
2363  private void noteInput(VariablesForProfiling vars, StructureMapGroupInputComponent inp, VariableMode mode, XhtmlNode xs) {
2364    VariableForProfiling v = vars.get(mode, inp.getName());
2365    if (v != null)
2366      xs.addText("Input: "+v.property.getPath());
2367  }
2368
2369  private void analyseRule(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, StructureMapAnalysis result) throws FHIRException {
2370    log(indent+"Analyse rule : "+rule.getName());
2371    XhtmlNode tr = result.summary.addTag("tr");
2372    XhtmlNode xs = tr.addTag("td");
2373    XhtmlNode xt = tr.addTag("td");
2374
2375    VariablesForProfiling srcVars = vars.copy();
2376    if (rule.getSource().size() != 1)
2377      throw new FHIRException("Rule \""+rule.getName()+"\": not handled yet");
2378    VariablesForProfiling source = analyseSource(rule.getName(), context, srcVars, rule.getSourceFirstRep(), xs);
2379
2380    TargetWriter tw = new TargetWriter();
2381      for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) {
2382      analyseTarget(rule.getName(), context, source, map, t, rule.getSourceFirstRep().getVariable(), tw, result.profiles, rule.getName());
2383      }
2384    tw.commit(xt);
2385
2386          for (StructureMapGroupRuleComponent childrule : rule.getRule()) {
2387      analyseRule(indent+"  ", context, map, source, group, childrule, result);
2388          }
2389//    for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) {
2390//      executeDependency(indent+"  ", context, map, v, group, dependent); // do we need group here?
2391//    }
2392          }
2393
2394  public class StringPair {
2395    private String var;
2396    private String desc;
2397    public StringPair(String var, String desc) {
2398      super();
2399      this.var = var;
2400      this.desc = desc;
2401        }
2402    public String getVar() {
2403      return var;
2404      }
2405    public String getDesc() {
2406      return desc;
2407    }
2408  }
2409  public class TargetWriter {
2410    private Map<String, String> newResources = new HashMap<String, String>();
2411    private List<StringPair> assignments = new ArrayList<StringPair>();
2412    private List<StringPair> keyProps = new ArrayList<StringPair>();
2413    private CommaSeparatedStringBuilder txt = new CommaSeparatedStringBuilder();
2414
2415    public void newResource(String var, String name) {
2416      newResources.put(var, name);
2417      txt.append("new "+name);
2418    }
2419
2420    public void valueAssignment(String context, String desc) {
2421      assignments.add(new StringPair(context, desc));      
2422      txt.append(desc);
2423        }
2424
2425    public void keyAssignment(String context, String desc) {
2426      keyProps.add(new StringPair(context, desc));      
2427      txt.append(desc);
2428    }
2429    public void commit(XhtmlNode xt) {
2430      if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 1 && newResources.containsKey(keyProps.get(0).getVar()) ) {
2431        xt.addText("new "+assignments.get(0).desc+" ("+keyProps.get(0).desc.substring(keyProps.get(0).desc.indexOf(".")+1)+")");
2432      } else if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 0) {
2433        xt.addText("new "+assignments.get(0).desc);
2434      } else {
2435        xt.addText(txt.toString());        
2436    }
2437    }
2438  }
2439
2440  private VariablesForProfiling analyseSource(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMapGroupRuleSourceComponent src, XhtmlNode td) throws FHIRException {
2441    VariableForProfiling var = vars.get(VariableMode.INPUT, src.getContext());
2442    if (var == null)
2443      throw new FHIRException("Rule \""+ruleId+"\": Unknown input variable "+src.getContext());
2444    PropertyWithType prop = var.getProperty();
2445
2446    boolean optional = false;
2447    boolean repeating = false;
2448
2449    if (src.hasCondition()) {
2450      optional = true;
2451    }
2452
2453    if (src.hasElement()) {
2454      Property element = prop.getBaseProperty().getChild(prop.types.getType(), src.getElement());
2455      if (element == null)
2456        throw new FHIRException("Rule \""+ruleId+"\": Unknown element name "+src.getElement());
2457      if (element.getDefinition().getMin() == 0)
2458        optional = true;
2459      if (element.getDefinition().getMax().equals("*"))
2460        repeating = true;
2461      VariablesForProfiling result = vars.copy(optional, repeating);
2462      TypeDetails type = new TypeDetails(CollectionStatus.SINGLETON);
2463      for (TypeRefComponent tr : element.getDefinition().getType()) {
2464        if (!tr.hasCode())
2465          throw new Error("Rule \""+ruleId+"\": Element has no type");
2466        ProfiledType pt = new ProfiledType(tr.getWorkingCode());
2467        if (tr.hasProfile())
2468          pt.addProfiles(tr.getProfile());
2469        if (element.getDefinition().hasBinding())
2470          pt.addBinding(element.getDefinition().getBinding());
2471        type.addType(pt);
2472    } 
2473      td.addText(prop.getPath()+"."+src.getElement()); 
2474      if (src.hasVariable())
2475        result.add(VariableMode.INPUT, src.getVariable(), new PropertyWithType(prop.getPath()+"."+src.getElement(), element, null, type));
2476    return result;
2477    } else {
2478      td.addText(prop.getPath()); // ditto!
2479      return vars.copy(optional, repeating);
2480    }
2481  }
2482
2483
2484  private void analyseTarget(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMap map, StructureMapGroupRuleTargetComponent tgt, String tv, TargetWriter tw, List<StructureDefinition> profiles, String sliceName) throws FHIRException {
2485    VariableForProfiling var = null;
2486    if (tgt.hasContext()) {
2487      var = vars.get(VariableMode.OUTPUT, tgt.getContext());
2488      if (var == null)
2489        throw new FHIRException("Rule \""+ruleId+"\": target context not known: "+tgt.getContext());
2490      if (!tgt.hasElement())
2491        throw new FHIRException("Rule \""+ruleId+"\": Not supported yet");
2492    }
2493
2494    
2495    TypeDetails type = null;
2496    if (tgt.hasTransform()) {
2497      type = analyseTransform(context, map, tgt, var, vars);
2498        // profiling: dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v);
2499    } else {
2500      Property vp = var.property.baseProperty.getChild(tgt.getElement(),  tgt.getElement());
2501      if (vp == null)
2502        throw new FHIRException("Unknown Property "+tgt.getElement()+" on "+var.property.path);
2503      
2504      type = new TypeDetails(CollectionStatus.SINGLETON, vp.getType(tgt.getElement()));
2505    }
2506
2507    if (tgt.getTransform() == StructureMapTransform.CREATE) {
2508      String s = getParamString(vars, tgt.getParameter().get(0));
2509      if (worker.getResourceNames().contains(s))
2510        tw.newResource(tgt.getVariable(), s);
2511    } else { 
2512      boolean mapsSrc = false;
2513      for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) {
2514        Type pr = p.getValue();
2515        if (pr instanceof IdType && ((IdType) pr).asStringValue().equals(tv)) 
2516          mapsSrc = true;
2517      }
2518      if (mapsSrc) { 
2519        if (var == null)
2520          throw new Error("Rule \""+ruleId+"\": Attempt to assign with no context");
2521        tw.valueAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+getTransformSuffix(tgt.getTransform()));
2522      } else if (tgt.hasContext()) {
2523        if (isSignificantElement(var.property, tgt.getElement())) {
2524          String td = describeTransform(tgt);
2525          if (td != null)
2526            tw.keyAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+" = "+td);
2527        }
2528      }
2529    }
2530    Type fixed = generateFixedValue(tgt);
2531    
2532    PropertyWithType prop = updateProfile(var, tgt.getElement(), type, map, profiles, sliceName, fixed, tgt);
2533    if (tgt.hasVariable())
2534      if (tgt.hasElement())
2535        vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); 
2536      else
2537        vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); 
2538  }
2539  
2540  private Type generateFixedValue(StructureMapGroupRuleTargetComponent tgt) {
2541    if (!allParametersFixed(tgt))
2542      return null;
2543    if (!tgt.hasTransform())
2544      return null;
2545    switch (tgt.getTransform()) {
2546    case COPY: return tgt.getParameter().get(0).getValue(); 
2547    case TRUNCATE: return null; 
2548    //case ESCAPE: 
2549    //case CAST: 
2550    //case APPEND: 
2551    case TRANSLATE: return null; 
2552  //case DATEOP, 
2553  //case UUID, 
2554  //case POINTER, 
2555  //case EVALUATE, 
2556    case CC: 
2557      CodeableConcept cc = new CodeableConcept();
2558      cc.addCoding(buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue()));
2559      return cc;
2560    case C: 
2561      return buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue());
2562    case QTY: return null; 
2563  //case ID, 
2564  //case CP, 
2565    default:
2566      return null;
2567    }
2568  }
2569
2570  @SuppressWarnings("rawtypes")
2571  private Coding buildCoding(Type value1, Type value2) {
2572    return new Coding().setSystem(((PrimitiveType) value1).asStringValue()).setCode(((PrimitiveType) value2).asStringValue()) ;
2573  }
2574
2575  private boolean allParametersFixed(StructureMapGroupRuleTargetComponent tgt) {
2576    for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) {
2577      Type pr = p.getValue();
2578      if (pr instanceof IdType)
2579        return false;
2580    }
2581    return true;
2582  }
2583
2584  private String describeTransform(StructureMapGroupRuleTargetComponent tgt) throws FHIRException {
2585    switch (tgt.getTransform()) {
2586    case COPY: return null; 
2587    case TRUNCATE: return null; 
2588    //case ESCAPE: 
2589    //case CAST: 
2590    //case APPEND: 
2591    case TRANSLATE: return null; 
2592  //case DATEOP, 
2593  //case UUID, 
2594  //case POINTER, 
2595  //case EVALUATE, 
2596    case CC: return describeTransformCCorC(tgt); 
2597    case C: return describeTransformCCorC(tgt); 
2598    case QTY: return null; 
2599  //case ID, 
2600  //case CP, 
2601    default:
2602      return null;
2603    }
2604  }
2605
2606  @SuppressWarnings("rawtypes")
2607  private String describeTransformCCorC(StructureMapGroupRuleTargetComponent tgt) throws FHIRException {
2608    if (tgt.getParameter().size() < 2)
2609      return null;
2610    Type p1 = tgt.getParameter().get(0).getValue();
2611    Type p2 = tgt.getParameter().get(1).getValue();
2612    if (p1 instanceof IdType || p2 instanceof IdType)
2613      return null;
2614    if (!(p1 instanceof PrimitiveType) || !(p2 instanceof PrimitiveType))
2615      return null;
2616    String uri = ((PrimitiveType) p1).asStringValue();
2617    String code = ((PrimitiveType) p2).asStringValue();
2618    if (Utilities.noString(uri))
2619      throw new FHIRException("Describe Transform, but the uri is blank");
2620    if (Utilities.noString(code))
2621      throw new FHIRException("Describe Transform, but the code is blank");
2622    Coding c = buildCoding(uri, code);
2623    return NarrativeGenerator.describeSystem(c.getSystem())+"#"+c.getCode()+(c.hasDisplay() ? "("+c.getDisplay()+")" : "");
2624  }
2625
2626
2627  private boolean isSignificantElement(PropertyWithType property, String element) {
2628    if ("Observation".equals(property.getPath()))
2629      return "code".equals(element);
2630    else if ("Bundle".equals(property.getPath()))
2631      return "type".equals(element);
2632    else
2633      return false;
2634  }
2635
2636  private String getTransformSuffix(StructureMapTransform transform) {
2637    switch (transform) {
2638    case COPY: return ""; 
2639    case TRUNCATE: return " (truncated)"; 
2640    //case ESCAPE: 
2641    //case CAST: 
2642    //case APPEND: 
2643    case TRANSLATE: return " (translated)"; 
2644  //case DATEOP, 
2645  //case UUID, 
2646  //case POINTER, 
2647  //case EVALUATE, 
2648    case CC: return " (--> CodeableConcept)"; 
2649    case C: return " (--> Coding)"; 
2650    case QTY: return " (--> Quantity)"; 
2651  //case ID, 
2652  //case CP, 
2653    default:
2654      return " {??)";
2655    }
2656  }
2657
2658  private PropertyWithType updateProfile(VariableForProfiling var, String element, TypeDetails type, StructureMap map, List<StructureDefinition> profiles, String sliceName, Type fixed, StructureMapGroupRuleTargetComponent tgt) throws FHIRException {
2659    if (var == null) {
2660      assert (Utilities.noString(element));
2661      // 1. start the new structure definition
2662      StructureDefinition sdn = worker.fetchResource(StructureDefinition.class, type.getType());
2663      if (sdn == null)
2664        throw new FHIRException("Unable to find definition for "+type.getType());
2665      ElementDefinition edn = sdn.getSnapshot().getElementFirstRep();
2666      PropertyWithType pn = createProfile(map, profiles, new PropertyWithType(sdn.getId(), new Property(worker, edn, sdn), null, type), sliceName, tgt);
2667
2668//      // 2. hook it into the base bundle
2669//      if (type.getType().startsWith("http://hl7.org/fhir/StructureDefinition/") && worker.getResourceNames().contains(type.getType().substring(40))) {
2670//        StructureDefinition sd = var.getProperty().profileProperty.getStructure();
2671//        ElementDefinition ed = sd.getDifferential().addElement();
2672//        ed.setPath("Bundle.entry");
2673//        ed.setName(sliceName);
2674//        ed.setMax("1"); // well, it is for now...
2675//        ed = sd.getDifferential().addElement();
2676//        ed.setPath("Bundle.entry.fullUrl");
2677//        ed.setMin(1);
2678//        ed = sd.getDifferential().addElement();
2679//        ed.setPath("Bundle.entry.resource");
2680//        ed.setMin(1);
2681//        ed.addType().setCode(pn.getProfileProperty().getStructure().getType()).setProfile(pn.getProfileProperty().getStructure().getUrl());
2682//      }
2683      return pn; 
2684    } else {
2685      assert (!Utilities.noString(element));
2686      Property pvb = var.getProperty().getBaseProperty();
2687      Property pvd = var.getProperty().getProfileProperty();
2688      Property pc = pvb.getChild(element, var.property.types);
2689      if (pc == null)
2690        throw new DefinitionException("Unable to find a definition for "+pvb.getDefinition().getPath()+"."+element);
2691      
2692      // the profile structure definition (derived)
2693      StructureDefinition sd = var.getProperty().profileProperty.getStructure();
2694      ElementDefinition ednew = sd.getDifferential().addElement();
2695      ednew.setPath(var.getProperty().profileProperty.getDefinition().getPath()+"."+pc.getName());
2696      ednew.setUserData("slice-name", sliceName);
2697      ednew.setFixed(fixed);
2698      for (ProfiledType pt : type.getProfiledTypes()) {
2699        if (pt.hasBindings())
2700          ednew.setBinding(pt.getBindings().get(0));
2701        if (pt.getUri().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
2702          String t = pt.getUri().substring(40);
2703          t = checkType(t, pc, pt.getProfiles());
2704          if (t != null) {
2705            if (pt.hasProfiles()) {
2706              for (String p : pt.getProfiles())
2707                if (t.equals("Reference"))
2708                  ednew.getType(t).addTargetProfile(p);
2709                else
2710                  ednew.getType(t).addProfile(p);
2711            } else 
2712            ednew.getType(t);
2713      }
2714        }
2715      }
2716      
2717      return new PropertyWithType(var.property.path+"."+element, pc, new Property(worker, ednew, sd), type);
2718    }
2719  }
2720  
2721
2722
2723  private String checkType(String t, Property pvb, List<String> profiles) throws FHIRException {
2724    if (pvb.getDefinition().getType().size() == 1 && isCompatibleType(t, pvb.getDefinition().getType().get(0).getWorkingCode()) && profilesMatch(profiles, pvb.getDefinition().getType().get(0).getProfile())) 
2725      return null;
2726    for (TypeRefComponent tr : pvb.getDefinition().getType()) {
2727      if (isCompatibleType(t, tr.getWorkingCode()))
2728        return tr.getWorkingCode(); // note what is returned - the base type, not the inferred mapping type
2729    }
2730    throw new FHIRException("The type "+t+" is not compatible with the allowed types for "+pvb.getDefinition().getPath());
2731  }
2732
2733  private boolean profilesMatch(List<String> profiles, List<CanonicalType> profile) {
2734    return profiles == null || profiles.size() == 0 || profile.size() == 0 || (profiles.size() == 1 && profiles.get(0).equals(profile.get(0).getValue()));
2735  }
2736
2737  private boolean isCompatibleType(String t, String code) {
2738    if (t.equals(code))
2739      return true;
2740    if (t.equals("string")) {
2741      StructureDefinition sd = worker.fetchTypeDefinition(code);
2742      if (sd != null && sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/string"))
2743        return true;
2744    }
2745    return false;
2746  }
2747
2748  private TypeDetails analyseTransform(TransformContext context, StructureMap map, StructureMapGroupRuleTargetComponent tgt, VariableForProfiling var, VariablesForProfiling vars) throws FHIRException {
2749    switch (tgt.getTransform()) {
2750    case CREATE :
2751      String p = getParamString(vars, tgt.getParameter().get(0));
2752      return new TypeDetails(CollectionStatus.SINGLETON, p);
2753    case COPY : 
2754      return getParam(vars, tgt.getParameter().get(0));
2755    case EVALUATE :
2756      ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION);
2757      if (expr == null) {
2758        expr = fpe.parse(getParamString(vars, tgt.getParameter().get(tgt.getParameter().size()-1)));
2759        tgt.setUserData(MAP_WHERE_EXPRESSION, expr);
2760      }
2761      return fpe.check(vars, null, expr);
2762
2763////case TRUNCATE : 
2764////  String src = getParamString(vars, tgt.getParameter().get(0));
2765////  String len = getParamString(vars, tgt.getParameter().get(1));
2766////  if (Utilities.isInteger(len)) {
2767////    int l = Integer.parseInt(len);
2768////    if (src.length() > l)
2769////      src = src.substring(0, l);
2770////  }
2771////  return new StringType(src);
2772////case ESCAPE : 
2773////  throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
2774////case CAST :
2775////  throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
2776////case APPEND : 
2777////  throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
2778    case TRANSLATE : 
2779      return new TypeDetails(CollectionStatus.SINGLETON, "CodeableConcept");
2780   case CC:
2781     ProfiledType res = new ProfiledType("CodeableConcept");
2782     if (tgt.getParameter().size() >= 2  && isParamId(vars, tgt.getParameter().get(1))) {
2783       TypeDetails td = vars.get(null, getParamId(vars, tgt.getParameter().get(1))).property.types;
2784       if (td != null && td.hasBinding())
2785         // todo: do we need to check that there's no implicit translation her? I don't think we do...
2786         res.addBinding(td.getBinding());
2787     }
2788     return new TypeDetails(CollectionStatus.SINGLETON, res);
2789   case C:
2790     return new TypeDetails(CollectionStatus.SINGLETON, "Coding");
2791   case QTY:
2792     return new TypeDetails(CollectionStatus.SINGLETON, "Quantity");
2793   case REFERENCE :
2794      VariableForProfiling vrs = vars.get(VariableMode.OUTPUT, getParamId(vars, tgt.getParameterFirstRep()));
2795      if (vrs == null)
2796        throw new FHIRException("Unable to resolve variable \""+getParamId(vars, tgt.getParameterFirstRep())+"\"");
2797      String profile = vrs.property.getProfileProperty().getStructure().getUrl();
2798     TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON);
2799     td.addType("Reference", profile);
2800     return td;  
2801////case DATEOP :
2802////  throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
2803////case UUID :
2804////  return new IdType(UUID.randomUUID().toString());
2805////case POINTER :
2806////  Base b = getParam(vars, tgt.getParameter().get(0));
2807////  if (b instanceof Resource)
2808////    return new UriType("urn:uuid:"+((Resource) b).getId());
2809////  else
2810////    throw new FHIRException("Transform engine cannot point at an element of type "+b.fhirType());
2811    default:
2812      throw new Error("Transform Unknown or not handled yet: "+tgt.getTransform().toCode());
2813    }
2814  }
2815  private String getParamString(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) {
2816    Type p = parameter.getValue();
2817    if (p == null || p instanceof IdType)
2818      return null;
2819    if (!p.hasPrimitiveValue())
2820      return null;
2821    return p.primitiveValue();
2822  }
2823
2824  private String getParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) {
2825    Type p = parameter.getValue();
2826    if (p == null || !(p instanceof IdType))
2827      return null;
2828    return p.primitiveValue();
2829  }
2830
2831  private boolean isParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) {
2832    Type p = parameter.getValue();
2833    if (p == null || !(p instanceof IdType))
2834      return false;
2835    return vars.get(null, p.primitiveValue()) != null;
2836  }
2837
2838  private TypeDetails getParam(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException {
2839    Type p = parameter.getValue();
2840    if (!(p instanceof IdType))
2841      return new TypeDetails(CollectionStatus.SINGLETON, ProfileUtilities.sdNs(p.fhirType(), worker.getOverrideVersionNs()));
2842    else { 
2843      String n = ((IdType) p).asStringValue();
2844      VariableForProfiling b = vars.get(VariableMode.INPUT, n);
2845      if (b == null)
2846        b = vars.get(VariableMode.OUTPUT, n);
2847      if (b == null)
2848        throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")");
2849      return b.getProperty().getTypes();
2850    }
2851  }
2852
2853  private PropertyWithType createProfile(StructureMap map, List<StructureDefinition> profiles, PropertyWithType prop, String sliceName, Base ctxt) throws FHIRException {
2854    if (prop.getBaseProperty().getDefinition().getPath().contains(".")) 
2855      throw new DefinitionException("Unable to process entry point");
2856
2857    String type = prop.getBaseProperty().getDefinition().getPath();
2858    String suffix = "";
2859    if (ids.containsKey(type)) {
2860      int id = ids.get(type);
2861      id++;
2862      ids.put(type, id);
2863      suffix = "-"+Integer.toString(id);
2864    } else
2865      ids.put(type, 0);
2866    
2867    StructureDefinition profile = new StructureDefinition();
2868    profiles.add(profile);
2869    profile.setDerivation(TypeDerivationRule.CONSTRAINT);
2870    profile.setType(type);
2871    profile.setBaseDefinition(prop.getBaseProperty().getStructure().getUrl());
2872    profile.setName("Profile for "+profile.getType()+" for "+sliceName);
2873    profile.setUrl(map.getUrl().replace("StructureMap", "StructureDefinition")+"-"+profile.getType()+suffix);
2874    ctxt.setUserData("profile", profile.getUrl()); // then we can easily assign this profile url for validation later when we actually transform
2875    profile.setId(map.getId()+"-"+profile.getType()+suffix);
2876    profile.setStatus(map.getStatus());
2877    profile.setExperimental(map.getExperimental());
2878    profile.setDescription("Generated automatically from the mapping by the Java Reference Implementation");
2879    for (ContactDetail c : map.getContact()) {
2880      ContactDetail p = profile.addContact();
2881      p.setName(c.getName());
2882      for (ContactPoint cc : c.getTelecom()) 
2883        p.addTelecom(cc);
2884    }
2885    profile.setDate(map.getDate());
2886    profile.setCopyright(map.getCopyright());
2887    profile.setFhirVersion(FHIRVersion.fromCode(Constants.VERSION));
2888    profile.setKind(prop.getBaseProperty().getStructure().getKind());
2889    profile.setAbstract(false);
2890    ElementDefinition ed = profile.getDifferential().addElement();
2891    ed.setPath(profile.getType());
2892    prop.profileProperty = new Property(worker, ed, profile);
2893    return prop;
2894  }
2895
2896  private PropertyWithType resolveType(StructureMap map, String type, StructureMapInputMode mode) throws FHIRException {
2897    for (StructureMapStructureComponent imp : map.getStructure()) {
2898      if ((imp.getMode() == StructureMapModelMode.SOURCE && mode == StructureMapInputMode.SOURCE) || 
2899          (imp.getMode() == StructureMapModelMode.TARGET && mode == StructureMapInputMode.TARGET)) {
2900        StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl());
2901        if (sd == null)
2902          throw new FHIRException("Import "+imp.getUrl()+" cannot be resolved");
2903        if (sd.getId().equals(type)) {
2904          return new PropertyWithType(sd.getType(), new Property(worker, sd.getSnapshot().getElement().get(0), sd), null, new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()));
2905        }
2906      }
2907    }
2908    throw new FHIRException("Unable to find structure definition for "+type+" in imports");
2909  }
2910
2911
2912  public StructureMap generateMapFromMappings(StructureDefinition sd) throws IOException, FHIRException {
2913    String id = getLogicalMappingId(sd);
2914    if (id == null) 
2915        return null;
2916    String prefix = ToolingExtensions.readStringExtension(sd,  ToolingExtensions.EXT_MAPPING_PREFIX);
2917    String suffix = ToolingExtensions.readStringExtension(sd,  ToolingExtensions.EXT_MAPPING_SUFFIX);
2918    if (prefix == null || suffix == null)
2919      return null;
2920    // we build this by text. Any element that has a mapping, we put it's mappings inside it....
2921    StringBuilder b = new StringBuilder();
2922    b.append(prefix);
2923
2924    ElementDefinition root = sd.getSnapshot().getElementFirstRep();
2925    String m = getMapping(root, id);
2926    if (m != null)
2927      b.append(m+"\r\n");
2928    addChildMappings(b, id, "", sd, root, false);
2929    b.append("\r\n");
2930    b.append(suffix);
2931    b.append("\r\n");
2932    StructureMap map = parse(b.toString(), sd.getUrl());
2933    map.setId(tail(map.getUrl()));
2934    if (!map.hasStatus())
2935      map.setStatus(PublicationStatus.DRAFT);
2936    map.getText().setStatus(NarrativeStatus.GENERATED);
2937    map.getText().setDiv(new XhtmlNode(NodeType.Element, "div"));
2938    map.getText().getDiv().addTag("pre").addText(render(map));
2939    return map;
2940  }
2941
2942
2943  private String tail(String url) {
2944    return url.substring(url.lastIndexOf("/")+1);
2945  }
2946
2947
2948  private void addChildMappings(StringBuilder b, String id, String indent, StructureDefinition sd, ElementDefinition ed, boolean inner) throws DefinitionException {
2949    boolean first = true;
2950    List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed);
2951    for (ElementDefinition child : children) {
2952      if (first && inner) {
2953        b.append(" then {\r\n");
2954        first = false;
2955      }
2956      String map = getMapping(child, id);
2957      if (map != null) {
2958        b.append(indent+"  "+child.getPath()+": "+map);
2959        addChildMappings(b, id, indent+"  ", sd, child, true);
2960        b.append("\r\n");
2961      }
2962    }
2963    if (!first && inner)
2964      b.append(indent+"}");
2965    
2966  }
2967
2968
2969  private String getMapping(ElementDefinition ed, String id) {
2970    for (ElementDefinitionMappingComponent map : ed.getMapping())
2971      if (id.equals(map.getIdentity()))
2972        return map.getMap();
2973    return null;
2974  }
2975
2976
2977  private String getLogicalMappingId(StructureDefinition sd) {
2978    String id = null;
2979    for (StructureDefinitionMappingComponent map : sd.getMapping()) {
2980      if ("http://hl7.org/fhir/logical".equals(map.getUri()))
2981        return map.getIdentity();
2982    }
2983    return null;
2984  }
2985
2986  public TerminologyServiceOptions getTerminologyServiceOptions() {
2987    return terminologyServiceOptions;
2988  }
2989
2990  public void setTerminologyServiceOptions(TerminologyServiceOptions terminologyServiceOptions) {
2991    this.terminologyServiceOptions = terminologyServiceOptions;
2992  }
2993        
2994}