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