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