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