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