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