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