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