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