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