
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; 008import java.util.Set; 009 010import lombok.extern.slf4j.Slf4j; 011import org.hl7.fhir.exceptions.DefinitionException; 012import org.hl7.fhir.exceptions.FHIRException; 013import org.hl7.fhir.exceptions.FHIRFormatError; 014import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison; 015import org.hl7.fhir.r5.conformance.profile.BindingResolution; 016import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider; 017import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 018import org.hl7.fhir.r5.context.IWorkerContext; 019import org.hl7.fhir.r5.formats.IParser; 020import org.hl7.fhir.r5.formats.JsonParser; 021import org.hl7.fhir.r5.model.Base; 022import org.hl7.fhir.r5.model.Coding; 023import org.hl7.fhir.r5.model.DataType; 024import org.hl7.fhir.r5.model.ElementDefinition; 025import org.hl7.fhir.r5.model.ElementDefinition.AggregationMode; 026import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; 027import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent; 028import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent; 029import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 030import org.hl7.fhir.r5.model.Enumeration; 031import org.hl7.fhir.r5.model.Enumerations.BindingStrength; 032import org.hl7.fhir.r5.model.IntegerType; 033import org.hl7.fhir.r5.model.PrimitiveType; 034import org.hl7.fhir.r5.model.Resource; 035import org.hl7.fhir.r5.model.StringType; 036import org.hl7.fhir.r5.model.StructureDefinition; 037import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 038import org.hl7.fhir.r5.model.ValueSet; 039import org.hl7.fhir.r5.renderers.Renderer.RenderingStatus; 040import org.hl7.fhir.r5.renderers.StructureDefinitionRenderer; 041import org.hl7.fhir.r5.renderers.utils.RenderingContext; 042import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; 043import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; 044import org.hl7.fhir.r5.utils.DefinitionNavigator; 045import org.hl7.fhir.r5.utils.UserDataNames; 046import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 047import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 048import org.hl7.fhir.utilities.Utilities; 049import org.hl7.fhir.utilities.validation.ValidationMessage; 050import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 051import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 052import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; 053import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 054import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableGenerationMode; 055import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; 056import org.hl7.fhir.utilities.xhtml.XhtmlNode; 057 058import kotlin.NotImplementedError; 059 060@MarkedToMoveToAdjunctPackage 061@Slf4j 062public class StructureDefinitionComparer extends CanonicalResourceComparer implements ProfileKnowledgeProvider { 063 064 public class ProfileComparison extends CanonicalResourceComparison<StructureDefinition> { 065 066 private StructuralMatch<ElementDefinitionNode> combined; 067 068 public ProfileComparison(StructureDefinition left, StructureDefinition right) { 069 super(left, right); 070 combined = new StructuralMatch<ElementDefinitionNode>(); // base 071 } 072 073 public StructuralMatch<ElementDefinitionNode> getCombined() { 074 return combined; 075 } 076 077 @Override 078 protected String abbreviation() { 079 return "sd"; 080 } 081 082 @Override 083 protected String summary() { 084 return "Profile: "+left.present()+" vs "+right.present(); 085 } 086 087 @Override 088 protected String fhirType() { 089 return "StructureDefinition"; 090 } 091 @Override 092 protected void countMessages(MessageCounts cnts) { 093 super.countMessages(cnts); 094 combined.countMessages(cnts); 095 } 096 097 } 098 099 100 private class ElementDefinitionNode { 101 private ElementDefinition def; 102 private StructureDefinition src; 103 private ElementDefinitionNode(StructureDefinition src, ElementDefinition def) { 104 super(); 105 this.src = src; 106 this.def = def; 107 } 108 public ElementDefinition getDef() { 109 return def; 110 } 111 public StructureDefinition getSrc() { 112 return src; 113 } 114 } 115 116 private ProfileUtilities utilsLeft; 117 private ProfileUtilities utilsRight; 118 119 public StructureDefinitionComparer(ComparisonSession session, ProfileUtilities utilsLeft, ProfileUtilities utilsRight) { 120 super(session); 121 this.utilsLeft = utilsLeft; 122 this.utilsRight = utilsRight; 123 } 124 125 @Override 126 protected String fhirType() { 127 return "StructureDefinition"; 128 } 129 130 public ProfileComparison compare(StructureDefinition left, StructureDefinition right) throws DefinitionException, FHIRFormatError, IOException { 131 check(left, "left"); 132 check(right, "right"); 133 134 ProfileComparison res = new ProfileComparison(left, right); 135 session.identify(res); 136 StructureDefinition sd = new StructureDefinition(); 137 res.setUnion(sd); 138 session.identify(sd); 139 sd.setName("Union"+left.getName()+"And"+right.getName()); 140 sd.setTitle("Union of "+left.getTitle()+" And "+right.getTitle()); 141 sd.setStatus(left.getStatus()); 142 sd.setDate(new Date()); 143 144 StructureDefinition sd1 = new StructureDefinition(); 145 res.setIntersection(sd1); 146 session.identify(sd1); 147 sd1.setName("Intersection"+left.getName()+"And"+right.getName()); 148 sd1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle()); 149 sd1.setStatus(left.getStatus()); 150 sd1.setDate(new Date()); 151 152 List<String> chMetadata = new ArrayList<>(); 153 boolean ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right); 154 if (comparePrimitives("fhirVersion", left.getFhirVersionElement(), right.getFhirVersionElement(), res.getMetadata(), IssueSeverity.WARNING, res)) { 155 ch = true; 156 chMetadata.add("fhirVersion"); 157 } 158 if (comparePrimitives("kind", left.getKindElement(), right.getKindElement(), res.getMetadata(), IssueSeverity.WARNING, res)) { 159 ch = true; 160 chMetadata.add("kind"); 161 } 162 if (comparePrimitives("abstract", left.getAbstractElement(), right.getAbstractElement(), res.getMetadata(), IssueSeverity.WARNING, res)) { 163 ch = true; 164 chMetadata.add("abstract"); 165 } 166 res.updatedMetadataState(ch, chMetadata); 167 168 ch = false; 169 ch = comparePrimitives("type", left.getTypeElement(), right.getTypeElement(), res.getMetadata(), IssueSeverity.ERROR, res) || ch; 170 ch = comparePrimitives("baseDefinition", left.getBaseDefinitionElement(), right.getBaseDefinitionElement(), res.getMetadata(), IssueSeverity.ERROR, res) || ch; 171 if (left.getType().equals(right.getType())) { 172 DefinitionNavigator ln = new DefinitionNavigator(session.getContextLeft(), left, false, false); 173 DefinitionNavigator rn = new DefinitionNavigator(session.getContextRight(), right, false, false); 174 StructuralMatch<ElementDefinitionNode> sm = new StructuralMatch<ElementDefinitionNode>(new ElementDefinitionNode(left, ln.current()), new ElementDefinitionNode(right, rn.current())); 175 compareElements(res, sm, ln.path(), null, ln, rn); 176 res.combined = sm; 177 ln = new DefinitionNavigator(session.getContextLeft(), left, true, false); 178 rn = new DefinitionNavigator(session.getContextRight(), right, true, false); 179 List<Base> parents = new ArrayList<Base>(); 180 parents.add(right); 181 ch = compareDiff(ln.path(), null, ln, rn, res, parents) || ch; 182 // we don't preserve the differences - we only want the annotations 183 } 184 res.updateDefinitionsState(ch); 185 186 session.annotate(right, res); 187 return res; 188 } 189 190 private void check(StructureDefinition sd, String name) { 191 if (sd == null) 192 throw new DefinitionException("No StructureDefinition provided ("+name+": "+sd.getName()+")"); 193// if (sd.getType().equals("Extension")) { 194// throw new DefinitionException("StructureDefinition is for an extension - use ExtensionComparer instead ("+name+": "+sd.getName()+")"); 195// } 196 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 197 throw new DefinitionException("StructureDefinition is not for an profile - can't be compared ("+name+": "+sd.getName()+")"); 198 } 199 if (sd.getSnapshot().getElement().isEmpty()) 200 throw new DefinitionException("StructureDefinition snapshot is empty ("+name+": "+sd.getName()+")"); 201 } 202 203 private boolean compareElements(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, String sliceName, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, FHIRFormatError, IOException { 204 assert(path != null); 205 assert(left != null); 206 assert(right != null); 207 assert(left.path().equals(right.path())); 208 209 boolean def = false; 210 211 212 log.debug("Compare elements at "+path); 213 214 215 // not allowed to be different: 216// ruleEqual(comp, res, left.current().getDefaultValue(), right.current().getDefaultValue(), "defaultValue", path); 217// ruleEqual(comp, res, left.current().getMeaningWhenMissingElement(), right.current().getMeaningWhenMissingElement(), "meaningWhenMissing", path); 218// ruleEqual(comp, res, left.current().getIsModifierElement(), right.current().getIsModifierElement(), "isModifier", path); - this check belongs in the core 219// ruleEqual(comp, res, left.current().getIsSummaryElement(), right.current().getIsSummaryElement(), "isSummary", path); - so does this 220 221 // we ignore slicing right now - we're going to clone the root one anyway, and then think about clones 222 // simple stuff 223 ElementDefinition subset = new ElementDefinition(); 224 subset.setPath(left.path()); 225 if (sliceName != null) 226 subset.setSliceName(sliceName); 227 228 subset.getRepresentation().addAll(left.current().getRepresentation()); // can't be bothered even testing this one 229 subset.setDefaultValue(left.current().getDefaultValue()); 230 subset.setMeaningWhenMissing(left.current().getMeaningWhenMissing()); 231 subset.setIsModifier(left.current().getIsModifier()); 232 subset.setIsSummary(left.current().getIsSummary()); 233 234 // descriptive properties from ElementDefinition - merge them: 235 subset.setLabel(mergeText(comp, res, path, "label", left.current().getLabel(), right.current().getLabel(), false)); 236 comparePrimitivesWithTracking("label", left.current().getLabelElement(), right.current().getLabelElement(), null, IssueSeverity.INFORMATION, comp, right.current()); 237 238 subset.setShort(mergeText(comp, res, path, "short", left.current().getShort(), right.current().getShort(), false)); 239 def = comparePrimitivesWithTracking("short", left.current().getShortElement(), right.current().getShortElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def; 240 241 subset.setDefinition(mergeText(comp, res, path, "definition", left.current().getDefinition(), right.current().getDefinition(), false)); 242 def = comparePrimitivesWithTracking("definition", left.current().getDefinitionElement(), right.current().getDefinitionElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def; 243 244 subset.setComment(mergeText(comp, res, path, "comments", left.current().getComment(), right.current().getComment(), false)); 245 def = comparePrimitivesWithTracking("comment", left.current().getCommentElement(), right.current().getCommentElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def; 246 247 subset.setRequirements(mergeText(comp, res, path, "requirements", left.current().getRequirements(), right.current().getRequirements(), false)); 248 def = comparePrimitivesWithTracking("requirements", left.current().getRequirementsElement(), right.current().getRequirementsElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def; 249 250 subset.getCode().addAll(mergeCodings(left.current().getCode(), right.current().getCode())); 251 subset.getAlias().addAll(mergeStrings(left.current().getAlias(), right.current().getAlias())); 252 subset.getMapping().addAll(mergeMappings(left.current().getMapping(), right.current().getMapping())); 253 // left will win for example 254 subset.setExample(left.current().hasExample() ? left.current().getExample() : right.current().getExample()); 255 256 if (left.current().getMustSupport() != right.current().getMustSupport()) { 257 vm(IssueSeverity.WARNING, "Elements differ in definition for mustSupport: '"+left.current().getMustSupport()+"' vs '"+right.current().getMustSupport()+"'", path, comp.getMessages(), res.getMessages()); 258 259 } 260 subset.setMustSupport(left.current().getMustSupport() || right.current().getMustSupport()); 261 def = comparePrimitivesWithTracking("mustSupport", left.current().getMustSupportElement(), right.current().getMustSupportElement(), null, IssueSeverity.INFORMATION, null, right.current()) || def; 262 263 ElementDefinition superset = subset.copy(); 264 265 def = comparePrimitivesWithTracking("min", left.current().getMinElement(), right.current().getMinElement(), null, IssueSeverity.INFORMATION, null, right.current()) || def; 266 def = comparePrimitivesWithTracking("max", left.current().getMaxElement(), right.current().getMaxElement(), null, IssueSeverity.INFORMATION, null, right.current()) || def; 267 268 // compare and intersect 269 int leftMin = left.current().getMin(); 270 int rightMin = right.current().getMin(); 271 int leftMax = "*".equals(left.current().getMax()) ? Integer.MAX_VALUE : Utilities.parseInt(left.current().getMax(), -1); 272 int rightMax = "*".equals(right.current().getMax()) ? Integer.MAX_VALUE : Utilities.parseInt(right.current().getMax(), -1); 273 274 checkMinMax(comp, res, path, leftMin, rightMin, leftMax, rightMax); 275 superset.setMin(unionMin(leftMin, rightMin)); 276 superset.setMax(unionMax(leftMax, rightMax, left.current().getMax(), right.current().getMax())); 277 subset.setMin(intersectMin(leftMin, rightMin)); 278 subset.setMax(intersectMax(leftMax, rightMax, left.current().getMax(), right.current().getMax())); 279 280 superset.getType().addAll(unionTypes(comp, res, path, left.current().getType(), right.current().getType(), left.getStructure(), right.getStructure())); 281 subset.getType().addAll(intersectTypes(comp, res, subset, path, left.current().getType(), right.current().getType())); 282 rule(comp, res, !subset.getType().isEmpty() || (!left.current().hasType() && !right.current().hasType()), path, "Type Mismatch: "+typeCode(left)+" vs "+typeCode(right)); 283 // <fixed[x]><!-- ?? 0..1 * Value must be exactly this --></fixed[x]> 284 // <pattern[x]><!-- ?? 0..1 * Value must have at least these property values --></pattern[x]> 285 superset.setMaxLengthElement(unionMaxLength(left.current().getMaxLength(), right.current().getMaxLength())); 286 subset.setMaxLengthElement(intersectMaxLength(left.current().getMaxLength(), right.current().getMaxLength())); 287 if (left.current().hasBinding() || right.current().hasBinding()) { 288 compareBindings(comp, res, subset, superset, path, left.current(), right.current(), left.getStructure(), right.getStructure()); 289 } 290 // note these are backwards 291 superset.getConstraint().addAll(intersectConstraints(path, left.current().getConstraint(), right.current().getConstraint())); 292 subset.getConstraint().addAll(unionConstraints(comp, res, path, left.current().getConstraint(), right.current().getConstraint())); 293 comp.getIntersection().getSnapshot().getElement().add(subset); 294 comp.getUnion().getSnapshot().getElement().add(superset); 295 296 // add the children 297 def = compareChildren(comp, res, path, left, right) || def; 298// 299// // now process the slices 300// if (left.current().hasSlicing() || right.current().hasSlicing()) { 301// assert sliceName == null; 302// if (isExtension(left.path())) 303// return compareExtensions(outcome, path, superset, subset, left, right); 304// // return true; 305// else { 306// ElementDefinitionSlicingComponent slicingL = left.current().getSlicing(); 307// ElementDefinitionSlicingComponent slicingR = right.current().getSlicing(); 308// // 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. 309// if (left.current().hasSlicing() && !right.current().hasSlicing()) { 310// // 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) 311// // the minimum set is the slicing specified in the slicer 312// subset.setSlicing(slicingL); 313// // stick everything from the right to do with the slices to the subset 314// copySlices(outcome.subset.getSnapshot().getElement(), left.getStructure().getSnapshot().getElement(), left.slices()); 315// } else if (!left.current().hasSlicing() && right.current().hasSlicing()) { 316// // 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) 317// // the minimum set is the slicing specified in the slicer 318// subset.setSlicing(slicingR); 319// // stick everything from the right to do with the slices to the subset 320// copySlices(outcome.subset.getSnapshot().getElement(), right.getStructure().getSnapshot().getElement(), right.slices()); 321// } else if (isTypeSlicing(slicingL) || isTypeSlicing(slicingR)) { 322// superset.getSlicing().setRules(SlicingRules.OPEN).setOrdered(false).addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 323// subset.getSlicing().setRules(slicingL.getRules() == SlicingRules.CLOSED || slicingR.getRules() == SlicingRules.CLOSED ? SlicingRules.OPEN : SlicingRules.CLOSED).setOrdered(false).addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 324// 325// // the superset is the union of the types 326// // the subset is the intersection of them 327// List<DefinitionNavigator> handled = new ArrayList<>(); 328// for (DefinitionNavigator t : left.slices()) { 329// DefinitionNavigator r = findMatchingSlice(right.slices(), t); 330// if (r == null) { 331// copySlice(outcome.superset.getSnapshot().getElement(), left.getStructure().getSnapshot().getElement(), t); 332// } else { 333// handled.add(r); 334// ret = compareElements(outcome, path+":"+t.current().getSliceName(), t, r, t.current().getSliceName()) && ret; 335// } 336// } 337// for (DefinitionNavigator t : right.slices()) { 338// if (!handled.contains(t)) { 339// copySlice(outcome.superset.getSnapshot().getElement(), right.getStructure().getSnapshot().getElement(), t); 340// } 341// } 342// } else if (slicingMatches(slicingL, slicingR)) { 343// // if it's the same, we can try matching the slices - though we might have to give up without getting matches correct 344// // there amy be implied consistency we can't reason about 345// throw new DefinitionException("Slicing matches but is not handled yet at "+left.current().getId()+": ("+ProfileUtilities.summarizeSlicing(slicingL)+")"); 346// } else { 347// // if the slicing is different, we can't compare them - or can we? 348// throw new DefinitionException("Slicing doesn't match at "+left.current().getId()+": ("+ProfileUtilities.summarizeSlicing(slicingL)+" / "+ProfileUtilities.summarizeSlicing(slicingR)+")"); 349// } 350// } 351// // todo: name 352// } 353// return ret; 354// 355// // TODO Auto-generated method stub 356// return null; 357 return def; 358 } 359 360 361 private boolean compareChildren(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, IOException, FHIRFormatError { 362 boolean def = false; 363 364 List<DefinitionNavigator> lc = left.children(); 365 List<DefinitionNavigator> rc = right.children(); 366 // it's possible that one of these profiles walks into a data type and the other doesn't 367 // if it does, we have to load the children for that data into the profile that doesn't 368 // walk into it 369 if (lc.isEmpty() && !rc.isEmpty() && right.current().getType().size() == 1 && left.hasTypeChildren(right.current().getType().get(0), left.getStructure())) 370 lc = left.childrenFromType(right.current().getType().get(0), right.getStructure()); 371 if (rc.isEmpty() && !lc.isEmpty() && left.current().getType().size() == 1 && right.hasTypeChildren(left.current().getType().get(0), right.getStructure())) 372 rc = right.childrenFromType(left.current().getType().get(0), left.getStructure()); 373 374 List<DefinitionNavigator> matchR = new ArrayList<>(); 375 for (DefinitionNavigator l : lc) { 376 DefinitionNavigator r = findInList(rc, l); 377 if (r == null) { 378 comp.getUnion().getSnapshot().getElement().add(l.current().copy()); 379 res.getChildren().add(new StructuralMatch<ElementDefinitionNode>(new ElementDefinitionNode(l.getStructure(), l.current()), vmI(IssueSeverity.INFORMATION, "Removed this element", path))); 380 } else { 381 matchR.add(r); 382 StructuralMatch<ElementDefinitionNode> sm = new StructuralMatch<ElementDefinitionNode>(new ElementDefinitionNode(l.getStructure(), l.current()), new ElementDefinitionNode(r.getStructure(), r.current())); 383 res.getChildren().add(sm); 384 def = compareElements(comp, sm, l.path(), null, l, r) || def; 385 } 386 } 387 for (DefinitionNavigator r : rc) { 388 if (!matchR.contains(r)) { 389 comp.getUnion().getSnapshot().getElement().add(r.current().copy()); 390 res.getChildren().add(new StructuralMatch<ElementDefinitionNode>(vmI(IssueSeverity.INFORMATION, "Added this element", path), new ElementDefinitionNode(r.getStructure(), r.current()))); 391 } 392 } 393 return def; 394 } 395 396 397 private boolean compareDiff(String path, String sliceName, DefinitionNavigator left, DefinitionNavigator right, ProfileComparison res, List<Base> parents) throws DefinitionException, FHIRFormatError, IOException { 398 assert(path != null); 399 assert(left != null); 400 assert(right != null); 401 assert(left.path().equals(right.path())); 402 assert(parents.size() > 0); 403 404 boolean def = false; 405 boolean ch = false; 406 407 // not allowed to be different: 408// ruleEqual(comp, res, left.current().getDefaultValue(), right.current().getDefaultValue(), "defaultValue", path); 409// ruleEqual(comp, res, left.current().getMeaningWhenMissingElement(), right.current().getMeaningWhenMissingElement(), "meaningWhenMissing", path); 410// ruleEqual(comp, res, left.current().getIsModifierElement(), right.current().getIsModifierElement(), "isModifier", path); - this check belongs in the core 411// ruleEqual(comp, res, left.current().getIsSummaryElement(), right.current().getIsSummaryElement(), "isSummary", path); - so does this 412 413 ElementDefinition edl = left.current(); 414 ElementDefinition edr = right.current(); 415 if (edl == null && edr == null) { 416 // both are sparse at this point, do nothing 417 } else if (edl == null) { 418 session.markAdded(edr); 419 } else if (edr == null) { 420 session.markDeleted(right.parent(), "element", edl); 421 } else { 422 // descriptive properties from ElementDefinition 423 comparePrimitivesWithTracking("label", edl.getLabelElement(), edr.getLabelElement(), null, IssueSeverity.INFORMATION, null, edr); 424 comparePrimitivesWithTracking("sliceName", edl.getSliceNameElement(), edr.getSliceNameElement(), null, IssueSeverity.INFORMATION, null, edr); 425 comparePrimitivesWithTracking("sliceIsConstraining", edl.getSliceIsConstrainingElement(), edr.getSliceIsConstrainingElement(), null, IssueSeverity.INFORMATION, null, edr); 426 comparePrimitivesWithTracking("alias", edl.getAlias(), edr.getAlias(), null, IssueSeverity.INFORMATION, null, edr); 427 compareDataTypesWithTracking("code", edl.getCode(), edr.getCode(), null, IssueSeverity.INFORMATION, null, edr); 428 429 def = comparePrimitivesWithTracking("short", edl.getShortElement(), edr.getShortElement(), null, IssueSeverity.INFORMATION, null, edr) || def; 430 def = comparePrimitivesWithTracking("definition", edl.getDefinitionElement(), edr.getDefinitionElement(), null, IssueSeverity.INFORMATION, null, edr) || def; 431 def = comparePrimitivesWithTracking("comment", edl.getCommentElement(), edr.getCommentElement(), null, IssueSeverity.INFORMATION, null, edr) || def; 432 def = comparePrimitivesWithTracking("requirements", edl.getRequirementsElement(), edr.getRequirementsElement(), null, IssueSeverity.INFORMATION, null, edr) || def; 433 def = comparePrimitivesWithTracking("mustSupport", edl.getMustSupportElement(), edr.getMustSupportElement(), null, IssueSeverity.INFORMATION, null, edr) || def; 434 def = comparePrimitivesWithTracking("meaningWhenMissing", edl.getMeaningWhenMissingElement(), edr.getMeaningWhenMissingElement(), null, IssueSeverity.INFORMATION, null, edr) || def; 435 def = comparePrimitivesWithTracking("isModifierReason", edl.getIsModifierReasonElement(), edr.getIsModifierReasonElement(), null, IssueSeverity.INFORMATION, null, edr) || def; 436 437 ch = comparePrimitivesWithTracking("min", edl.getMinElement(), edr.getMinElement(), null, IssueSeverity.ERROR, null, edr) || ch; 438 ch = comparePrimitivesWithTracking("max", edl.getMaxElement(), edr.getMaxElement(), null, IssueSeverity.ERROR, null, edr) || ch; 439 ch = compareDataTypesWithTracking("defaultValue", edl.getDefaultValue(), edr.getDefaultValue(), null, IssueSeverity.ERROR, null, edr) || ch; 440 ch = compareDataTypesWithTracking("fixed", edl.getFixed(), edr.getFixed(), null, IssueSeverity.ERROR, null, edr) || ch; 441 ch = compareDataTypesWithTracking("pattern", edl.getPattern(), edr.getPattern(), null, IssueSeverity.ERROR, null, edr) || ch; 442 ch = compareDataTypesWithTracking("minValue", edl.getMinValue(), edr.getMinValue(), null, IssueSeverity.ERROR, null, edr) || ch; 443 ch = compareDataTypesWithTracking("maxValue", edl.getMaxValue(), edr.getMaxValue(), null, IssueSeverity.ERROR, null, edr) || ch; 444 ch = comparePrimitivesWithTracking("maxLength", edl.getMaxLengthElement(), edr.getMaxLengthElement(), null, IssueSeverity.INFORMATION, null, edr) || def; 445 ch = comparePrimitivesWithTracking("mustHaveValue", edl.getMustHaveValueElement(), edr.getMustHaveValueElement(), null, IssueSeverity.INFORMATION, null, edr) || def; 446 ch = comparePrimitivesWithTracking("valueAlternatives", edl.getValueAlternatives(), edr.getValueAlternatives(), null, IssueSeverity.INFORMATION, null, edr) || ch; 447 ch = comparePrimitivesWithTracking("isModifier", edl.getIsModifierElement(), edr.getIsModifierElement(), null, IssueSeverity.INFORMATION, null, edr) || def; 448 449 def = compareTypes(path, sliceName, edl, edr, res) || def; 450 451 452 ElementDefinitionBindingComponent bl = edl.getBinding(); 453 ElementDefinitionBindingComponent br = edr.getBinding(); 454 if (bl == null && br == null) { 455 // both are sparse at this point, do nothing 456 } else if (bl == null) { 457 session.markAdded(edr); 458 } else if (br == null) { 459 session.markDeleted(right.parent(), "element", edl); 460 } else { 461 ch = comparePrimitivesWithTracking("strength", bl.getStrengthElement(), br.getStrengthElement(), null, IssueSeverity.ERROR, null, edr) || ch; 462 def = comparePrimitivesWithTracking("description", bl.getDescriptionElement(), br.getDescriptionElement(), null, IssueSeverity.ERROR, null, edr) || def; 463 ch = comparePrimitivesWithTracking("valueSet", bl.getValueSetElement(), br.getValueSetElement(), null, IssueSeverity.ERROR, null, edr) || ch; 464 // todo: additional 465 } 466 467 def = compareInvariants(path, sliceName, edl, edr, res) || def; 468 469 // main todos: 470 // invariants, slicing 471 // mappings 472 } 473 // add the children 474 if (ch) { 475 res.updateContentState(true); 476 } 477 def = compareDiffChildren(path, left, right, edr == null ? parents : newParents(parents, edr), res) || def; 478// 479// // now process the slices 480// if (left.current().hasSlicing() || right.current().hasSlicing()) { 481// assert sliceName == null; 482// if (isExtension(left.path())) 483// return compareExtensions(outcome, path, superset, subset, left, right); 484// // return true; 485// else { 486// ElementDefinitionSlicingComponent slicingL = left.current().getSlicing(); 487// ElementDefinitionSlicingComponent slicingR = right.current().getSlicing(); 488// // 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. 489// if (left.current().hasSlicing() && !right.current().hasSlicing()) { 490// // 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) 491// // the minimum set is the slicing specified in the slicer 492// subset.setSlicing(slicingL); 493// // stick everything from the right to do with the slices to the subset 494// copySlices(outcome.subset.getSnapshot().getElement(), left.getStructure().getSnapshot().getElement(), left.slices()); 495// } else if (!left.current().hasSlicing() && right.current().hasSlicing()) { 496// // 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) 497// // the minimum set is the slicing specified in the slicer 498// subset.setSlicing(slicingR); 499// // stick everything from the right to do with the slices to the subset 500// copySlices(outcome.subset.getSnapshot().getElement(), right.getStructure().getSnapshot().getElement(), right.slices()); 501// } else if (isTypeSlicing(slicingL) || isTypeSlicing(slicingR)) { 502// superset.getSlicing().setRules(SlicingRules.OPEN).setOrdered(false).addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 503// subset.getSlicing().setRules(slicingL.getRules() == SlicingRules.CLOSED || slicingR.getRules() == SlicingRules.CLOSED ? SlicingRules.OPEN : SlicingRules.CLOSED).setOrdered(false).addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 504// 505// // the superset is the union of the types 506// // the subset is the intersection of them 507// List<DefinitionNavigator> handled = new ArrayList<>(); 508// for (DefinitionNavigator t : left.slices()) { 509// DefinitionNavigator r = findMatchingSlice(right.slices(), t); 510// if (r == null) { 511// copySlice(outcome.superset.getSnapshot().getElement(), left.getStructure().getSnapshot().getElement(), t); 512// } else { 513// handled.add(r); 514// ret = compareElements(outcome, path+":"+t.current().getSliceName(), t, r, t.current().getSliceName()) && ret; 515// } 516// } 517// for (DefinitionNavigator t : right.slices()) { 518// if (!handled.contains(t)) { 519// copySlice(outcome.superset.getSnapshot().getElement(), right.getStructure().getSnapshot().getElement(), t); 520// } 521// } 522// } else if (slicingMatches(slicingL, slicingR)) { 523// // if it's the same, we can try matching the slices - though we might have to give up without getting matches correct 524// // there amy be implied consistency we can't reason about 525// throw new DefinitionException("Slicing matches but is not handled yet at "+left.current().getId()+": ("+ProfileUtilities.summarizeSlicing(slicingL)+")"); 526// } else { 527// // if the slicing is different, we can't compare them - or can we? 528// throw new DefinitionException("Slicing doesn't match at "+left.current().getId()+": ("+ProfileUtilities.summarizeSlicing(slicingL)+" / "+ProfileUtilities.summarizeSlicing(slicingR)+")"); 529// } 530// } 531// // todo: name 532// } 533// return ret; 534// 535// // TODO Auto-generated method stub 536// return null; 537 return def; 538 } 539 540 private List<Base> newParents(List<Base> parents, ElementDefinition edr) { 541 List<Base> list = new ArrayList<Base>(); 542 list.addAll(parents); 543 list.add(edr); 544 return list; 545 } 546 547 private boolean compareDiffChildren(String path, DefinitionNavigator left, DefinitionNavigator right, List<Base> parents, ProfileComparison res) throws DefinitionException, IOException, FHIRFormatError { 548 boolean def = false; 549 550 if (left.hasSlices() || right.hasSlices()) { 551 List<DefinitionNavigator> lc = left.slices(); 552 List<DefinitionNavigator> rc = right.slices(); 553 554 List<DefinitionNavigator> matchR = new ArrayList<>(); 555 for (DefinitionNavigator l : lc) { 556 DefinitionNavigator r = findInSliceList(rc, l); 557 if (r == null) { 558 session.markDeleted(realParent(parents), "element", l.current()); 559 res.updateContentState(true); 560 } else { 561 matchR.add(r); 562 def = compareDiff(l.path(), null, l, r, res, parents) || def; 563 } 564 } 565 for (DefinitionNavigator r : rc) { 566 if (!matchR.contains(r)) { 567 session.markAdded(r.current()); 568 res.updateContentState(true); 569 } 570 } 571 } 572 List<DefinitionNavigator> lc = left.children(); 573 List<DefinitionNavigator> rc = right.children(); 574 575 List<DefinitionNavigator> matchR = new ArrayList<>(); 576 for (DefinitionNavigator l : lc) { 577 DefinitionNavigator r = findInList(rc, l); 578 if (r == null) { 579 session.markDeleted(realParent(parents), "element", l.current()); 580 res.updateContentState(true); 581 } else { 582 matchR.add(r); 583 def = compareDiff(l.path(), null, l, r, res, parents) || def; 584 } 585 } 586 for (DefinitionNavigator r : rc) { 587 if (!matchR.contains(r)) { 588 session.markAdded(r.current()); 589 res.updateContentState(true); 590 } 591 } 592 return def; 593 } 594 595 private Base realParent(List<Base> list) { 596 for (int i = list.size() - 1; i >= 1; i--) { 597 if (!list.get(i).hasUserData(UserDataNames.DN_TRANSIENT)) { 598 return list.get(i); 599 } 600 } 601 return list.get(0); 602 } 603 604 private DefinitionNavigator findInSliceList(List<DefinitionNavigator> rc, DefinitionNavigator l) { 605 String s = l.current().getSliceName(); 606 for (DefinitionNavigator t : rc) { 607 String ts = t.current().getSliceName(); 608 if (ts.equals(s)) { 609 return t; 610 } 611 } 612 return null; 613 } 614 private DefinitionNavigator findInList(List<DefinitionNavigator> rc, DefinitionNavigator l) { 615 String s = l.getId(); 616 for (DefinitionNavigator t : rc) { 617 String ts = t.getId(); 618 if (tail(ts).equals(tail(s))) { 619 return t; 620 } 621 } 622 return null; 623 } 624 625 private boolean compareTypes(String path, String sliceName, ElementDefinition left, ElementDefinition right, ProfileComparison res) { 626 boolean def = false; 627 628 List<TypeRefComponent> matchR = new ArrayList<>(); 629 for (TypeRefComponent l : left.getType()) { 630 TypeRefComponent r = findInList(right.getType(), l); 631 if (r == null) { 632 session.markDeleted(right, "type", l); 633 res.updateContentState(true); 634 } else { 635 matchR.add(r); 636 def = compareType(path+".type", l, r, res) || def; 637 } 638 } 639 for (TypeRefComponent r : right.getType()) { 640 if (!matchR.contains(r)) { 641 session.markAdded(r); 642 res.updateContentState(true); 643 } 644 } 645 return def; 646 } 647 648 private TypeRefComponent findInList(List<TypeRefComponent> rc, TypeRefComponent l) { 649 for (TypeRefComponent t : rc) { 650 if (t.getCodeElement().equalsDeep(l.getCodeElement())) { 651 return t; 652 } 653 } 654 return null; 655 } 656 657 658 private boolean compareType(String string, TypeRefComponent l, TypeRefComponent r, ProfileComparison res) { 659 boolean def = false; 660 boolean ch = false; 661 // codes must match 662 ch = comparePrimitivesWithTracking("profile", l.getProfile(), r.getProfile(), null, IssueSeverity.ERROR, null, r) || ch; 663 ch = comparePrimitivesWithTracking("targetProfile", l.getTargetProfile(), r.getTargetProfile(), null, IssueSeverity.ERROR, null, r) || ch; 664 ch = comparePrimitivesWithTracking("aggregation", l.getAggregation(), r.getAggregation(), null, IssueSeverity.ERROR, null, r) || ch; 665 def = comparePrimitivesWithTracking("versioning", l.getVersioningElement(), r.getVersioningElement(), null, IssueSeverity.INFORMATION, null, r) || def; 666 if (ch) { 667 res.updateContentState(true); 668 } 669 return def; 670 } 671 672 673 private boolean compareInvariants(String path, String sliceName, ElementDefinition left, ElementDefinition right, ProfileComparison res) { 674 boolean def = false; 675 676 List<ElementDefinitionConstraintComponent> matchR = new ArrayList<>(); 677 for (ElementDefinitionConstraintComponent l : left.getConstraint()) { 678 ElementDefinitionConstraintComponent r = findInList(right.getConstraint(), l); 679 if (r == null) { 680 session.markDeleted(right, "invariant", l); 681 res.updateContentState(true); 682 } else { 683 matchR.add(r); 684 def = compareInvariant(path+".type", l, r, res) || def; 685 } 686 } 687 for (ElementDefinitionConstraintComponent r : right.getConstraint()) { 688 if (!matchR.contains(r)) { 689 session.markAdded(r); 690 res.updateContentState(true); 691 } 692 } 693 return def; 694 } 695 696 private ElementDefinitionConstraintComponent findInList(List<ElementDefinitionConstraintComponent> rc, ElementDefinitionConstraintComponent l) { 697 for (ElementDefinitionConstraintComponent t : rc) { 698 if (t.getKeyElement().equalsDeep(l.getKeyElement())) { 699 return t; 700 } 701 } 702 return null; 703 } 704 705 706 private boolean compareInvariant(String string, ElementDefinitionConstraintComponent l, ElementDefinitionConstraintComponent r, ProfileComparison res) { 707 boolean def = false; 708 boolean ch = false; 709 // codes must match 710 def = comparePrimitivesWithTracking("requirements", l.getRequirementsElement(), r.getRequirementsElement(), null, IssueSeverity.INFORMATION, null, r) || def; 711 ch = comparePrimitivesWithTracking("severity", l.getSeverityElement(), r.getSeverityElement(), null, IssueSeverity.ERROR, null, r) || ch; 712 comparePrimitivesWithTracking("suppress", l.getSuppressElement(), r.getSuppressElement(), null, IssueSeverity.INFORMATION, null, r); 713 def = comparePrimitivesWithTracking("human", l.getHumanElement(), r.getHumanElement(), null, IssueSeverity.INFORMATION, null, r) || def; 714 ch = comparePrimitivesWithTracking("expression", l.getExpressionElement(), r.getExpressionElement(), null, IssueSeverity.ERROR, null, r) || ch; 715 if (ch) { 716 res.updateContentState(true); 717 } 718 return def; 719 } 720 721// private void ruleEqual(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, DataType vLeft, DataType vRight, String name, String path) throws IOException { 722// if (vLeft == null && vRight == null) { 723// // nothing 724// } else if (vLeft == null) { 725// vm(IssueSeverity.ERROR, "Added "+name, path, comp.getMessages(), res.getMessages()); 726// } else if (vRight == null) { 727// vm(IssueSeverity.ERROR, "Removed "+name, path, comp.getMessages(), res.getMessages()); 728// } else if (!Base.compareDeep(vLeft, vRight, false)) { 729// vm(IssueSeverity.ERROR, name+" must be the same ("+toString(vLeft, true)+"/"+toString(vRight, false)+")", path, comp.getMessages(), res.getMessages()); 730// } 731// } 732// 733 private String toString(DataType val, boolean left) throws IOException { 734 if (val instanceof PrimitiveType) 735 return "'" + ((PrimitiveType) val).getValueAsString()+"'"; 736 737 IParser jp = new JsonParser(); 738 return jp.composeString(val, "value"); 739 } 740 741 private String stripLinks(String s) { 742 while (s.contains("](")) { 743 int i = s.indexOf("]("); 744 int j = s.substring(i).indexOf(")"); 745 if (j == -1) 746 return s; 747 else 748 s = s.substring(0, i+1)+s.substring(i+j+1); 749 } 750 return s; 751 } 752 753 private boolean rule(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, boolean test, String path, String message) { 754 if (!test) { 755 vm(IssueSeverity.ERROR, message, path, comp.getMessages(), res.getMessages()); 756 } 757 return test; 758 } 759 760 private String mergeText(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, String name, String left, String right, boolean isError) { 761 if (left == null && right == null) 762 return null; 763 if (left == null) 764 return right; 765 if (right == null) 766 return left; 767 left = stripLinks(left); 768 right = stripLinks(right); 769 if (left.equalsIgnoreCase(right)) 770 return left; 771 return "left: "+left+"; right: "+right; 772 } 773 774 private List<Coding> mergeCodings(List<Coding> left, List<Coding> right) { 775 List<Coding> result = new ArrayList<Coding>(); 776 result.addAll(left); 777 for (Coding c : right) { 778 boolean found = false; 779 for (Coding ct : left) 780 if (Utilities.equals(c.getSystem(), ct.getSystem()) && Utilities.equals(c.getCode(), ct.getCode())) 781 found = true; 782 if (!found) 783 result.add(c); 784 } 785 return result; 786 } 787 788 private List<StringType> mergeStrings(List<StringType> left, List<StringType> right) { 789 List<StringType> result = new ArrayList<StringType>(); 790 result.addAll(left); 791 for (StringType c : right) { 792 boolean found = false; 793 for (StringType ct : left) 794 if (Utilities.equals(c.getValue(), ct.getValue())) 795 found = true; 796 if (!found) 797 result.add(c); 798 } 799 return result; 800 } 801 802 private List<ElementDefinitionMappingComponent> mergeMappings(List<ElementDefinitionMappingComponent> left, List<ElementDefinitionMappingComponent> right) { 803 List<ElementDefinitionMappingComponent> result = new ArrayList<ElementDefinitionMappingComponent>(); 804 result.addAll(left); 805 for (ElementDefinitionMappingComponent c : right) { 806 boolean found = false; 807 for (ElementDefinitionMappingComponent ct : left) 808 if (Utilities.equals(c.getIdentity(), ct.getIdentity()) && Utilities.equals(c.getLanguage(), ct.getLanguage()) && Utilities.equals(c.getMap(), ct.getMap())) 809 found = true; 810 if (!found) 811 result.add(c); 812 } 813 return result; 814 } 815 816 private int intersectMin(int left, int right) { 817 if (left > right) 818 return left; 819 else 820 return right; 821 } 822 823 private void checkMinMax(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, int leftMin, int rightMin, int leftMax, int rightMax) { 824 if (leftMin != rightMin) { 825 if (leftMin == 0) { 826 vm(IssueSeverity.INFORMATION, "Element minimum cardinalities differ: '"+leftMin+"' vs '"+rightMin+"'", path, comp.getMessages(), res.getMessages()); 827 } else if (rightMin == 0) { 828 vm(IssueSeverity.INFORMATION, "Element minimum cardinalities differ: '"+leftMin+"' vs '"+rightMin+"'", path, comp.getMessages(), res.getMessages()); 829 } else { 830 vm(IssueSeverity.INFORMATION, "Element minimum cardinalities differ: '"+leftMin+"' vs '"+rightMin+"'", path, comp.getMessages(), res.getMessages()); 831 } 832 } 833 if (leftMax != rightMax) { 834 if (leftMax == Integer.MAX_VALUE) { 835 vm(IssueSeverity.INFORMATION, "Element maximum cardinalities differ: '"+leftMax+"' vs '"+rightMax+"'", path, comp.getMessages(), res.getMessages()); 836 } else if (rightMax == Integer.MAX_VALUE) { 837 vm(IssueSeverity.INFORMATION, "Element maximum cardinalities differ: '"+leftMax+"' vs '"+rightMax+"'", path, comp.getMessages(), res.getMessages()); 838 } else { 839 vm(IssueSeverity.INFORMATION, "Element maximum cardinalities differ: '"+leftMax+"' vs '"+rightMax+"'", path, comp.getMessages(), res.getMessages()); 840 } 841 } 842// rule(comp, res, subset.getMax().equals("*") || Integer.parseInt(subset.getMax()) >= subset.getMin(), path, "Cardinality Mismatch: "+card(left)+"/"+card(right)); 843 844 // cross comparison - if max > min in either direction, there can be no instances that are valid against both 845 if (leftMax < rightMin) { 846 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()); 847 } 848 if (rightMax < leftMin) { 849 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()); 850 } 851 } 852 853 private int unionMin(int left, int right) { 854 if (left > right) 855 return right; 856 else 857 return left; 858 } 859 860 private String intersectMax(int l, int r, String left, String right) { 861 if (l < r) 862 return left; 863 else 864 return right; 865 } 866 867 private String unionMax(int l, int r, String left, String right) { 868 if (l < r) 869 return right; 870 else 871 return left; 872 } 873 874 private IntegerType intersectMaxLength(int left, int right) { 875 if (left == 0) 876 left = Integer.MAX_VALUE; 877 if (right == 0) 878 right = Integer.MAX_VALUE; 879 if (left < right) 880 return left == Integer.MAX_VALUE ? null : new IntegerType(left); 881 else 882 return right == Integer.MAX_VALUE ? null : new IntegerType(right); 883 } 884 885 private IntegerType unionMaxLength(int left, int right) { 886 if (left == 0) 887 left = Integer.MAX_VALUE; 888 if (right == 0) 889 right = Integer.MAX_VALUE; 890 if (left < right) 891 return right == Integer.MAX_VALUE ? null : new IntegerType(right); 892 else 893 return left == Integer.MAX_VALUE ? null : new IntegerType(left); 894 } 895 896 private String card(DefinitionNavigator defn) { 897 return Integer.toString(defn.current().getMin())+".."+defn.current().getMax(); 898 } 899 900 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 { 901 List<TypeRefComponent> result = new ArrayList<TypeRefComponent>(); 902 for (TypeRefComponent l : left) 903 checkAddTypeUnion(comp, res, path, result, l, session.getContextLeft(), leftSrc); 904 for (TypeRefComponent r : right) 905 checkAddTypeUnion(comp, res, path, result, r, session.getContextRight(), rightSrc); 906 return result; 907 } 908 909 private void checkAddTypeUnion(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, List<TypeRefComponent> results, TypeRefComponent nw, IWorkerContext ctxt, Resource nwSource) throws DefinitionException, IOException, FHIRFormatError { 910 boolean pfound = false; 911 boolean tfound = false; 912 nw = nw.copy(); 913 for (TypeRefComponent ex : results) { 914 if (Utilities.equals(ex.getWorkingCode(), nw.getWorkingCode())) { 915 for (Enumeration<AggregationMode> a : nw.getAggregation()) { 916 if (!ex.hasAggregation(a.getValue())) { 917 ex.addAggregation(a.getValue()); 918 } 919 } 920 if (!ex.hasProfile() && !nw.hasProfile()) 921 pfound = true; 922 else if (!ex.hasProfile()) { 923 pfound = true; 924 } else if (!nw.hasProfile()) { 925 pfound = true; 926 ex.setProfile(null); 927 } else { 928 // both have profiles. Is one derived from the other? 929 StructureDefinition sdex = ((IWorkerContext) ex.getUserData(UserDataNames.COMP_CONTEXT)).fetchResource(StructureDefinition.class, ex.getProfile().get(0).getValue(), nwSource); 930 StructureDefinition sdnw = ctxt.fetchResource(StructureDefinition.class, nw.getProfile().get(0).getValue(), nwSource); 931 if (sdex != null && sdnw != null) { 932 if (sdex.getUrl().equals(sdnw.getUrl())) { 933 pfound = true; 934 } else if (derivesFrom(sdex, sdnw, ((IWorkerContext) ex.getUserData(UserDataNames.COMP_CONTEXT)))) { 935 ex.setProfile(nw.getProfile()); 936 pfound = true; 937 } else if (derivesFrom(sdnw, sdex, ctxt)) { 938 pfound = true; 939 } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) { 940 ProfileComparison compP = (ProfileComparison) session.compare(sdex, sdnw); 941 if (compP != null && compP.getUnion() != null) { // might be null if circular 942 pfound = true; 943 ex.addProfile("#"+compP.getId()); 944 } 945 } 946 } 947 } 948 if (!ex.hasTargetProfile() && !nw.hasTargetProfile()) 949 tfound = true; 950 else if (!ex.hasTargetProfile()) { 951 tfound = true; 952 } else if (!nw.hasTargetProfile()) { 953 tfound = true; 954 ex.setTargetProfile(null); 955 } else { 956 // both have profiles. Is one derived from the other? 957 StructureDefinition sdex = ((IWorkerContext) ex.getUserData(UserDataNames.COMP_CONTEXT)).fetchResource(StructureDefinition.class, ex.getTargetProfile().get(0).getValue(), nwSource); 958 StructureDefinition sdnw = ctxt.fetchResource(StructureDefinition.class, nw.getTargetProfile().get(0).getValue(), nwSource); 959 if (sdex != null && sdnw != null) { 960 if (matches(sdex, sdnw)) { 961 tfound = true; 962 } else if (derivesFrom(sdex, sdnw, ((IWorkerContext) ex.getUserData(UserDataNames.COMP_CONTEXT)))) { 963 ex.setTargetProfile(nw.getTargetProfile()); 964 tfound = true; 965 } else if (derivesFrom(sdnw, sdex, ctxt)) { 966 tfound = true; 967 } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) { 968 ResourceComparison cmp = session.compare(sdex, sdnw); 969 if (cmp instanceof ProfileComparison) { 970 ProfileComparison compP = (ProfileComparison) cmp; 971 if (compP.getUnion() != null) { 972 tfound = true; 973 ex.addTargetProfile("#"+compP.getId()); 974 } 975 } else { 976 // ? 977 } 978 } 979 } 980 } 981 } 982 } 983 if (!tfound || !pfound) { 984 nw.setUserData(UserDataNames.COMP_CONTEXT, ctxt); 985 results.add(nw); 986 } 987 } 988 989 private boolean matches(StructureDefinition s1, StructureDefinition s2) { 990 if (!s1.getUrl().equals(s2.getUrl())) { 991 return false; 992 } 993 if (s1.getDerivation() == TypeDerivationRule.SPECIALIZATION && s2.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 994 return true; // arbitrary; we're just not interested in pursuing cross version differences 995 } 996 if (s1.hasVersion()) { 997 return s1.getVersion().equals(s2.getVersion()); 998 } else { 999 return !s2.hasVersion(); 1000 } 1001 } 1002 1003 private boolean derivesFrom(StructureDefinition left, StructureDefinition right, IWorkerContext ctxt) { 1004 StructureDefinition sd = left; 1005 while (sd != null) { 1006 if (right.getUrl().equals(sd.getBaseDefinition())) { 1007 return true; 1008 } 1009 sd = sd.hasBaseDefinition() ? ctxt.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd) : null; 1010 } 1011 return false; 1012 } 1013 1014 private Collection<? extends TypeRefComponent> intersectTypes(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, ElementDefinition ed, String path, List<TypeRefComponent> left, List<TypeRefComponent> right) throws DefinitionException, IOException, FHIRFormatError { 1015 List<TypeRefComponent> result = new ArrayList<TypeRefComponent>(); 1016 for (TypeRefComponent l : left) { 1017 boolean pfound = false; 1018 boolean tfound = false; 1019 TypeRefComponent c = l.copy(); 1020 for (TypeRefComponent r : right) { 1021 if (!l.hasProfile() && !r.hasProfile()) { 1022 pfound = true; 1023 } else if (!r.hasProfile()) { 1024 pfound = true; 1025 } else if (!l.hasProfile()) { 1026 pfound = true; 1027 c.setProfile(r.getProfile()); 1028 } else { 1029 StructureDefinition sdl = resolveProfile(comp, res, path, l.getProfile().get(0).getValue(), comp.getLeft().getName(), session.getContextLeft(), comp.getLeft()); 1030 StructureDefinition sdr = resolveProfile(comp, res, path, r.getProfile().get(0).getValue(), comp.getRight().getName(), session.getContextRight(), comp.getRight()); 1031 if (sdl != null && sdr != null) { 1032 if (sdl == sdr) { 1033 pfound = true; 1034 } else if (derivesFrom(sdl, sdr, session.getContextLeft())) { 1035 pfound = true; 1036 } else if (derivesFrom(sdr, sdl, session.getContextRight())) { 1037 c.setProfile(r.getProfile()); 1038 pfound = true; 1039 } else if (sdl.getType().equals(sdr.getType())) { 1040 ResourceComparison cmp = session.compare(sdl, sdr); 1041 if (cmp instanceof ProfileComparison) { 1042 ProfileComparison compP = (ProfileComparison) cmp; 1043 if (compP != null && compP.getIntersection() != null) { 1044 pfound = true; 1045 c.addProfile("#"+compP.getId()); 1046 } 1047 } else { 1048 // not sure how to handle this error? 1049 } 1050 } 1051 } 1052 } 1053 if (!l.hasTargetProfile() && !r.hasTargetProfile()) { 1054 tfound = true; 1055 } else if (!r.hasTargetProfile()) { 1056 tfound = true; 1057 } else if (!l.hasTargetProfile()) { 1058 tfound = true; 1059 c.setTargetProfile(r.getTargetProfile()); 1060 } else { 1061 StructureDefinition sdl = resolveProfile(comp, res, path, l.getTargetProfile().get(0).getValue(), comp.getLeft().getName(), session.getContextLeft(), comp.getLeft()); 1062 StructureDefinition sdr = resolveProfile(comp, res, path, r.getTargetProfile().get(0).getValue(), comp.getRight().getName(), session.getContextRight(), comp.getRight()); 1063 if (sdl != null && sdr != null) { 1064 if (matches(sdl, sdr)) { 1065 tfound = true; 1066 } else if (derivesFrom(sdl, sdr, session.getContextLeft())) { 1067 tfound = true; 1068 } else if (derivesFrom(sdr, sdl, session.getContextRight())) { 1069 c.setTargetProfile(r.getTargetProfile()); 1070 tfound = true; 1071 } else if (sdl.getType().equals(sdr.getType())) { 1072 ProfileComparison compP = (ProfileComparison) session.compare(sdl, sdr); 1073 if (compP != null && compP.getIntersection() != null) { 1074 tfound = true; 1075 c.addTargetProfile("#"+compP.getId()); 1076 } 1077 } 1078 } 1079 } 1080 if (pfound && tfound) { 1081 for (Enumeration<AggregationMode> a : l.getAggregation()) { 1082 if (!r.hasAggregation(a.getValue())) { 1083 c.getAggregation().removeIf(n -> n.getValue() == a.getValue()); 1084 } 1085 } 1086 } 1087 } 1088 if (pfound && tfound) { 1089 result.add(c); 1090 } 1091 } 1092 return result; 1093 } 1094 1095 private String typeCode(DefinitionNavigator defn) { 1096 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1097 for (TypeRefComponent t : defn.current().getType()) 1098 b.append(t.getWorkingCode()+(t.hasProfile() ? "("+t.getProfile()+")" : "")+(t.hasTargetProfile() ? "("+t.getTargetProfile()+")" : "")); // todo: other properties 1099 return b.toString(); 1100 } 1101 1102 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 { 1103 assert(lDef.hasBinding() || rDef.hasBinding()); 1104 if (!lDef.hasBinding()) { 1105 subset.setBinding(rDef.getBinding()); 1106 // technically, the super set is unbound, but that's not very useful - so we use the provided on as an example 1107 superset.setBinding(rDef.getBinding().copy()); 1108 superset.getBinding().setStrength(BindingStrength.EXAMPLE); 1109 return true; 1110 } 1111 if (!rDef.hasBinding()) { 1112 subset.setBinding(lDef.getBinding()); 1113 superset.setBinding(lDef.getBinding().copy()); 1114 superset.getBinding().setStrength(BindingStrength.EXAMPLE); 1115 return true; 1116 } 1117 ElementDefinitionBindingComponent left = lDef.getBinding(); 1118 ElementDefinitionBindingComponent right = rDef.getBinding(); 1119 if (Base.compareDeep(left, right, false)) { 1120 subset.setBinding(left); 1121 superset.setBinding(right); 1122 } 1123 1124 // if they're both examples/preferred then: 1125 // subset: left wins if they're both the same 1126 // superset: 1127 if (isPreferredOrExample(left) && isPreferredOrExample(right)) { 1128 if (right.getStrength() == BindingStrength.PREFERRED && left.getStrength() == BindingStrength.EXAMPLE && !Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) { 1129 vm(IssueSeverity.INFORMATION, "Example/preferred bindings differ at "+path+" using binding from "+comp.getRight().getName(), path, comp.getMessages(), res.getMessages()); 1130 subset.setBinding(right); 1131 superset.setBinding(unionBindings(comp, res, path, left, right, leftSrc, rightSrc)); 1132 } else { 1133 if ((right.getStrength() != BindingStrength.EXAMPLE || left.getStrength() != BindingStrength.EXAMPLE) && !Base.compareDeep(left.getValueSet(), right.getValueSet(), false) ) { 1134 vm(IssueSeverity.INFORMATION, "Example/preferred bindings differ at "+path+" using binding from "+comp.getLeft().getName(), path, comp.getMessages(), res.getMessages()); 1135 } 1136 subset.setBinding(left); 1137 superset.setBinding(unionBindings(comp, res, path, left, right, leftSrc, rightSrc)); 1138 } 1139 return true; 1140 } 1141 // if either of them are extensible/required, then it wins 1142 if (isPreferredOrExample(left)) { 1143 subset.setBinding(right); 1144 superset.setBinding(unionBindings(comp, res, path, left, right, leftSrc, rightSrc)); 1145 return true; 1146 } 1147 if (isPreferredOrExample(right)) { 1148 subset.setBinding(left); 1149 superset.setBinding(unionBindings(comp, res, path, left, right, leftSrc, rightSrc)); 1150 return true; 1151 } 1152 1153 // ok, both are extensible or required. 1154 ElementDefinitionBindingComponent subBinding = new ElementDefinitionBindingComponent(); 1155 subset.setBinding(subBinding); 1156 ElementDefinitionBindingComponent superBinding = new ElementDefinitionBindingComponent(); 1157 superset.setBinding(superBinding); 1158 subBinding.setDescription(mergeText(comp, res, path, "description", left.getDescription(), right.getDescription(), false)); 1159 superBinding.setDescription(mergeText(comp, res, path, "description", left.getDescription(), right.getDescription(), false)); 1160 if (left.getStrength() == BindingStrength.REQUIRED || right.getStrength() == BindingStrength.REQUIRED) 1161 subBinding.setStrength(BindingStrength.REQUIRED); 1162 else 1163 subBinding.setStrength(BindingStrength.EXTENSIBLE); 1164 if (left.getStrength() == BindingStrength.EXTENSIBLE || right.getStrength() == BindingStrength.EXTENSIBLE) 1165 superBinding.setStrength(BindingStrength.EXTENSIBLE); 1166 else 1167 superBinding.setStrength(BindingStrength.REQUIRED); 1168 1169 if (Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) { 1170 subBinding.setValueSet(left.getValueSet()); 1171 superBinding.setValueSet(left.getValueSet()); 1172 return true; 1173 } else if (!left.hasValueSet()) { 1174 vm(IssueSeverity.ERROR, "No left Value set at "+path, path, comp.getMessages(), res.getMessages()); 1175 return true; 1176 } else if (!right.hasValueSet()) { 1177 vm(IssueSeverity.ERROR, "No right Value set at "+path, path, comp.getMessages(), res.getMessages()); 1178 return true; 1179 } else { 1180 // ok, now we compare the value sets. This may be unresolvable. 1181 ValueSet lvs = resolveVS(comp.getLeft(), left.getValueSet(), leftSrc, session.getContextLeft()); 1182 ValueSet rvs = resolveVS(comp.getRight(), right.getValueSet(), rightSrc, session.getContextRight()); 1183 if (lvs == null) { 1184 vm(IssueSeverity.ERROR, "Unable to resolve left value set "+left.getValueSet().toString()+" at "+path, path, comp.getMessages(), res.getMessages()); 1185 return true; 1186 } else if (rvs == null) { 1187 vm(IssueSeverity.ERROR, "Unable to resolve right value set "+right.getValueSet().toString()+" at "+path, path, comp.getMessages(), res.getMessages()); 1188 return true; 1189 } else if (sameValueSets(lvs, rvs)) { 1190 subBinding.setValueSet(lvs.getUrl()); 1191 superBinding.setValueSet(lvs.getUrl()); 1192 } else { 1193 ValueSetComparison compP = (ValueSetComparison) session.compare(lvs, rvs); 1194 if (compP != null) { 1195 subBinding.setValueSet(compP.getIntersection().getUrl()); 1196 superBinding.setValueSet(compP.getUnion().getUrl()); 1197 } 1198 } 1199 } 1200 return false; 1201 } 1202 1203 private boolean sameValueSets(ValueSet lvs, ValueSet rvs) { 1204 if (!lvs.getUrl().equals(rvs.getUrl())) { 1205 return false; 1206 } 1207 if (isCore(lvs) && isCore(rvs)) { 1208 return true; 1209 } 1210 if (lvs.hasVersion()) { 1211 if (!lvs.getVersion().equals(rvs.getVersion())) { 1212 return false; 1213 } else if (!rvs.hasVersion()) { 1214 return false; 1215 } 1216 } 1217 return true; 1218 } 1219 1220 private boolean isCore(ValueSet vs) { 1221 return vs.getUrl().startsWith("http://hl7.org/fhir/ValueSet"); 1222 } 1223 1224 private List<ElementDefinitionConstraintComponent> intersectConstraints(String path, List<ElementDefinitionConstraintComponent> left, List<ElementDefinitionConstraintComponent> right) { 1225 List<ElementDefinitionConstraintComponent> result = new ArrayList<ElementDefinitionConstraintComponent>(); 1226 for (ElementDefinitionConstraintComponent l : left) { 1227 boolean found = false; 1228 for (ElementDefinitionConstraintComponent r : right) 1229 if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getExpression(), l.getExpression()) && r.getSeverity() == l.getSeverity())) 1230 found = true; 1231 if (found) 1232 result.add(l); 1233 } 1234 return result; 1235 } 1236 1237 // we can't really know about constraints. We create warnings, and collate them 1238 private List<ElementDefinitionConstraintComponent> unionConstraints(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, List<ElementDefinitionConstraintComponent> left, List<ElementDefinitionConstraintComponent> right) { 1239 List<ElementDefinitionConstraintComponent> result = new ArrayList<ElementDefinitionConstraintComponent>(); 1240 for (ElementDefinitionConstraintComponent l : left) { 1241 boolean found = false; 1242 for (ElementDefinitionConstraintComponent r : right) 1243 if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getExpression(), l.getExpression()) && r.getSeverity() == l.getSeverity())) 1244 found = true; 1245 if (!found) { 1246 if (!Utilities.existsInList(l.getExpression(), "hasValue() or (children().count() > id.count())", "extension.exists() != value.exists()")) { 1247 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()); 1248 } 1249 } 1250 result.add(l); 1251 } 1252 for (ElementDefinitionConstraintComponent r : right) { 1253 boolean found = false; 1254 for (ElementDefinitionConstraintComponent l : left) 1255 if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getExpression(), l.getExpression()) && r.getSeverity() == l.getSeverity())) 1256 found = true; 1257 if (!found) { 1258 if (!Utilities.existsInList(r.getExpression(), "hasValue() or (children().count() > id.count())", "extension.exists() != value.exists()")) { 1259 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()); 1260 } 1261 } 1262 } 1263 return result; 1264 } 1265 1266 private StructureDefinition resolveProfile(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, String url, String name, IWorkerContext ctxt, Resource urlSource) { 1267 StructureDefinition sd = ctxt.fetchResource(StructureDefinition.class, url, urlSource); 1268 if (sd == null) { 1269 ValidationMessage vm = vmI(IssueSeverity.WARNING, "Unable to resolve profile "+url+" in profile "+name, path); 1270 } 1271 return sd; 1272 } 1273 1274 private boolean isPreferredOrExample(ElementDefinitionBindingComponent binding) { 1275 return binding.getStrength() == BindingStrength.EXAMPLE || binding.getStrength() == BindingStrength.PREFERRED; 1276 } 1277 1278 private ElementDefinitionBindingComponent unionBindings(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, ElementDefinitionBindingComponent left, ElementDefinitionBindingComponent right, Resource leftSrc, Resource rightSrc) throws FHIRFormatError, DefinitionException, IOException { 1279 ElementDefinitionBindingComponent union = new ElementDefinitionBindingComponent(); 1280 if (left.getStrength().compareTo(right.getStrength()) < 0) 1281 union.setStrength(left.getStrength()); 1282 else 1283 union.setStrength(right.getStrength()); 1284 union.setDescription(mergeText(comp, res, path, "binding.description", left.getDescription(), right.getDescription(), false)); 1285 if (Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) 1286 union.setValueSet(left.getValueSet()); 1287 else { 1288 ValueSet lvs = resolveVS(comp.getLeft(), left.getValueSet(), leftSrc, session.getContextLeft()); 1289 ValueSet rvs = resolveVS(comp.getRight(), right.getValueSet(), rightSrc, session.getContextRight()); 1290 if (lvs != null && rvs != null) { 1291 ValueSetComparison compP = (ValueSetComparison) session.compare(lvs, rvs); 1292 if (compP != null) { 1293 union.setValueSet(compP.getUnion().getUrl()); 1294 } 1295 } else if (lvs != null) { 1296 union.setValueSet(lvs.getUrl()); 1297 } else if (rvs != null) { 1298 union.setValueSet(rvs.getUrl()); 1299 } 1300 } 1301 return union; 1302 } 1303 1304 private ValueSet resolveVS(StructureDefinition ctxtLeft, String vsRef, Resource src, IWorkerContext ctxt) { 1305 if (vsRef == null) 1306 return null; 1307 return ctxt.fetchResource(ValueSet.class, vsRef, src); 1308 } 1309 1310 public XhtmlNode renderStructure(ProfileComparison comp, String id, String prefix, String corePath) throws FHIRException, IOException { 1311 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(session.getI18n(), Utilities.path("[tmp]", "compare"), false, true, "cmp"); 1312 TableModel model = gen.initComparisonTable(corePath, id); 1313 genElementComp(null /* come back to this later */, null /* come back to this later */, gen, model.getRows(), comp.combined, corePath, prefix, null, true); 1314 return gen.generate(model, prefix, 0, null); 1315 } 1316 1317 public XhtmlNode renderUnion(ProfileComparison comp, String id, String prefix, String corePath) throws FHIRException, IOException { 1318 StructureDefinitionRenderer sdr = new StructureDefinitionRenderer(new RenderingContext(utilsLeft.getContext(), null, utilsLeft.getTerminologyServiceOptions(), corePath, prefix, utilsLeft.getContext().getLocale(), ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER).setPkp(this)); 1319 return sdr.generateTable(new RenderingStatus(), corePath, comp.union, false, prefix, false, id, true, corePath, prefix, false, true, null, false, sdr.getContext().withUniqueLocalPrefix("u"), "u", null, "C1"); 1320 } 1321 1322 1323 public XhtmlNode renderIntersection(ProfileComparison comp, String id, String prefix, String corePath) throws FHIRException, IOException { 1324 StructureDefinitionRenderer sdr = new StructureDefinitionRenderer(new RenderingContext(utilsLeft.getContext(), null, utilsLeft.getTerminologyServiceOptions(), corePath, prefix, utilsLeft.getContext().getLocale(), ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER).setPkp(this)); 1325 return sdr.generateTable(new RenderingStatus(), corePath, comp.intersection, false, prefix, false, id, true, corePath, prefix, false, true, null, false, sdr.getContext().withUniqueLocalPrefix("i"), "i", null, "C2"); 1326 } 1327 1328 private void genElementComp(String defPath, String anchorPrefix, HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<ElementDefinitionNode> combined, String corePath, String prefix, Row slicingRow, boolean root) throws IOException { 1329 Row originalRow = slicingRow; 1330 Row typesRow = null; 1331 1332 List<StructuralMatch<ElementDefinitionNode>> children = combined.getChildren(); 1333 1334 Row row = gen.new Row(); 1335 rows.add(row); 1336 String path = combined.either().getDef().getPath(); 1337 row.setAnchor(path); 1338 row.setColor(utilsRight.getRowColor(combined.either().getDef(), false)); 1339 if (eitherHasSlicing(combined)) 1340 row.setLineColor(1); 1341 else if (eitherHasSliceName(combined)) 1342 row.setLineColor(2); 1343 else 1344 row.setLineColor(0); 1345 boolean ext = false; 1346 if (tail(path).equals("extension")) { 1347 if (elementIsComplex(combined)) 1348 row.setIcon("icon_extension_complex.png", session.getI18n().formatPhrase(RenderingContext.TEXT_ICON_EXTENSION_COMPLEX)); 1349 else 1350 row.setIcon("icon_extension_simple.png", session.getI18n().formatPhrase(RenderingContext.TEXT_ICON_EXTENSION_SIMPLE)); 1351 ext = true; 1352 } else if (tail(path).equals("modifierExtension")) { 1353 if (elementIsComplex(combined)) 1354 row.setIcon("icon_modifier_extension_complex.png", session.getI18n().formatPhrase(RenderingContext.TEXT_ICON_EXTENSION_COMPLEX)); 1355 else 1356 row.setIcon("icon_modifier_extension_simple.png", session.getI18n().formatPhrase(RenderingContext.TEXT_ICON_EXTENSION_SIMPLE)); 1357 } else if (hasChoice(combined)) { 1358 if (allAreReference(combined)) 1359 row.setIcon("icon_reference.png", session.getI18n().formatPhrase(RenderingContext.TEXT_ICON_REFERENCE)); 1360 else { 1361 row.setIcon("icon_choice.gif", session.getI18n().formatPhrase(RenderingContext.TEXT_ICON_CHOICE)); 1362 typesRow = row; 1363 } 1364 } else if (combined.either().getDef().hasContentReference()) 1365 row.setIcon("icon_reuse.png", session.getI18n().formatPhrase(RenderingContext.TEXT_ICON_REUSE)); 1366 else if (isPrimitive(combined)) 1367 row.setIcon("icon_primitive.png", session.getI18n().formatPhrase(RenderingContext.TEXT_ICON_PRIMITIVE)); 1368 else if (hasTarget(combined)) 1369 row.setIcon("icon_reference.png", session.getI18n().formatPhrase(RenderingContext.TEXT_ICON_REFERENCE)); 1370 else if (isDataType(combined)) 1371 row.setIcon("icon_datatype.gif", session.getI18n().formatPhrase(RenderingContext.TEXT_ICON_DATATYPE)); 1372 else 1373 row.setIcon("icon_resource.png", session.getI18n().formatPhrase(RenderingContext.GENERAL_RESOURCE)); 1374 String ref = defPath == null ? null : defPath + combined.either().getDef().getId(); 1375 String sName = tail(path); 1376 String sn = getSliceName(combined); 1377 if (sn != null) 1378 sName = sName +":"+sn; 1379 StructureDefinitionRenderer.UnusedTracker used = new StructureDefinitionRenderer.UnusedTracker(); 1380 StructureDefinitionRenderer sdrLeft = new StructureDefinitionRenderer(new RenderingContext(utilsLeft.getContext(), null, utilsLeft.getTerminologyServiceOptions(), corePath, prefix, utilsLeft.getContext().getLocale(), ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER).setPkp(this)); 1381 StructureDefinitionRenderer sdrRight= new StructureDefinitionRenderer(new RenderingContext(utilsRight.getContext(), null, utilsRight.getTerminologyServiceOptions(), corePath, prefix, utilsRight.getContext().getLocale(), ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER).setPkp(this)); 1382 1383 1384 1385 Cell nc; 1386 String leftColor = !combined.hasLeft() ? COLOR_NO_ROW_LEFT : combined.hasErrors() ? COLOR_DIFFERENT : null; 1387 String rightColor = !combined.hasRight() ? COLOR_NO_ROW_LEFT : combined.hasErrors() ? COLOR_DIFFERENT : null; 1388 if (combined.hasLeft()) { 1389 nc = sdrLeft.genElementNameCell(gen, combined.getLeft().getDef(), "??", true, corePath, prefix, root, false, false, combined.getLeft().getSrc(), typesRow, row, false, ext, used , ref, sName, null); 1390 } else { 1391 nc = sdrRight.genElementNameCell(gen, combined.getRight().getDef(), "??", true, corePath, prefix, root, false, false, combined.getRight().getSrc(), typesRow, row, false, ext, used , ref, sName, null); 1392 } 1393 if (combined.hasLeft()) { 1394 frame(sdrLeft.genElementCells(new RenderingStatus(), gen, combined.getLeft().getDef(), "??", true, corePath, prefix, root, false, false, combined.getLeft().getSrc(), typesRow, row, true, ext, used , ref, nc, false, false, sdrLeft.getContext(), children.size() > 0, defPath, anchorPrefix, new ArrayList<ElementDefinition>(), null), leftColor); 1395 } else { 1396 frame(spacers(row, 4, gen), leftColor); 1397 } 1398 if (combined.hasRight()) { 1399 frame(sdrRight.genElementCells(new RenderingStatus(), gen, combined.getRight().getDef(), "??", true, corePath, prefix, root, false, false, combined.getRight().getSrc(), typesRow, row, true, ext, used, ref, nc, false, false, sdrRight.getContext(), children.size() > 0, defPath, anchorPrefix, new ArrayList<ElementDefinition>(), null), rightColor); 1400 } else { 1401 frame(spacers(row, 4, gen), rightColor); 1402 } 1403 row.getCells().add(cellForMessages(gen, combined.getMessages())); 1404 1405 for (StructuralMatch<ElementDefinitionNode> child : children) { 1406 genElementComp(defPath, anchorPrefix, gen, row.getSubRows(), child, corePath, prefix, originalRow, false); 1407 } 1408 } 1409 1410 private void frame(List<Cell> cells, String color) { 1411 for (Cell cell : cells) { 1412 if (color != null) { 1413 cell.setStyle("background-color: "+color); 1414 } 1415 } 1416 cells.get(0).setStyle("border-left: 1px grey solid"+(color == null ? "" : "; background-color: "+color)); 1417 cells.get(cells.size()-1).setStyle("border-right: 1px grey solid"+(color == null ? "" : "; background-color: "+color)); 1418 } 1419 1420 private List<Cell> spacers(Row row, int count, HierarchicalTableGenerator gen) { 1421 List<Cell> res = new ArrayList<>(); 1422 for (int i = 0; i < count; i++) { 1423 Cell c = gen.new Cell(); 1424 res.add(c); 1425 row.getCells().add(c); 1426 } 1427 return res; 1428 } 1429 1430 private String getSliceName(StructuralMatch<ElementDefinitionNode> combined) { 1431 // TODO Auto-generated method stub 1432 return null; 1433 } 1434 1435 private boolean isDataType(StructuralMatch<ElementDefinitionNode> combined) { 1436 // TODO Auto-generated method stub 1437 return false; 1438 } 1439 1440 private boolean hasTarget(StructuralMatch<ElementDefinitionNode> combined) { 1441 // TODO Auto-generated method stub 1442 return false; 1443 } 1444 1445 private boolean isPrimitive(StructuralMatch<ElementDefinitionNode> combined) { 1446 // TODO Auto-generated method stub 1447 return false; 1448 } 1449 1450 private boolean allAreReference(StructuralMatch<ElementDefinitionNode> combined) { 1451 // TODO Auto-generated method stub 1452 return false; 1453 } 1454 1455 private boolean hasChoice(StructuralMatch<ElementDefinitionNode> combined) { 1456 // TODO Auto-generated method stub 1457 return false; 1458 } 1459 1460 private boolean elementIsComplex(StructuralMatch<ElementDefinitionNode> combined) { 1461 // TODO Auto-generated method stub velement.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue() 1462 return false; 1463 } 1464 1465 private boolean eitherHasSliceName(StructuralMatch<ElementDefinitionNode> combined) { 1466 // TODO Auto-generated method stub 1467 return false; 1468 } 1469 1470 private boolean eitherHasSlicing(StructuralMatch<ElementDefinitionNode> combined) { 1471 // TODO Auto-generated method stub 1472 return false; 1473 } 1474 1475 1476 1477 1478private String tail(String path) { 1479 if (path.contains(".")) 1480 return path.substring(path.lastIndexOf('.')+1); 1481 else 1482 return path; 1483} 1484 1485@Override 1486public boolean isDatatype(String typeSimple) { 1487 // TODO Auto-generated method stub 1488 return false; 1489} 1490 1491@Override 1492public boolean isPrimitiveType(String typeSimple) { 1493 // TODO Auto-generated method stub 1494 return false; 1495} 1496 1497@Override 1498public boolean isResource(String typeSimple) { 1499// return false; 1500 throw new NotImplementedError(); 1501} 1502 1503@Override 1504public boolean hasLinkFor(String typeSimple) { 1505 return false; 1506} 1507 1508@Override 1509public String getLinkFor(String corePath, String typeSimple) { 1510 return "??|??"; 1511} 1512 1513@Override 1514public BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, String path) 1515 throws FHIRException { 1516 return new BindingResolution("??", "??"); 1517} 1518 1519@Override 1520public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException { 1521 return new BindingResolution("??", "??"); 1522} 1523 1524@Override 1525public String getLinkForProfile(StructureDefinition profile, String url) { 1526 return "??|??"; 1527} 1528 1529@Override 1530public boolean prependLinks() { 1531 return false; 1532} 1533 1534@Override 1535public String getLinkForUrl(String corePath, String s) { 1536 return null; 1537} 1538 1539@Override 1540public String getCanonicalForDefaultContext() { 1541 // TODO Auto-generated method stub 1542 return null; 1543} 1544 1545@Override 1546public String getDefinitionsName(Resource r) { 1547 // TODO Auto-generated method stub 1548 return null; 1549} 1550 1551 1552}