001package org.hl7.fhir.r5.comparison;
002
003import java.io.IOException;
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Date;
007import java.util.List;
008
009import org.hl7.fhir.exceptions.DefinitionException;
010import org.hl7.fhir.exceptions.FHIRException;
011import org.hl7.fhir.exceptions.FHIRFormatError;
012import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison;
013import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
014import org.hl7.fhir.r5.context.IWorkerContext;
015import org.hl7.fhir.r5.formats.IParser;
016import org.hl7.fhir.r5.formats.JsonParser;
017import org.hl7.fhir.r5.model.Base;
018import org.hl7.fhir.r5.model.Coding;
019import org.hl7.fhir.r5.model.DataType;
020import org.hl7.fhir.r5.model.ElementDefinition;
021import org.hl7.fhir.r5.model.ElementDefinition.AggregationMode;
022import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
023import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
024import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent;
025import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
026import org.hl7.fhir.r5.model.Enumeration;
027import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
028import org.hl7.fhir.r5.model.IntegerType;
029import org.hl7.fhir.r5.model.PrimitiveType;
030import org.hl7.fhir.r5.model.Resource;
031import org.hl7.fhir.r5.model.StringType;
032import org.hl7.fhir.r5.model.StructureDefinition;
033import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
034import org.hl7.fhir.r5.model.ValueSet;
035import org.hl7.fhir.r5.renderers.StructureDefinitionRenderer;
036import org.hl7.fhir.r5.renderers.utils.RenderingContext;
037import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
038import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode;
039import org.hl7.fhir.r5.utils.DefinitionNavigator;
040import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
041import org.hl7.fhir.utilities.MarkDownProcessor;
042import org.hl7.fhir.utilities.Utilities;
043import org.hl7.fhir.utilities.validation.ValidationMessage;
044import org.hl7.fhir.utilities.validation.ValidationOptions;
045import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
046import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
047import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
048import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
049import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
050import org.hl7.fhir.utilities.xhtml.XhtmlNode;
051
052public class ProfileComparer extends CanonicalResourceComparer {
053
054  public class ProfileComparison extends CanonicalResourceComparison<StructureDefinition> {
055
056    private StructuralMatch<ElementDefinitionNode> combined;                                             
057
058    public ProfileComparison(StructureDefinition left, StructureDefinition right) {
059      super(left, right);
060      combined = new StructuralMatch<ElementDefinitionNode>(); // base
061    }
062
063    public StructuralMatch<ElementDefinitionNode> getCombined() {
064      return combined;
065    }
066
067    @Override
068    protected String abbreviation() {
069      return "sd";
070    }
071
072    @Override
073    protected String summary() {
074      return "Profile: "+left.present()+" vs "+right.present();
075    }
076
077    @Override
078    protected String fhirType() {
079      return "StructureDefinition";
080    }
081    @Override
082    protected void countMessages(MessageCounts cnts) {
083      super.countMessages(cnts);
084      combined.countMessages(cnts);
085    }
086
087  }
088
089
090  private class ElementDefinitionNode {
091    private ElementDefinition def;
092    private StructureDefinition src;
093    private ElementDefinitionNode(StructureDefinition src, ElementDefinition def) {
094      super();
095      this.src = src;
096      this.def = def;
097    }
098    public ElementDefinition getDef() {
099      return def;
100    }
101    public StructureDefinition getSrc() {
102      return src;
103    }
104  }
105
106  private ProfileUtilities utilsLeft;
107  private ProfileUtilities utilsRight;
108
109  public ProfileComparer(ComparisonSession session, ProfileUtilities utilsLeft, ProfileUtilities utilsRight) {
110    super(session);
111    this.utilsLeft = utilsLeft;
112    this.utilsRight = utilsRight;
113  }
114
115  @Override
116  protected String fhirType() {
117    return "StructureDefinition";
118  }
119
120  public ProfileComparison compare(StructureDefinition left, StructureDefinition right) throws DefinitionException, FHIRFormatError, IOException {
121    check(left, "left");
122    check(right, "right");
123
124    ProfileComparison res = new ProfileComparison(left, right);
125    session.identify(res);
126    StructureDefinition sd = new StructureDefinition();
127    res.setUnion(sd);
128    session.identify(sd);
129    sd.setName("Union"+left.getName()+"And"+right.getName());
130    sd.setTitle("Union of "+left.getTitle()+" And "+right.getTitle());
131    sd.setStatus(left.getStatus());
132    sd.setDate(new Date());
133
134    StructureDefinition sd1 = new StructureDefinition();
135    res.setIntersection(sd1);
136    session.identify(sd1);
137    sd1.setName("Intersection"+left.getName()+"And"+right.getName());
138    sd1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle());
139    sd1.setStatus(left.getStatus());
140    sd1.setDate(new Date());
141
142    compareMetadata(left, right, res.getMetadata(), res);
143    comparePrimitives("fhirVersion", left.getFhirVersionElement(), right.getFhirVersionElement(), res.getMetadata(), IssueSeverity.WARNING, res);
144    comparePrimitives("kind", left.getKindElement(), right.getKindElement(), res.getMetadata(), IssueSeverity.WARNING, res);
145    comparePrimitives("abstract", left.getAbstractElement(), right.getAbstractElement(), res.getMetadata(), IssueSeverity.WARNING, res);
146    comparePrimitives("type", left.getTypeElement(), right.getTypeElement(), res.getMetadata(), IssueSeverity.ERROR, res);
147    comparePrimitives("baseDefinition", left.getBaseDefinitionElement(), right.getBaseDefinitionElement(), res.getMetadata(), IssueSeverity.ERROR, res);
148
149    if (left.getType().equals(right.getType())) {
150      DefinitionNavigator ln = new DefinitionNavigator(session.getContextLeft(), left);
151      DefinitionNavigator rn = new DefinitionNavigator(session.getContextRight(), right);
152      StructuralMatch<ElementDefinitionNode> sm = new StructuralMatch<ElementDefinitionNode>(new ElementDefinitionNode(left, ln.current()), new ElementDefinitionNode(right, rn.current()));
153      compareElements(res, sm, ln.path(), null, ln, rn);
154      res.combined = sm;
155    }
156    return res;
157  }
158
159  private void check(StructureDefinition sd, String name) {
160    if (sd == null)
161      throw new DefinitionException("No StructureDefinition provided ("+name+": "+sd.getName()+")");
162//    if (sd.getType().equals("Extension")) {
163//      throw new DefinitionException("StructureDefinition is for an extension - use ExtensionComparer instead ("+name+": "+sd.getName()+")");
164//    }
165    if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
166      throw new DefinitionException("StructureDefinition is not for an profile - can't be compared ("+name+": "+sd.getName()+")");
167    }
168    if (sd.getSnapshot().getElement().isEmpty())
169      throw new DefinitionException("StructureDefinition snapshot is empty ("+name+": "+sd.getName()+")");
170  }
171
172  private void compareElements(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res,  String path, String sliceName, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, FHIRFormatError, IOException {
173    assert(path != null);  
174    assert(left != null);
175    assert(right != null);
176    assert(left.path().equals(right.path()));
177
178    if (session.isDebug()) {
179      System.out.println("Compare elements at "+path);
180    }
181    
182    // not allowed to be different:   
183//    ruleEqual(comp, res, left.current().getDefaultValue(), right.current().getDefaultValue(), "defaultValue", path);
184//    ruleEqual(comp, res, left.current().getMeaningWhenMissingElement(), right.current().getMeaningWhenMissingElement(), "meaningWhenMissing", path);
185//    ruleEqual(comp, res, left.current().getIsModifierElement(), right.current().getIsModifierElement(), "isModifier", path); - this check belongs in the core
186//    ruleEqual(comp, res, left.current().getIsSummaryElement(), right.current().getIsSummaryElement(), "isSummary", path); - so does this
187
188    // we ignore slicing right now - we're going to clone the root one anyway, and then think about clones 
189    // simple stuff
190    ElementDefinition subset = new ElementDefinition();
191    subset.setPath(left.path());
192    if (sliceName != null)
193      subset.setSliceName(sliceName);
194
195
196    subset.getRepresentation().addAll(left.current().getRepresentation()); // can't be bothered even testing this one
197    subset.setDefaultValue(left.current().getDefaultValue());
198    subset.setMeaningWhenMissing(left.current().getMeaningWhenMissing());
199    subset.setIsModifier(left.current().getIsModifier());
200    subset.setIsSummary(left.current().getIsSummary());
201
202    // descriptive properties from ElementDefinition - merge them:
203    subset.setLabel(mergeText(comp, res, path, "label", left.current().getLabel(), right.current().getLabel(), false));
204    subset.setShort(mergeText(comp, res, path, "short", left.current().getShort(), right.current().getShort(), false));
205    subset.setDefinition(mergeText(comp, res, path, "definition", left.current().getDefinition(), right.current().getDefinition(), false));
206    subset.setComment(mergeText(comp, res, path, "comments", left.current().getComment(), right.current().getComment(), false));
207    subset.setRequirements(mergeText(comp, res, path, "requirements", left.current().getRequirements(), right.current().getRequirements(), false));
208    subset.getCode().addAll(mergeCodings(left.current().getCode(), right.current().getCode()));
209    subset.getAlias().addAll(mergeStrings(left.current().getAlias(), right.current().getAlias()));
210    subset.getMapping().addAll(mergeMappings(left.current().getMapping(), right.current().getMapping()));
211    // left will win for example
212    subset.setExample(left.current().hasExample() ? left.current().getExample() : right.current().getExample());
213
214    if (left.current().getMustSupport() != right.current().getMustSupport()) {
215      vm(IssueSeverity.WARNING, "Elements differ in definition for mustSupport: '"+left.current().getMustSupport()+"' vs '"+right.current().getMustSupport()+"'", path, comp.getMessages(), res.getMessages());
216
217    }
218    subset.setMustSupport(left.current().getMustSupport() || right.current().getMustSupport());
219    ElementDefinition superset = subset.copy();
220
221
222    // compare and intersect
223    int leftMin = left.current().getMin();
224    int rightMin = right.current().getMin();
225    int leftMax = "*".equals(left.current().getMax()) ? Integer.MAX_VALUE : Integer.parseInt(left.current().getMax());
226    int rightMax = "*".equals(right.current().getMax()) ? Integer.MAX_VALUE : Integer.parseInt(right.current().getMax());
227    
228    checkMinMax(comp, res, path, leftMin, rightMin, leftMax, rightMax);
229    superset.setMin(unionMin(leftMin, rightMin));
230    superset.setMax(unionMax(leftMax, rightMax, left.current().getMax(), right.current().getMax()));
231    subset.setMin(intersectMin(leftMin, rightMin));
232    subset.setMax(intersectMax(leftMax, rightMax, left.current().getMax(), right.current().getMax()));
233
234    superset.getType().addAll(unionTypes(comp, res, path, left.current().getType(), right.current().getType(), left.getStructure(), right.getStructure()));
235    subset.getType().addAll(intersectTypes(comp, res, subset, path, left.current().getType(), right.current().getType()));
236    rule(comp, res, !subset.getType().isEmpty() || (!left.current().hasType() && !right.current().hasType()), path, "Type Mismatch: "+typeCode(left)+" vs "+typeCode(right));
237    //    <fixed[x]><!-- ?? 0..1 * Value must be exactly this --></fixed[x]>
238    //    <pattern[x]><!-- ?? 0..1 * Value must have at least these property values --></pattern[x]>
239    superset.setMaxLengthElement(unionMaxLength(left.current().getMaxLength(), right.current().getMaxLength()));
240    subset.setMaxLengthElement(intersectMaxLength(left.current().getMaxLength(), right.current().getMaxLength()));
241    if (left.current().hasBinding() || right.current().hasBinding()) {
242      compareBindings(comp, res, subset, superset, path, left.current(), right.current(), left.getStructure(), right.getStructure());
243    }
244    // note these are backwards
245    superset.getConstraint().addAll(intersectConstraints(path, left.current().getConstraint(), right.current().getConstraint()));
246    subset.getConstraint().addAll(unionConstraints(comp, res, path, left.current().getConstraint(), right.current().getConstraint()));
247    comp.getIntersection().getSnapshot().getElement().add(subset);
248    comp.getUnion().getSnapshot().getElement().add(superset);
249
250    // add the children
251    compareChildren(comp, res, path, left, right);
252//
253//    // now process the slices
254//    if (left.current().hasSlicing() || right.current().hasSlicing()) {
255//      assert sliceName == null;
256//      if (isExtension(left.path()))
257//        return compareExtensions(outcome, path, superset, subset, left, right);
258//      //      return true;
259//      else {
260//        ElementDefinitionSlicingComponent slicingL = left.current().getSlicing();
261//        ElementDefinitionSlicingComponent slicingR = right.current().getSlicing();
262//        // well, this is tricky. If one is sliced, and the other is not, then in general, the union just ignores the slices, and the intersection is the slices.
263//        if (left.current().hasSlicing() && !right.current().hasSlicing()) { 
264//          // the super set is done. Any restrictions in the slices are irrelevant to what the super set says, except that we're going sum up the value sets if we can (for documentation purposes) (todo)
265//          // the minimum set is the slicing specified in the slicer
266//          subset.setSlicing(slicingL);
267//          // stick everything from the right to do with the slices to the subset 
268//          copySlices(outcome.subset.getSnapshot().getElement(), left.getStructure().getSnapshot().getElement(), left.slices());
269//        } else if (!left.current().hasSlicing() && right.current().hasSlicing()) { 
270//          // the super set is done. Any restrictions in the slices are irrelevant to what the super set says, except that we're going sum up the value sets if we can (for documentation purposes) (todo)
271//          // the minimum set is the slicing specified in the slicer
272//          subset.setSlicing(slicingR);
273//          // stick everything from the right to do with the slices to the subset 
274//          copySlices(outcome.subset.getSnapshot().getElement(), right.getStructure().getSnapshot().getElement(), right.slices());
275//        } else if (isTypeSlicing(slicingL) || isTypeSlicing(slicingR)) {
276//          superset.getSlicing().setRules(SlicingRules.OPEN).setOrdered(false).addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
277//          subset.getSlicing().setRules(slicingL.getRules() == SlicingRules.CLOSED || slicingR.getRules() == SlicingRules.CLOSED ? SlicingRules.OPEN : SlicingRules.CLOSED).setOrdered(false).addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
278//
279//          // the superset is the union of the types 
280//          // the subset is the intersection of them 
281//          List<DefinitionNavigator> handled = new ArrayList<>();
282//          for (DefinitionNavigator t : left.slices()) {
283//            DefinitionNavigator r = findMatchingSlice(right.slices(), t);
284//            if (r == null) {
285//              copySlice(outcome.superset.getSnapshot().getElement(), left.getStructure().getSnapshot().getElement(), t);              
286//            } else {
287//              handled.add(r);
288//              ret = compareElements(outcome, path+":"+t.current().getSliceName(), t, r, t.current().getSliceName()) && ret;
289//            }
290//          }
291//          for (DefinitionNavigator t : right.slices()) {
292//            if (!handled.contains(t)) {
293//              copySlice(outcome.superset.getSnapshot().getElement(), right.getStructure().getSnapshot().getElement(), t);
294//            }
295//          }
296//        } else if (slicingMatches(slicingL, slicingR)) {
297//          // if it's the same, we can try matching the slices - though we might have to give up without getting matches correct
298//          // there amy be implied consistency we can't reason about 
299//          throw new DefinitionException("Slicing matches but is not handled yet at "+left.current().getId()+": ("+ProfileUtilities.summarizeSlicing(slicingL)+")");
300//        } else  {
301//          // if the slicing is different, we can't compare them - or can we?
302//          throw new DefinitionException("Slicing doesn't match at "+left.current().getId()+": ("+ProfileUtilities.summarizeSlicing(slicingL)+" / "+ProfileUtilities.summarizeSlicing(slicingR)+")");
303//        }
304//      }
305//      // todo: name 
306//    }
307//    return ret;
308//
309//    // TODO Auto-generated method stub
310//    return null;
311  }
312
313
314  private void compareChildren(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, IOException, FHIRFormatError {
315    List<DefinitionNavigator> lc = left.children();
316    List<DefinitionNavigator> rc = right.children();
317    // it's possible that one of these profiles walks into a data type and the other doesn't
318    // if it does, we have to load the children for that data into the profile that doesn't 
319    // walk into it
320    if (lc.isEmpty() && !rc.isEmpty() && right.current().getType().size() == 1 && left.hasTypeChildren(right.current().getType().get(0), left.getStructure()))
321      lc = left.childrenFromType(right.current().getType().get(0), right.getStructure());
322    if (rc.isEmpty() && !lc.isEmpty() && left.current().getType().size() == 1 && right.hasTypeChildren(left.current().getType().get(0), right.getStructure()))
323      rc = right.childrenFromType(left.current().getType().get(0), left.getStructure());
324    
325    List<DefinitionNavigator> matchR = new ArrayList<>();
326    for (DefinitionNavigator l : lc) {
327      DefinitionNavigator r = findInList(rc, l);
328      if (r == null) {
329        comp.getUnion().getSnapshot().getElement().add(l.current().copy());
330        res.getChildren().add(new StructuralMatch<ElementDefinitionNode>(new ElementDefinitionNode(l.getStructure(), l.current()), vmI(IssueSeverity.INFORMATION, "Removed this element", path)));
331      } else {
332        matchR.add(r);
333        StructuralMatch<ElementDefinitionNode> sm = new StructuralMatch<ElementDefinitionNode>(new ElementDefinitionNode(l.getStructure(), l.current()), new ElementDefinitionNode(r.getStructure(), r.current()));
334        res.getChildren().add(sm);
335        compareElements(comp, sm, l.path(), null, l, r);
336      }
337    }
338    for (DefinitionNavigator r : rc) {
339      if (!matchR.contains(r)) {
340        comp.getUnion().getSnapshot().getElement().add(r.current().copy());
341        res.getChildren().add(new StructuralMatch<ElementDefinitionNode>(vmI(IssueSeverity.INFORMATION, "Added this element", path), new ElementDefinitionNode(r.getStructure(), r.current())));        
342      }
343    }
344  }
345
346  private DefinitionNavigator findInList(List<DefinitionNavigator> rc, DefinitionNavigator l) {
347    for (DefinitionNavigator t : rc) {
348      if (tail(t.current().getPath()).equals(tail(l.current().getPath()))) {
349        return t;
350      }
351    }
352    return null;
353  }
354
355  private void ruleEqual(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, DataType vLeft, DataType vRight, String name, String path) throws IOException {
356    if (vLeft == null && vRight == null) {
357      // nothing
358    } else if (vLeft == null) {
359      vm(IssueSeverity.ERROR, "Added "+name, path, comp.getMessages(), res.getMessages());
360    } else if (vRight == null) {
361      vm(IssueSeverity.ERROR, "Removed "+name, path, comp.getMessages(), res.getMessages());
362    } else if (!Base.compareDeep(vLeft, vRight, false)) {
363      vm(IssueSeverity.ERROR, name+" must be the same ("+toString(vLeft, true)+"/"+toString(vRight, false)+")", path, comp.getMessages(), res.getMessages());
364    }
365  }
366
367  private String toString(DataType val, boolean left) throws IOException {
368    if (val instanceof PrimitiveType) 
369      return "'" + ((PrimitiveType) val).getValueAsString()+"'";
370    
371    IParser jp = new JsonParser();
372    return jp.composeString(val, "value");
373  }
374  
375  private String stripLinks(String s) {
376    while (s.contains("](")) {
377      int i = s.indexOf("](");
378      int j = s.substring(i).indexOf(")");
379      if (j == -1)
380        return s;
381      else
382        s = s.substring(0, i+1)+s.substring(i+j+1);
383    }
384    return s;
385  }
386  
387  private boolean rule(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, boolean test, String path, String message) {
388    if (!test)  {
389      vm(IssueSeverity.ERROR, message, path, comp.getMessages(), res.getMessages());
390    }
391    return test;
392  }
393
394  private String mergeText(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, String name, String left, String right, boolean isError) {
395    if (left == null && right == null)
396      return null;
397    if (left == null)
398      return right;
399    if (right == null)
400      return left;
401    left = stripLinks(left);
402    right = stripLinks(right);
403    if (left.equalsIgnoreCase(right))
404      return left;
405    if (path != null) {
406      vm(isError ? IssueSeverity.ERROR : IssueSeverity.WARNING, "Elements differ in "+name+": '"+left+"' vs '"+right+"'", path, comp.getMessages(), res.getMessages());
407    }
408    return "left: "+left+"; right: "+right;
409  }
410
411  private List<Coding> mergeCodings(List<Coding> left, List<Coding> right) {
412    List<Coding> result = new ArrayList<Coding>();
413    result.addAll(left);
414    for (Coding c : right) {
415      boolean found = false;
416      for (Coding ct : left)
417        if (Utilities.equals(c.getSystem(), ct.getSystem()) && Utilities.equals(c.getCode(), ct.getCode()))
418          found = true;
419      if (!found)
420        result.add(c);
421    }
422    return result;
423  }
424
425  private List<StringType> mergeStrings(List<StringType> left, List<StringType> right) {
426    List<StringType> result = new ArrayList<StringType>();
427    result.addAll(left);
428    for (StringType c : right) {
429      boolean found = false;
430      for (StringType ct : left)
431        if (Utilities.equals(c.getValue(), ct.getValue()))
432          found = true;
433      if (!found)
434        result.add(c);
435    }
436    return result;
437  }
438
439  private List<ElementDefinitionMappingComponent> mergeMappings(List<ElementDefinitionMappingComponent> left, List<ElementDefinitionMappingComponent> right) {
440    List<ElementDefinitionMappingComponent> result = new ArrayList<ElementDefinitionMappingComponent>();
441    result.addAll(left);
442    for (ElementDefinitionMappingComponent c : right) {
443      boolean found = false;
444      for (ElementDefinitionMappingComponent ct : left)
445        if (Utilities.equals(c.getIdentity(), ct.getIdentity()) && Utilities.equals(c.getLanguage(), ct.getLanguage()) && Utilities.equals(c.getMap(), ct.getMap()))
446          found = true;
447      if (!found)
448        result.add(c);
449    }
450    return result;
451  }
452
453  private int intersectMin(int left, int right) {
454    if (left > right)
455      return left;
456    else
457      return right;
458  }
459
460  private void checkMinMax(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, int leftMin, int rightMin, int leftMax, int rightMax) {
461    if (leftMin != rightMin) {
462      if (leftMin == 0) {
463        vm(IssueSeverity.INFORMATION, "Element minimum cardinalities differ:  '"+leftMin+"' vs '"+rightMin+"'", path, comp.getMessages(), res.getMessages());
464      } else if (rightMin == 0) { 
465        vm(IssueSeverity.INFORMATION, "Element minimum cardinalities differ:  '"+leftMin+"' vs '"+rightMin+"'", path, comp.getMessages(), res.getMessages());
466      } else {
467        vm(IssueSeverity.INFORMATION, "Element minimum cardinalities differ:  '"+leftMin+"' vs '"+rightMin+"'", path, comp.getMessages(), res.getMessages());
468      }
469    }    
470    if (leftMax != rightMax) {
471      if (leftMax == Integer.MAX_VALUE) {
472        vm(IssueSeverity.INFORMATION, "Element maximum cardinalities differ:  '"+leftMax+"' vs '"+rightMax+"'", path, comp.getMessages(), res.getMessages());
473      } else if (rightMax == Integer.MAX_VALUE) { 
474        vm(IssueSeverity.INFORMATION, "Element maximum cardinalities differ:  '"+leftMax+"' vs '"+rightMax+"'", path, comp.getMessages(), res.getMessages());
475      } else {
476        vm(IssueSeverity.INFORMATION, "Element maximum cardinalities differ:  '"+leftMax+"' vs '"+rightMax+"'", path, comp.getMessages(), res.getMessages());
477      }
478    }    
479//    rule(comp, res, subset.getMax().equals("*") || Integer.parseInt(subset.getMax()) >= subset.getMin(), path, "Cardinality Mismatch: "+card(left)+"/"+card(right));
480
481    // cross comparison - if max > min in either direction, there can be no instances that are valid against both
482    if (leftMax < rightMin) {
483      vm(IssueSeverity.ERROR, "Element minimum cardinalities conflict:  '"+leftMin+".."+leftMax+"' vs '"+rightMin+".."+rightMax+"': No instances can be valid against both profiles", path, comp.getMessages(), res.getMessages());      
484    }
485    if (rightMax < leftMin) {
486      vm(IssueSeverity.ERROR, "Element minimum cardinalities conflict:  '"+leftMin+".."+leftMax+"' vs '"+rightMin+".."+rightMax+"': No instances can be valid against both profiles", path, comp.getMessages(), res.getMessages());            
487    }
488  }
489  
490  private int unionMin(int left, int right) {
491    if (left > right)
492      return right;
493    else
494      return left;
495  }
496
497  private String intersectMax(int l, int r, String left, String right) {
498    if (l < r)
499      return left;
500    else
501      return right;
502  }
503
504  private String unionMax(int l, int r, String left, String right) {
505    if (l < r)
506      return right;
507    else
508      return left;
509  }
510
511  private IntegerType intersectMaxLength(int left, int right) {
512    if (left == 0) 
513      left = Integer.MAX_VALUE;
514    if (right == 0) 
515      right = Integer.MAX_VALUE;
516    if (left < right)
517      return left == Integer.MAX_VALUE ? null : new IntegerType(left);
518    else
519      return right == Integer.MAX_VALUE ? null : new IntegerType(right);
520  }
521
522  private IntegerType unionMaxLength(int left, int right) {
523    if (left == 0) 
524      left = Integer.MAX_VALUE;
525    if (right == 0) 
526      right = Integer.MAX_VALUE;
527    if (left < right)
528      return right == Integer.MAX_VALUE ? null : new IntegerType(right);
529    else
530      return left == Integer.MAX_VALUE ? null : new IntegerType(left);
531  }
532
533  private String card(DefinitionNavigator defn) {
534    return Integer.toString(defn.current().getMin())+".."+defn.current().getMax();
535  }
536
537  private Collection<? extends TypeRefComponent> unionTypes(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, List<TypeRefComponent> left, List<TypeRefComponent> right, Resource leftSrc, Resource rightSrc) throws DefinitionException, IOException, FHIRFormatError {
538    List<TypeRefComponent> result = new ArrayList<TypeRefComponent>();
539    for (TypeRefComponent l : left) 
540      checkAddTypeUnion(comp, res, path, result, l, session.getContextLeft(), leftSrc);
541    for (TypeRefComponent r : right) 
542      checkAddTypeUnion(comp, res, path, result, r, session.getContextRight(), rightSrc);
543    return result;
544  }    
545
546  private void checkAddTypeUnion(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, List<TypeRefComponent> results, TypeRefComponent nw, IWorkerContext ctxt, Resource nwSource) throws DefinitionException, IOException, FHIRFormatError {
547    boolean pfound = false;
548    boolean tfound = false;
549    nw = nw.copy();
550    for (TypeRefComponent ex : results) {
551      if (Utilities.equals(ex.getWorkingCode(), nw.getWorkingCode())) {
552        for (Enumeration<AggregationMode> a : nw.getAggregation()) {
553          if (!ex.hasAggregation(a.getValue())) {
554            ex.addAggregation(a.getValue());
555          }
556        }
557        if (!ex.hasProfile() && !nw.hasProfile())
558          pfound = true;
559        else if (!ex.hasProfile()) {
560          pfound = true; 
561        } else if (!nw.hasProfile()) {
562          pfound = true;
563          ex.setProfile(null);
564        } else {
565          // both have profiles. Is one derived from the other? 
566          StructureDefinition sdex = ((IWorkerContext) ex.getUserData("ctxt")).fetchResource(StructureDefinition.class, ex.getProfile().get(0).getValue(), nwSource);
567          StructureDefinition sdnw = ctxt.fetchResource(StructureDefinition.class, nw.getProfile().get(0).getValue(), nwSource);
568          if (sdex != null && sdnw != null) {
569            if (sdex.getUrl().equals(sdnw.getUrl())) {
570              pfound = true;
571            } else if (derivesFrom(sdex, sdnw, ((IWorkerContext) ex.getUserData("ctxt")))) {
572              ex.setProfile(nw.getProfile());
573              pfound = true;
574            } else if (derivesFrom(sdnw, sdex, ctxt)) {
575              pfound = true;
576            } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) {
577              ProfileComparison compP = (ProfileComparison) session.compare(sdex, sdnw);
578              if (compP != null && compP.getUnion() != null) { // might be null if circular
579                pfound = true;
580                ex.addProfile("#"+compP.getId());
581              }
582            }
583          }
584        }        
585        if (!ex.hasTargetProfile() && !nw.hasTargetProfile())
586          tfound = true;
587        else if (!ex.hasTargetProfile()) {
588          tfound = true; 
589        } else if (!nw.hasTargetProfile()) {
590          tfound = true;
591          ex.setTargetProfile(null);
592        } else {
593          // both have profiles. Is one derived from the other? 
594          StructureDefinition sdex = ((IWorkerContext) ex.getUserData("ctxt")).fetchResource(StructureDefinition.class, ex.getTargetProfile().get(0).getValue(), nwSource);
595          StructureDefinition sdnw = ctxt.fetchResource(StructureDefinition.class, nw.getTargetProfile().get(0).getValue(), nwSource);
596          if (sdex != null && sdnw != null) {
597            if (matches(sdex, sdnw)) {
598              tfound = true;
599            } else if (derivesFrom(sdex, sdnw, ((IWorkerContext) ex.getUserData("ctxt")))) {
600              ex.setTargetProfile(nw.getTargetProfile());
601              tfound = true;
602            } else if (derivesFrom(sdnw, sdex, ctxt)) {
603              tfound = true;
604            } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) {
605              ProfileComparison compP = (ProfileComparison) session.compare(sdex, sdnw);
606              if (compP.getUnion() != null) {
607                tfound = true;
608                ex.addTargetProfile("#"+compP.getId());
609              }
610            }
611          }
612        }        
613      }
614    }
615    if (!tfound || !pfound) {
616      nw.setUserData("ctxt", ctxt);
617      results.add(nw);      
618    }
619  }
620
621  private boolean matches(StructureDefinition s1, StructureDefinition s2) {
622    if (!s1.getUrl().equals(s2.getUrl())) {
623      return false;
624    }
625    if (s1.getDerivation() == TypeDerivationRule.SPECIALIZATION && s2.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
626      return true; // arbitrary; we're just not interested in pursuing cross version differences
627    }
628    if (s1.hasVersion()) {
629      return  s1.getVersion().equals(s2.getVersion());
630    } else {
631      return !s2.hasVersion();
632    }
633  }
634
635  private boolean derivesFrom(StructureDefinition left, StructureDefinition right, IWorkerContext ctxt) {
636    StructureDefinition sd = left;
637    while (sd != null) {
638      if (right.getUrl().equals(sd.getBaseDefinition())) {
639        return true;
640      }
641      sd = sd.hasBaseDefinition() ? ctxt.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd) : null;
642    }
643    return false;
644  }
645
646  private Collection<? extends TypeRefComponent> intersectTypes(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, ElementDefinition ed, String path, List<TypeRefComponent> left, List<TypeRefComponent> right) throws DefinitionException, IOException, FHIRFormatError {
647    List<TypeRefComponent> result = new ArrayList<TypeRefComponent>();
648    for (TypeRefComponent l : left) {
649      boolean pfound = false;
650      boolean tfound = false;
651      TypeRefComponent c = l.copy();
652      for (TypeRefComponent r : right) {
653        if (!l.hasProfile() && !r.hasProfile()) {
654          pfound = true;    
655        } else if (!r.hasProfile()) {
656          pfound = true; 
657        } else if (!l.hasProfile()) {
658          pfound = true;
659          c.setProfile(r.getProfile());
660        } else {
661          StructureDefinition sdl = resolveProfile(comp, res, path, l.getProfile().get(0).getValue(), comp.getLeft().getName(), session.getContextLeft(), comp.getLeft());
662          StructureDefinition sdr = resolveProfile(comp, res, path, r.getProfile().get(0).getValue(), comp.getRight().getName(), session.getContextRight(), comp.getRight());
663          if (sdl != null && sdr != null) {
664            if (sdl == sdr) {
665              pfound = true;
666            } else if (derivesFrom(sdl, sdr, session.getContextLeft())) {
667              pfound = true;
668            } else if (derivesFrom(sdr, sdl, session.getContextRight())) {
669              c.setProfile(r.getProfile());
670              pfound = true;
671            } else if (sdl.getType().equals(sdr.getType())) {
672              ProfileComparison compP = (ProfileComparison) session.compare(sdl, sdr);
673              if (compP != null && compP.getIntersection() != null) {
674                pfound = true;
675                c.addProfile("#"+compP.getId());
676              }
677            }
678          }
679        }
680        if (!l.hasTargetProfile() && !r.hasTargetProfile()) {
681          tfound = true;    
682        } else if (!r.hasTargetProfile()) {
683          tfound = true; 
684        } else if (!l.hasTargetProfile()) {
685          tfound = true;
686          c.setTargetProfile(r.getTargetProfile());
687        } else {
688          StructureDefinition sdl = resolveProfile(comp, res, path, l.getTargetProfile().get(0).getValue(), comp.getLeft().getName(), session.getContextLeft(), comp.getLeft());
689          StructureDefinition sdr = resolveProfile(comp, res, path, r.getTargetProfile().get(0).getValue(), comp.getRight().getName(), session.getContextRight(), comp.getRight());
690          if (sdl != null && sdr != null) {
691            if (matches(sdl, sdr)) {
692              tfound = true;
693            } else if (derivesFrom(sdl, sdr, session.getContextLeft())) {
694              tfound = true;
695            } else if (derivesFrom(sdr, sdl, session.getContextRight())) {
696              c.setTargetProfile(r.getTargetProfile());
697              tfound = true;
698            } else if (sdl.getType().equals(sdr.getType())) {
699              ProfileComparison compP = (ProfileComparison) session.compare(sdl, sdr);
700              if (compP != null && compP.getIntersection() != null) {
701                tfound = true;
702                c.addTargetProfile("#"+compP.getId());
703              }
704            }
705          }
706        }
707        if (pfound && tfound) {
708          for (Enumeration<AggregationMode> a : l.getAggregation()) {
709            if (!r.hasAggregation(a.getValue())) {
710              c.getAggregation().removeIf(n -> n.getValue() == a.getValue());
711            }
712          }
713        }
714      }
715      if (pfound && tfound) {
716        result.add(c);
717      }
718    }
719    return result;
720  }
721
722  private String typeCode(DefinitionNavigator defn) {
723    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
724    for (TypeRefComponent t : defn.current().getType())
725      b.append(t.getWorkingCode()+(t.hasProfile() ? "("+t.getProfile()+")" : "")+(t.hasTargetProfile() ? "("+t.getTargetProfile()+")" : "")); // todo: other properties
726    return b.toString();
727  }
728
729  private boolean compareBindings(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, ElementDefinition subset, ElementDefinition superset, String path, ElementDefinition lDef, ElementDefinition rDef, Resource leftSrc, Resource rightSrc) throws FHIRFormatError, DefinitionException, IOException {
730    assert(lDef.hasBinding() || rDef.hasBinding());
731    if (!lDef.hasBinding()) {
732      subset.setBinding(rDef.getBinding());
733      // technically, the super set is unbound, but that's not very useful - so we use the provided on as an example
734      superset.setBinding(rDef.getBinding().copy());
735      superset.getBinding().setStrength(BindingStrength.EXAMPLE);
736      return true;
737    }
738    if (!rDef.hasBinding()) {
739      subset.setBinding(lDef.getBinding());
740      superset.setBinding(lDef.getBinding().copy());
741      superset.getBinding().setStrength(BindingStrength.EXAMPLE);
742      return true;
743    }
744    ElementDefinitionBindingComponent left = lDef.getBinding();
745    ElementDefinitionBindingComponent right = rDef.getBinding();
746    if (Base.compareDeep(left, right, false)) {
747      subset.setBinding(left);
748      superset.setBinding(right);      
749    }
750
751    // if they're both examples/preferred then:
752    // subset: left wins if they're both the same
753    // superset: 
754    if (isPreferredOrExample(left) && isPreferredOrExample(right)) {
755      if (right.getStrength() == BindingStrength.PREFERRED && left.getStrength() == BindingStrength.EXAMPLE && !Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) {
756        vm(IssueSeverity.INFORMATION, "Example/preferred bindings differ at "+path+" using binding from "+comp.getRight().getName(), path, comp.getMessages(), res.getMessages());
757        subset.setBinding(right);
758        superset.setBinding(unionBindings(comp, res, path, left, right, leftSrc, rightSrc));
759      } else {
760        if ((right.getStrength() != BindingStrength.EXAMPLE || left.getStrength() != BindingStrength.EXAMPLE) && !Base.compareDeep(left.getValueSet(), right.getValueSet(), false) ) { 
761          vm(IssueSeverity.INFORMATION, "Example/preferred bindings differ at "+path+" using binding from "+comp.getLeft().getName(), path, comp.getMessages(), res.getMessages());
762        }
763        subset.setBinding(left);
764        superset.setBinding(unionBindings(comp, res, path, left, right, leftSrc, rightSrc));
765      }
766      return true;
767    }
768    // if either of them are extensible/required, then it wins
769    if (isPreferredOrExample(left)) {
770      subset.setBinding(right);
771      superset.setBinding(unionBindings(comp, res, path, left, right, leftSrc, rightSrc));
772      return true;
773    }
774    if (isPreferredOrExample(right)) {
775      subset.setBinding(left);
776      superset.setBinding(unionBindings(comp, res, path, left, right, leftSrc, rightSrc));
777      return true;
778    }
779
780    // ok, both are extensible or required.
781    ElementDefinitionBindingComponent subBinding = new ElementDefinitionBindingComponent();
782    subset.setBinding(subBinding);
783    ElementDefinitionBindingComponent superBinding = new ElementDefinitionBindingComponent();
784    superset.setBinding(superBinding);
785    subBinding.setDescription(mergeText(comp, res, path, "description", left.getDescription(), right.getDescription(), false));
786    superBinding.setDescription(mergeText(comp, res, path, "description", left.getDescription(), right.getDescription(), false));
787    if (left.getStrength() == BindingStrength.REQUIRED || right.getStrength() == BindingStrength.REQUIRED)
788      subBinding.setStrength(BindingStrength.REQUIRED);
789    else
790      subBinding.setStrength(BindingStrength.EXTENSIBLE);
791    if (left.getStrength() == BindingStrength.EXTENSIBLE || right.getStrength() == BindingStrength.EXTENSIBLE)
792      superBinding.setStrength(BindingStrength.EXTENSIBLE);
793    else
794      superBinding.setStrength(BindingStrength.REQUIRED);
795
796    if (Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) {
797      subBinding.setValueSet(left.getValueSet());
798      superBinding.setValueSet(left.getValueSet());
799      return true;
800    } else if (!left.hasValueSet()) {
801      vm(IssueSeverity.ERROR, "No left Value set at "+path, path, comp.getMessages(), res.getMessages());
802      return true;      
803    } else if (!right.hasValueSet()) {
804      vm(IssueSeverity.ERROR, "No right Value set at "+path, path, comp.getMessages(), res.getMessages());
805      return true;      
806    } else {
807      // ok, now we compare the value sets. This may be unresolvable. 
808      ValueSet lvs = resolveVS(comp.getLeft(), left.getValueSet(), leftSrc, session.getContextLeft());
809      ValueSet rvs = resolveVS(comp.getRight(), right.getValueSet(), rightSrc, session.getContextRight());
810      if (lvs == null) {
811        vm(IssueSeverity.ERROR, "Unable to resolve left value set "+left.getValueSet().toString()+" at "+path, path, comp.getMessages(), res.getMessages());
812        return true;
813      } else if (rvs == null) {
814        vm(IssueSeverity.ERROR, "Unable to resolve right value set "+right.getValueSet().toString()+" at "+path, path, comp.getMessages(), res.getMessages());
815        return true;        
816      } else if (sameValueSets(lvs, rvs)) {
817        subBinding.setValueSet(lvs.getUrl());
818        superBinding.setValueSet(lvs.getUrl());
819      } else {
820        ValueSetComparison compP = (ValueSetComparison) session.compare(lvs, rvs);
821        if (compP != null) {
822          subBinding.setValueSet(compP.getIntersection().getUrl());
823          superBinding.setValueSet(compP.getUnion().getUrl());
824        }
825      }
826    }
827    return false;
828  }
829
830  private boolean sameValueSets(ValueSet lvs, ValueSet rvs) {
831    if (!lvs.getUrl().equals(rvs.getUrl())) {
832      return false;
833    }
834    if (isCore(lvs) && isCore(rvs)) {
835      return true;
836    }
837    if (lvs.hasVersion()) {
838      if (!lvs.getVersion().equals(rvs.getVersion())) {
839        return false;
840      } else if (!rvs.hasVersion()) {
841        return false;
842      }
843    }
844    return true;
845  }
846
847  private boolean isCore(ValueSet vs) {
848    return vs.getUrl().startsWith("http://hl7.org/fhir/ValueSet");
849  }
850
851  private List<ElementDefinitionConstraintComponent> intersectConstraints(String path, List<ElementDefinitionConstraintComponent> left, List<ElementDefinitionConstraintComponent> right) {
852    List<ElementDefinitionConstraintComponent> result = new ArrayList<ElementDefinitionConstraintComponent>();
853    for (ElementDefinitionConstraintComponent l : left) {
854      boolean found = false;
855      for (ElementDefinitionConstraintComponent r : right)
856        if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getExpression(), l.getExpression()) && r.getSeverity() == l.getSeverity()))
857          found = true;
858      if (found)
859        result.add(l);
860    }
861    return result;
862  }
863
864  // we can't really know about constraints. We create warnings, and collate them 
865  private List<ElementDefinitionConstraintComponent> unionConstraints(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, List<ElementDefinitionConstraintComponent> left, List<ElementDefinitionConstraintComponent> right) {
866    List<ElementDefinitionConstraintComponent> result = new ArrayList<ElementDefinitionConstraintComponent>();
867    for (ElementDefinitionConstraintComponent l : left) {
868      boolean found = false;
869      for (ElementDefinitionConstraintComponent r : right)
870        if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getExpression(), l.getExpression()) && r.getSeverity() == l.getSeverity()))
871          found = true;
872      if (!found) {
873        if (!Utilities.existsInList(l.getExpression(), "hasValue() or (children().count() > id.count())", "extension.exists() != value.exists()")) {
874          vm(IssueSeverity.INFORMATION,  "StructureDefinition "+comp.getLeft().getName()+" has a constraint that is removed in "+comp.getRight().getName()+" and it is uncertain whether they are compatible ("+l.getExpression()+")", path, comp.getMessages(), res.getMessages());
875        }
876      }
877      result.add(l);
878    }
879    for (ElementDefinitionConstraintComponent r : right) {
880      boolean found = false;
881      for (ElementDefinitionConstraintComponent l : left)
882        if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getExpression(), l.getExpression()) && r.getSeverity() == l.getSeverity()))
883          found = true;
884      if (!found) {
885        if (!Utilities.existsInList(r.getExpression(), "hasValue() or (children().count() > id.count())", "extension.exists() != value.exists()")) {
886          vm(IssueSeverity.INFORMATION,  "StructureDefinition "+comp.getRight().getName()+" has added constraint that is not found in "+comp.getLeft().getName()+" and it is uncertain whether they are compatible ("+r.getExpression()+")", path, comp.getMessages(), res.getMessages());
887        }
888      }
889    }
890    return result;
891  }
892
893  private StructureDefinition resolveProfile(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, String url, String name, IWorkerContext ctxt, Resource urlSource) {
894    StructureDefinition sd = ctxt.fetchResource(StructureDefinition.class, url, urlSource);
895    if (sd == null) {
896      ValidationMessage vm = vmI(IssueSeverity.WARNING, "Unable to resolve profile "+url+" in profile "+name, path);
897    }
898    return sd;
899  }
900
901  private boolean isPreferredOrExample(ElementDefinitionBindingComponent binding) {
902    return binding.getStrength() == BindingStrength.EXAMPLE || binding.getStrength() == BindingStrength.PREFERRED;
903  }
904
905  private ElementDefinitionBindingComponent unionBindings(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, ElementDefinitionBindingComponent left, ElementDefinitionBindingComponent right, Resource leftSrc, Resource rightSrc) throws FHIRFormatError, DefinitionException, IOException {
906    ElementDefinitionBindingComponent union = new ElementDefinitionBindingComponent();
907    if (left.getStrength().compareTo(right.getStrength()) < 0)
908      union.setStrength(left.getStrength());
909    else
910      union.setStrength(right.getStrength());
911    union.setDescription(mergeText(comp, res, path, "binding.description", left.getDescription(), right.getDescription(), false));
912    if (Base.compareDeep(left.getValueSet(), right.getValueSet(), false))
913      union.setValueSet(left.getValueSet());
914    else {
915      ValueSet lvs = resolveVS(comp.getLeft(), left.getValueSet(), leftSrc, session.getContextLeft());
916      ValueSet rvs = resolveVS(comp.getRight(), right.getValueSet(), rightSrc, session.getContextRight());
917      if (lvs != null && rvs != null) {
918        ValueSetComparison compP = (ValueSetComparison) session.compare(lvs, rvs);
919        if (compP != null) {
920          union.setValueSet(compP.getUnion().getUrl());
921        }
922      } else if (lvs != null) {
923        union.setValueSet(lvs.getUrl());
924      } else if (rvs != null) {
925        union.setValueSet(rvs.getUrl());
926      }
927    }
928    return union;
929  }
930
931  private ValueSet resolveVS(StructureDefinition ctxtLeft, String vsRef, Resource src, IWorkerContext ctxt) {
932    if (vsRef == null)
933      return null;
934    return ctxt.fetchResource(ValueSet.class, vsRef, src);
935  }
936
937  public XhtmlNode renderStructure(ProfileComparison comp, String id, String prefix, String corePath) throws FHIRException, IOException {
938    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "compare"), false, true);
939    gen.setTranslator(session.getContextRight().translator());
940    TableModel model = gen.initComparisonTable(corePath, id);
941    genElementComp(null /* oome back to this later */, gen, model.getRows(), comp.combined, corePath, prefix, null, true);
942    return gen.generate(model, prefix, 0, null);
943  }
944
945  private void genElementComp(String defPath, HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<ElementDefinitionNode> combined, String corePath, String prefix, Row slicingRow, boolean root) throws IOException {
946    Row originalRow = slicingRow;
947    Row typesRow = null;
948    
949    List<StructuralMatch<ElementDefinitionNode>> children = combined.getChildren();
950
951    Row row = gen.new Row();
952    rows.add(row);
953    String path = combined.either().getDef().getPath();
954    row.setAnchor(path);
955      row.setColor(utilsRight.getRowColor(combined.either().getDef(), false));
956      if (eitherHasSlicing(combined))
957        row.setLineColor(1);
958      else if (eitherHasSliceName(combined))
959        row.setLineColor(2);
960      else
961        row.setLineColor(0);
962      boolean ext = false;
963      if (tail(path).equals("extension")) {
964        if (elementIsComplex(combined))
965          row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
966        else
967          row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
968        ext = true;
969      } else if (tail(path).equals("modifierExtension")) {
970        if (elementIsComplex(combined))
971          row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
972        else
973          row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
974      } else if (hasChoice(combined)) {
975        if (allAreReference(combined))
976          row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
977        else {
978          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
979          typesRow = row;
980        }
981      } else if (combined.either().getDef().hasContentReference())
982        row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
983      else if (isPrimitive(combined))
984        row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
985      else if (hasTarget(combined))
986        row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
987      else if (isDataType(combined))
988        row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
989      else
990        row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
991      String ref = defPath == null ? null : defPath + combined.either().getDef().getId();
992      String sName = tail(path);
993      String sn = getSliceName(combined);
994      if (sn != null)
995        sName = sName +":"+sn;
996      StructureDefinitionRenderer.UnusedTracker used = new StructureDefinitionRenderer.UnusedTracker();
997      StructureDefinitionRenderer sdrLeft = new StructureDefinitionRenderer(new RenderingContext(utilsLeft.getContext(), null, utilsLeft.getTerminologyServiceOptions(), corePath, prefix, null, ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER));
998      StructureDefinitionRenderer sdrRight= new StructureDefinitionRenderer(new RenderingContext(utilsRight.getContext(), null, utilsRight.getTerminologyServiceOptions(), corePath, prefix, null, ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER));
999
1000
1001        
1002      Cell nc;
1003      String leftColor = !combined.hasLeft() ? COLOR_NO_ROW_LEFT : combined.hasErrors() ? COLOR_DIFFERENT : null;
1004      String rightColor = !combined.hasRight() ? COLOR_NO_ROW_LEFT : combined.hasErrors() ? COLOR_DIFFERENT : null;
1005      if (combined.hasLeft()) {
1006        nc = sdrLeft.genElementNameCell(gen, combined.getLeft().getDef(),  "??", true, corePath, prefix, root, false, false, combined.getLeft().getSrc(), typesRow, row, false, ext, used , ref, sName, null);
1007      } else {
1008        nc = sdrRight.genElementNameCell(gen, combined.getRight().getDef(),  "??", true, corePath, prefix, root, false, false, combined.getRight().getSrc(), typesRow, row, false, ext, used , ref, sName, null);
1009      }
1010      if (combined.hasLeft()) {
1011        frame(sdrLeft.genElementCells(gen, combined.getLeft().getDef(),  "??", true, corePath, prefix, root, false, false, combined.getLeft().getSrc(), typesRow, row, true, ext, used , ref, sName, nc, false, false, null), leftColor);
1012      } else {
1013        frame(spacers(row, 4, gen), leftColor);
1014      }
1015      if (combined.hasRight()) {
1016        frame(sdrRight.genElementCells(gen, combined.getRight().getDef(), "??", true, corePath, prefix, root, false, false, combined.getRight().getSrc(), typesRow, row, true, ext, used, ref, sName, nc, false, false, null), rightColor);
1017      } else {
1018        frame(spacers(row, 4, gen), rightColor);
1019      }
1020      row.getCells().add(cellForMessages(gen, combined.getMessages()));
1021
1022      for (StructuralMatch<ElementDefinitionNode> child : children) {
1023        genElementComp(defPath, gen, row.getSubRows(), child, corePath, prefix, originalRow, false);
1024      }
1025    }
1026
1027  private void frame(List<Cell> cells, String color) {
1028    for (Cell cell : cells) {
1029      if (color != null) {
1030        cell.setStyle("background-color: "+color);
1031      }
1032    }
1033    cells.get(0).setStyle("border-left: 1px grey solid"+(color == null ? "" : "; background-color: "+color));
1034    cells.get(cells.size()-1).setStyle("border-right: 1px grey solid"+(color == null ? "" : "; background-color: "+color));
1035  }
1036
1037  private List<Cell> spacers(Row row, int count, HierarchicalTableGenerator gen) {
1038    List<Cell> res = new ArrayList<>();
1039    for (int i = 0; i < count; i++) {
1040      Cell c = gen.new Cell();
1041      res.add(c);
1042      row.getCells().add(c);
1043    }
1044    return res;
1045  }
1046
1047  private String getSliceName(StructuralMatch<ElementDefinitionNode> combined) {
1048    // TODO Auto-generated method stub
1049    return null;
1050  }
1051
1052  private boolean isDataType(StructuralMatch<ElementDefinitionNode> combined) {
1053    // TODO Auto-generated method stub
1054    return false;
1055  }
1056
1057  private boolean hasTarget(StructuralMatch<ElementDefinitionNode> combined) {
1058    // TODO Auto-generated method stub
1059    return false;
1060  }
1061
1062  private boolean isPrimitive(StructuralMatch<ElementDefinitionNode> combined) {
1063    // TODO Auto-generated method stub
1064    return false;
1065  }
1066
1067  private boolean allAreReference(StructuralMatch<ElementDefinitionNode> combined) {
1068    // TODO Auto-generated method stub
1069    return false;
1070  }
1071
1072  private boolean hasChoice(StructuralMatch<ElementDefinitionNode> combined) {
1073    // TODO Auto-generated method stub
1074    return false;
1075  }
1076
1077  private boolean elementIsComplex(StructuralMatch<ElementDefinitionNode> combined) {
1078    // TODO Auto-generated method stub velement.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()
1079    return false;
1080  }
1081
1082  private boolean eitherHasSliceName(StructuralMatch<ElementDefinitionNode> combined) {
1083    // TODO Auto-generated method stub
1084    return false;
1085  }
1086
1087  private boolean eitherHasSlicing(StructuralMatch<ElementDefinitionNode> combined) {
1088    // TODO Auto-generated method stub
1089    return false;
1090  }
1091  
1092
1093  
1094
1095private String tail(String path) {
1096  if (path.contains("."))
1097    return path.substring(path.lastIndexOf('.')+1);
1098  else
1099    return path;
1100}
1101  
1102
1103}