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