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