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