001package org.hl7.fhir.r5.conformance.profile;
002
003import java.math.BigDecimal;
004import java.util.ArrayList;
005import java.util.HashSet;
006import java.util.List;
007import java.util.Set;
008import java.util.Stack;
009
010import lombok.extern.slf4j.Slf4j;
011import org.hl7.fhir.exceptions.DefinitionException;
012import org.hl7.fhir.exceptions.FHIRException;
013import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.SourcedChildDefinitions;
014import org.hl7.fhir.r5.context.IWorkerContext;
015import org.hl7.fhir.r5.model.Base;
016import org.hl7.fhir.r5.model.CanonicalType;
017import org.hl7.fhir.r5.model.CodeType;
018import org.hl7.fhir.r5.model.DataType;
019import org.hl7.fhir.r5.model.DateTimeType;
020import org.hl7.fhir.r5.model.ElementDefinition;
021import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType;
022import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent;
023import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
024import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules;
025import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
026import org.hl7.fhir.r5.model.Extension;
027import org.hl7.fhir.r5.model.Property;
028import org.hl7.fhir.r5.model.Quantity;
029import org.hl7.fhir.r5.model.StructureDefinition;
030import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionDifferentialComponent;
031import org.hl7.fhir.r5.utils.DefinitionNavigator;
032import org.hl7.fhir.r5.utils.ToolingExtensions;
033import org.hl7.fhir.r5.utils.TypesUtilities;
034import org.hl7.fhir.r5.utils.UserDataNames;
035import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
036import org.hl7.fhir.utilities.Utilities;
037import org.hl7.fhir.utilities.i18n.I18nConstants;
038
039/**
040 * when a slice is encountered, it may have additional details defined after the slice that must be merged into 
041 * each of the slices. That's kind of multiple inheritance, and fiendishly complicated to add to the snapshot generator
042 * 
043 * This class pre-processes the differential, finding the slices that have these trailing properties, and 
044 * filling them out in the slices that follow
045 * 
046 * There's potential problems here, mostly around slicing extensions (other kind of slicing isn't allowed)
047 * and also the merging logic might need to be sophisticated.
048 * 
049 */
050@Slf4j
051public class SnapshotGenerationPreProcessor {
052
053  public class ElementAnalysis {
054    private StructureDefinition structure;
055    private ElementDefinition element;
056    private String type;
057    public SourcedChildDefinitions children;
058    protected ElementAnalysis(StructureDefinition structure, ElementDefinition element, String type) {
059      super();
060      this.structure = structure;
061      this.element = element;
062      this.type = type;
063    }
064    public StructureDefinition getStructure() {
065      return structure;
066    }
067    public ElementDefinition getElement() {
068      return element;
069    }
070    public SourcedChildDefinitions getChildren() {
071      return children;
072    }
073    public void setChildren(SourcedChildDefinitions children) {
074      this.children = children;
075    }
076    public String getType() {
077      return type;
078    }
079    public String summary() {
080      return element.getName()+":"+type;
081    }
082  }
083
084  public class SliceInfo {
085    SliceInfo parent;
086    String path;
087    boolean closed;
088    ElementDefinition slicer;
089    List<ElementDefinition> sliceStuff;
090    List<ElementDefinition> slices;
091
092    public SliceInfo(SliceInfo parent, ElementDefinition ed) {
093      this.parent = parent;
094      path = ed.getPath();
095      slicer = ed;
096      sliceStuff = new ArrayList<>();
097      if (parent != null) {
098        parent.add(ed);
099      }
100    }
101
102    public void newSlice(ElementDefinition ed) {
103      if (slices == null) {
104        slices = new ArrayList<ElementDefinition>();
105      }
106      slices.add(ed);
107      if (parent != null) {
108        parent.add(ed);
109      }
110    }
111    public void add(ElementDefinition ed) {
112      if (slices == null) {
113        sliceStuff.add(ed);
114      }      
115      if (parent != null) {
116        parent.add(ed);
117      }
118    }
119  }
120
121  private IWorkerContext context;
122  private ProfileUtilities utils;
123  Set<String> typeNames;
124  private List<SliceInfo> slicings = new ArrayList<>();
125
126  public SnapshotGenerationPreProcessor(ProfileUtilities utils) {
127    super();
128    this.utils = utils;
129    this.context = utils.getContext();
130  }
131
132  public void process(StructureDefinitionDifferentialComponent diff, StructureDefinition srcOriginal) {
133    StructureDefinition srcWrapper = shallowClone(srcOriginal, diff); 
134    processSlices(diff, srcWrapper);  
135    if (srcWrapper.hasExtension(ToolingExtensions.EXT_ADDITIONAL_BASE)) {
136       insertMissingSparseElements(diff.getElement(), srcWrapper.getTypeName());
137       for (Extension ext : srcWrapper.getExtensionsByUrl(ToolingExtensions.EXT_ADDITIONAL_BASE)) {
138         StructureDefinition ab = context.fetchResource(StructureDefinition.class, ext.getValue().primitiveValue());
139         if (ab == null) {
140           throw new FHIRException("Unable to find additional base '"+ext.getValue().primitiveValue()+"'");
141         }
142         if (!srcWrapper.getType().equals(ab.getType())) {
143           throw new FHIRException("Type mismatch");
144         }
145         SnapshotGenerationPreProcessor abpp = new SnapshotGenerationPreProcessor(utils);
146         abpp.process(ab.getDifferential(), ab);
147         abpp.insertMissingSparseElements(ab.getDifferential().getElement(), srcWrapper.getTypeName());
148         mergeElementsFromAdditionalBase(srcWrapper, ab);         
149       }
150    }
151  }
152  
153  private StructureDefinition shallowClone(StructureDefinition src, StructureDefinitionDifferentialComponent diff) {
154    StructureDefinition sd = new StructureDefinition();
155    sd.setUrl(src.getUrl());
156    sd.setVersion(src.getVersion());
157    sd.setType(src.getType());
158    sd.setDerivation(src.getDerivation());
159    sd.setBaseDefinition(src.getBaseDefinition());
160    sd.setExtension(src.getExtension());
161    sd.setDifferential(diff);
162    return sd;
163  }
164
165  private void mergeElementsFromAdditionalBase(StructureDefinition sourceSD, StructureDefinition baseSD) {
166    List<ElementDefinition> output = new ArrayList<ElementDefinition>();
167    output.add(mergeElementDefinitions(baseSD.getDifferential().getElementFirstRep(), sourceSD.getDifferential().getElementFirstRep(), baseSD));
168    DefinitionNavigator base = new DefinitionNavigator(context, baseSD, true, false);
169    DefinitionNavigator source = new DefinitionNavigator(context, sourceSD, true, false);
170    StructureDefinition sdt = context.fetchTypeDefinition(sourceSD.getType());
171    SourcedChildDefinitions children = utils.getChildMap(sdt, sdt.getSnapshot().getElementFirstRep(), false);
172    mergeElements(output, base, source, children, baseSD);
173    sourceSD.getDifferential().setElement(output);    
174  }
175
176  private void mergeElements(List<ElementDefinition> output, DefinitionNavigator base, DefinitionNavigator source, SourcedChildDefinitions children, StructureDefinition baseSD) {
177    for (ElementDefinition child : children.getList()) {
178      DefinitionNavigator baseChild = base == null ? null : base.childByName(child.getName());
179      DefinitionNavigator sourceChild = source == null ? null : source.childByName(child.getName());
180      if (baseChild != null && sourceChild != null) {
181        if (!baseChild.hasSlices() && !sourceChild.hasSlices()) {
182          output.add(mergeElementDefinitions(baseChild.current(), sourceChild.current(), baseSD));
183          if (sourceChild.hasChildren() || baseChild.hasChildren()) {
184            mergeElements(output, baseChild, sourceChild, getChildren(children, child, sourceChild, baseChild, baseSD), baseSD);
185          }
186        } else if (baseChild.hasSlices() && sourceChild.hasSlices()) {
187          if (!slicingIsConsistent(baseChild.getSlicing(), sourceChild.getSlicing())) {
188            throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, baseSD.getVersionedUrl(), child.getPath()+".slicing", 
189                describeDiscriminators(baseChild.getSlicing()), describeDiscriminators(sourceChild.getSlicing())));                
190          }
191          output.add(mergeElementDefinitions(baseChild.current(), sourceChild.current(), baseSD));
192          mergeElements(output, baseChild, sourceChild, getChildren(children, child, sourceChild, baseChild, baseSD), baseSD);
193          List<DefinitionNavigator> handled = new ArrayList<>();
194          for (DefinitionNavigator slice : sourceChild.slices()) {
195            DefinitionNavigator match = getMatchingSlice(baseChild, slice, sourceChild.getSlicing());
196            if (match != null) {
197              handled.add(match);
198              output.add(mergeElementDefinitions(match.current(), slice.current(), baseSD));
199              mergeElements(output, match, slice, getChildren(children, child, match, slice, baseSD), baseSD);
200            } else {
201              // this slice isn't in the base 
202              output.add(slice.current().copy());
203              mergeElements(output, null, slice, getChildren(children, child, slice, null, baseSD), baseSD);
204            }            
205          }
206          for (DefinitionNavigator slice : baseChild.slices()) {
207            if (!handled.contains(slice)) {
208              output.add(slice.current().copy());
209              mergeElements(output, slice, null, getChildren(children, child, null, slice, baseSD), baseSD);
210            }
211          }          
212        } else  if (baseChild.hasSlices()) {
213          throw new FHIRException("Not done yet");
214        } else { // sourceChild.hasSlices()         
215          throw new FHIRException("Not done yet");
216        }
217      } else if (baseChild != null) {
218        output.add(baseChild.current().copy());
219        if (baseChild.hasChildren()) {
220          mergeElements(output, baseChild, sourceChild, getChildren(children, child, null, baseChild, baseSD), baseSD);
221        }
222        if (baseChild.hasSlices()) {
223          for (DefinitionNavigator slice : baseChild.slices()) {
224            mergeElements(output, slice, null, getChildren(children, child, null, slice, baseSD), baseSD);            
225          }
226        }
227      } else if (sourceChild != null) {
228        output.add(sourceChild.current().copy());
229        if (sourceChild.hasSlices()) {
230          for (DefinitionNavigator slice : sourceChild.slices()) {
231            mergeElements(output, null, slice, getChildren(children, child, slice, null, baseSD), baseSD);            
232          }
233        }
234        if (sourceChild.hasChildren()) {
235          mergeElements(output, baseChild, sourceChild, getChildren(children, child, sourceChild, null, baseSD), baseSD);
236        }
237        // slices
238      } else {
239        // do nothing - no match on either side
240      }
241    }
242  }
243
244  private DefinitionNavigator getMatchingSlice(DefinitionNavigator base, DefinitionNavigator slice, ElementDefinitionSlicingComponent slicing) {
245    List<DataType> values = new ArrayList<>();
246    for (ElementDefinitionSlicingDiscriminatorComponent d : slicing.getDiscriminator()) {
247      values.add(getDiscriminatorValue(slice, d));
248    }
249    DefinitionNavigator match = null;
250    for (DefinitionNavigator t : base.slices()) {
251      List<DataType> values2 = new ArrayList<>();
252      for (ElementDefinitionSlicingDiscriminatorComponent d : slicing.getDiscriminator()) {
253        values2.add(getDiscriminatorValue(t, d));
254      }
255      if (valuesMatch(values, values2)) {
256        if (match == null) {
257          match = t;
258        } else {
259          throw new Error("Duplicate slice");
260        }
261      }      
262    }    
263    return match;
264  }
265
266  private DataType getDiscriminatorValue(DefinitionNavigator slice, ElementDefinitionSlicingDiscriminatorComponent d) {
267    // we're not following types, because we want to stop where the differential stops. but right here and now,
268    // we have to follow the types. So we're going to clone the navigator 
269    DefinitionNavigator dn = new DefinitionNavigator(slice, true);
270    switch (d.getType() ) {
271    case EXISTS:
272      throw new Error("Not supported yet");
273    case NULL:
274      throw new Error("Not supported yet");
275    case PATTERN:
276      throw new Error("Not supported yet");
277    case POSITION:
278      throw new Error("Not supported yet");
279    case PROFILE:
280      throw new Error("Not supported yet");
281    case TYPE:
282      if ("$this".equals(d.getPath())) {
283        return new CodeType(dn.getManualType() != null ? dn.getManualType().getCode() : dn.current().typeSummary());
284      } else {
285        throw new Error("Not supported yet");
286      }
287    case VALUE:
288      DefinitionNavigator child = dn.childByName(d.getPath());
289      if (child != null) {
290        ElementDefinition ed = child.current();
291        if (ed.hasFixed()) {
292          return ed.getFixed();
293        } else if (ed.hasPattern()) {
294          return ed.getPattern();
295        }
296      } else {
297        return null;
298      }
299    default:
300      throw new Error("Not supported yet");    
301    }
302  }
303
304  private boolean valuesMatch(List<DataType> values1, List<DataType> values2) {
305    for (int i = 0; i < values1.size(); i++) {
306      DataType v1 = values1.get(i);
307      DataType v2 = values2.get(i);
308      if (!valuesMatch(v1, v2)) {
309        return false;
310      }
311    }
312    return true;
313  }
314
315  private boolean valuesMatch(DataType v1, DataType v2) {
316    if (v1 == null && v2 == null) {
317      return true;
318    } else if (v1 != null && v2 != null) {
319      return v1.equalsDeep(v2);
320    } else {
321      return false;
322    }
323  }
324
325  private boolean slicingIsConsistent(ElementDefinitionSlicingComponent src, ElementDefinitionSlicingComponent base) {
326    if (src.getRules() != base.getRules()) {
327      return false;
328    }
329    if (src.getDiscriminator().size() != base.getDiscriminator().size()) {
330      return false;
331    }
332    for (ElementDefinitionSlicingDiscriminatorComponent d1 : src.getDiscriminator()) {
333      boolean found = false;
334      for (ElementDefinitionSlicingDiscriminatorComponent d2 : base.getDiscriminator()) {
335        found = found || (d1.getType() == d2.getType() && d1.getPath().equals(d2.getPath()));
336      }
337      if (!found) {
338        return false;
339      }
340    }
341    return true;
342  }
343
344  private Object describeDiscriminators(ElementDefinitionSlicingComponent slicing) {
345    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();    
346    for (ElementDefinitionSlicingDiscriminatorComponent t : slicing.getDiscriminator()) {
347      b.append(t.getType().toCode()+":"+t.getPath());
348    }
349    return (slicing.hasRules() ? slicing.getRules().toCode()+":" : "")+b.toString()+(slicing.hasOrdered() ? " (ordered)" : "");
350  }
351
352  private SourcedChildDefinitions getChildren(SourcedChildDefinitions children, ElementDefinition child, DefinitionNavigator source, DefinitionNavigator base, StructureDefinition baseSD) {
353    if (child.getType().size() > 1) {
354      String type = null;
355      if (source != null && base != null) {
356        String typeSource = statedOrImpliedType(source);
357        String typeBase = statedOrImpliedType(base);
358        if (typeSource != null && typeBase != null) {
359          if (typeSource.equals(typeBase)) {
360            type = typeSource;
361          } else {
362            throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, baseSD.getVersionedUrl(), child.getPath()+".type", typeSource, typeBase));
363          }
364        } else if (typeSource != null) {
365          type = typeSource;
366        } else if (typeBase != null) {
367          type = typeBase;          
368        }
369      } else if (source != null) {
370        type = statedOrImpliedType(source);       
371      } else if (base != null) {
372        type = statedOrImpliedType(base);        
373      } else {
374        // type = "DataType";
375      }
376      if (type == null) {          
377        throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INDETERMINATE_TYPE, baseSD.getVersionedUrl(), child.getPath()+".type"));
378        
379      } else {
380        return utils.getChildMap(children.getSource(), child, true, type);
381      }
382    } else {
383      return utils.getChildMap(children.getSource(), child, true);
384    }
385  }
386
387  private String statedOrImpliedType(DefinitionNavigator source) {
388    if (source.getManualType() != null) {
389      return source.getManualType().getCode();
390    } else if (source.current().getType().size() == 1) {
391      return source.current().getTypeFirstRep().getCode();
392    } else {
393      return null;
394    }
395  }
396
397  private ElementDefinition mergeElementDefinitions(ElementDefinition base, ElementDefinition source, StructureDefinition baseSD) {
398    ElementDefinition merged = new ElementDefinition();
399    merged.setPath(source.getPath());
400    if (source.hasSlicing()) {
401      merged.setSlicing(source.getSlicing());
402    }
403    
404    merged.setLabelElement(chooseProp(source.getLabelElement(),  base.getLabelElement()));
405    merged.setShortElement(chooseProp(source.getShortElement(), base.getShortElement()));
406    merged.setDefinitionElement(chooseProp(source.getDefinitionElement(), base.getDefinitionElement()));
407    merged.setCommentElement(chooseProp(source.getCommentElement(), base.getCommentElement()));
408    merged.setRequirementsElement(chooseProp(source.getRequirementsElement(), base.getRequirementsElement()));
409    merged.setMeaningWhenMissingElement(chooseProp(source.getMeaningWhenMissingElement(), base.getMeaningWhenMissingElement()));
410    merged.setOrderMeaningElement(chooseProp(source.getOrderMeaningElement(), base.getOrderMeaningElement()));
411    merged.setMaxLengthElement(chooseProp(source.getMaxLengthElement(), base.getMaxLengthElement()));
412    merged.setMustHaveValueElement(chooseProp( source.getMustHaveValueElement(), base.getMustHaveValueElement()));
413    merged.setMustSupportElement(chooseProp(source.getMustSupportElement(), base.getMustSupportElement()));
414    merged.setIsModifierElement(chooseProp(source.getIsModifierElement(), base.getIsModifierElement()));
415    merged.setIsModifierReasonElement(chooseProp(source.getIsModifierReasonElement(), base.getIsModifierReasonElement()));
416    merged.setIsSummaryElement(chooseProp(source.getIsSummaryElement(), base.getIsSummaryElement()));
417
418    if (source.hasMin() && base.hasMin()) {
419      merged.setMinElement(source.getMin() < base.getMin() ? source.getMinElement().copy() : base.getMinElement().copy());      
420    } else {
421      merged.setMinElement(chooseProp(source.getMinElement(), base.getMinElement().copy()));
422    }
423    if (source.hasMax() && base.hasMax()) {
424      merged.setMaxElement(source.getMaxAsInt() < base.getMaxAsInt() ? source.getMaxElement().copy() : base.getMaxElement().copy());      
425    } else {
426      merged.setMaxElement(chooseProp(source.getMaxElement(), base.getMaxElement()));
427    }
428    
429    if (source.hasFixed() || base.hasFixed()) {
430      if (source.hasFixed()) {
431        if (base.hasFixed()) {
432          if (!source.getFixed().equalsDeep(base.getFixed())) {            
433            throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, baseSD.getVersionedUrl(), source.getPath()+".fixed", source.getFixed().toString(), base.getFixed().toString()));
434          } else {
435            merged.setFixed(source.getFixed().copy());          
436          }
437        } else if (base.hasPattern()) {
438          merged.setFixed(checkPatternValues(baseSD.getVersionedUrl(), source.getPath()+".fixed", source.getFixed(), base.getPattern(), false)); 
439        } else {
440          merged.setFixed(source.getFixed().copy());          
441        }
442      } else if (source.hasPattern()) { // base.hasFixed() == true
443        merged.setFixed(checkPatternValues(baseSD.getVersionedUrl(), source.getPath()+".pattern", base.getFixed(), source.getPattern(), false)); 
444      } else {
445        merged.setFixed(base.getFixed().copy());          
446      }
447    } else if (source.hasPattern() && base.hasPattern()) {
448      merged.setPattern(checkPatternValues(baseSD.getVersionedUrl(), source.getPath()+".pattern", source.getFixed(), base.getFixed(), true));
449    } else {
450      merged.setPattern(chooseProp(source.getPattern(), base.getPattern()));
451    }
452    
453    if (source.hasMinValue() && base.hasMinValue()) {
454      merged.setMinValue(isLower(baseSD.getVersionedUrl(), source.getPath(), "minValue", source.getMinValue(), base.getMinValue()) ? base.getMinValue().copy() : source.getMinValue().copy());      
455    } else {
456      merged.setMinValue(chooseProp(source.getMinValue(), base.getMinValue()));
457    }
458    if (source.hasMaxValue() && base.hasMaxValue()) {
459      merged.setMaxValue(isLower(baseSD.getVersionedUrl(), source.getPath(), "maxValue", source.getMaxValue(), base.getMaxValue()) ? source.getMaxValue().copy() : base.getMaxValue().copy());            
460    } else {
461      merged.setMaxValue(chooseProp(source.getMaxValue(), base.getMaxValue()));
462    }
463    if (source.hasMaxLength() && base.hasMaxLength()) {
464      merged.setMaxLengthElement(base.getMaxLength() < source.getMaxLength() ? source.getMaxLengthElement().copy() : base.getMaxLengthElement().copy());            
465    } else {
466      merged.setMaxLengthElement(chooseProp(source.getMaxLengthElement(), base.getMaxLengthElement().copy()));
467    }
468    // union
469    union(merged.getAlias(), source.getAlias(), base.getAlias());
470    union(merged.getCode(), source.getCode(), base.getCode());
471    union(merged.getExample(), source.getExample(), base.getExample());
472    union(merged.getConstraint(), source.getConstraint(), base.getConstraint());
473    union(merged.getMapping(), source.getMapping(), base.getMapping());
474
475    // intersection
476    if (source.hasValueAlternatives() && base.hasValueAlternatives()) {
477      for (CanonicalType st : source.getValueAlternatives()) {
478        boolean exists = false;
479        for (CanonicalType st2 : base.getValueAlternatives()) {
480          exists = exists || st.equals(st2);
481        }
482        if (exists) {
483          merged.getValueAlternatives().add(st.copy());
484        }
485      }
486    } else if (source.hasValueAlternatives()) {
487      for (CanonicalType st : source.getValueAlternatives()) {
488        merged.getValueAlternatives().add(st.copy());
489      }
490    } else if (base.hasValueAlternatives()) {
491      for (CanonicalType st : base.getValueAlternatives()) {
492        merged.getValueAlternatives().add(st.copy());
493      }
494    }
495
496    if (source.hasType() && base.hasType()) {
497      for (TypeRefComponent t1 : source.getType()) {
498        for (TypeRefComponent t2 : base.getType()) {
499          if (Utilities.stringsEqual(t1.getWorkingCode(), t2.getWorkingCode())) {
500            merged.getType().add(mergeTypes(baseSD.getVersionedUrl(), source.getPath(), t1, t2));
501          }
502        }
503      }
504      if (merged.getType().isEmpty()) {
505        throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, baseSD.getVersionedUrl(), source.getPath()+".type", source.typeSummary(), base.typeSummary()));
506        
507      }
508    } else if (source.hasType()) {
509      for (TypeRefComponent st : source.getType()) {
510        merged.getType().add(st.copy());
511      }
512    } else if (base.hasType()) {
513      for (TypeRefComponent st : base.getType()) {
514        merged.getType().add(st.copy());
515      }
516    }
517
518    // binding
519    if (source.hasBinding() && base.hasBinding()) {
520      throw new Error("not done yet");
521    } else if (source.hasBinding()) {
522      merged.setBinding(source.getBinding().copy());
523    } else if (base.hasBinding()) {
524      merged.setBinding(base.getBinding().copy());
525    }
526    
527    
528    return merged;
529  }
530
531  private <T extends DataType> T chooseProp(T source, T base) {
532    if (source != null && !source.isEmpty()) {
533      return (T) source.copy();
534    }
535    if (base != null && !base.isEmpty()) {
536      return (T) base.copy();
537    }
538    return null;
539  }
540
541  private TypeRefComponent mergeTypes(String vurl, String path, TypeRefComponent t1, TypeRefComponent t2) {
542    TypeRefComponent tr = t1.copy();
543    if (t1.hasProfile() && t2.hasProfile()) {
544      // here, this is tricky, because we need to know what the merged additional bases of the pairings will be
545      if (t1.getProfile().size() > 1 || t2.getProfile().size() > 1) {
546        throw new FHIRException("Not handled yet: multiple profiles");        
547      }
548      StructureDefinition sd1 = context.fetchResource(StructureDefinition.class, t1.getProfile().get(0).asStringValue());
549      if (sd1 == null) {
550        throw new FHIRException("Unknown type profile at '"+path+"': "+t1.getProfile().get(0).asStringValue());                
551      }
552      StructureDefinition sd2 = context.fetchResource(StructureDefinition.class, t2.getProfile().get(0).asStringValue());
553      if (sd2 == null) {
554        throw new FHIRException("Unknown type profile at '"+path+"': "+t2.getProfile().get(0).asStringValue());                
555      }
556      tr.getProfile().clear();
557      if (specialises(sd1, sd2)) { 
558        // both sd1 and sd2 apply, but sd1 applies everything in sd2, so it's just sd1
559        tr.getProfile().add(t1.getProfile().get(0).copy());
560      } else if (specialises(sd2, sd1)) { 
561        // both sd1 and sd2 apply, but sd2 applies everything in sd1, so it's just sd2
562        tr.getProfile().add(t2.getProfile().get(0).copy());
563      } else {
564        // oh dear. We have to find a type that is both of them 
565        StructureDefinition sd3 = findJointProfile(sd1, sd2);
566        if (sd3 == null) {
567          throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_NO_TYPE, vurl, path, sd1.getVersionedUrl(), sd2.getVersionedUrl()));
568        } else {
569          tr.getProfile().add(new CanonicalType(sd3.getUrl()));          
570        }
571      }
572    } else if (t2.hasProfile()) {
573      for (CanonicalType ct : t2.getProfile()) {
574        tr.getProfile().add(ct.copy());
575      }
576    }
577    if (t1.hasTargetProfile() && t2.hasTargetProfile()) {
578      // here, this is tricky, because we need to know what the merged additional bases of the pairings will be
579    } else if (t2.hasTargetProfile()) {
580      for (CanonicalType ct : t2.getTargetProfile()) {
581        tr.getTargetProfile().add(ct.copy());
582      }
583    }
584    if (t1.hasAggregation() && t2.hasAggregation() && !t1.getAggregation().equals(t2.getAggregation())) {
585      throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, vurl, path+".type["+tr.getWorkingCode()+"].aggregation", t1.getAggregation(), t2.getAggregation()));
586    }
587    if (t1.hasVersioning() && t2.hasVersioning() && t1.getVersioning() != t2.getVersioning()) {
588      throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, vurl, path+".type["+tr.getWorkingCode()+"].aggregation", t1.getVersioning(), t2.getVersioning()));
589    }
590    return tr;
591  }
592
593  private StructureDefinition findJointProfile(StructureDefinition sd1, StructureDefinition sd2) {
594    for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
595      boolean b1 = sd.getBaseDefinitions().contains(sd1.getUrl()) || sd.getBaseDefinitions().contains(sd1.getVersionedUrl());
596      boolean b2 = sd.getBaseDefinitions().contains(sd2.getUrl()) || sd.getBaseDefinitions().contains(sd2.getVersionedUrl());
597      if (b1 && b2) {
598        return sd;
599      }
600    }
601    return null;
602  }
603
604  private boolean specialises(StructureDefinition focus, StructureDefinition other) {
605    // we ignore impose and compliesWith - for now?
606    for (String url : focus.getBaseDefinitions()) {
607      StructureDefinition base = context.fetchResource(StructureDefinition.class, url);
608      if (base != null) {
609        if (base == other || specialises(base, other)) {
610          return true;
611        }
612      }
613    }
614    return false;
615  }
616
617  private <T extends Base> void union(List<T> merged, List<T> source, List<T> base) {
618    for (T st : source) {
619      merged.add((T) st.copy());
620    }
621    for (T st : base) {
622      boolean exists = false;
623      for (T st2 : merged) {
624        exists = exists || st.equals(st2);
625      }
626      if (!exists) {
627        merged.add((T) st.copy());        
628      }
629    }
630  }
631
632  private boolean isLower(String vurl, String path, String property, DataType v1, DataType v2) {
633    if (v1 instanceof Quantity && v2 instanceof Quantity) {
634      Quantity q1 = (Quantity) v1;
635      Quantity q2 = (Quantity) v2;
636      if (q1.hasUnit() || q2.hasUnit()) {
637        if (Utilities.stringsEqual(q1.getUnit(), q2.getUnit())) {
638          throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, vurl, path+"."+property+".unit", v1.fhirType(), v2.fhirType()));
639        }
640      }
641      return isLower(vurl, path, property+".value", q1.getValueElement(), q2.getValueElement());
642    } else if (v1.isDateTime() && v2.isDateTime()) {
643      DateTimeType d1 = (DateTimeType) v1;
644      DateTimeType d2 = (DateTimeType) v2;
645      return d1.before(d2);      
646    } else if (Utilities.isDecimal(v1.primitiveValue(), true) && Utilities.isDecimal(v2.primitiveValue(), true)) {
647      BigDecimal d1 = new BigDecimal(v1.primitiveValue());
648      BigDecimal d2 = new BigDecimal(v2.primitiveValue());
649      return d1.compareTo(d2) < 0;
650    } else {
651      throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, vurl, path+"."+property, v1.fhirType(), v2.fhirType()));
652    }
653  }
654
655  private DataType checkPatternValues(String vurl, String path, DataType v1, DataType v2, boolean extras) {
656    if (!v1.fhirType().equals(v2.fhirType())) {
657      throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, vurl, path, v1.fhirType(), v2.fhirType()));
658    }
659    DataType merged = v1.copy();
660    if (v1.isPrimitive()) {
661      if (!Utilities.stringsEqual(v1.primitiveValue(), v2.primitiveValue())) {
662        throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, vurl, path+".value", v1.primitiveValue(), v2.primitiveValue()));        
663      }
664    }
665    for (Property p1 : v1.children()) {
666      Property p2 = v2.getChildByName(p1.getName());
667      if (p1.hasValues() && p2.hasValues()) {
668        if (p1.getValues().size() > 1 || p1.getValues().size() > 2) {
669          throw new Error("Not supported");          
670        }
671        merged.setProperty(p1.getName(), checkPatternValues(vurl, path+"."+p1.getName(), (DataType) p1.getValues().get(0), (DataType) p2.getValues().get(0), extras));
672      } else if (p2.hasValues()) {
673        if (!extras) {
674          throw new FHIRException(context.formatMessage(I18nConstants.SD_ADDITIONAL_BASE_INCOMPATIBLE_VALUES, vurl, path+"."+p1.getName(), "null", v2.primitiveValue()));            
675        }
676        if (p2.getValues().size() > 1) {
677          throw new Error("Not supported");
678        }
679        merged.setProperty(p1.getName(), p2.getValues().get(0).copy());
680      }
681    }
682    return merged;
683  }
684
685
686  private void processSlices(StructureDefinitionDifferentialComponent diff, StructureDefinition src) {
687    // first pass, divide it up 
688    for (int cursor = 0; cursor < diff.getElement().size(); cursor++) {      
689      ElementDefinition ed = diff.getElement().get(cursor);
690
691      SliceInfo si = getSlicing(ed);
692      if (si == null) {
693        if (ed.hasSlicing() && !isExtensionSlicing(ed)) {
694          si = new SliceInfo(null, ed);
695          slicings.add(si);
696        } else {
697          // ignore this
698        }
699      } else {
700        if (ed.hasSliceName() && ed.getPath().equals(si.path)) {
701          si.newSlice(ed);
702        } else if (ed.hasSlicing() && !isExtensionSlicing(ed)) {
703          si = new SliceInfo(si, ed);
704          slicings.add(si);
705        } else {
706          si.add(ed);
707        }
708      }
709    }       
710
711    for (SliceInfo si : slicings) {
712      if (!si.sliceStuff.isEmpty() && si.slices != null) {
713        for (ElementDefinition ed : si.sliceStuff) {
714          if (ed.hasSlicing() && !isExtensionSlicing(ed)) {
715            String message = context.formatMessage(I18nConstants.UNSUPPORTED_SLICING_COMPLEXITY, si.slicer.getPath(), ed.getPath(), ed.getSlicing().summary());
716            log.warn(message);
717            return;
718          }
719        }
720      }
721    }
722
723    // working backward
724    for (int i = slicings.size() - 1; i >= 0; i--) {
725      SliceInfo si = slicings.get(i);
726      if (!si.sliceStuff.isEmpty() && si.slices != null) {
727        // for each actual slice, we need to merge sliceStuff in
728        for (ElementDefinition slice : si.slices) {
729          mergeElements(diff.getElement(), si.sliceStuff, slice, si.slicer);
730        }
731      } else {
732        // we just ignore these - nothing to do
733      }
734    }
735
736    for (ElementDefinition ed : diff.getElement()) {
737      ProfileUtilities.markExtensions(ed, false, src);
738    }
739  }
740
741  private void mergeElements(List<ElementDefinition> elements, List<ElementDefinition> allSlices, ElementDefinition slice, ElementDefinition slicer) {
742    // we have
743    //   elements - the list of all the elements
744    //   allSlices which is the content defined for all the slices
745    //   slice -the anchor element for the slice
746
747    int sliceIndex = elements.indexOf(slice);
748    int startOfSlice = sliceIndex + 1;
749    int endOfSlice = findEndOfSlice(elements, slice);
750
751    Set<String> missing = new HashSet<>();
752    // the simple case is that all the stuff in allSlices exists between startOfSlice and endOfSlice
753    boolean allFound = true;
754    for (int i = 0; i < allSlices.size(); i++) {
755      boolean found = false;
756      for (int j = startOfSlice; j <= endOfSlice; j++) {
757        if (elementsMatch(elements.get(j), allSlices.get(i))) {
758          found = true;
759          break;
760        }
761      }
762      if (!found) {
763        missing.add(allSlices.get(i).getPath());
764        allFound = false;
765      }
766    }
767
768    if (allFound) {
769      // then we just merge it in
770      for (int j = startOfSlice; j <= endOfSlice; j++) {
771        for (int i = 0; i < allSlices.size(); i++) {
772          if (elementsMatch(elements.get(j), allSlices.get(i))) {
773            merge(elements.get(j), allSlices.get(i));
774          }
775        }
776      }
777    } else {
778      Set<ElementDefinition> handled = new HashSet<>();
779
780      // merge the simple stuff
781      for (int j = startOfSlice; j <= endOfSlice; j++) {
782        for (int i = 0; i < allSlices.size(); i++) {
783          if (elementsMatch(elements.get(j), allSlices.get(i))) {
784            handled.add(allSlices.get(i));
785            merge(elements.get(j), allSlices.get(i));
786          }
787        }
788      }
789
790      // we have a lot of work to do
791      // the challenge is that the things missing from startOfSlice..endOfSlice have to injected in the correct order 
792      // which means that we need to know the definitions
793      // and is extra tricky because we're sparse. so we just use the stated path
794      for (ElementDefinition ed : allSlices) {
795        if (!handled.contains(ed)) {
796          List<ElementAnalysis> edDef = analysePath(ed);
797          String id = ed.getId().replace(slicer.getId(), slice.getId());
798          int index = determineInsertionPoint(elements, startOfSlice, endOfSlice, id, ed.getPath(), edDef);
799          ElementDefinition edc = ed.copy();
800          edc.setUserData(UserDataNames.SNAPSHOT_PREPROCESS_INJECTED, true);
801          edc.setId(id);
802          elements.add(index, edc);
803          endOfSlice++;
804        }
805      }
806    }   
807
808  }
809
810  private boolean elementsMatch(ElementDefinition ed1, ElementDefinition ed2) {
811    if (!pathsMatch(ed1.getPath(), ed2.getPath())) {
812      return false;
813    } else if (ed1.getSliceName() != null && ed2.getSliceName() != null) {
814      return ed1.getSliceName().equals(ed2.getSliceName());
815    } else  if (ed1.getSliceName() != null || ed2.getSliceName() != null) {
816      return false;
817    } else {
818      return true;
819    }
820  }
821
822  private boolean pathsMatch(String path1, String path2) {
823    if (path1.equals(path2)) {
824      return true;
825    }
826    if (path1.endsWith("[x]")) {
827      path1 = path1.substring(0, path1.length()-3);
828      if (path2.startsWith(path1)) {
829        if (!path2.substring(path1.length()).contains(".")) {
830          return true;
831        }
832      }
833    }
834    if (path2.endsWith("[x]")) {
835      path2 = path2.substring(0, path2.length()-3);
836      if (path1.startsWith(path2)) {
837        if (!path1.substring(path2.length()).contains(".")) {
838          return true;
839        }
840      }
841    }
842    return false;
843  }
844
845  private int determineInsertionPoint(List<ElementDefinition> elements, int startOfSlice, int endOfSlice, String id, String path, List<ElementAnalysis> edDef) {
846    // we work backwards through the id, looking for peers (this is the only way we can manage slicing)
847    String[] p = id.split("\\.");
848    for (int i = p.length-1; i >= 1; i--) {
849      String subId = p[0];
850      for (int j = 1; j <= i; j++) {
851        subId += "."+p[j];
852      }
853      List<ElementDefinition> peers = findPeers(elements, startOfSlice, endOfSlice, subId);
854      if (!peers.isEmpty()) {
855        // Once we find some, we figure out the insertion point - before one of them, or after the last? 
856        for (ElementDefinition ed : peers) {
857          if (comesAfterThis(id, path, edDef, ed)) {
858            return elements.indexOf(ed);
859          }
860        }
861        return elements.indexOf(peers.get(peers.size() -1))+1;
862      }
863    }
864    return endOfSlice+1;
865  }
866
867  private List<ElementDefinition> findPeers(List<ElementDefinition> elements, int startOfSlice, int endOfSlice, String subId) {
868    List<ElementDefinition> peers =  new ArrayList<>();
869    for (int i = startOfSlice; i <= endOfSlice; i++) {
870      ElementDefinition ed = elements.get(i);
871      if (ed.getId().startsWith(subId)) {
872        peers.add(ed);
873      }
874    }
875    return peers;
876  }
877
878  private String summary(List<ElementAnalysis> edDef) {
879    List<String> s = new ArrayList<>();
880    for (ElementAnalysis ed : edDef) {
881      s.add(ed.summary());
882    }
883
884    return CommaSeparatedStringBuilder.join(",", s);
885  }
886
887  private boolean comesAfterThis(String id, String path, List<ElementAnalysis> edDef, ElementDefinition ed) {
888    String[] p1 = id.split("\\.");
889    String[] p2 = ed.getId().split("\\.");
890    for (int i = 0; i < Integer.min(p1.length,  p2.length); i++) {
891      if (!p1[i].equals(p2[i])) {
892        ElementAnalysis sed = edDef.get(i-1);
893        int i1 = indexOfName(sed, p1[i]);
894        int i2 = indexOfName(sed, p2[i]);
895        if (i == Integer.min(p1.length,  p2.length) -1 && i1 == i2) {
896          if (!p1[i].contains(":") && p2[i].contains(":")) {
897            // launched straight into slicing without setting it up, 
898            // and now it's being set up 
899            return true;
900          }
901        }
902        return i1 < i2;
903      } else {
904        // well, we just go on
905      }
906    }
907    return p1.length < p2.length;
908  }
909
910  private int indexOfName(ElementAnalysis sed, String name) {
911    if (name.contains(":")) {
912      name = name.substring(0, name.indexOf(":"));
913    }
914    for (int i = 0; i < sed.getChildren().getList().size(); i++) {
915      if (name.equals(sed.getChildren().getList().get(i).getName())) {
916        return i;
917      }      
918    }
919    return -1;
920  }
921
922  private List<ElementAnalysis> analysePath(ElementDefinition ed) {
923    List<ElementAnalysis> res = new ArrayList<>();
924    for (String pn : ed.getPath().split("\\.")) {
925      analysePathSegment(ed, res, pn);
926    }
927    return res;
928  }
929
930  private void analysePathSegment(ElementDefinition ed, List<ElementAnalysis> res, String pn) {
931    if (res.isEmpty()) {
932      StructureDefinition sd = context.fetchTypeDefinition(pn);
933      if (sd == null) {
934        String message = context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, pn, ed.getId());
935        throw new DefinitionException(message);
936      }
937      res.add(new ElementAnalysis(sd, sd.getSnapshot().getElementFirstRep(), null));
938    } else {
939      ElementAnalysis sed = res.get(res.size()-1);
940      sed.setChildren(utils.getChildMap(sed.getStructure(), sed.getElement(), true, sed.getType()));
941      ElementDefinition t = null;
942      String type = null;
943      for (ElementDefinition child : sed.getChildren().getList()) {
944        if (pn.equals(child.getName())) {
945          t = child;
946          break;
947        }
948        if (child.getName().endsWith("[x]")) {
949          String rn = child.getName().substring(0, child.getName().length()-3);
950          if (pn.startsWith(rn)) {
951            t = child;
952            String tn = pn.substring(rn.length());
953            if (TypesUtilities.isPrimitive(Utilities.uncapitalize(tn))) {
954              type = Utilities.uncapitalize(tn);
955            } else {
956              type = tn;
957            }
958            break;
959          }
960        }
961      }
962      if (t == null) {
963        String message = context.formatMessage(I18nConstants.UNKNOWN_PROPERTY, pn, ed.getPath());
964        throw new DefinitionException("Unknown path "+pn+" in path "+ed.getPath()+": "+message);          
965      } else {
966        res.add(new ElementAnalysis(sed.getChildren().getSource(), t, type));
967      }
968    }
969  }
970
971  private int findEndOfSlice(List<ElementDefinition> elements, ElementDefinition slice) {
972    for (int i = elements.indexOf(slice); i < elements.size(); i++) {
973      ElementDefinition e = elements.get(i);
974      if (!(e.getPath().startsWith(slice.getPath()+".") ||
975          (e.getPath().equals(slice.getPath()) && slice.getSliceName().equals(e.getSliceName())))) {
976        return i-1;
977      }
978    }
979    return elements.size() - 1;
980  }
981
982  private void merge(ElementDefinition focus, ElementDefinition base) {
983    if (base.hasLabel() && !focus.hasLabel()) {
984      focus.setLabelElement(base.getLabelElement());
985    }    
986    if (base.hasCode() && !focus.hasCode()) {
987      focus.getCode().addAll(base.getCode());
988    }
989    if (base.hasShort() && !focus.hasShort()) {
990      focus.setShortElement(base.getShortElement());
991    }    
992    if (base.hasDefinition() && !focus.hasDefinition()) {
993      focus.setDefinitionElement(base.getDefinitionElement());
994    }    
995    if (base.hasComment() && !focus.hasComment()) {
996      focus.setCommentElement(base.getCommentElement());
997    }    
998    if (base.hasRequirements() && !focus.hasRequirements()) {
999      focus.setRequirementsElement(base.getRequirementsElement());
1000    }    
1001    if (base.hasAlias() && !focus.hasAlias()) {
1002      focus.getAlias().addAll(base.getAlias());
1003    }
1004    if (base.hasMin() && !focus.hasMin()) {
1005      focus.setMinElement(base.getMinElement());
1006    }    
1007    if (base.hasMax() && !focus.hasMax()) {
1008      focus.setMaxElement(base.getMaxElement());
1009    }    
1010    if (base.hasType() && !focus.hasType()) {
1011      focus.getType().addAll(base.getType());
1012    }
1013    if (base.hasDefaultValue() && !focus.hasDefaultValue()) {
1014      focus.setDefaultValue(base.getDefaultValue());
1015    }
1016    if (base.hasMeaningWhenMissing() && !focus.hasMeaningWhenMissing()) {
1017      focus.setMeaningWhenMissingElement(base.getMeaningWhenMissingElement());
1018    }    
1019    if (base.hasOrderMeaning() && !focus.hasOrderMeaning()) {
1020      focus.setOrderMeaningElement(base.getOrderMeaningElement());
1021    }    
1022    if (base.hasFixed() && !focus.hasFixed()) {
1023      focus.setFixed(base.getFixed());
1024    }
1025    if (base.hasPattern() && !focus.hasPattern()) {
1026      focus.setPattern(base.getPattern());
1027    }
1028    if (base.hasExample() && !focus.hasExample()) {
1029      focus.getExample().addAll(base.getExample());
1030    }
1031    if (base.hasMinValue() && !focus.hasMinValue()) {
1032      focus.setMinValue(base.getMinValue());
1033    }
1034    if (base.hasMaxValue() && !focus.hasMaxValue()) {
1035      focus.setMaxValue(base.getMaxValue());
1036    }
1037    if (base.hasMaxLength() && !focus.hasMaxLength()) {
1038      focus.setMaxLengthElement(base.getMaxLengthElement());
1039    }    
1040    if (base.hasConstraint() && !focus.hasConstraint()) {
1041      focus.getConstraint().addAll(base.getConstraint());
1042    }
1043    if (base.hasMustHaveValue() && !focus.hasMustHaveValue()) {
1044      focus.setMustHaveValueElement(base.getMustHaveValueElement());
1045    }    
1046    if (base.hasValueAlternatives() && !focus.hasValueAlternatives()) {
1047      focus.getValueAlternatives().addAll(base.getValueAlternatives());
1048    }
1049    if (base.hasMustSupport() && !focus.hasMustSupport()) {
1050      focus.setMustSupportElement(base.getMustSupportElement());
1051    }    
1052    if (base.hasIsModifier() && !focus.hasIsModifier()) {
1053      focus.setIsModifierElement(base.getIsModifierElement());
1054    }    
1055    if (base.hasIsModifierReason() && !focus.hasIsModifierReason()) {
1056      focus.setIsModifierReasonElement(base.getIsModifierReasonElement());
1057    }    
1058    if (base.hasIsSummary() && !focus.hasIsSummary()) {
1059      focus.setIsSummaryElement(base.getIsSummaryElement());
1060    }    
1061    if (base.hasBinding() && !focus.hasBinding()) {
1062      focus.setBinding(base.getBinding());
1063    }
1064  }
1065
1066  private boolean isExtensionSlicing(ElementDefinition ed) {
1067    if (!Utilities.existsInList(ed.getName(), "extension", "modiferExtension")) {
1068      return false;
1069    }
1070    if (ed.getSlicing().getRules() != SlicingRules.OPEN || (!ed.getSlicing().hasOrdered() || ed.getSlicing().getOrdered()) || ed.getSlicing().getDiscriminator().size() != 1) {
1071      return false;
1072    }
1073    ElementDefinitionSlicingDiscriminatorComponent d = ed.getSlicing().getDiscriminatorFirstRep();
1074    return d.getType() == DiscriminatorType.VALUE && "url".equals(d.getPath());
1075  }
1076
1077  private SliceInfo getSlicing(ElementDefinition ed) {
1078    for (int i = slicings.size() - 1; i >= 0; i--) {
1079      SliceInfo si = slicings.get(i);
1080      if (!si.closed) {
1081        if (si.path.length() > ed.getPath().length()) {
1082          si.closed = true;
1083        } else if (ed.getPath().startsWith(si.path)) {
1084          return si;
1085        }
1086      }
1087    }
1088    return null;
1089  }
1090
1091  public List<ElementDefinition> supplementMissingDiffElements(StructureDefinition profile) { 
1092    List<ElementDefinition> list = new ArrayList<>(); 
1093    list.addAll(profile.getDifferential().getElement()); 
1094    if (list.isEmpty()) { 
1095      ElementDefinition root = new ElementDefinition().setPath(profile.getTypeName()); 
1096      root.setId(profile.getTypeName()); 
1097      list.add(root); 
1098    } else { 
1099      if (list.get(0).getPath().contains(".")) { 
1100        ElementDefinition root = new ElementDefinition().setPath(profile.getTypeName()); 
1101        root.setId(profile.getTypeName()); 
1102        list.add(0, root); 
1103      } 
1104    } 
1105    insertMissingSparseElements(list, profile.getTypeName()); 
1106    return list; 
1107  } 
1108
1109  private void insertMissingSparseElements(List<ElementDefinition> list, String typeName) {
1110    if (list.isEmpty() || list.get(0).getPath().contains(".")) {
1111      ElementDefinition ed = new ElementDefinition();
1112      ed.setPath(typeName);
1113      list.add(0, ed);
1114    }
1115    int i = 1; 
1116    while (i < list.size()) { 
1117      String[] pathCurrent = list.get(i).getPath().split("\\."); 
1118      String[] pathLast = list.get(i-1).getPath().split("\\."); 
1119      int firstDiff = 0; // the first entry must be a match 
1120      while (firstDiff < pathCurrent.length && firstDiff < pathLast.length && pathCurrent[firstDiff].equals(pathLast[firstDiff])) { 
1121        firstDiff++; 
1122      } 
1123      if (!(isSibling(pathCurrent, pathLast, firstDiff) || isChild(pathCurrent, pathLast, firstDiff))) { 
1124        // now work backwards down to lastMatch inserting missing path nodes 
1125        ElementDefinition parent = findParent(list, i, list.get(i).getPath()); 
1126        int parentDepth = Utilities.charCount(parent.getPath(), '.')+1; 
1127        int childDepth =  Utilities.charCount(list.get(i).getPath(), '.')+1; 
1128        if (childDepth > parentDepth + 1) { 
1129          String basePath = parent.getPath(); 
1130          String baseId = parent.getId(); 
1131          for (int index = parentDepth; index >= firstDiff; index--) { 
1132            String mtail = makeTail(pathCurrent, parentDepth, index); 
1133            ElementDefinition root = new ElementDefinition().setPath(basePath+"."+mtail); 
1134            root.setId(baseId+"."+mtail); 
1135            list.add(i, root); 
1136          } 
1137        } 
1138      }  
1139      i++; 
1140    } 
1141  } 
1142
1143
1144  private ElementDefinition findParent(List<ElementDefinition> list, int i, String path) { 
1145    while (i > 0 && !path.startsWith(list.get(i).getPath()+".")) { 
1146      i--; 
1147    } 
1148    return list.get(i); 
1149  } 
1150
1151  private boolean isSibling(String[] pathCurrent, String[] pathLast, int firstDiff) { 
1152    return pathCurrent.length == pathLast.length && firstDiff == pathCurrent.length-1; 
1153  } 
1154
1155
1156  private boolean isChild(String[] pathCurrent, String[] pathLast, int firstDiff) { 
1157    return pathCurrent.length == pathLast.length+1 && firstDiff == pathLast.length; 
1158  } 
1159
1160  private String makeTail(String[] pathCurrent, int start, int index) { 
1161    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("."); 
1162    for (int i = start; i <= index; i++) { 
1163      b.append(pathCurrent[i]); 
1164    } 
1165    return b.toString(); 
1166  }
1167
1168  public StructureDefinition trimSnapshot(StructureDefinition profile) {
1169    // first pass: mark elements from the diff
1170    Stack<ElementDefinition> stack = new Stack<ElementDefinition>();
1171    ElementDefinition edRoot = profile.getSnapshot().getElementFirstRep();
1172    if (!edRoot.hasUserData(UserDataNames.SNAPSHOT_FROM_DIFF)) {
1173      stack.push(edRoot);
1174      for (int i = 1; i < profile.getSnapshot().getElement().size(); i++) {
1175        ElementDefinition ed = profile.getSnapshot().getElement().get(i);
1176        String cpath = ed.getPath();
1177        boolean fromDiff = ed.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_DIFF);
1178
1179        String spath = stack.peek().getPath();
1180        while (!(cpath.equals(spath) || cpath.startsWith(spath+"."))) {
1181          stack.pop();
1182          spath = stack.peek().getPath();
1183        }
1184        stack.push(ed);
1185        if (fromDiff) {
1186          for (int j = stack.size() - 1; j >= 0; j--) {
1187            if (stack.get(j).hasUserData(UserDataNames.SNAPSHOT_FROM_DIFF)) {
1188              break;
1189            } else {
1190              stack.get(j).setUserData(UserDataNames.SNAPSHOT_FROM_DIFF, true);
1191            }
1192          }
1193        }
1194      }
1195    }
1196    edRoot.setUserData(UserDataNames.SNAPSHOT_FROM_DIFF, true);
1197
1198    StructureDefinition res = new StructureDefinition();
1199    res.setUrl(profile.getUrl());
1200    res.setVersion(profile.getVersion());
1201    res.setName(profile.getName());
1202    res.setBaseDefinition(profile.getBaseDefinition());
1203    for (ElementDefinition ed : profile.getSnapshot().getElement()) {
1204      if (ed.hasUserData(UserDataNames.SNAPSHOT_FROM_DIFF)) {
1205        res.getSnapshot().getElement().add(ed);
1206      }
1207    }
1208    res.setWebPath(profile.getWebPath());
1209    return res;
1210  } 
1211
1212}