001package org.hl7.fhir.r4.conformance; 002 003import java.io.IOException; 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.Collections; 007import java.util.HashMap; 008import java.util.List; 009import java.util.Map; 010 011import org.hl7.fhir.r4.context.IWorkerContext; 012import org.hl7.fhir.r4.formats.IParser; 013import org.hl7.fhir.r4.model.Base; 014import org.hl7.fhir.r4.model.Coding; 015import org.hl7.fhir.r4.model.ElementDefinition; 016import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent; 017import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionConstraintComponent; 018import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionMappingComponent; 019import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 020import org.hl7.fhir.r4.model.Enumerations.BindingStrength; 021import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; 022import org.hl7.fhir.r4.model.IntegerType; 023import org.hl7.fhir.r4.model.PrimitiveType; 024import org.hl7.fhir.r4.model.Reference; 025import org.hl7.fhir.r4.model.StringType; 026import org.hl7.fhir.r4.model.StructureDefinition; 027import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; 028import org.hl7.fhir.r4.model.Type; 029import org.hl7.fhir.r4.model.UriType; 030import org.hl7.fhir.r4.model.ValueSet; 031import org.hl7.fhir.r4.model.ValueSet.ConceptReferenceComponent; 032import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; 033import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; 034import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 035import org.hl7.fhir.r4.utils.DefinitionNavigator; 036import org.hl7.fhir.r4.utils.ToolingExtensions; 037import org.hl7.fhir.exceptions.DefinitionException; 038import org.hl7.fhir.exceptions.FHIRFormatError; 039import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 040import org.hl7.fhir.utilities.Utilities; 041import org.hl7.fhir.utilities.validation.ValidationMessage; 042import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 043 044/** 045 * A engine that generates difference analysis between two sets of structure 046 * definitions, typically from 2 different implementation guides. 047 * 048 * How this class works is that you create it with access to a bunch of underying 049 * resources that includes all the structure definitions from both implementation 050 * guides 051 * 052 * Once the class is created, you repeatedly pass pairs of structure definitions, 053 * one from each IG, building up a web of difference analyses. This class will 054 * automatically process any internal comparisons that it encounters 055 * 056 * When all the comparisons have been performed, you can then generate a variety 057 * of output formats 058 * 059 * @author Grahame Grieve 060 * 061 */ 062public class ProfileComparer { 063 064 private IWorkerContext context; 065 066 public ProfileComparer(IWorkerContext context) { 067 super(); 068 this.context = context; 069 } 070 071 private static final int BOTH_NULL = 0; 072 private static final int EITHER_NULL = 1; 073 074 public class ProfileComparison { 075 private String id; 076 /** 077 * the first of two structures that were compared to generate this comparison 078 * 079 * In a few cases - selection of example content and value sets - left gets 080 * preference over right 081 */ 082 private StructureDefinition left; 083 084 /** 085 * the second of two structures that were compared to generate this comparison 086 * 087 * In a few cases - selection of example content and value sets - left gets 088 * preference over right 089 */ 090 private StructureDefinition right; 091 092 093 public String getId() { 094 return id; 095 } 096 private String leftName() { 097 return left.getName(); 098 } 099 private String rightName() { 100 return right.getName(); 101 } 102 103 /** 104 * messages generated during the comparison. There are 4 grades of messages: 105 * information - a list of differences between structures 106 * warnings - notifies that the comparer is unable to fully compare the structures (constraints differ, open value sets) 107 * errors - where the structures are incompatible 108 * fatal errors - some error that prevented full analysis 109 * 110 * @return 111 */ 112 private List<ValidationMessage> messages = new ArrayList<ValidationMessage>(); 113 114 /** 115 * The structure that describes all instances that will conform to both structures 116 */ 117 private StructureDefinition subset; 118 119 /** 120 * The structure that describes all instances that will conform to either structures 121 */ 122 private StructureDefinition superset; 123 124 public StructureDefinition getLeft() { 125 return left; 126 } 127 128 public StructureDefinition getRight() { 129 return right; 130 } 131 132 public List<ValidationMessage> getMessages() { 133 return messages; 134 } 135 136 public StructureDefinition getSubset() { 137 return subset; 138 } 139 140 public StructureDefinition getSuperset() { 141 return superset; 142 } 143 144 private boolean ruleEqual(String path, ElementDefinition ed, String vLeft, String vRight, String description, boolean nullOK) { 145 if (vLeft == null && vRight == null && nullOK) 146 return true; 147 if (vLeft == null && vRight == null) { 148 messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, description+" and not null (null/null)", ValidationMessage.IssueSeverity.ERROR)); 149 if (ed != null) 150 status(ed, ProfileUtilities.STATUS_ERROR); 151 } 152 if (vLeft == null || !vLeft.equals(vRight)) { 153 messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, description+" ("+vLeft+"/"+vRight+")", ValidationMessage.IssueSeverity.ERROR)); 154 if (ed != null) 155 status(ed, ProfileUtilities.STATUS_ERROR); 156 } 157 return true; 158 } 159 160 private boolean ruleCompares(ElementDefinition ed, Type vLeft, Type vRight, String path, int nullStatus) throws IOException { 161 if (vLeft == null && vRight == null && nullStatus == BOTH_NULL) 162 return true; 163 if (vLeft == null && vRight == null) { 164 messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Must be the same and not null (null/null)", ValidationMessage.IssueSeverity.ERROR)); 165 status(ed, ProfileUtilities.STATUS_ERROR); 166 } 167 if (vLeft == null && nullStatus == EITHER_NULL) 168 return true; 169 if (vRight == null && nullStatus == EITHER_NULL) 170 return true; 171 if (vLeft == null || vRight == null || !Base.compareDeep(vLeft, vRight, false)) { 172 messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Must be the same ("+toString(vLeft)+"/"+toString(vRight)+")", ValidationMessage.IssueSeverity.ERROR)); 173 status(ed, ProfileUtilities.STATUS_ERROR); 174 } 175 return true; 176 } 177 178 private boolean rule(ElementDefinition ed, boolean test, String path, String message) { 179 if (!test) { 180 messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, message, ValidationMessage.IssueSeverity.ERROR)); 181 status(ed, ProfileUtilities.STATUS_ERROR); 182 } 183 return test; 184 } 185 186 private boolean ruleEqual(ElementDefinition ed, boolean vLeft, boolean vRight, String path, String elementName) { 187 if (vLeft != vRight) { 188 messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, elementName+" must be the same ("+vLeft+"/"+vRight+")", ValidationMessage.IssueSeverity.ERROR)); 189 status(ed, ProfileUtilities.STATUS_ERROR); 190 } 191 return true; 192 } 193 194 private String toString(Type val) throws IOException { 195 if (val instanceof PrimitiveType) 196 return "\"" + ((PrimitiveType) val).getValueAsString()+"\""; 197 198 IParser jp = context.newJsonParser(); 199 return jp.composeString(val, "value"); 200 } 201 202 public String getErrorCount() { 203 int c = 0; 204 for (ValidationMessage vm : messages) 205 if (vm.getLevel() == ValidationMessage.IssueSeverity.ERROR) 206 c++; 207 return Integer.toString(c); 208 } 209 210 public String getWarningCount() { 211 int c = 0; 212 for (ValidationMessage vm : messages) 213 if (vm.getLevel() == ValidationMessage.IssueSeverity.WARNING) 214 c++; 215 return Integer.toString(c); 216 } 217 218 public String getHintCount() { 219 int c = 0; 220 for (ValidationMessage vm : messages) 221 if (vm.getLevel() == ValidationMessage.IssueSeverity.INFORMATION) 222 c++; 223 return Integer.toString(c); 224 } 225 } 226 227 /** 228 * Value sets used in the subset and superset 229 */ 230 private List<ValueSet> valuesets = new ArrayList<ValueSet>(); 231 private List<ProfileComparison> comparisons = new ArrayList<ProfileComparison>(); 232 private String id; 233 private String title; 234 private String leftLink; 235 private String leftName; 236 private String rightLink; 237 private String rightName; 238 239 240 public List<ValueSet> getValuesets() { 241 return valuesets; 242 } 243 244 public void status(ElementDefinition ed, int value) { 245 ed.setUserData(ProfileUtilities.UD_ERROR_STATUS, Math.max(value, ed.getUserInt("error-status"))); 246 } 247 248 public List<ProfileComparison> getComparisons() { 249 return comparisons; 250 } 251 252 /** 253 * Compare left and right structure definitions to see whether they are consistent or not 254 * 255 * Note that left and right are arbitrary choices. In one respect, left 256 * is 'preferred' - the left's example value and data sets will be selected 257 * over the right ones in the common structure definition 258 * @throws DefinitionException 259 * @throws IOException 260 * @throws FHIRFormatError 261 * 262 * @ 263 */ 264 public ProfileComparison compareProfiles(StructureDefinition left, StructureDefinition right) throws DefinitionException, IOException, FHIRFormatError { 265 ProfileComparison outcome = new ProfileComparison(); 266 outcome.left = left; 267 outcome.right = right; 268 269 if (left == null) 270 throw new DefinitionException("No StructureDefinition provided (left)"); 271 if (right == null) 272 throw new DefinitionException("No StructureDefinition provided (right)"); 273 if (!left.hasSnapshot()) 274 throw new DefinitionException("StructureDefinition has no snapshot (left: "+outcome.leftName()+")"); 275 if (!right.hasSnapshot()) 276 throw new DefinitionException("StructureDefinition has no snapshot (right: "+outcome.rightName()+")"); 277 if (left.getSnapshot().getElement().isEmpty()) 278 throw new DefinitionException("StructureDefinition snapshot is empty (left: "+outcome.leftName()+")"); 279 if (right.getSnapshot().getElement().isEmpty()) 280 throw new DefinitionException("StructureDefinition snapshot is empty (right: "+outcome.rightName()+")"); 281 282 for (ProfileComparison pc : comparisons) 283 if (pc.left.getUrl().equals(left.getUrl()) && pc.right.getUrl().equals(right.getUrl())) 284 return pc; 285 286 outcome.id = Integer.toString(comparisons.size()+1); 287 comparisons.add(outcome); 288 289 DefinitionNavigator ln = new DefinitionNavigator(context, left); 290 DefinitionNavigator rn = new DefinitionNavigator(context, right); 291 292 // from here on in, any issues go in messages 293 outcome.superset = new StructureDefinition(); 294 outcome.subset = new StructureDefinition(); 295 if (outcome.ruleEqual(ln.path(), null,ln.path(), rn.path(), "Base Type is not compatible", false)) { 296 if (compareElements(outcome, ln.path(), ln, rn)) { 297 outcome.subset.setName("intersection of "+outcome.leftName()+" and "+outcome.rightName()); 298 outcome.subset.setStatus(PublicationStatus.DRAFT); 299 outcome.subset.setKind(outcome.left.getKind()); 300 outcome.subset.setType(outcome.left.getType()); 301 outcome.subset.setBaseDefinition("http://hl7.org/fhir/StructureDefinition/"+outcome.subset.getType()); 302 outcome.subset.setDerivation(TypeDerivationRule.CONSTRAINT); 303 outcome.subset.setAbstract(false); 304 outcome.superset.setName("union of "+outcome.leftName()+" and "+outcome.rightName()); 305 outcome.superset.setStatus(PublicationStatus.DRAFT); 306 outcome.superset.setKind(outcome.left.getKind()); 307 outcome.superset.setType(outcome.left.getType()); 308 outcome.superset.setBaseDefinition("http://hl7.org/fhir/StructureDefinition/"+outcome.subset.getType()); 309 outcome.superset.setAbstract(false); 310 outcome.superset.setDerivation(TypeDerivationRule.CONSTRAINT); 311 } else { 312 outcome.subset = null; 313 outcome.superset = null; 314 } 315 } 316 return outcome; 317 } 318 319 /** 320 * left and right refer to the same element. Are they compatible? 321 * @param outcome 322 * @param outcome 323 * @param path 324 * @param left 325 * @param right 326 * @- if there's a problem that needs fixing in this code 327 * @throws DefinitionException 328 * @throws IOException 329 * @throws FHIRFormatError 330 */ 331 private boolean compareElements(ProfileComparison outcome, String path, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, IOException, FHIRFormatError { 332// preconditions: 333 assert(path != null); 334 assert(left != null); 335 assert(right != null); 336 assert(left.path().equals(right.path())); 337 338 // we ignore slicing right now - we're going to clone the root one anyway, and then think about clones 339 // simple stuff 340 ElementDefinition subset = new ElementDefinition(); 341 subset.setPath(left.path()); 342 343 // not allowed to be different: 344 subset.getRepresentation().addAll(left.current().getRepresentation()); // can't be bothered even testing this one 345 if (!outcome.ruleCompares(subset, left.current().getDefaultValue(), right.current().getDefaultValue(), path+".defaultValue[x]", BOTH_NULL)) 346 return false; 347 subset.setDefaultValue(left.current().getDefaultValue()); 348 if (!outcome.ruleEqual(path, subset, left.current().getMeaningWhenMissing(), right.current().getMeaningWhenMissing(), "meaningWhenMissing Must be the same", true)) 349 return false; 350 subset.setMeaningWhenMissing(left.current().getMeaningWhenMissing()); 351 if (!outcome.ruleEqual(subset, left.current().getIsModifier(), right.current().getIsModifier(), path, "isModifier")) 352 return false; 353 subset.setIsModifier(left.current().getIsModifier()); 354 if (!outcome.ruleEqual(subset, left.current().getIsSummary(), right.current().getIsSummary(), path, "isSummary")) 355 return false; 356 subset.setIsSummary(left.current().getIsSummary()); 357 358 // descriptive properties from ElementDefinition - merge them: 359 subset.setLabel(mergeText(subset, outcome, path, "label", left.current().getLabel(), right.current().getLabel())); 360 subset.setShort(mergeText(subset, outcome, path, "short", left.current().getShort(), right.current().getShort())); 361 subset.setDefinition(mergeText(subset, outcome, path, "definition", left.current().getDefinition(), right.current().getDefinition())); 362 subset.setComment(mergeText(subset, outcome, path, "comments", left.current().getComment(), right.current().getComment())); 363 subset.setRequirements(mergeText(subset, outcome, path, "requirements", left.current().getRequirements(), right.current().getRequirements())); 364 subset.getCode().addAll(mergeCodings(left.current().getCode(), right.current().getCode())); 365 subset.getAlias().addAll(mergeStrings(left.current().getAlias(), right.current().getAlias())); 366 subset.getMapping().addAll(mergeMappings(left.current().getMapping(), right.current().getMapping())); 367 // left will win for example 368 subset.setExample(left.current().hasExample() ? left.current().getExample() : right.current().getExample()); 369 370 subset.setMustSupport(left.current().getMustSupport() || right.current().getMustSupport()); 371 ElementDefinition superset = subset.copy(); 372 373 374 // compare and intersect 375 superset.setMin(unionMin(left.current().getMin(), right.current().getMin())); 376 superset.setMax(unionMax(left.current().getMax(), right.current().getMax())); 377 subset.setMin(intersectMin(left.current().getMin(), right.current().getMin())); 378 subset.setMax(intersectMax(left.current().getMax(), right.current().getMax())); 379 outcome.rule(subset, subset.getMax().equals("*") || Integer.parseInt(subset.getMax()) >= subset.getMin(), path, "Cardinality Mismatch: "+card(left)+"/"+card(right)); 380 381 superset.getType().addAll(unionTypes(path, left.current().getType(), right.current().getType())); 382 subset.getType().addAll(intersectTypes(subset, outcome, path, left.current().getType(), right.current().getType())); 383 outcome.rule(subset, !subset.getType().isEmpty() || (!left.current().hasType() && !right.current().hasType()), path, "Type Mismatch:\r\n "+typeCode(left)+"\r\n "+typeCode(right)); 384// <fixed[x]><!-- ?? 0..1 * Value must be exactly this --></fixed[x]> 385// <pattern[x]><!-- ?? 0..1 * Value must have at least these property values --></pattern[x]> 386 superset.setMaxLengthElement(unionMaxLength(left.current().getMaxLength(), right.current().getMaxLength())); 387 subset.setMaxLengthElement(intersectMaxLength(left.current().getMaxLength(), right.current().getMaxLength())); 388 if (left.current().hasBinding() || right.current().hasBinding()) { 389 compareBindings(outcome, subset, superset, path, left.current(), right.current()); 390 } 391 392 // note these are backwards 393 superset.getConstraint().addAll(intersectConstraints(path, left.current().getConstraint(), right.current().getConstraint())); 394 subset.getConstraint().addAll(unionConstraints(subset, outcome, path, left.current().getConstraint(), right.current().getConstraint())); 395 396 // now process the slices 397 if (left.current().hasSlicing() || right.current().hasSlicing()) { 398 if (isExtension(left.path())) 399 return compareExtensions(outcome, path, superset, subset, left, right); 400// return true; 401 else 402 throw new DefinitionException("Slicing is not handled yet"); 403 // todo: name 404 } 405 406 // add the children 407 outcome.subset.getSnapshot().getElement().add(subset); 408 outcome.superset.getSnapshot().getElement().add(superset); 409 return compareChildren(subset, outcome, path, left, right); 410 } 411 412 private class ExtensionUsage { 413 private DefinitionNavigator defn; 414 private int minSuperset; 415 private int minSubset; 416 private String maxSuperset; 417 private String maxSubset; 418 private boolean both = false; 419 420 public ExtensionUsage(DefinitionNavigator defn, int min, String max) { 421 super(); 422 this.defn = defn; 423 this.minSubset = min; 424 this.minSuperset = min; 425 this.maxSubset = max; 426 this.maxSuperset = max; 427 } 428 429 } 430 private boolean compareExtensions(ProfileComparison outcome, String path, ElementDefinition superset, ElementDefinition subset, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException { 431 // for now, we don't handle sealed (or ordered) extensions 432 433 // for an extension the superset is all extensions, and the subset is.. all extensions - well, unless thay are sealed. 434 // but it's not useful to report that. instead, we collate the defined ones, and just adjust the cardinalities 435 Map<String, ExtensionUsage> map = new HashMap<String, ExtensionUsage>(); 436 437 if (left.slices() != null) 438 for (DefinitionNavigator ex : left.slices()) { 439 String url = ex.current().getType().get(0).getProfile().get(0).getValue(); 440 if (map.containsKey(url)) 441 throw new DefinitionException("Duplicate Extension "+url+" at "+path); 442 else 443 map.put(url, new ExtensionUsage(ex, ex.current().getMin(), ex.current().getMax())); 444 } 445 if (right.slices() != null) 446 for (DefinitionNavigator ex : right.slices()) { 447 String url = ex.current().getType().get(0).getProfile().get(0).getValue(); 448 if (map.containsKey(url)) { 449 ExtensionUsage exd = map.get(url); 450 exd.minSuperset = unionMin(exd.defn.current().getMin(), ex.current().getMin()); 451 exd.maxSuperset = unionMax(exd.defn.current().getMax(), ex.current().getMax()); 452 exd.minSubset = intersectMin(exd.defn.current().getMin(), ex.current().getMin()); 453 exd.maxSubset = intersectMax(exd.defn.current().getMax(), ex.current().getMax()); 454 exd.both = true; 455 outcome.rule(subset, exd.maxSubset.equals("*") || Integer.parseInt(exd.maxSubset) >= exd.minSubset, path, "Cardinality Mismatch on extension: "+card(exd.defn)+"/"+card(ex)); 456 } else { 457 map.put(url, new ExtensionUsage(ex, ex.current().getMin(), ex.current().getMax())); 458 } 459 } 460 List<String> names = new ArrayList<String>(); 461 names.addAll(map.keySet()); 462 Collections.sort(names); 463 for (String name : names) { 464 ExtensionUsage exd = map.get(name); 465 if (exd.both) 466 outcome.subset.getSnapshot().getElement().add(exd.defn.current().copy().setMin(exd.minSubset).setMax(exd.maxSubset)); 467 outcome.superset.getSnapshot().getElement().add(exd.defn.current().copy().setMin(exd.minSuperset).setMax(exd.maxSuperset)); 468 } 469 return true; 470 } 471 472 private boolean isExtension(String path) { 473 return path.endsWith(".extension") || path.endsWith(".modifierExtension"); 474 } 475 476 private boolean compareChildren(ElementDefinition ed, ProfileComparison outcome, String path, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, IOException, FHIRFormatError { 477 List<DefinitionNavigator> lc = left.children(); 478 List<DefinitionNavigator> rc = right.children(); 479 // it's possible that one of these profiles walks into a data type and the other doesn't 480 // if it does, we have to load the children for that data into the profile that doesn't 481 // walk into it 482 if (lc.isEmpty() && !rc.isEmpty() && right.current().getType().size() == 1 && left.hasTypeChildren(right.current().getType().get(0))) 483 lc = left.childrenFromType(right.current().getType().get(0)); 484 if (rc.isEmpty() && !lc.isEmpty() && left.current().getType().size() == 1 && right.hasTypeChildren(left.current().getType().get(0))) 485 rc = right.childrenFromType(left.current().getType().get(0)); 486 if (lc.size() != rc.size()) { 487 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Different number of children at "+path+" ("+Integer.toString(lc.size())+"/"+Integer.toString(rc.size())+")", ValidationMessage.IssueSeverity.ERROR)); 488 status(ed, ProfileUtilities.STATUS_ERROR); 489 return false; 490 } else { 491 for (int i = 0; i < lc.size(); i++) { 492 DefinitionNavigator l = lc.get(i); 493 DefinitionNavigator r = rc.get(i); 494 String cpath = comparePaths(l.path(), r.path(), path, l.nameTail(), r.nameTail()); 495 if (cpath != null) { 496 if (!compareElements(outcome, cpath, l, r)) 497 return false; 498 } else { 499 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Different path at "+path+"["+Integer.toString(i)+"] ("+l.path()+"/"+r.path()+")", ValidationMessage.IssueSeverity.ERROR)); 500 status(ed, ProfileUtilities.STATUS_ERROR); 501 return false; 502 } 503 } 504 } 505 return true; 506 } 507 508 private String comparePaths(String path1, String path2, String path, String tail1, String tail2) { 509 if (tail1.equals(tail2)) { 510 return path+"."+tail1; 511 } else if (tail1.endsWith("[x]") && tail2.startsWith(tail1.substring(0, tail1.length()-3))) { 512 return path+"."+tail1; 513 } else if (tail2.endsWith("[x]") && tail1.startsWith(tail2.substring(0, tail2.length()-3))) { 514 return path+"."+tail2; 515 } else 516 return null; 517 } 518 519 private boolean compareBindings(ProfileComparison outcome, ElementDefinition subset, ElementDefinition superset, String path, ElementDefinition lDef, ElementDefinition rDef) throws FHIRFormatError { 520 assert(lDef.hasBinding() || rDef.hasBinding()); 521 if (!lDef.hasBinding()) { 522 subset.setBinding(rDef.getBinding()); 523 // technically, the super set is unbound, but that's not very useful - so we use the provided on as an example 524 superset.setBinding(rDef.getBinding().copy()); 525 superset.getBinding().setStrength(BindingStrength.EXAMPLE); 526 return true; 527 } 528 if (!rDef.hasBinding()) { 529 subset.setBinding(lDef.getBinding()); 530 superset.setBinding(lDef.getBinding().copy()); 531 superset.getBinding().setStrength(BindingStrength.EXAMPLE); 532 return true; 533 } 534 ElementDefinitionBindingComponent left = lDef.getBinding(); 535 ElementDefinitionBindingComponent right = rDef.getBinding(); 536 if (Base.compareDeep(left, right, false)) { 537 subset.setBinding(left); 538 superset.setBinding(right); 539 } 540 541 // if they're both examples/preferred then: 542 // subset: left wins if they're both the same 543 // superset: 544 if (isPreferredOrExample(left) && isPreferredOrExample(right)) { 545 if (right.getStrength() == BindingStrength.PREFERRED && left.getStrength() == BindingStrength.EXAMPLE && !Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) { 546 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Example/preferred bindings differ at "+path+" using binding from "+outcome.rightName(), ValidationMessage.IssueSeverity.INFORMATION)); 547 status(subset, ProfileUtilities.STATUS_HINT); 548 subset.setBinding(right); 549 superset.setBinding(unionBindings(superset, outcome, path, left, right)); 550 } else { 551 if ((right.getStrength() != BindingStrength.EXAMPLE || left.getStrength() != BindingStrength.EXAMPLE) && !Base.compareDeep(left.getValueSet(), right.getValueSet(), false) ) { 552 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Example/preferred bindings differ at "+path+" using binding from "+outcome.leftName(), ValidationMessage.IssueSeverity.INFORMATION)); 553 status(subset, ProfileUtilities.STATUS_HINT); 554 } 555 subset.setBinding(left); 556 superset.setBinding(unionBindings(superset, outcome, path, left, right)); 557 } 558 return true; 559 } 560 // if either of them are extensible/required, then it wins 561 if (isPreferredOrExample(left)) { 562 subset.setBinding(right); 563 superset.setBinding(unionBindings(superset, outcome, path, left, right)); 564 return true; 565 } 566 if (isPreferredOrExample(right)) { 567 subset.setBinding(left); 568 superset.setBinding(unionBindings(superset, outcome, path, left, right)); 569 return true; 570 } 571 572 // ok, both are extensible or required. 573 ElementDefinitionBindingComponent subBinding = new ElementDefinitionBindingComponent(); 574 subset.setBinding(subBinding); 575 ElementDefinitionBindingComponent superBinding = new ElementDefinitionBindingComponent(); 576 superset.setBinding(superBinding); 577 subBinding.setDescription(mergeText(subset, outcome, path, "description", left.getDescription(), right.getDescription())); 578 superBinding.setDescription(mergeText(subset, outcome, null, "description", left.getDescription(), right.getDescription())); 579 if (left.getStrength() == BindingStrength.REQUIRED || right.getStrength() == BindingStrength.REQUIRED) 580 subBinding.setStrength(BindingStrength.REQUIRED); 581 else 582 subBinding.setStrength(BindingStrength.EXTENSIBLE); 583 if (left.getStrength() == BindingStrength.EXTENSIBLE || right.getStrength() == BindingStrength.EXTENSIBLE) 584 superBinding.setStrength(BindingStrength.EXTENSIBLE); 585 else 586 superBinding.setStrength(BindingStrength.REQUIRED); 587 588 if (Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) { 589 subBinding.setValueSet(left.getValueSet()); 590 superBinding.setValueSet(left.getValueSet()); 591 return true; 592 } else if (!left.hasValueSet()) { 593 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "No left Value set at "+path, ValidationMessage.IssueSeverity.ERROR)); 594 return true; 595 } else if (!right.hasValueSet()) { 596 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "No right Value set at "+path, ValidationMessage.IssueSeverity.ERROR)); 597 return true; 598 } else { 599 // ok, now we compare the value sets. This may be unresolvable. 600 ValueSet lvs = resolveVS(outcome.left, left.getValueSet()); 601 ValueSet rvs = resolveVS(outcome.right, right.getValueSet()); 602 if (lvs == null) { 603 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Unable to resolve left value set "+left.getValueSet().toString()+" at "+path, ValidationMessage.IssueSeverity.ERROR)); 604 return true; 605 } else if (rvs == null) { 606 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Unable to resolve right value set "+right.getValueSet().toString()+" at "+path, ValidationMessage.IssueSeverity.ERROR)); 607 return true; 608 } else { 609 // first, we'll try to do it by definition 610 ValueSet cvs = intersectByDefinition(lvs, rvs); 611 if(cvs == null) { 612 // if that didn't work, we'll do it by expansion 613 ValueSetExpansionOutcome le; 614 ValueSetExpansionOutcome re; 615 try { 616 le = context.expandVS(lvs, true, false); 617 re = context.expandVS(rvs, true, false); 618 if (!closed(le.getValueset()) || !closed(re.getValueset())) 619 throw new DefinitionException("unclosed value sets are not handled yet"); 620 cvs = intersectByExpansion(lvs, rvs); 621 if (!cvs.getCompose().hasInclude()) { 622 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "The value sets "+lvs.getUrl()+" and "+rvs.getUrl()+" do not intersect", ValidationMessage.IssueSeverity.ERROR)); 623 status(subset, ProfileUtilities.STATUS_ERROR); 624 return false; 625 } 626 } catch (Exception e){ 627 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "Unable to expand or process value sets "+lvs.getUrl()+" and "+rvs.getUrl()+": "+e.getMessage(), ValidationMessage.IssueSeverity.ERROR)); 628 status(subset, ProfileUtilities.STATUS_ERROR); 629 return false; 630 } 631 } 632 subBinding.setValueSet(new Reference().setReference("#"+addValueSet(cvs))); 633 superBinding.setValueSet(new Reference().setReference("#"+addValueSet(unite(superset, outcome, path, lvs, rvs)))); 634 } 635 } 636 return false; 637 } 638 639 private ElementDefinitionBindingComponent unionBindings(ElementDefinition ed, ProfileComparison outcome, String path, ElementDefinitionBindingComponent left, ElementDefinitionBindingComponent right) throws FHIRFormatError { 640 ElementDefinitionBindingComponent union = new ElementDefinitionBindingComponent(); 641 if (left.getStrength().compareTo(right.getStrength()) < 0) 642 union.setStrength(left.getStrength()); 643 else 644 union.setStrength(right.getStrength()); 645 union.setDescription(mergeText(ed, outcome, path, "binding.description", left.getDescription(), right.getDescription())); 646 if (Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) 647 union.setValueSet(left.getValueSet()); 648 else { 649 ValueSet lvs = resolveVS(outcome.left, left.getValueSet()); 650 ValueSet rvs = resolveVS(outcome.left, right.getValueSet()); 651 if (lvs != null && rvs != null) 652 union.setValueSet(new Reference().setReference("#"+addValueSet(unite(ed, outcome, path, lvs, rvs)))); 653 else if (lvs != null) 654 union.setValueSet(new Reference().setReference("#"+addValueSet(lvs))); 655 else if (rvs != null) 656 union.setValueSet(new Reference().setReference("#"+addValueSet(rvs))); 657 } 658 return union; 659 } 660 661 662 private ValueSet unite(ElementDefinition ed, ProfileComparison outcome, String path, ValueSet lvs, ValueSet rvs) { 663 ValueSet vs = new ValueSet(); 664 if (lvs.hasCompose()) { 665 for (ConceptSetComponent inc : lvs.getCompose().getInclude()) 666 vs.getCompose().getInclude().add(inc); 667 if (lvs.getCompose().hasExclude()) { 668 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "The value sets "+lvs.getUrl()+" has exclude statements, and no union involving it can be correctly determined", ValidationMessage.IssueSeverity.ERROR)); 669 status(ed, ProfileUtilities.STATUS_ERROR); 670 } 671 } 672 if (rvs.hasCompose()) { 673 for (ConceptSetComponent inc : rvs.getCompose().getInclude()) 674 if (!mergeIntoExisting(vs.getCompose().getInclude(), inc)) 675 vs.getCompose().getInclude().add(inc); 676 if (rvs.getCompose().hasExclude()) { 677 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "The value sets "+lvs.getUrl()+" has exclude statements, and no union involving it can be correctly determined", ValidationMessage.IssueSeverity.ERROR)); 678 status(ed, ProfileUtilities.STATUS_ERROR); 679 } 680 } 681 return vs; 682 } 683 684 private boolean mergeIntoExisting(List<ConceptSetComponent> include, ConceptSetComponent inc) { 685 for (ConceptSetComponent dst : include) { 686 if (Base.compareDeep(dst, inc, false)) 687 return true; // they're actually the same 688 if (dst.getSystem().equals(inc.getSystem())) { 689 if (inc.hasFilter() || dst.hasFilter()) { 690 return false; // just add the new one as a a parallel 691 } else if (inc.hasConcept() && dst.hasConcept()) { 692 for (ConceptReferenceComponent cc : inc.getConcept()) { 693 boolean found = false; 694 for (ConceptReferenceComponent dd : dst.getConcept()) { 695 if (dd.getCode().equals(cc.getCode())) 696 found = true; 697 if (found) { 698 if (cc.hasDisplay() && !dd.hasDisplay()) 699 dd.setDisplay(cc.getDisplay()); 700 break; 701 } 702 } 703 if (!found) 704 dst.getConcept().add(cc.copy()); 705 } 706 } else 707 dst.getConcept().clear(); // one of them includes the entire code system 708 } 709 } 710 return false; 711 } 712 713 private ValueSet resolveVS(StructureDefinition ctxtLeft, Type vsRef) { 714 if (vsRef == null) 715 return null; 716 if (vsRef instanceof UriType) 717 return null; 718 else { 719 Reference ref = (Reference) vsRef; 720 if (!ref.hasReference()) 721 return null; 722 return context.fetchResource(ValueSet.class, ref.getReference()); 723 } 724 } 725 726 private ValueSet intersectByDefinition(ValueSet lvs, ValueSet rvs) { 727 // this is just a stub. The idea is that we try to avoid expanding big open value sets from SCT, RxNorm, LOINC. 728 // there's a bit of long hand logic coming here, but that's ok. 729 return null; 730 } 731 732 private ValueSet intersectByExpansion(ValueSet lvs, ValueSet rvs) { 733 // this is pretty straight forward - we intersect the lists, and build a compose out of the intersection 734 ValueSet vs = new ValueSet(); 735 vs.setStatus(PublicationStatus.DRAFT); 736 737 Map<String, ValueSetExpansionContainsComponent> left = new HashMap<String, ValueSetExpansionContainsComponent>(); 738 scan(lvs.getExpansion().getContains(), left); 739 Map<String, ValueSetExpansionContainsComponent> right = new HashMap<String, ValueSetExpansionContainsComponent>(); 740 scan(rvs.getExpansion().getContains(), right); 741 Map<String, ConceptSetComponent> inc = new HashMap<String, ConceptSetComponent>(); 742 743 for (String s : left.keySet()) { 744 if (right.containsKey(s)) { 745 ValueSetExpansionContainsComponent cc = left.get(s); 746 ConceptSetComponent c = inc.get(cc.getSystem()); 747 if (c == null) { 748 c = vs.getCompose().addInclude().setSystem(cc.getSystem()); 749 inc.put(cc.getSystem(), c); 750 } 751 c.addConcept().setCode(cc.getCode()).setDisplay(cc.getDisplay()); 752 } 753 } 754 return vs; 755 } 756 757 private void scan(List<ValueSetExpansionContainsComponent> list, Map<String, ValueSetExpansionContainsComponent> map) { 758 for (ValueSetExpansionContainsComponent cc : list) { 759 if (cc.hasSystem() && cc.hasCode()) { 760 String s = cc.getSystem()+"::"+cc.getCode(); 761 if (!map.containsKey(s)) 762 map.put(s, cc); 763 } 764 if (cc.hasContains()) 765 scan(cc.getContains(), map); 766 } 767 } 768 769 private boolean closed(ValueSet vs) { 770 return !ToolingExtensions.findBooleanExtension(vs.getExpansion(), ToolingExtensions.EXT_UNCLOSED); 771 } 772 773 private boolean isPreferredOrExample(ElementDefinitionBindingComponent binding) { 774 return binding.getStrength() == BindingStrength.EXAMPLE || binding.getStrength() == BindingStrength.PREFERRED; 775 } 776 777 private Collection<? extends TypeRefComponent> intersectTypes(ElementDefinition ed, ProfileComparison outcome, String path, List<TypeRefComponent> left, List<TypeRefComponent> right) throws DefinitionException, IOException, FHIRFormatError { 778 List<TypeRefComponent> result = new ArrayList<TypeRefComponent>(); 779 for (TypeRefComponent l : left) { 780 if (l.hasAggregation()) 781 throw new DefinitionException("Aggregation not supported: "+path); 782 boolean pfound = false; 783 boolean tfound = false; 784 TypeRefComponent c = l.copy(); 785 for (TypeRefComponent r : right) { 786 if (r.hasAggregation()) 787 throw new DefinitionException("Aggregation not supported: "+path); 788 if (!l.hasProfile() && !r.hasProfile()) { 789 pfound = true; 790 } else if (!r.hasProfile()) { 791 pfound = true; 792 } else if (!l.hasProfile()) { 793 pfound = true; 794 c.setProfile(r.getProfile()); 795 } else { 796 StructureDefinition sdl = resolveProfile(ed, outcome, path, l.getProfile().get(0).getValue(), outcome.leftName()); 797 StructureDefinition sdr = resolveProfile(ed, outcome, path, r.getProfile().get(0).getValue(), outcome.rightName()); 798 if (sdl != null && sdr != null) { 799 if (sdl == sdr) { 800 pfound = true; 801 } else if (derivesFrom(sdl, sdr)) { 802 pfound = true; 803 } else if (derivesFrom(sdr, sdl)) { 804 c.setProfile(r.getProfile()); 805 pfound = true; 806 } else if (sdl.getType().equals(sdr.getType())) { 807 ProfileComparison comp = compareProfiles(sdl, sdr); 808 if (comp.getSubset() != null) { 809 pfound = true; 810 c.addProfile("#"+comp.id); 811 } 812 } 813 } 814 } 815 if (!l.hasTargetProfile() && !r.hasTargetProfile()) { 816 tfound = true; 817 } else if (!r.hasTargetProfile()) { 818 tfound = true; 819 } else if (!l.hasTargetProfile()) { 820 tfound = true; 821 c.setTargetProfile(r.getTargetProfile()); 822 } else { 823 StructureDefinition sdl = resolveProfile(ed, outcome, path, l.getProfile().get(0).getValue(), outcome.leftName()); 824 StructureDefinition sdr = resolveProfile(ed, outcome, path, r.getProfile().get(0).getValue(), outcome.rightName()); 825 if (sdl != null && sdr != null) { 826 if (sdl == sdr) { 827 tfound = true; 828 } else if (derivesFrom(sdl, sdr)) { 829 tfound = true; 830 } else if (derivesFrom(sdr, sdl)) { 831 c.setTargetProfile(r.getTargetProfile()); 832 tfound = true; 833 } else if (sdl.getType().equals(sdr.getType())) { 834 ProfileComparison comp = compareProfiles(sdl, sdr); 835 if (comp.getSubset() != null) { 836 tfound = true; 837 c.addTargetProfile("#"+comp.id); 838 } 839 } 840 } 841 } 842 } 843 if (pfound && tfound) 844 result.add(c); 845 } 846 return result; 847 } 848 849 private StructureDefinition resolveProfile(ElementDefinition ed, ProfileComparison outcome, String path, String url, String name) { 850 StructureDefinition res = context.fetchResource(StructureDefinition.class, url); 851 if (res == null) { 852 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.INFORMATIONAL, path, "Unable to resolve profile "+url+" in profile "+name, ValidationMessage.IssueSeverity.WARNING)); 853 status(ed, ProfileUtilities.STATUS_HINT); 854 } 855 return res; 856 } 857 858 private Collection<? extends TypeRefComponent> unionTypes(String path, List<TypeRefComponent> left, List<TypeRefComponent> right) throws DefinitionException, IOException, FHIRFormatError { 859 List<TypeRefComponent> result = new ArrayList<TypeRefComponent>(); 860 for (TypeRefComponent l : left) 861 checkAddTypeUnion(path, result, l); 862 for (TypeRefComponent r : right) 863 checkAddTypeUnion(path, result, r); 864 return result; 865 } 866 867 private void checkAddTypeUnion(String path, List<TypeRefComponent> results, TypeRefComponent nw) throws DefinitionException, IOException, FHIRFormatError { 868 boolean pfound = false; 869 boolean tfound = false; 870 nw = nw.copy(); 871 if (nw.hasAggregation()) 872 throw new DefinitionException("Aggregation not supported: "+path); 873 for (TypeRefComponent ex : results) { 874 if (Utilities.equals(ex.getCode(), nw.getCode())) { 875 if (!ex.hasProfile() && !nw.hasProfile()) 876 pfound = true; 877 else if (!ex.hasProfile()) { 878 pfound = true; 879 } else if (!nw.hasProfile()) { 880 pfound = true; 881 ex.setProfile(null); 882 } else { 883 // both have profiles. Is one derived from the other? 884 StructureDefinition sdex = context.fetchResource(StructureDefinition.class, ex.getProfile().get(0).getValue()); 885 StructureDefinition sdnw = context.fetchResource(StructureDefinition.class, nw.getProfile().get(0).getValue()); 886 if (sdex != null && sdnw != null) { 887 if (sdex == sdnw) { 888 pfound = true; 889 } else if (derivesFrom(sdex, sdnw)) { 890 ex.setProfile(nw.getProfile()); 891 pfound = true; 892 } else if (derivesFrom(sdnw, sdex)) { 893 pfound = true; 894 } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) { 895 ProfileComparison comp = compareProfiles(sdex, sdnw); 896 if (comp.getSuperset() != null) { 897 pfound = true; 898 ex.addProfile("#"+comp.id); 899 } 900 } 901 } 902 } 903 if (!ex.hasTargetProfile() && !nw.hasTargetProfile()) 904 tfound = true; 905 else if (!ex.hasTargetProfile()) { 906 tfound = true; 907 } else if (!nw.hasTargetProfile()) { 908 tfound = true; 909 ex.setTargetProfile(null); 910 } else { 911 // both have profiles. Is one derived from the other? 912 StructureDefinition sdex = context.fetchResource(StructureDefinition.class, ex.getTargetProfile().get(0).getValue()); 913 StructureDefinition sdnw = context.fetchResource(StructureDefinition.class, nw.getTargetProfile().get(0).getValue()); 914 if (sdex != null && sdnw != null) { 915 if (sdex == sdnw) { 916 tfound = true; 917 } else if (derivesFrom(sdex, sdnw)) { 918 ex.setTargetProfile(nw.getTargetProfile()); 919 tfound = true; 920 } else if (derivesFrom(sdnw, sdex)) { 921 tfound = true; 922 } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) { 923 ProfileComparison comp = compareProfiles(sdex, sdnw); 924 if (comp.getSuperset() != null) { 925 tfound = true; 926 ex.addTargetProfile("#"+comp.id); 927 } 928 } 929 } 930 } 931 } 932 } 933 if (!tfound || !pfound) 934 results.add(nw); 935 } 936 937 938 private boolean derivesFrom(StructureDefinition left, StructureDefinition right) { 939 // left derives from right if it's base is the same as right 940 // todo: recursive... 941 return left.hasBaseDefinition() && left.getBaseDefinition().equals(right.getUrl()); 942 } 943 944 945 private String mergeText(ElementDefinition ed, ProfileComparison outcome, String path, String name, String left, String right) { 946 if (left == null && right == null) 947 return null; 948 if (left == null) 949 return right; 950 if (right == null) 951 return left; 952 if (left.equalsIgnoreCase(right)) 953 return left; 954 if (path != null) { 955 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.INFORMATIONAL, path, "Elements differ in definition for "+name+":\r\n \""+left+"\"\r\n \""+right+"\"", 956 "Elements differ in definition for "+name+":<br/>\""+Utilities.escapeXml(left)+"\"<br/>\""+Utilities.escapeXml(right)+"\"", ValidationMessage.IssueSeverity.INFORMATION)); 957 status(ed, ProfileUtilities.STATUS_HINT); 958 } 959 return "left: "+left+"; right: "+right; 960 } 961 962 private List<Coding> mergeCodings(List<Coding> left, List<Coding> right) { 963 List<Coding> result = new ArrayList<Coding>(); 964 result.addAll(left); 965 for (Coding c : right) { 966 boolean found = false; 967 for (Coding ct : left) 968 if (Utilities.equals(c.getSystem(), ct.getSystem()) && Utilities.equals(c.getCode(), ct.getCode())) 969 found = true; 970 if (!found) 971 result.add(c); 972 } 973 return result; 974 } 975 976 private List<StringType> mergeStrings(List<StringType> left, List<StringType> right) { 977 List<StringType> result = new ArrayList<StringType>(); 978 result.addAll(left); 979 for (StringType c : right) { 980 boolean found = false; 981 for (StringType ct : left) 982 if (Utilities.equals(c.getValue(), ct.getValue())) 983 found = true; 984 if (!found) 985 result.add(c); 986 } 987 return result; 988 } 989 990 private List<ElementDefinitionMappingComponent> mergeMappings(List<ElementDefinitionMappingComponent> left, List<ElementDefinitionMappingComponent> right) { 991 List<ElementDefinitionMappingComponent> result = new ArrayList<ElementDefinitionMappingComponent>(); 992 result.addAll(left); 993 for (ElementDefinitionMappingComponent c : right) { 994 boolean found = false; 995 for (ElementDefinitionMappingComponent ct : left) 996 if (Utilities.equals(c.getIdentity(), ct.getIdentity()) && Utilities.equals(c.getLanguage(), ct.getLanguage()) && Utilities.equals(c.getMap(), ct.getMap())) 997 found = true; 998 if (!found) 999 result.add(c); 1000 } 1001 return result; 1002 } 1003 1004 // we can't really know about constraints. We create warnings, and collate them 1005 private List<ElementDefinitionConstraintComponent> unionConstraints(ElementDefinition ed, ProfileComparison outcome, String path, List<ElementDefinitionConstraintComponent> left, List<ElementDefinitionConstraintComponent> right) { 1006 List<ElementDefinitionConstraintComponent> result = new ArrayList<ElementDefinitionConstraintComponent>(); 1007 for (ElementDefinitionConstraintComponent l : left) { 1008 boolean found = false; 1009 for (ElementDefinitionConstraintComponent r : right) 1010 if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getXpath(), l.getXpath()) && r.getSeverity() == l.getSeverity())) 1011 found = true; 1012 if (!found) { 1013 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "StructureDefinition "+outcome.leftName()+" has a constraint that is not found in "+outcome.rightName()+" and it is uncertain whether they are compatible ("+l.getXpath()+")", ValidationMessage.IssueSeverity.INFORMATION)); 1014 status(ed, ProfileUtilities.STATUS_WARNING); 1015 } 1016 result.add(l); 1017 } 1018 for (ElementDefinitionConstraintComponent r : right) { 1019 boolean found = false; 1020 for (ElementDefinitionConstraintComponent l : left) 1021 if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getXpath(), l.getXpath()) && r.getSeverity() == l.getSeverity())) 1022 found = true; 1023 if (!found) { 1024 outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "StructureDefinition "+outcome.rightName()+" has a constraint that is not found in "+outcome.leftName()+" and it is uncertain whether they are compatible ("+r.getXpath()+")", ValidationMessage.IssueSeverity.INFORMATION)); 1025 status(ed, ProfileUtilities.STATUS_WARNING); 1026 result.add(r); 1027 } 1028 } 1029 return result; 1030 } 1031 1032 1033 private List<ElementDefinitionConstraintComponent> intersectConstraints(String path, List<ElementDefinitionConstraintComponent> left, List<ElementDefinitionConstraintComponent> right) { 1034 List<ElementDefinitionConstraintComponent> result = new ArrayList<ElementDefinitionConstraintComponent>(); 1035 for (ElementDefinitionConstraintComponent l : left) { 1036 boolean found = false; 1037 for (ElementDefinitionConstraintComponent r : right) 1038 if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getXpath(), l.getXpath()) && r.getSeverity() == l.getSeverity())) 1039 found = true; 1040 if (found) 1041 result.add(l); 1042 } 1043 return result; 1044} 1045 1046 private String card(DefinitionNavigator defn) { 1047 return Integer.toString(defn.current().getMin())+".."+defn.current().getMax(); 1048 } 1049 1050 private String typeCode(DefinitionNavigator defn) { 1051 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1052 for (TypeRefComponent t : defn.current().getType()) 1053 b.append(t.getCode()+(t.hasProfile() ? "("+t.getProfile()+")" : "")+(t.hasTargetProfile() ? "("+t.getTargetProfile()+")" : "")); // todo: other properties 1054 return b.toString(); 1055 } 1056 1057 private int intersectMin(int left, int right) { 1058 if (left > right) 1059 return left; 1060 else 1061 return right; 1062 } 1063 1064 private int unionMin(int left, int right) { 1065 if (left > right) 1066 return right; 1067 else 1068 return left; 1069 } 1070 1071 private String intersectMax(String left, String right) { 1072 int l = "*".equals(left) ? Integer.MAX_VALUE : Integer.parseInt(left); 1073 int r = "*".equals(right) ? Integer.MAX_VALUE : Integer.parseInt(right); 1074 if (l < r) 1075 return left; 1076 else 1077 return right; 1078 } 1079 1080 private String unionMax(String left, String right) { 1081 int l = "*".equals(left) ? Integer.MAX_VALUE : Integer.parseInt(left); 1082 int r = "*".equals(right) ? Integer.MAX_VALUE : Integer.parseInt(right); 1083 if (l < r) 1084 return right; 1085 else 1086 return left; 1087 } 1088 1089 private IntegerType intersectMaxLength(int left, int right) { 1090 if (left == 0) 1091 left = Integer.MAX_VALUE; 1092 if (right == 0) 1093 right = Integer.MAX_VALUE; 1094 if (left < right) 1095 return left == Integer.MAX_VALUE ? null : new IntegerType(left); 1096 else 1097 return right == Integer.MAX_VALUE ? null : new IntegerType(right); 1098 } 1099 1100 private IntegerType unionMaxLength(int left, int right) { 1101 if (left == 0) 1102 left = Integer.MAX_VALUE; 1103 if (right == 0) 1104 right = Integer.MAX_VALUE; 1105 if (left < right) 1106 return right == Integer.MAX_VALUE ? null : new IntegerType(right); 1107 else 1108 return left == Integer.MAX_VALUE ? null : new IntegerType(left); 1109 } 1110 1111 1112 public String addValueSet(ValueSet cvs) { 1113 String id = Integer.toString(valuesets.size()+1); 1114 cvs.setId(id); 1115 valuesets.add(cvs); 1116 return id; 1117 } 1118 1119 1120 1121 public String getId() { 1122 return id; 1123 } 1124 1125 public void setId(String id) { 1126 this.id = id; 1127 } 1128 1129 public String getTitle() { 1130 return title; 1131 } 1132 1133 public void setTitle(String title) { 1134 this.title = title; 1135 } 1136 1137 public String getLeftLink() { 1138 return leftLink; 1139 } 1140 1141 public void setLeftLink(String leftLink) { 1142 this.leftLink = leftLink; 1143 } 1144 1145 public String getLeftName() { 1146 return leftName; 1147 } 1148 1149 public void setLeftName(String leftName) { 1150 this.leftName = leftName; 1151 } 1152 1153 public String getRightLink() { 1154 return rightLink; 1155 } 1156 1157 public void setRightLink(String rightLink) { 1158 this.rightLink = rightLink; 1159 } 1160 1161 public String getRightName() { 1162 return rightName; 1163 } 1164 1165 public void setRightName(String rightName) { 1166 this.rightName = rightName; 1167 } 1168 1169 1170 1171 1172}