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