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}