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