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