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