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