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