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