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