
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.WITHDRAWN) { 418 return true; 419 } 420 for (ConceptPropertyComponent p : def.getProperty()) { 421 if ("status".equals(p.getCode()) && p.hasValueStringType()) { 422 return Utilities.existsInList(p.getValueStringType().primitiveValue(), "inactive", "retired"); 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 boolean makeShareable(@Nonnull CodeSystem cs, boolean extension) { 470 boolean changed = false; 471 if (!cs.hasExperimental()) { 472 cs.setExperimental(false); 473 changed = true; 474 } 475 476 if (extension) { 477 if (!cs.hasMeta()) 478 cs.setMeta(new Meta()); 479 for (UriType t : cs.getMeta().getProfile()) 480 if ("http://hl7.org/fhir/StructureDefinition/shareablecodesystem".equals(t.getValue())) 481 return changed; 482 cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem")); 483 } 484 return changed; 485 } 486 487 public static void setOID(@Nonnull CodeSystem cs, @Nonnull String oid) { 488 if (!oid.startsWith("urn:oid:")) 489 oid = "urn:oid:" + oid; 490 if (!cs.hasIdentifier()) 491 cs.addIdentifier(new Identifier().setSystem("urn:ietf:rfc:3986").setValue(oid)); 492 else if ("urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:")) 493 cs.getIdentifierFirstRep().setValue(oid); 494 else 495 throw new Error("unable to set OID on code system"); 496 497 } 498 499 public static boolean hasOID(@Nonnull CanonicalResource cs) { 500 return getOID(cs) != null; 501 } 502 503 public static String getOID(@Nonnull CanonicalResource cs) { 504 if (cs != null && cs.hasIdentifier() && "urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:")) 505 return cs.getIdentifierFirstRep().getValue().substring(8); 506 return null; 507 } 508 509 public static ConceptDefinitionComponent findCode(@Nonnull List<ConceptDefinitionComponent> list, @Nonnull String code) { 510 for (ConceptDefinitionComponent c : list) { 511 if (c.hasCode() && c.getCode().equals(code)) 512 return c; 513 ConceptDefinitionComponent s = findCode(c.getConcept(), code); 514 if (s != null) 515 return s; 516 } 517 return null; 518 } 519 520 521 public static List<ConceptDefinitionComponent> findCodeWithParents(@Nonnull List<ConceptDefinitionComponent> parents, @Nonnull List<ConceptDefinitionComponent> list, @Nonnull String code) { 522 for (ConceptDefinitionComponent c : list) { 523 if (c.hasCode() && c.getCode().equals(code)) { 524 return addToList(parents, c); 525 } 526 List<ConceptDefinitionComponent> s = findCodeWithParents(addToList(parents, c), c.getConcept(), code); 527 if (s != null) 528 return s; 529 } 530 return null; 531 } 532 533 private static List<ConceptDefinitionComponent> addToList(List<ConceptDefinitionComponent> parents, ConceptDefinitionComponent c) { 534 List<ConceptDefinitionComponent> res = new ArrayList<CodeSystem.ConceptDefinitionComponent>(); 535 if (parents != null) { 536 res.addAll(parents); 537 } 538 res.add(c); 539 return res; 540 } 541 542 public static ConceptDefinitionComponent findCodeOrAltCode(@Nonnull List<ConceptDefinitionComponent> list, @Nonnull String code, @Nonnull String use) { 543 for (ConceptDefinitionComponent c : list) { 544 if (c.hasCode() && c.getCode().equals(code)) 545 return c; 546 for (ConceptPropertyComponent p : c.getProperty()) { 547 if ("alternateCode".equals(p.getCode()) && (use == null || hasUse(p, use)) && p.hasValue() && p.getValue().isPrimitive() && code.equals(p.getValue().primitiveValue())) { 548 return c; 549 } 550 } 551 ConceptDefinitionComponent s = findCodeOrAltCode(c.getConcept(), code, use); 552 if (s != null) 553 return s; 554 } 555 return null; 556 } 557 558 private static boolean hasUse(ConceptPropertyComponent p, String use) { 559 for (Extension ext : p.getExtensionsByUrl(ExtensionDefinitions.EXT_CS_ALTERNATE_USE)) { 560 if (ext.hasValueCoding() && use.equals(ext.getValueCoding().getCode())) { 561 return true; 562 } 563 } 564 return false; 565 } 566 567 public static void markStatus(@Nonnull CodeSystem cs, String wg, StandardsStatus status, String fmm, String normativeVersion) throws FHIRException { 568 if (wg != null) { 569 if (!ExtensionUtilities.hasExtension(cs, ExtensionDefinitions.EXT_WORKGROUP) || 570 (Utilities.existsInList(ExtensionUtilities.readStringExtension(cs, ExtensionDefinitions.EXT_WORKGROUP), "fhir", "vocab") && !Utilities.existsInList(wg, "fhir", "vocab"))) { 571 CanonicalResourceUtilities.setHl7WG(cs, wg); 572 } 573 } 574 if (status != null) { 575 StandardsStatus ss = ExtensionUtilities.getStandardsStatus(cs); 576 if (ss == null || ss.isLowerThan(status)) 577 ExtensionUtilities.setStandardsStatus(cs, status, normativeVersion); 578 if (status == StandardsStatus.NORMATIVE) { 579 cs.setStatus(PublicationStatus.ACTIVE); 580 } 581 } 582 if (fmm != null) { 583 String sfmm = ExtensionUtilities.readStringExtension(cs, ExtensionDefinitions.EXT_FMM_LEVEL); 584 if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm)) { 585 ExtensionUtilities.setIntegerExtension(cs, ExtensionDefinitions.EXT_FMM_LEVEL, Integer.parseInt(fmm)); 586 } 587 } 588 } 589 590 591 public static DataType readProperty(@Nonnull ConceptDefinitionComponent concept, @Nonnull String code) { 592 for (ConceptPropertyComponent p : concept.getProperty()) 593 if (p.hasCode() && p.getCode().equals(code)) 594 return p.getValue(); 595 return null; 596 } 597 598 public static ConceptPropertyComponent getProperty(@Nonnull ConceptDefinitionComponent concept, @Nonnull String code) { 599 for (ConceptPropertyComponent p : concept.getProperty()) 600 if (p.hasCode() && p.getCode().equals(code)) 601 return p; 602 return null; 603 } 604 605 public static List<ConceptPropertyComponent> getPropertyValues(@Nonnull ConceptDefinitionComponent concept, @Nonnull String code) { 606 List<ConceptPropertyComponent> res = new ArrayList<>(); 607 if (code != null) { 608 for (ConceptPropertyComponent p : concept.getProperty()) { 609 if (code.equals(p.getCode())) { 610 res.add(p); 611 } 612 } 613 } 614 return res; 615 } 616 617 // see http://hl7.org/fhir/R4/codesystem.html#hierachy 618 // returns additional parents not in the hierarchy 619 public static List<String> getOtherChildren(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) { 620 List<String> res = new ArrayList<String>(); 621 for (ConceptPropertyComponent p : c.getProperty()) { 622 if ("parent".equals(p.getCode())) { 623 res.add(p.getValue().primitiveValue()); 624 } 625 } 626 return res; 627 } 628 629 // see http://hl7.org/fhir/R4/codesystem.html#hierachy 630 public static void addOtherChild(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent owner, @Nonnull String code) { 631 defineChildProperty(cs); 632 owner.addProperty().setCode("child").setValue(new CodeType(code)); 633 } 634 635 public static boolean hasProperty(@Nonnull ConceptDefinitionComponent c, @Nonnull String code) { 636 for (ConceptPropertyComponent cp : c.getProperty()) { 637 if (code.equals(cp.getCode())) { 638 return true; 639 } 640 } 641 return false; 642 } 643 644 public static String getProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c, @Nonnull String uri, @Nonnull String code) { 645 for (ConceptPropertyComponent cp : c.getProperty()) { 646 if (code.equals(cp.getCode())) { 647 return cp.getValue().primitiveValue(); 648 } 649 } 650 for (PropertyComponent p : cs.getProperty()) { 651 if (uri.equals(p.getUri())) { 652 var t = getProperty(c, p.getCode()); 653 if (t != null) { 654 return t.primitiveValue(); 655 } 656 } 657 } 658 return null; 659 } 660 661 public static boolean hasProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c, @Nonnull String uri, @Nonnull String code) { 662 for (ConceptPropertyComponent cp : c.getProperty()) { 663 if (code.equals(cp.getCode())) { 664 return true; 665 } 666 } 667 for (PropertyComponent p : cs.getProperty()) { 668 if (uri.equals(p.getUri())) { 669 return hasProperty(c, p.getCode()); 670 } 671 } 672 return false; 673 } 674 675 public static boolean hasCode(@Nonnull CodeSystem cs, @Nonnull String code) { 676 for (ConceptDefinitionComponent cc : cs.getConcept()) { 677 if (hasCode(cc, code)) { 678 return true; 679 } 680 } 681 return false; 682 } 683 684 private static boolean hasCode(@Nonnull ConceptDefinitionComponent cc, @Nonnull String code) { 685 if (code.equals(cc.getCode())) { 686 return true; 687 } 688 for (ConceptDefinitionComponent c : cc.getConcept()) { 689 if (hasCode(c, code)) { 690 return true; 691 } 692 } 693 return false; 694 } 695 696 public static ConceptDefinitionComponent getCode(@Nonnull CodeSystem cs, @Nonnull String code) { 697 for (ConceptDefinitionComponent cc : cs.getConcept()) { 698 ConceptDefinitionComponent cd = getCode(cc, code); 699 if (cd != null) { 700 return cd; 701 } 702 } 703 return null; 704 } 705 706 private static ConceptDefinitionComponent getCode(@Nonnull ConceptDefinitionComponent cc, @Nonnull String code) { 707 if (code.equals(cc.getCode())) { 708 return cc; 709 } 710 for (ConceptDefinitionComponent c : cc.getConcept()) { 711 ConceptDefinitionComponent cd = getCode(c, code); 712 if (cd != null) { 713 return cd; 714 } 715 } 716 return null; 717 } 718 719 public static void crossLinkCodeSystem(@Nonnull CodeSystem cs) { 720 String parent = getPropertyByUrl(cs, "http://hl7.org/fhir/concept-properties#parent"); 721 if ((parent != null)) { 722 crossLinkConcepts(cs.getConcept(), cs.getConcept(), parent); 723 } 724 } 725 726 private static String getPropertyByUrl(@Nonnull CodeSystem cs, String url) { 727 for (PropertyComponent pc : cs.getProperty()) { 728 if (url.equals(pc.getUri())) { 729 return pc.getCode(); 730 } 731 } 732 return null; 733 } 734 735 private static void crossLinkConcepts(List<ConceptDefinitionComponent> root, List<ConceptDefinitionComponent> focus, String parent) { 736 for (ConceptDefinitionComponent def : focus) { 737 List<ConceptPropertyComponent> pcl = getPropertyValues(def, parent); 738 for (ConceptPropertyComponent pc : pcl) { 739 String code = pc.getValue().primitiveValue(); 740 ConceptDefinitionComponent tgt = findCode(root, code); 741 if (!tgt.hasUserData(USER_DATA_CROSS_LINK)) { 742 tgt.setUserData(USER_DATA_CROSS_LINK, new ArrayList<>()); 743 } 744 @SuppressWarnings("unchecked") 745 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) tgt.getUserData(USER_DATA_CROSS_LINK); 746 children.add(def); 747 } 748 if (def.hasConcept()) { 749 crossLinkConcepts(root, def.getConcept(), parent); 750 } 751 } 752 753 } 754 755 public static boolean hasHierarchy(@Nonnull CodeSystem cs) { 756 for (ConceptDefinitionComponent c : cs.getConcept()) { 757 if (c.hasConcept()) { 758 return true; 759 } 760 } 761 return false; 762 } 763 764 public static void sortAllCodes(@Nonnull CodeSystem cs) { 765 sortAllCodes(cs.getConcept()); 766 } 767 768 private static void sortAllCodes(List<ConceptDefinitionComponent> list) { 769 Collections.sort(list, new ConceptDefinitionComponentSorter()); 770 for (ConceptDefinitionComponent cd : list) { 771 if (cd.hasConcept()) { 772 sortAllCodes(cd.getConcept()); 773 } 774 } 775 } 776 777 public static Coding readCoding(String jurisdiction) { 778 return jurisdiction == null || !jurisdiction.contains("#") ? null : new Coding().setCode(jurisdiction.substring(jurisdiction.indexOf("#")+1)).setSystem(jurisdiction.substring(0, jurisdiction.indexOf("#"))); 779 } 780 781 public static SystemReference getSystemReference(String system, IWorkerContext ctxt) { 782 if (system == null) { 783 return null; 784 } if ("http://snomed.info/sct".equals(system)) { 785 return new SystemReference("SNOMED CT", "https://browser.ihtsdotools.org/"); 786 } else if ("http://loinc.org".equals(system)) { 787 return new SystemReference("LOINC", "https://loinc.org/"); 788 } else if ("http://unitsofmeasure.org".equals(system)) { 789 return new SystemReference("UCUM", "http://ucum.org"); 790 } else if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) { 791 return new SystemReference("RxNorm", "http://www.nlm.nih.gov/research/umls/rxnorm"); 792 } else if (ctxt != null) { 793 CodeSystem cs = ctxt.fetchCodeSystem(system); 794 if (cs != null && cs.hasWebPath()) { 795 return new SystemReference(cs.present(), cs.getWebPath(), Utilities.isAbsoluteUrl(cs.getWebPath())); 796 } else if (cs != null) { 797 return new SystemReference(cs.present(), null); 798 } 799 } 800 return null; 801 } 802 803 public static boolean isNotCurrent(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) { 804 return isInactive(cs, c) || isDeprecated(cs, c, false); 805 } 806 807 public static List<String> getDisplays(@Nonnull CodeSystem srcCS, @Nonnull ConceptDefinitionComponent cd) { 808 List<String> list = new ArrayList<>(); 809 if (cd.hasDisplay()) { 810 list.add(cd.getDisplay()); 811 } 812 for (ConceptDefinitionDesignationComponent d : cd.getDesignation()) { 813 if (!list.contains(d.getValue())) { 814 list.add(d.getValue()); 815 } 816 } 817 return list; 818 } 819 820 public static boolean checkDisplay(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent cd, @Nonnull String display) { 821 List<String> displays = getDisplays(cs, cd); 822 for (String s : displays) { 823 if (s.equalsIgnoreCase(display)) { 824 return true; 825 } 826 } 827 return false; 828 } 829 830 public static int countCodes(@Nonnull CodeSystem cs) { 831 return countCodes(cs.getConcept()); 832 } 833 834 private static int countCodes(List<ConceptDefinitionComponent> concept) { 835 int t = concept.size(); 836 for (ConceptDefinitionComponent cd : concept) { 837 t = t + (cd.hasConcept() ? countCodes(cd.getConcept()) : 0); 838 } 839 return t; 840 } 841 842 public static CodeSystem mergeSupplements(@Nonnull CodeSystem cs, @Nonnull List<CodeSystem> supplements) { 843 CodeSystem ret = cs.copy(); 844 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 845 for (CodeSystem sup : supplements) { 846 b.append(sup.getVersionedUrl()); 847 } 848 ret.setUserData(UserDataNames.tx_known_supplements, b.toString()); 849 850 for (ConceptDefinitionComponent t : ret.getConcept()) { 851 mergeSupplements(ret, t, supplements); 852 } 853 return ret; 854 } 855 856 private static void mergeSupplements(CodeSystem ret, ConceptDefinitionComponent fdef, List<CodeSystem> supplements) { 857 for (CodeSystem cs : supplements) { 858 ConceptDefinitionComponent def = CodeSystemUtilities.findCode(cs.getConcept(), fdef.getCode()); 859 if (def != null) { 860 for (Extension ext : def.getExtension()) { 861 fdef.addExtension(ext.copy()); 862 } 863 for (ConceptDefinitionDesignationComponent d : def.getDesignation()) { 864 fdef.addDesignation(d.copy()); 865 } 866 for (ConceptPropertyComponent p : def.getProperty()) { 867 PropertyComponent pd = CodeSystemUtilities.getPropertyDefinition(cs, p); 868 String code; 869 if (pd != null) { 870 code = defineProperty(ret, pd, propertyTypeForType(p.getValue())); 871 } else { 872 code = defineProperty(ret, p.getCode(), propertyTypeForType(p.getValue())); 873 } 874 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"); 875 } 876 } 877 for (ConceptDefinitionComponent t : fdef.getConcept()) { 878 mergeSupplements(ret, t, supplements); 879 } 880 } 881 } 882 883 private static PropertyType propertyTypeForType(DataType value) { 884 if (value == null) { 885 return PropertyType.NULL; 886 } 887 if (value instanceof CodeType) { 888 return PropertyType.CODE; 889 } 890 if (value instanceof Coding) { 891 return PropertyType.CODING; 892 } 893 if (value instanceof StringType) { 894 return PropertyType.STRING; 895 } 896 if (value instanceof IntegerType) { 897 return PropertyType.INTEGER; 898 } 899 if (value instanceof BooleanType) { 900 return PropertyType.BOOLEAN; 901 } 902 if (value instanceof DateTimeType) { 903 return PropertyType.DATETIME; 904 } 905 if (value instanceof DecimalType) { 906 return PropertyType.DECIMAL; 907 } 908 throw new FHIRException("Unsupported property value for a CodeSystem Property: "+value.fhirType()); 909 } 910 911 private static String defineProperty(CodeSystem cs, PropertyComponent pd, PropertyType pt) { 912 for (PropertyComponent p : cs.getProperty()) { 913 if (p.hasCode() && p.getCode().equals(pd.getCode())) { 914 if (!p.getUri().equals(pd.getUri())) { 915 throw new Error("URI mismatch for code "+pd.getCode()+" url = "+p.getUri()+" vs "+pd.getUri()); 916 } 917 if (!p.getType().equals(pt)) { 918 throw new Error("Type mismatch for code "+pd.getCode()+" type = "+p.getType().toCode()+" vs "+pt.toCode()); 919 } 920 return pd.getCode(); 921 } 922 } 923 cs.addProperty().setCode(pd.getCode()).setUri(pd.getUri()).setType(pt); 924 return pd.getCode(); 925 926 } 927 928 929 private static PropertyComponent getPropertyDefinition(CodeSystem cs, ConceptPropertyComponent p) { 930 for (PropertyComponent t : cs.getProperty()) { 931 if (t.hasCode() && t.getCode().equals(p.getCode())) { 932 return t; 933 } 934 } 935 return null; 936 } 937 938 public static boolean hasProperties(@Nonnull CodeSystem cs) { 939 return hasProperties(cs.getConcept()); 940 } 941 942 private static boolean hasProperties(List<ConceptDefinitionComponent> list) { 943 for (ConceptDefinitionComponent c : list) { 944 if (c.hasProperty() || hasProperties(c.getConcept())) { 945 return true; 946 } 947 } 948 return false; 949 } 950 951 public static boolean hasDesignations(@Nonnull CodeSystem cs) { 952 return hasDesignations(cs.getConcept()); 953 } 954 955 private static boolean hasDesignations(List<ConceptDefinitionComponent> list) { 956 for (ConceptDefinitionComponent c : list) { 957 if (c.hasDesignation() || hasDesignations(c.getConcept())) { 958 return true; 959 } 960 } 961 return false; 962 } 963 964 public static boolean hasPropertyDef(@Nonnull CodeSystem cs, @Nonnull String property) { 965 966 for (PropertyComponent pd : cs.getProperty()) { 967 if (pd.hasCode() && pd.getCode().equals(property)) { 968 return true; 969 } 970 } 971 return false; 972 } 973 974 public static DataType getProperty(@Nonnull CodeSystem cs, @Nonnull String code, @Nonnull String property) { 975 ConceptDefinitionComponent def = getCode(cs, code); 976 return getProperty(cs, def, property); 977 } 978 979 public static DataType getProperty(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent def, @Nonnull String property) { 980 PropertyComponent defn = getPropertyDefinition(cs, property); 981 if (defn != null) { 982 property = defn.getCode(); 983 } 984 ConceptPropertyComponent cp = getProperty(def, property); 985 return cp == null ? null : cp.getValue(); 986 } 987 988 public static boolean hasMarkdownInDefinitions(@Nonnull CodeSystem cs, @Nonnull MarkDownProcessor md) { 989 return hasMarkdownInDefinitions(cs.getConcept(), md); 990 } 991 992 private static boolean hasMarkdownInDefinitions(List<ConceptDefinitionComponent> concepts, MarkDownProcessor md) { 993 for (ConceptDefinitionComponent c : concepts) { 994 if (c.hasDefinition() && md.isProbablyMarkdown(c.getDefinition(), true)) { 995 return true; 996 } 997 if (c.hasConcept() && hasMarkdownInDefinitions(c.getConcept(), md)) { 998 return true; 999 } 1000 } 1001 return false; 1002 } 1003 1004 public static String getStatus(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent cc) { 1005 StandardsStatus ss = ExtensionUtilities.getStandardsStatus(cc); 1006 if (ss == StandardsStatus.DEPRECATED || ss == StandardsStatus.WITHDRAWN) { 1007 return ss.toCode(); 1008 } 1009 DataType v = getProperty(cs, cc, "status"); 1010 if (v == null || !v.isPrimitive()) { 1011 return null; 1012 } else { 1013 return v.primitiveValue(); 1014 } 1015 } 1016 1017 public static Boolean subsumes(@Nonnull CodeSystem cs, @Nonnull String pc, @Nonnull String cc) { 1018 if (pc.equals(cc)) { 1019 return true; 1020 } 1021 List<ConceptDefinitionComponent> child = findCodeWithParents(null, cs.getConcept(), cc); 1022 for (ConceptDefinitionComponent item : child) { 1023 if (pc.equals(item.getCode())) { 1024 return true; 1025 } 1026 } 1027 return false; 1028 } 1029 1030 public static Set<String> codes(@Nonnull CodeSystem cs) { 1031 Set<String> res = new HashSet<>(); 1032 addCodes(res, cs.getConcept()); 1033 return res; 1034 } 1035 1036 private static void addCodes(Set<String> res, List<ConceptDefinitionComponent> list) { 1037 for (ConceptDefinitionComponent cd : list) { 1038 if (cd.hasCode()) { 1039 res.add(cd.getCode()); 1040 } 1041 if (cd.hasConcept()) { 1042 addCodes(res, cd.getConcept()); 1043 } 1044 } 1045 } 1046 1047 /** 1048 * property in this case is the name of a property that appears in a ValueSet filter 1049 * 1050 * @param cs 1051 * @param property 1052 * @return 1053 */ 1054 public static PropertyComponent getPropertyDefinition(@Nonnull CodeSystem cs, @Nonnull String property) { 1055 String uri = getStandardPropertyUri(property); 1056 if (uri != null) { 1057 for (PropertyComponent cp : cs.getProperty()) { 1058 if (uri.equals(cp.getUri())) { 1059 return cp; 1060 } 1061 } 1062 } 1063 for (PropertyComponent cp : cs.getProperty()) { 1064 if (cp.getCode().equals(property)) { 1065 return cp; 1066 } 1067 } 1068 return null; 1069 } 1070 1071 public static boolean isDefinedProperty(@Nonnull CodeSystem cs, @Nonnull String property) { 1072 String uri = getStandardPropertyUri(property); 1073 if (uri != null) { 1074 for (PropertyComponent cp : cs.getProperty()) { 1075 if (uri.equals(cp.getUri())) { 1076 return true; 1077 } 1078 } 1079 } 1080 for (PropertyComponent cp : cs.getProperty()) { 1081 if (cp.getCode().equals(property) && (uri == null || !cp.hasUri())) { // if uri is right, will return from above 1082 return true; 1083 } 1084 } 1085 return false; 1086 } 1087 1088 1089 private static String getStandardPropertyUri(String property) { 1090 switch (property) { 1091 case "status" : return "http://hl7.org/fhir/concept-properties#status"; 1092 case "inactive" : return "http://hl7.org/fhir/concept-properties#inactive"; 1093 case "effectiveDate" : return "http://hl7.org/fhir/concept-properties#effectiveDate"; 1094 case "deprecationDate" : return "http://hl7.org/fhir/concept-properties#deprecationDate"; 1095 case "retirementDate" : return "http://hl7.org/fhir/concept-properties#retirementDate"; 1096 case "notSelectable" : return "http://hl7.org/fhir/concept-properties#notSelectable"; 1097 case "parent" : return "http://hl7.org/fhir/concept-properties#parent"; 1098 case "child" : return "http://hl7.org/fhir/concept-properties#child"; 1099 case "partOf" : return "http://hl7.org/fhir/concept-properties#partOf"; 1100 case "synonym" : return "http://hl7.org/fhir/concept-properties#synonym"; 1101 case "comment" : return "http://hl7.org/fhir/concept-properties#comment"; 1102 case "itemWeight" : return "http://hl7.org/fhir/concept-properties#itemWeight"; 1103 } 1104 return null; 1105 } 1106 1107 public static boolean isExemptFromMultipleVersionChecking(String url) { 1108 return Utilities.existsInList(url, "http://snomed.info/sct", "http://loinc.org"); 1109 } 1110 1111 public static PropertyComponent getPropertyByUri(@Nonnull CodeSystem cs, @Nonnull String uri) { 1112 for (PropertyComponent t : cs.getProperty()) { 1113 if (uri.equals(t.getUri())) { 1114 return t; 1115 } 1116 } 1117 return null; 1118 } 1119 1120 public static CodeSystem convertSD(@Nonnull StructureDefinition sd) { 1121 CodeSystem cs = new CodeSystem(); 1122 cs.setId(sd.getId()); 1123 cs.setUrl(sd.getUrl()); 1124 cs.setVersion(sd.getVersion()); 1125 cs.setStatus(sd.getStatus()); 1126 cs.setContent(Enumerations.CodeSystemContentMode.COMPLETE); 1127 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 1128 ConceptDefinitionComponent cd = cs.addConcept(); 1129 cd.setCode(ed.getId()); 1130 cd.setDisplay(ed.getId()); 1131 ed.setDefinition(ed.getDefinition()); 1132 } 1133 return cs; 1134 } 1135 1136 public static boolean hasCSComments(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) { 1137 return hasProperty(cs, c, "http://hl7.org/fhir/concept-properties#comments", "comments"); 1138 } 1139 1140 1141 public static String getCSComments(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent c) { 1142 return getProperty(cs, c, "http://hl7.org/fhir/concept-properties#comments", "comments"); 1143 } 1144 1145 public static void addCSComments(@Nonnull CodeSystem cs, @Nonnull ConceptDefinitionComponent cc, @Nonnull String comments) { 1146 String code = "comments"; 1147 1148 for (PropertyComponent p : cs.getProperty()) { 1149 if ("http://hl7.org/fhir/concept-properties#comments".equals(p.getUri())) { 1150 code = p.getCode(); 1151 } 1152 } 1153 setProperty(cs, cc, code, new StringType(comments)); 1154 } 1155 1156} 1157