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