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