
001package org.hl7.fhir.r5.terminologies; 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.util.ArrayList; 035import java.util.Calendar; 036import java.util.Collection; 037import java.util.Collections; 038import java.util.Comparator; 039import java.util.HashSet; 040import java.util.List; 041import java.util.Set; 042 043import lombok.extern.slf4j.Slf4j; 044import org.hl7.fhir.exceptions.FHIRException; 045import org.hl7.fhir.exceptions.FHIRFormatError; 046import org.hl7.fhir.r5.context.IWorkerContext; 047import org.hl7.fhir.r5.extensions.ExtensionDefinitions; 048import org.hl7.fhir.r5.extensions.ExtensionUtilities; 049import org.hl7.fhir.r5.model.*; 050import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 051import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent; 052import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 053import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent; 054import org.hl7.fhir.r5.model.CodeSystem.PropertyType; 055import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 056import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptDefinitionComponentSorter; 057import org.hl7.fhir.r5.terminologies.providers.SpecialCodeSystem; 058import org.hl7.fhir.r5.utils.CanonicalResourceUtilities; 059 060import org.hl7.fhir.r5.utils.UserDataNames; 061import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 062import org.hl7.fhir.utilities.MarkDownProcessor; 063import org.hl7.fhir.utilities.StandardsStatus; 064import org.hl7.fhir.utilities.Utilities; 065import org.hl7.fhir.utilities.VersionUtilities; 066 067import javax.annotation.Nonnull; 068 069@Slf4j 070public class CodeSystemUtilities extends TerminologyUtilities { 071 072 public static class CodeSystemSorter implements Comparator<CodeSystem> { 073 074 @Override 075 public int compare(CodeSystem o1, CodeSystem o2) { 076 String url1 = o1.getUrl(); 077 String url2 = o2.getUrl(); 078 int c = compareString(url1, url2); 079 if (c == 0) { 080 String ver1 = o1.getVersion(); 081 String ver2 = o2.getVersion(); 082 c = VersionUtilities.compareVersions(ver1, ver2); 083 if (c == 0) { 084 String d1 = o1.getDateElement().asStringValue(); 085 String d2 = o2.getDateElement().asStringValue(); 086 c = compareString(url1, url2); 087 } 088 } 089 return c; 090 } 091 092 private int compareString(String s1, String s2) { 093 if (s1 == null) { 094 return s2 == null ? 0 : 1; 095 } else { 096 return s1.compareTo(s2); 097 } 098 } 099 100 } 101 102 103 104 public static class SystemReference { 105 private String link; 106 private String text; 107 private boolean local; 108 109 public SystemReference(String text, String link) { 110 super(); 111 this.link = link; 112 this.text = text; 113 } 114 public SystemReference(String text, String link, boolean local) { 115 super(); 116 this.link = link; 117 this.text = text; 118 this.local = local; 119 } 120 121 public String getLink() { 122 return link; 123 } 124 public String getText() { 125 return text; 126 } 127 public boolean isLocal() { 128 return local; 129 } 130 131 } 132 133 public static class ConceptDefinitionComponentSorter implements Comparator<ConceptDefinitionComponent> { 134 135 @Override 136 public int compare(ConceptDefinitionComponent o1, ConceptDefinitionComponent o2) { 137 return o1.hasCode() ? o1.getCode().compareToIgnoreCase(o2.getCode()) : 0; 138 } 139 140 } 141 142 public static final String USER_DATA_CROSS_LINK = "cs.utils.cross.link"; 143 144 public static class CodeSystemNavigator { 145 146 private CodeSystem cs; 147 private boolean restructure; 148 private Set<String> processed = new HashSet<>(); 149 150 public CodeSystemNavigator(CodeSystem cs) { 151 this.cs = cs; 152 restructure = hasExtraRelationships(cs.getConcept()); 153 } 154 155 public boolean isRestructure() { 156 return restructure; 157 } 158 159 private boolean hasExtraRelationships(List<ConceptDefinitionComponent> concept) { 160 for (ConceptDefinitionComponent cd : concept) { 161 if (!getSubsumedBy(cd).isEmpty()) { 162 return true; 163 } 164 for (ConceptDefinitionComponent cdc : cd.getConcept()) { 165 if (hasExtraRelationships(cdc.getConcept())) { 166 return true; 167 } 168 } 169 } 170 return false; 171 } 172 173 public List<ConceptDefinitionComponent> getConcepts(@Nonnull ConceptDefinitionComponent context) { 174 if (context == null) { 175 if (restructure) { 176 List<ConceptDefinitionComponent> res = new ArrayList<>(); 177 for (ConceptDefinitionComponent cd : cs.getConcept()) { 178 if (getSubsumedBy(cd).isEmpty()) { 179 res.add(cd); 180 processed.add(cd.getCode()); 181 } 182 } 183 return res; 184 } else { 185 return cs.getConcept(); 186 } 187 } else { 188 if (restructure) { 189 List<ConceptDefinitionComponent> res = new ArrayList<>(); 190 for (ConceptDefinitionComponent cd : context.getConcept()) { 191 res.add(cd); 192 processed.add(cd.getCode()); 193 } 194 for (ConceptDefinitionComponent cd : cs.getConcept()) { 195 if (getSubsumedBy(cd).contains(context.getCode()) && !processed.contains(cd.getCode())) { 196 res.add(cd); 197 processed.add(cd.getCode()); 198 } 199 } 200 return res; 201 } else { 202 return context.getConcept(); 203 } 204 } 205 } 206 207 private List<String> getSubsumedBy(ConceptDefinitionComponent cd) { 208 List<String> codes = new ArrayList<>(); 209 for (ConceptPropertyComponent cp : cd.getProperty()) { 210 if ("subsumedBy".equals(cp.getCode())) { 211 codes.add(cp.getValue().primitiveValue()); 212 } 213 } 214 return codes; 215 } 216 217 public List<ConceptDefinitionComponent> getOtherChildren(@Nonnull ConceptDefinitionComponent context) { 218 List<ConceptDefinitionComponent> res = new ArrayList<>(); 219 for (ConceptDefinitionComponent cd : cs.getConcept()) { 220 if (getSubsumedBy(cd).contains(context.getCode()) && processed.contains(cd.getCode())) { 221 res.add(cd); 222 } 223 } 224 return res; 225 } 226 } 227 228 229 public static boolean isNotSelectable(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent def) { 230 String pd = getPropertyByUrl(cs, "http://hl7.org/fhir/concept-properties#notSelectable"); 231 if (pd == null) { 232 pd = "notSelectable"; 233 } 234 for (ConceptPropertyComponent p : def.getProperty()) { 235 if (pd.equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 236 return ((BooleanType) p.getValue()).getValue(); 237 } 238 return false; 239 } 240 241 public static boolean isNotSelectable(@Nonnull CodeSystem cs, @Nonnull String code) { 242 ConceptDefinitionComponent cd = findCode(cs.getConcept(), code); 243 return cd == null ? false : isNotSelectable(cs, cd); 244 } 245 246 public static void setNotSelectable(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent concept) throws FHIRFormatError { 247 defineNotSelectableProperty(cs); 248 ConceptPropertyComponent p = getProperty(concept, "notSelectable"); 249 if (p != null) 250 p.setValue(new BooleanType(true)); 251 else 252 concept.addProperty().setCode("notSelectable").setValue(new BooleanType(true)); 253 } 254 255 public static void setProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent concept, @Nonnull String code, @Nonnull DataType value) throws FHIRFormatError { 256 defineProperty(cs, code, propertyTypeForValue(value)); 257 ConceptPropertyComponent p = getProperty(concept, code); 258 if (p != null) 259 p.setValue(value); 260 else 261 concept.addProperty().setCode(code).setValue(value); 262 } 263 264 public static void setProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent concept, @Nonnull String url, @Nonnull String code, @Nonnull DataType value) throws FHIRFormatError { 265 defineProperty(cs, code, propertyTypeForValue(value), url); 266 ConceptPropertyComponent p = getProperty(concept, code); 267 if (p != null) 268 p.setValue(value); 269 else 270 concept.addProperty().setCode(code).setValue(value); 271 } 272 273 274 private static PropertyType propertyTypeForValue(DataType value) { 275 if (value instanceof BooleanType) { 276 return PropertyType.BOOLEAN; 277 } 278 if (value instanceof CodeType) { 279 return PropertyType.CODE; 280 } 281 if (value instanceof Coding) { 282 return PropertyType.CODING; 283 } 284 if (value instanceof DateTimeType) { 285 return PropertyType.DATETIME; 286 } 287 if (value instanceof DecimalType) { 288 return PropertyType.DECIMAL; 289 } 290 if (value instanceof IntegerType) { 291 return PropertyType.INTEGER; 292 } 293 if (value instanceof StringType) { 294 return PropertyType.STRING; 295 } 296 throw new Error("Unknown property type "+value.getClass().getName()); 297 } 298 299 private static String defineProperty(CodeSystem cs, String code, PropertyType pt) { 300 String url = "http://hl7.org/fhir/concept-properties#"+code; 301 return defineProperty(cs, code, pt, url); 302 } 303 private static String defineProperty(CodeSystem cs, String code, PropertyType pt, String url) { 304 for (PropertyComponent p : cs.getProperty()) { 305 if (p.hasCode() && p.getCode().equals(code)) { 306 if (!p.getUri().equals(url)) { 307 throw new Error("URI mismatch for code "+code+" url = "+p.getUri()+" vs "+url); 308 } 309 if (!p.getType().equals(pt)) { 310 throw new Error("Type mismatch for code "+code+" type = "+p.getType()+" vs "+pt); 311 } 312 return code; 313 } 314 } 315 cs.addProperty().setCode(code).setUri(url).setType(pt).setUri(url); 316 return code; 317 } 318 319 public static void defineNotSelectableProperty(@Nonnull CodeSystem cs) { 320 defineCodeSystemProperty(cs, "notSelectable", "Indicates that the code is abstract - only intended to be used as a selector for other concepts", PropertyType.BOOLEAN); 321 } 322 323 324 public enum ConceptStatus { 325 Active, Experimental, Deprecated, Retired; 326 327 public String toCode() { 328 switch (this) { 329 case Active: return "active"; 330 case Experimental: return "experimental"; 331 case Deprecated: return "deprecated"; 332 case Retired: return "retired"; 333 default: return null; 334 } 335 } 336 } 337 338 public static void setStatus(@Nonnull CodeSystem cs, ConceptDefinitionComponent concept, ConceptStatus status) throws FHIRFormatError { 339 defineStatusProperty(cs); 340 ConceptPropertyComponent p = getProperty(concept, "status"); 341 if (p != null) 342 p.setValue(new CodeType(status.toCode())); 343 else 344 concept.addProperty().setCode("status").setValue(new CodeType(status.toCode())); 345 } 346 347 public static void defineStatusProperty(@Nonnull CodeSystem cs) { 348 defineCodeSystemProperty(cs, "status", "A property that indicates the status of the concept. One of active, experimental, deprecated, retired", PropertyType.CODE); 349 } 350 351 private static void defineDeprecatedProperty(CodeSystem cs) { 352 defineCodeSystemProperty(cs, "deprecationDate", "The date at which a concept was deprecated. Concepts that are deprecated but not inactive can still be used, but their use is discouraged", PropertyType.DATETIME); 353 } 354 355 public static void defineParentProperty(@Nonnull CodeSystem cs) { 356 defineCodeSystemProperty(cs, "parent", "The concept identified in this property is a parent of the concept on which it is a property. The property type will be 'code'. The meaning of parent/child relationships is defined by the hierarchyMeaning attribute", PropertyType.CODE); 357 } 358 359 public static void defineChildProperty(@Nonnull CodeSystem cs) { 360 defineCodeSystemProperty(cs, "child", "The concept identified in this property is a child of the concept on which it is a property. The property type will be 'code'. The meaning of parent/child relationships is defined by the hierarchyMeaning attribute", PropertyType.CODE); 361 } 362 363 public static boolean isDeprecated(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent def, boolean ignoreStatus) { 364 try { 365 for (ConceptPropertyComponent p : def.getProperty()) { 366 if (!ignoreStatus) { 367 if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "deprecated".equals(p.getValueCodeType().getCode())) 368 return true; 369 } 370 // this, though status should also be set 371 if ("deprecationDate".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof DateTimeType) 372 return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance())); 373 // legacy 374 if ("deprecated".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 375 return ((BooleanType) p.getValue()).getValue(); 376 } 377 StandardsStatus ss = ExtensionUtilities.getStandardsStatus(def); 378 if (ss == StandardsStatus.DEPRECATED) { 379 return true; 380 } 381 return false; 382 } catch (FHIRException e) { 383 return false; 384 } 385 } 386 387 public static boolean isInactive(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent def, boolean ignoreStatus) { 388 try { 389 for (ConceptPropertyComponent p : def.getProperty()) { 390 if (!ignoreStatus) { 391 if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "inactive".equals(p.getValueCodeType().getCode())) 392 return true; 393 } 394 // legacy 395 if ("inactive".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 396 return ((BooleanType) p.getValue()).getValue(); 397 } 398 return false; 399 } catch (FHIRException e) { 400 return false; 401 } 402 } 403 404 public static void setDeprecated(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent concept, @Nonnull DateTimeType date) throws FHIRFormatError { 405 setStatus(cs, concept, ConceptStatus.Deprecated); 406 defineDeprecatedProperty(cs); 407 concept.addProperty().setCode("deprecationDate").setValue(date); 408 } 409 410 411 public static void setDeprecated(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent concept) throws FHIRFormatError { 412 setStatus(cs, concept, ConceptStatus.Deprecated); 413 } 414 415 public static boolean isInactive(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent def) throws FHIRException { 416 StandardsStatus ss = ExtensionUtilities.getStandardsStatus(def); 417 if (ss == StandardsStatus.DEPRECATED || ss == StandardsStatus.WITHDRAWN) { 418 return true; 419 } 420 for (ConceptPropertyComponent p : def.getProperty()) { 421 if ("status".equals(p.getCode()) && p.hasValueStringType()) { 422 return "inactive".equals(p.getValueStringType().primitiveValue()) || "retired".equals(p.getValueStringType().primitiveValue()) || "deprecated".equals(p.getValueStringType().primitiveValue()); 423 } 424 if ("inactive".equals(p.getCode()) && p.hasValueBooleanType()) { 425 return p.getValueBooleanType().getValue(); 426 } 427 if ("inactive".equals(p.getCode()) && p.hasValueCodeType()) { 428 String code = p.getValueCodeType().primitiveValue(); 429 return "true".equals(code); 430 } 431 } 432 return false; 433 } 434 435 public static boolean isInactive(@Nonnull CodeSystem cs, @Nonnull String code) throws FHIRException { 436 if (cs.hasUserData(UserDataNames.tx_cs_special)) { 437 SpecialCodeSystem scs = (SpecialCodeSystem) cs.getUserData(UserDataNames.tx_cs_special); 438 return scs.inactive(code); 439 } 440 ConceptDefinitionComponent def = findCode(cs.getConcept(), code); 441 if (def == null) 442 return true; 443 return isInactive(cs, def); 444 } 445 446 public static void defineCodeSystemProperty(@Nonnull CodeSystem cs, @Nonnull String code, @Nonnull String description, @Nonnull PropertyType type) { 447 for (PropertyComponent p : cs.getProperty()) { 448 if (p.hasCode() && p.getCode().equals(code)) 449 return; 450 } 451 cs.addProperty().setCode(code).setDescription(description).setType(type).setUri("http://hl7.org/fhir/concept-properties#"+code); 452 } 453 454 public static String getCodeDefinition(@Nonnull CodeSystem cs, @Nonnull String code) { 455 return getCodeDefinition(cs.getConcept(), code); 456 } 457 458 private static String getCodeDefinition(List<ConceptDefinitionComponent> list, String code) { 459 for (ConceptDefinitionComponent c : list) { 460 if (c.hasCode() && c.getCode().equals(code)) 461 return c.getDefinition(); 462 String s = getCodeDefinition(c.getConcept(), code); 463 if (s != null) 464 return s; 465 } 466 return null; 467 } 468 469 public static CodeSystem makeShareable(@Nonnull CodeSystem cs) { 470 if (!cs.hasExperimental()) { 471 cs.setExperimental(false); 472 } 473 474 if (!cs.hasMeta()) 475 cs.setMeta(new Meta()); 476 for (UriType t : cs.getMeta().getProfile()) 477 if ("http://hl7.org/fhir/StructureDefinition/shareablecodesystem".equals(t.getValue())) 478 return cs; 479 cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem")); 480 return cs; 481 } 482 483 public static boolean checkMakeShareable(@Nonnull CodeSystem cs) { 484 boolean changed = false; 485 if (!cs.hasExperimental()) { 486 cs.setExperimental(false); 487 changed = true; 488 } 489 490 if (!cs.hasMeta()) 491 cs.setMeta(new Meta()); 492 for (UriType t : cs.getMeta().getProfile()) 493 if ("http://hl7.org/fhir/StructureDefinition/shareablecodesystem".equals(t.getValue())) 494 return changed; 495 cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem")); 496 return true; 497 } 498 499 public static void setOID(@Nonnull CodeSystem cs, @Nonnull String oid) { 500 if (!oid.startsWith("urn:oid:")) 501 oid = "urn:oid:" + oid; 502 if (!cs.hasIdentifier()) 503 cs.addIdentifier(new Identifier().setSystem("urn:ietf:rfc:3986").setValue(oid)); 504 else if ("urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:")) 505 cs.getIdentifierFirstRep().setValue(oid); 506 else 507 throw new Error("unable to set OID on code system"); 508 509 } 510 511 public static boolean hasOID(@Nonnull CanonicalResource cs) { 512 return getOID(cs) != null; 513 } 514 515 public static String getOID(@Nonnull CanonicalResource cs) { 516 if (cs != null && cs.hasIdentifier() && "urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:")) 517 return cs.getIdentifierFirstRep().getValue().substring(8); 518 return null; 519 } 520 521 public static ConceptDefinitionComponent findCode(@Nonnull List<ConceptDefinitionComponent> list, @Nonnull String code) { 522 for (ConceptDefinitionComponent c : list) { 523 if (c.hasCode() && c.getCode().equals(code)) 524 return c; 525 ConceptDefinitionComponent s = findCode(c.getConcept(), code); 526 if (s != null) 527 return s; 528 } 529 return null; 530 } 531 532 533 public static List<ConceptDefinitionComponent> findCodeWithParents(@Nonnull List<ConceptDefinitionComponent> parents, @Nonnull List<ConceptDefinitionComponent> list, @Nonnull String code) { 534 for (ConceptDefinitionComponent c : list) { 535 if (c.hasCode() && c.getCode().equals(code)) { 536 return addToList(parents, c); 537 } 538 List<ConceptDefinitionComponent> s = findCodeWithParents(addToList(parents, c), c.getConcept(), code); 539 if (s != null) 540 return s; 541 } 542 return null; 543 } 544 545 private static List<ConceptDefinitionComponent> addToList(List<ConceptDefinitionComponent> parents, ConceptDefinitionComponent c) { 546 List<ConceptDefinitionComponent> res = new ArrayList<CodeSystem.ConceptDefinitionComponent>(); 547 if (parents != null) { 548 res.addAll(parents); 549 } 550 res.add(c); 551 return res; 552 } 553 554 public static ConceptDefinitionComponent findCodeOrAltCode(@Nonnull List<ConceptDefinitionComponent> list, @Nonnull String code, @Nonnull String use) { 555 for (ConceptDefinitionComponent c : list) { 556 if (c.hasCode() && c.getCode().equals(code)) 557 return c; 558 for (ConceptPropertyComponent p : c.getProperty()) { 559 if ("alternateCode".equals(p.getCode()) && (use == null || hasUse(p, use)) && p.hasValue() && p.getValue().isPrimitive() && code.equals(p.getValue().primitiveValue())) { 560 return c; 561 } 562 } 563 ConceptDefinitionComponent s = findCodeOrAltCode(c.getConcept(), code, use); 564 if (s != null) 565 return s; 566 } 567 return null; 568 } 569 570 private static boolean hasUse(ConceptPropertyComponent p, String use) { 571 for (Extension ext : p.getExtensionsByUrl(ExtensionDefinitions.EXT_CS_ALTERNATE_USE)) { 572 if (ext.hasValueCoding() && use.equals(ext.getValueCoding().getCode())) { 573 return true; 574 } 575 } 576 return false; 577 } 578 579 public static void markStatus(@Nonnull CodeSystem cs, String wg, StandardsStatus status, String pckage, String fmm, String normativeVersion) throws FHIRException { 580 if (wg != null) { 581 if (!ExtensionUtilities.hasExtension(cs, ExtensionDefinitions.EXT_WORKGROUP) || 582 (Utilities.existsInList(ExtensionUtilities.readStringExtension(cs, ExtensionDefinitions.EXT_WORKGROUP), "fhir", "vocab") && !Utilities.existsInList(wg, "fhir", "vocab"))) { 583 CanonicalResourceUtilities.setHl7WG(cs, wg); 584 } 585 } 586 if (status != null) { 587 StandardsStatus ss = ExtensionUtilities.getStandardsStatus(cs); 588 if (ss == null || ss.isLowerThan(status)) 589 ExtensionUtilities.setStandardsStatus(cs, status, normativeVersion); 590 if (pckage != null) { 591 if (!cs.hasUserData(UserDataNames.kindling_ballot_package)) 592 cs.setUserData(UserDataNames.kindling_ballot_package, pckage); 593 else if (!pckage.equals(cs.getUserString(UserDataNames.kindling_ballot_package))) 594 if (!"infrastructure".equals(cs.getUserString(UserDataNames.kindling_ballot_package))) 595 log.warn("Code System "+cs.getUrl()+": ownership clash "+pckage+" vs "+cs.getUserString(UserDataNames.kindling_ballot_package)); 596 } 597 if (status == StandardsStatus.NORMATIVE) { 598 cs.setStatus(PublicationStatus.ACTIVE); 599 } 600 } 601 if (fmm != null) { 602 String sfmm = ExtensionUtilities.readStringExtension(cs, ExtensionDefinitions.EXT_FMM_LEVEL); 603 if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm)) { 604 ExtensionUtilities.setIntegerExtension(cs, ExtensionDefinitions.EXT_FMM_LEVEL, Integer.parseInt(fmm)); 605 } 606 } 607 } 608 609 610 public static DataType readProperty(@Nonnull ConceptDefinitionComponent concept, @Nonnull String code) { 611 for (ConceptPropertyComponent p : concept.getProperty()) 612 if (p.hasCode() && p.getCode().equals(code)) 613 return p.getValue(); 614 return null; 615 } 616 617 public static ConceptPropertyComponent getProperty(@Nonnull ConceptDefinitionComponent concept, @Nonnull String code) { 618 for (ConceptPropertyComponent p : concept.getProperty()) 619 if (p.hasCode() && p.getCode().equals(code)) 620 return p; 621 return null; 622 } 623 624 public static List<ConceptPropertyComponent> getPropertyValues(@Nonnull ConceptDefinitionComponent concept, @Nonnull String code) { 625 List<ConceptPropertyComponent> res = new ArrayList<>(); 626 if (code != null) { 627 for (ConceptPropertyComponent p : concept.getProperty()) { 628 if (code.equals(p.getCode())) { 629 res.add(p); 630 } 631 } 632 } 633 return res; 634 } 635 636 // see http://hl7.org/fhir/R4/codesystem.html#hierachy 637 // returns additional parents not in the hierarchy 638 public static List<String> getOtherChildren(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) { 639 List<String> res = new ArrayList<String>(); 640 for (ConceptPropertyComponent p : c.getProperty()) { 641 if ("parent".equals(p.getCode())) { 642 res.add(p.getValue().primitiveValue()); 643 } 644 } 645 return res; 646 } 647 648 // see http://hl7.org/fhir/R4/codesystem.html#hierachy 649 public static void addOtherChild(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent owner, @Nonnull String code) { 650 defineChildProperty(cs); 651 owner.addProperty().setCode("child").setValue(new CodeType(code)); 652 } 653 654 public static boolean hasProperty(@Nonnull ConceptDefinitionComponent c, @Nonnull String code) { 655 for (ConceptPropertyComponent cp : c.getProperty()) { 656 if (code.equals(cp.getCode())) { 657 return true; 658 } 659 } 660 return false; 661 } 662 663 public static String getProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c, @Nonnull String uri, @Nonnull String code) { 664 for (ConceptPropertyComponent cp : c.getProperty()) { 665 if (code.equals(cp.getCode())) { 666 return cp.getValue().primitiveValue(); 667 } 668 } 669 for (PropertyComponent p : cs.getProperty()) { 670 if (uri.equals(p.getUri())) { 671 var t = getProperty(c, p.getCode()); 672 if (t != null) { 673 return t.primitiveValue(); 674 } 675 } 676 } 677 return null; 678 } 679 680 public static boolean hasProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c, @Nonnull String uri, @Nonnull String code) { 681 for (ConceptPropertyComponent cp : c.getProperty()) { 682 if (code.equals(cp.getCode())) { 683 return true; 684 } 685 } 686 for (PropertyComponent p : cs.getProperty()) { 687 if (uri.equals(p.getUri())) { 688 return hasProperty(c, p.getCode()); 689 } 690 } 691 return false; 692 } 693 694 public static boolean hasCode(@Nonnull CodeSystem cs, @Nonnull String code) { 695 for (ConceptDefinitionComponent cc : cs.getConcept()) { 696 if (hasCode(cc, code)) { 697 return true; 698 } 699 } 700 return false; 701 } 702 703 private static boolean hasCode(@Nonnull ConceptDefinitionComponent cc, @Nonnull String code) { 704 if (code.equals(cc.getCode())) { 705 return true; 706 } 707 for (ConceptDefinitionComponent c : cc.getConcept()) { 708 if (hasCode(c, code)) { 709 return true; 710 } 711 } 712 return false; 713 } 714 715 public static ConceptDefinitionComponent getCode(@Nonnull CodeSystem cs, @Nonnull String code) { 716 for (ConceptDefinitionComponent cc : cs.getConcept()) { 717 ConceptDefinitionComponent cd = getCode(cc, code); 718 if (cd != null) { 719 return cd; 720 } 721 } 722 return null; 723 } 724 725 private static ConceptDefinitionComponent getCode(@Nonnull ConceptDefinitionComponent cc, @Nonnull String code) { 726 if (code.equals(cc.getCode())) { 727 return cc; 728 } 729 for (ConceptDefinitionComponent c : cc.getConcept()) { 730 ConceptDefinitionComponent cd = getCode(c, code); 731 if (cd != null) { 732 return cd; 733 } 734 } 735 return null; 736 } 737 738 public static void crossLinkCodeSystem(@Nonnull CodeSystem cs) { 739 String parent = getPropertyByUrl(cs, "http://hl7.org/fhir/concept-properties#parent"); 740 if ((parent != null)) { 741 crossLinkConcepts(cs.getConcept(), cs.getConcept(), parent); 742 } 743 } 744 745 private static String getPropertyByUrl(@Nonnull CodeSystem cs, String url) { 746 for (PropertyComponent pc : cs.getProperty()) { 747 if (url.equals(pc.getUri())) { 748 return pc.getCode(); 749 } 750 } 751 return null; 752 } 753 754 private static void crossLinkConcepts(List<ConceptDefinitionComponent> root, List<ConceptDefinitionComponent> focus, String parent) { 755 for (ConceptDefinitionComponent def : focus) { 756 List<ConceptPropertyComponent> pcl = getPropertyValues(def, parent); 757 for (ConceptPropertyComponent pc : pcl) { 758 String code = pc.getValue().primitiveValue(); 759 ConceptDefinitionComponent tgt = findCode(root, code); 760 if (!tgt.hasUserData(USER_DATA_CROSS_LINK)) { 761 tgt.setUserData(USER_DATA_CROSS_LINK, new ArrayList<>()); 762 } 763 @SuppressWarnings("unchecked") 764 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) tgt.getUserData(USER_DATA_CROSS_LINK); 765 children.add(def); 766 } 767 if (def.hasConcept()) { 768 crossLinkConcepts(root, def.getConcept(), parent); 769 } 770 } 771 772 } 773 774 public static boolean hasHierarchy(@Nonnull CodeSystem cs) { 775 for (ConceptDefinitionComponent c : cs.getConcept()) { 776 if (c.hasConcept()) { 777 return true; 778 } 779 } 780 return false; 781 } 782 783 public static void sortAllCodes(@Nonnull CodeSystem cs) { 784 sortAllCodes(cs.getConcept()); 785 } 786 787 private static void sortAllCodes(List<ConceptDefinitionComponent> list) { 788 Collections.sort(list, new ConceptDefinitionComponentSorter()); 789 for (ConceptDefinitionComponent cd : list) { 790 if (cd.hasConcept()) { 791 sortAllCodes(cd.getConcept()); 792 } 793 } 794 } 795 796 public static Coding readCoding(String jurisdiction) { 797 return jurisdiction == null || !jurisdiction.contains("#") ? null : new Coding().setCode(jurisdiction.substring(jurisdiction.indexOf("#")+1)).setSystem(jurisdiction.substring(0, jurisdiction.indexOf("#"))); 798 } 799 800 public static SystemReference getSystemReference(String system, IWorkerContext ctxt) { 801 if (system == null) { 802 return null; 803 } if ("http://snomed.info/sct".equals(system)) { 804 return new SystemReference("SNOMED CT", "https://browser.ihtsdotools.org/"); 805 } else if ("http://loinc.org".equals(system)) { 806 return new SystemReference("LOINC", "https://loinc.org/"); 807 } else if ("http://unitsofmeasure.org".equals(system)) { 808 return new SystemReference("UCUM", "http://ucum.org"); 809 } else if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) { 810 return new SystemReference("RxNorm", "http://www.nlm.nih.gov/research/umls/rxnorm"); 811 } else if (ctxt != null) { 812 CodeSystem cs = ctxt.fetchCodeSystem(system); 813 if (cs != null && cs.hasWebPath()) { 814 return new SystemReference(cs.present(), cs.getWebPath(), Utilities.isAbsoluteUrl(cs.getWebPath())); 815 } else if (cs != null) { 816 return new SystemReference(cs.present(), null); 817 } 818 } 819 return null; 820 } 821 822 public static boolean isNotCurrent(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) { 823 return isInactive(cs, c) || isDeprecated(cs, c, false); 824 } 825 826 public static List<String> getDisplays(@Nonnull CodeSystem srcCS, @Nonnull ConceptDefinitionComponent cd) { 827 List<String> list = new ArrayList<>(); 828 if (cd.hasDisplay()) { 829 list.add(cd.getDisplay()); 830 } 831 for (ConceptDefinitionDesignationComponent d : cd.getDesignation()) { 832 if (!list.contains(d.getValue())) { 833 list.add(d.getValue()); 834 } 835 } 836 return list; 837 } 838 839 public static boolean checkDisplay(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent cd, @Nonnull String display) { 840 List<String> displays = getDisplays(cs, cd); 841 for (String s : displays) { 842 if (s.equalsIgnoreCase(display)) { 843 return true; 844 } 845 } 846 return false; 847 } 848 849 public static int countCodes(@Nonnull CodeSystem cs) { 850 return countCodes(cs.getConcept()); 851 } 852 853 private static int countCodes(List<ConceptDefinitionComponent> concept) { 854 int t = concept.size(); 855 for (ConceptDefinitionComponent cd : concept) { 856 t = t + (cd.hasConcept() ? countCodes(cd.getConcept()) : 0); 857 } 858 return t; 859 } 860 861 public static CodeSystem mergeSupplements(@Nonnull CodeSystem cs, @Nonnull List<CodeSystem> supplements) { 862 CodeSystem ret = cs.copy(); 863 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 864 for (CodeSystem sup : supplements) { 865 b.append(sup.getVersionedUrl()); 866 } 867 ret.setUserData(UserDataNames.tx_known_supplements, b.toString()); 868 869 for (ConceptDefinitionComponent t : ret.getConcept()) { 870 mergeSupplements(ret, t, supplements); 871 } 872 return ret; 873 } 874 875 private static void mergeSupplements(CodeSystem ret, ConceptDefinitionComponent fdef, List<CodeSystem> supplements) { 876 for (CodeSystem cs : supplements) { 877 ConceptDefinitionComponent def = CodeSystemUtilities.findCode(cs.getConcept(), fdef.getCode()); 878 if (def != null) { 879 for (Extension ext : def.getExtension()) { 880 fdef.addExtension(ext.copy()); 881 } 882 for (ConceptDefinitionDesignationComponent d : def.getDesignation()) { 883 fdef.addDesignation(d.copy()); 884 } 885 for (ConceptPropertyComponent p : def.getProperty()) { 886 PropertyComponent pd = CodeSystemUtilities.getPropertyDefinition(cs, p); 887 String code; 888 if (pd != null) { 889 code = defineProperty(ret, pd, propertyTypeForType(p.getValue())); 890 } else { 891 code = defineProperty(ret, p.getCode(), propertyTypeForType(p.getValue())); 892 } 893 fdef.addProperty().setCode(code).setValue(p.getValue()).copyExtensions(p, "http://hl7.org/fhir/StructureDefinition/alternate-code-use", "http://hl7.org/fhir/StructureDefinition/alternate-code-status"); 894 } 895 } 896 for (ConceptDefinitionComponent t : fdef.getConcept()) { 897 mergeSupplements(ret, t, supplements); 898 } 899 } 900 } 901 902 private static PropertyType propertyTypeForType(DataType value) { 903 if (value == null) { 904 return PropertyType.NULL; 905 } 906 if (value instanceof CodeType) { 907 return PropertyType.CODE; 908 } 909 if (value instanceof CodeType) { 910 return PropertyType.CODING; 911 } 912 if (value instanceof CodeType) { 913 return PropertyType.STRING; 914 } 915 if (value instanceof CodeType) { 916 return PropertyType.INTEGER; 917 } 918 if (value instanceof CodeType) { 919 return PropertyType.BOOLEAN; 920 } 921 if (value instanceof CodeType) { 922 return PropertyType.DATETIME; 923 } 924 if (value instanceof CodeType) { 925 return PropertyType.DECIMAL; 926 } 927 throw new FHIRException("Unsupported property value for a CodeSystem Property: "+value.fhirType()); 928 } 929 930 private static String defineProperty(CodeSystem cs, PropertyComponent pd, PropertyType pt) { 931 for (PropertyComponent p : cs.getProperty()) { 932 if (p.hasCode() && p.getCode().equals(pd.getCode())) { 933 if (!p.getUri().equals(pd.getUri())) { 934 throw new Error("URI mismatch for code "+pd.getCode()+" url = "+p.getUri()+" vs "+pd.getUri()); 935 } 936 if (!p.getType().equals(pt)) { 937 throw new Error("Type mismatch for code "+pd.getCode()+" type = "+p.getType().toCode()+" vs "+pt.toCode()); 938 } 939 return pd.getCode(); 940 } 941 } 942 cs.addProperty().setCode(pd.getCode()).setUri(pd.getUri()).setType(pt); 943 return pd.getCode(); 944 945 } 946 947 948 private static PropertyComponent getPropertyDefinition(CodeSystem cs, ConceptPropertyComponent p) { 949 for (PropertyComponent t : cs.getProperty()) { 950 if (t.hasCode() && t.getCode().equals(p.getCode())) { 951 return t; 952 } 953 } 954 return null; 955 } 956 957 public static boolean hasProperties(@Nonnull CodeSystem cs) { 958 return hasProperties(cs.getConcept()); 959 } 960 961 private static boolean hasProperties(List<ConceptDefinitionComponent> list) { 962 for (ConceptDefinitionComponent c : list) { 963 if (c.hasProperty() || hasProperties(c.getConcept())) { 964 return true; 965 } 966 } 967 return false; 968 } 969 970 public static boolean hasDesignations(@Nonnull CodeSystem cs) { 971 return hasDesignations(cs.getConcept()); 972 } 973 974 private static boolean hasDesignations(List<ConceptDefinitionComponent> list) { 975 for (ConceptDefinitionComponent c : list) { 976 if (c.hasDesignation() || hasDesignations(c.getConcept())) { 977 return true; 978 } 979 } 980 return false; 981 } 982 983 public static boolean hasPropertyDef(@Nonnull CodeSystem cs, @Nonnull String property) { 984 985 for (PropertyComponent pd : cs.getProperty()) { 986 if (pd.hasCode() && pd.getCode().equals(property)) { 987 return true; 988 } 989 } 990 return false; 991 } 992 993 public static DataType getProperty(@Nonnull CodeSystem cs, @Nonnull String code, @Nonnull String property) { 994 ConceptDefinitionComponent def = getCode(cs, code); 995 return getProperty(cs, def, property); 996 } 997 998 public static DataType getProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent def, @Nonnull String property) { 999 PropertyComponent defn = getPropertyDefinition(cs, property); 1000 if (defn != null) { 1001 property = defn.getCode(); 1002 } 1003 ConceptPropertyComponent cp = getProperty(def, property); 1004 return cp == null ? null : cp.getValue(); 1005 } 1006 1007 public static boolean hasMarkdownInDefinitions(@Nonnull CodeSystem cs, @Nonnull MarkDownProcessor md) { 1008 return hasMarkdownInDefinitions(cs.getConcept(), md); 1009 } 1010 1011 private static boolean hasMarkdownInDefinitions(List<ConceptDefinitionComponent> concepts, MarkDownProcessor md) { 1012 for (ConceptDefinitionComponent c : concepts) { 1013 if (c.hasDefinition() && md.isProbablyMarkdown(c.getDefinition(), true)) { 1014 return true; 1015 } 1016 if (c.hasConcept() && hasMarkdownInDefinitions(c.getConcept(), md)) { 1017 return true; 1018 } 1019 } 1020 return false; 1021 } 1022 1023 public static String getStatus(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent cc) { 1024 StandardsStatus ss = ExtensionUtilities.getStandardsStatus(cc); 1025 if (ss == StandardsStatus.DEPRECATED || ss == StandardsStatus.WITHDRAWN) { 1026 return ss.toCode(); 1027 } 1028 DataType v = getProperty(cs, cc, "status"); 1029 if (v == null || !v.isPrimitive()) { 1030 return null; 1031 } else { 1032 return v.primitiveValue(); 1033 } 1034 } 1035 1036 public static Boolean subsumes(@Nonnull CodeSystem cs, @Nonnull String pc, @Nonnull String cc) { 1037 if (pc.equals(cc)) { 1038 return true; 1039 } 1040 List<ConceptDefinitionComponent> child = findCodeWithParents(null, cs.getConcept(), cc); 1041 for (ConceptDefinitionComponent item : child) { 1042 if (pc.equals(item.getCode())) { 1043 return true; 1044 } 1045 } 1046 return false; 1047 } 1048 1049 public static Set<String> codes(@Nonnull CodeSystem cs) { 1050 Set<String> res = new HashSet<>(); 1051 addCodes(res, cs.getConcept()); 1052 return res; 1053 } 1054 1055 private static void addCodes(Set<String> res, List<ConceptDefinitionComponent> list) { 1056 for (ConceptDefinitionComponent cd : list) { 1057 if (cd.hasCode()) { 1058 res.add(cd.getCode()); 1059 } 1060 if (cd.hasConcept()) { 1061 addCodes(res, cd.getConcept()); 1062 } 1063 } 1064 } 1065 1066 /** 1067 * property in this case is the name of a property that appears in a ValueSet filter 1068 * 1069 * @param cs 1070 * @param property 1071 * @return 1072 */ 1073 public static PropertyComponent getPropertyDefinition(@Nonnull CodeSystem cs, @Nonnull String property) { 1074 String uri = getStandardPropertyUri(property); 1075 if (uri != null) { 1076 for (PropertyComponent cp : cs.getProperty()) { 1077 if (uri.equals(cp.getUri())) { 1078 return cp; 1079 } 1080 } 1081 } 1082 for (PropertyComponent cp : cs.getProperty()) { 1083 if (cp.getCode().equals(property)) { 1084 return cp; 1085 } 1086 } 1087 return null; 1088 } 1089 1090 public static boolean isDefinedProperty(@Nonnull CodeSystem cs, @Nonnull String property) { 1091 String uri = getStandardPropertyUri(property); 1092 if (uri != null) { 1093 for (PropertyComponent cp : cs.getProperty()) { 1094 if (uri.equals(cp.getUri())) { 1095 return true; 1096 } 1097 } 1098 } 1099 for (PropertyComponent cp : cs.getProperty()) { 1100 if (cp.getCode().equals(property) && (uri == null || !cp.hasUri())) { // if uri is right, will return from above 1101 return true; 1102 } 1103 } 1104 return false; 1105 } 1106 1107 1108 private static String getStandardPropertyUri(String property) { 1109 switch (property) { 1110 case "status" : return "http://hl7.org/fhir/concept-properties#status"; 1111 case "inactive" : return "http://hl7.org/fhir/concept-properties#inactive"; 1112 case "effectiveDate" : return "http://hl7.org/fhir/concept-properties#effectiveDate"; 1113 case "deprecationDate" : return "http://hl7.org/fhir/concept-properties#deprecationDate"; 1114 case "retirementDate" : return "http://hl7.org/fhir/concept-properties#retirementDate"; 1115 case "notSelectable" : return "http://hl7.org/fhir/concept-properties#notSelectable"; 1116 case "parent" : return "http://hl7.org/fhir/concept-properties#parent"; 1117 case "child" : return "http://hl7.org/fhir/concept-properties#child"; 1118 case "partOf" : return "http://hl7.org/fhir/concept-properties#partOf"; 1119 case "synonym" : return "http://hl7.org/fhir/concept-properties#synonym"; 1120 case "comment" : return "http://hl7.org/fhir/concept-properties#comment"; 1121 case "itemWeight" : return "http://hl7.org/fhir/concept-properties#itemWeight"; 1122 } 1123 return null; 1124 } 1125 1126 public static boolean isExemptFromMultipleVersionChecking(String url) { 1127 return Utilities.existsInList(url, "http://snomed.info/sct", "http://loinc.org"); 1128 } 1129 1130 public static PropertyComponent getPropertyByUri(@Nonnull CodeSystem cs, @Nonnull String uri) { 1131 for (PropertyComponent t : cs.getProperty()) { 1132 if (uri.equals(t.getUri())) { 1133 return t; 1134 } 1135 } 1136 return null; 1137 } 1138 1139 public static CodeSystem convertSD(@Nonnull StructureDefinition sd) { 1140 CodeSystem cs = new CodeSystem(); 1141 cs.setId(sd.getId()); 1142 cs.setUrl(sd.getUrl()); 1143 cs.setVersion(sd.getVersion()); 1144 cs.setStatus(sd.getStatus()); 1145 cs.setContent(Enumerations.CodeSystemContentMode.COMPLETE); 1146 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 1147 ConceptDefinitionComponent cd = cs.addConcept(); 1148 cd.setCode(ed.getId()); 1149 cd.setDisplay(ed.getId()); 1150 ed.setDefinition(ed.getDefinition()); 1151 } 1152 return cs; 1153 } 1154 1155 public static boolean hasCSComments(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) { 1156 return hasProperty(cs, c, "http://hl7.org/fhir/concept-properties#comments", "comments"); 1157 } 1158 1159 1160 public static String getCSComments(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) { 1161 return getProperty(cs, c, "http://hl7.org/fhir/concept-properties#comments", "comments"); 1162 } 1163 1164 public static void addCSComments(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent cc, @Nonnull String comments) { 1165 String code = "comments"; 1166 1167 for (PropertyComponent p : cs.getProperty()) { 1168 if ("http://hl7.org/fhir/concept-properties#comments".equals(p.getUri())) { 1169 code = p.getCode(); 1170 } 1171 } 1172 setProperty(cs, cc, code, new StringType(comments)); 1173 } 1174 1175} 1176