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