001package org.hl7.fhir.r4.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 032import java.util.ArrayList; 033import java.util.Calendar; 034import java.util.Collections; 035import java.util.Comparator; 036import java.util.List; 037 038import org.hl7.fhir.exceptions.FHIRException; 039import org.hl7.fhir.exceptions.FHIRFormatError; 040import org.hl7.fhir.r4.model.BooleanType; 041import org.hl7.fhir.r4.model.CanonicalType; 042import org.hl7.fhir.r4.model.CodeSystem; 043import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; 044import org.hl7.fhir.r4.model.CodeSystem.ConceptPropertyComponent; 045import org.hl7.fhir.r4.model.CodeSystem.PropertyComponent; 046import org.hl7.fhir.r4.model.CodeSystem.PropertyType; 047import org.hl7.fhir.r4.model.CodeType; 048import org.hl7.fhir.r4.model.DateTimeType; 049import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; 050import org.hl7.fhir.r4.model.Identifier; 051import org.hl7.fhir.r4.model.Meta; 052import org.hl7.fhir.r4.model.Type; 053import org.hl7.fhir.r4.model.UriType; 054import org.hl7.fhir.r4.utils.ToolingExtensions; 055import org.hl7.fhir.r4.model.Coding; 056import org.hl7.fhir.utilities.StandardsStatus; 057import org.hl7.fhir.utilities.Utilities; 058 059public class CodeSystemUtilities { 060 061 public static boolean isNotSelectable(CodeSystem cs, ConceptDefinitionComponent def) { 062 for (ConceptPropertyComponent p : def.getProperty()) { 063 if (p.getCode().equals("notSelectable") && p.hasValue() && p.getValue() instanceof BooleanType) 064 return ((BooleanType) p.getValue()).getValue(); 065 } 066 return false; 067 } 068 069 public static void setNotSelectable(CodeSystem cs, ConceptDefinitionComponent concept) throws FHIRFormatError { 070 defineNotSelectableProperty(cs); 071 ConceptPropertyComponent p = getProperty(concept, "abstract"); 072 if (p != null) 073 p.setValue(new BooleanType(true)); 074 else 075 concept.addProperty().setCode("notSelectable").setValue(new BooleanType(true)); 076 } 077 078 public static void defineNotSelectableProperty(CodeSystem cs) { 079 defineCodeSystemProperty(cs, "notSelectable", 080 "Indicates that the code is abstract - only intended to be used as a selector for other concepts", 081 PropertyType.BOOLEAN); 082 } 083 084 085 public static Coding readCoding(String jurisdiction) { 086 return jurisdiction == null || !jurisdiction.contains("#") ? null : new Coding().setCode(jurisdiction.substring(jurisdiction.indexOf("#")+1)).setSystem(jurisdiction.substring(0, jurisdiction.indexOf("#"))); 087 } 088 089 public enum ConceptStatus { 090 Active, Experimental, Deprecated, Retired; 091 092 public String toCode() { 093 switch (this) { 094 case Active: 095 return "active"; 096 case Experimental: 097 return "experimental"; 098 case Deprecated: 099 return "deprecated"; 100 case Retired: 101 return "retired"; 102 default: 103 return null; 104 } 105 } 106 } 107 108 public static void setStatus(CodeSystem cs, ConceptDefinitionComponent concept, ConceptStatus status) 109 throws FHIRFormatError { 110 defineStatusProperty(cs); 111 ConceptPropertyComponent p = getProperty(concept, "status"); 112 if (p != null) 113 p.setValue(new CodeType(status.toCode())); 114 else 115 concept.addProperty().setCode("status").setValue(new CodeType(status.toCode())); 116 } 117 118 public static void defineStatusProperty(CodeSystem cs) { 119 defineCodeSystemProperty(cs, "status", 120 "A property that indicates the status of the concept. One of active, experimental, deprecated, retired", 121 PropertyType.CODE); 122 } 123 124 private static void defineDeprecatedProperty(CodeSystem cs) { 125 defineCodeSystemProperty(cs, "deprecationDate", 126 "The date at which a concept was deprecated. Concepts that are deprecated but not inactive can still be used, but their use is discouraged", 127 PropertyType.DATETIME); 128 } 129 130 public static void defineParentProperty(CodeSystem cs) { 131 defineCodeSystemProperty(cs, "parent", 132 "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", 133 PropertyType.CODE); 134 } 135 136 public static void defineChildProperty(CodeSystem cs) { 137 defineCodeSystemProperty(cs, "child", 138 "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", 139 PropertyType.CODE); 140 } 141 142 public static boolean isDeprecated(CodeSystem cs, ConceptDefinitionComponent def) { 143 try { 144 for (ConceptPropertyComponent p : def.getProperty()) { 145 if (p.getCode().equals("status") && p.hasValue() && p.hasValueCodeType() 146 && p.getValueCodeType().getCode().equals("deprecated")) 147 return true; 148 // this, though status should also be set 149 if (p.getCode().equals("deprecationDate") && p.hasValue() && p.getValue() instanceof DateTimeType) 150 return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance())); 151 // legacy 152 if (p.getCode().equals("deprecated") && p.hasValue() && p.getValue() instanceof BooleanType) 153 return ((BooleanType) p.getValue()).getValue(); 154 } 155 return false; 156 } catch (FHIRException e) { 157 return false; 158 } 159 } 160 161 public static void setDeprecated(CodeSystem cs, ConceptDefinitionComponent concept, DateTimeType date) 162 throws FHIRFormatError { 163 setStatus(cs, concept, ConceptStatus.Deprecated); 164 defineDeprecatedProperty(cs); 165 concept.addProperty().setCode("deprecationDate").setValue(date); 166 } 167 168 public static boolean isInactive(CodeSystem cs, ConceptDefinitionComponent def) throws FHIRException { 169 for (ConceptPropertyComponent p : def.getProperty()) { 170 if (p.getCode().equals("status") && p.hasValueStringType()) 171 return "inactive".equals(p.getValueStringType()); 172 } 173 return false; 174 } 175 176 public static boolean isInactive(CodeSystem cs, String code) throws FHIRException { 177 ConceptDefinitionComponent def = findCode(cs.getConcept(), code); 178 if (def == null) 179 return true; 180 return isInactive(cs, def); 181 } 182 183 public static PropertyComponent defineCodeSystemProperty(CodeSystem cs, String code, String description, 184 PropertyType type) { 185 for (PropertyComponent p : cs.getProperty()) { 186 if (p.getCode().equals(code)) 187 return p; 188 } 189 PropertyComponent p = cs.addProperty(); 190 p.setCode(code).setDescription(description).setType(type).setUri("http://hl7.org/fhir/concept-properties#" + code); 191 return p; 192 } 193 194 public static String getCodeDefinition(CodeSystem cs, String code) { 195 return getCodeDefinition(cs.getConcept(), code); 196 } 197 198 private static String getCodeDefinition(List<ConceptDefinitionComponent> list, String code) { 199 for (ConceptDefinitionComponent c : list) { 200 if (c.hasCode() && c.getCode().equals(code)) 201 return c.getDefinition(); 202 String s = getCodeDefinition(c.getConcept(), code); 203 if (s != null) 204 return s; 205 } 206 return null; 207 } 208 209 public static CodeSystem makeShareable(CodeSystem cs) { 210 if (!cs.hasMeta()) 211 cs.setMeta(new Meta()); 212 for (UriType t : cs.getMeta().getProfile()) 213 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablecodesystem")) 214 return cs; 215 cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem")); 216 return cs; 217 } 218 219 public static void setOID(CodeSystem cs, String oid) { 220 if (!oid.startsWith("urn:oid:")) 221 oid = "urn:oid:" + oid; 222 if (!cs.hasIdentifier()) 223 cs.addIdentifier(new Identifier().setSystem("urn:ietf:rfc:3986").setValue(oid)); 224 else if ("urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() 225 && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:")) 226 cs.getIdentifierFirstRep().setValue(oid); 227 else 228 throw new Error("unable to set OID on code system"); 229 230 } 231 232 public static boolean hasOID(CodeSystem cs) { 233 return getOID(cs) != null; 234 } 235 236 public static String getOID(CodeSystem cs) { 237 if (cs.hasIdentifier() && "urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) 238 && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:")) 239 return cs.getIdentifierFirstRep().getValue().substring(8); 240 return null; 241 } 242 243 private static ConceptDefinitionComponent findCode(List<ConceptDefinitionComponent> list, String code) { 244 for (ConceptDefinitionComponent c : list) { 245 if (c.getCode().equals(code)) 246 return c; 247 ConceptDefinitionComponent s = findCode(c.getConcept(), code); 248 if (s != null) 249 return s; 250 } 251 return null; 252 } 253 254 public static void markStatus(CodeSystem cs, String wg, StandardsStatus status, String pckage, String fmm, 255 String normativeVersion) throws FHIRException { 256 if (wg != null) { 257 if (!ToolingExtensions.hasExtension(cs, ToolingExtensions.EXT_WORKGROUP) 258 || (Utilities.existsInList(ToolingExtensions.readStringExtension(cs, ToolingExtensions.EXT_WORKGROUP), "fhir", 259 "vocab") && !Utilities.existsInList(wg, "fhir", "vocab"))) { 260 ToolingExtensions.setCodeExtension(cs, ToolingExtensions.EXT_WORKGROUP, wg); 261 } 262 } 263 if (status != null) { 264 StandardsStatus ss = ToolingExtensions.getStandardsStatus(cs); 265 if (ss == null || ss.isLowerThan(status)) 266 ToolingExtensions.setStandardsStatus(cs, status, normativeVersion); 267 if (pckage != null) { 268 if (!cs.hasUserData("ballot.package")) 269 cs.setUserData("ballot.package", pckage); 270 else if (!pckage.equals(cs.getUserString("ballot.package"))) 271 if (!"infrastructure".equals(cs.getUserString("ballot.package"))) 272 System.out.println("Code System " + cs.getUrl() + ": ownership clash " + pckage + " vs " 273 + cs.getUserString("ballot.package")); 274 } 275 if (status == StandardsStatus.NORMATIVE) { 276 cs.setExperimental(false); 277 cs.setStatus(PublicationStatus.ACTIVE); 278 } 279 } 280 if (fmm != null) { 281 String sfmm = ToolingExtensions.readStringExtension(cs, ToolingExtensions.EXT_FMM_LEVEL); 282 if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm)) 283 ToolingExtensions.setIntegerExtension(cs, ToolingExtensions.EXT_FMM_LEVEL, Integer.parseInt(fmm)); 284 } 285 } 286 287 public static Type readProperty(ConceptDefinitionComponent concept, String code) { 288 for (ConceptPropertyComponent p : concept.getProperty()) 289 if (p.getCode().equals(code)) 290 return p.getValue(); 291 return null; 292 } 293 294 public static ConceptPropertyComponent getProperty(ConceptDefinitionComponent concept, String code) { 295 for (ConceptPropertyComponent p : concept.getProperty()) 296 if (p.getCode().equals(code)) 297 return p; 298 return null; 299 } 300 301 // see http://hl7.org/fhir/R4/codesystem.html#hierachy 302 // returns additional parents not in the hierarchy 303 public static List<String> getOtherChildren(CodeSystem cs, ConceptDefinitionComponent c) { 304 List<String> res = new ArrayList<String>(); 305 for (ConceptPropertyComponent p : c.getProperty()) { 306 if ("parent".equals(p.getCode())) { 307 res.add(p.getValue().primitiveValue()); 308 } 309 } 310 return res; 311 } 312 313 // see http://hl7.org/fhir/R4/codesystem.html#hierachy 314 public static void addOtherChild(CodeSystem cs, ConceptDefinitionComponent owner, String code) { 315 defineChildProperty(cs); 316 owner.addProperty().setCode("child").setValue(new CodeType(code)); 317 } 318 319 public static class ConceptDefinitionComponentSorter implements Comparator<ConceptDefinitionComponent> { 320 321 @Override 322 public int compare(ConceptDefinitionComponent o1, ConceptDefinitionComponent o2) { 323 return o1.getCode().compareToIgnoreCase(o2.getCode()); 324 } 325 326 } 327 328 public static void sortAllCodes(CodeSystem cs) { 329 sortAllCodes(cs.getConcept()); 330 } 331 332 public static void sortAllCodes(List<ConceptDefinitionComponent> list) { 333 Collections.sort(list, new ConceptDefinitionComponentSorter()); 334 for (ConceptDefinitionComponent cd : list) { 335 if (cd.hasConcept()) { 336 sortAllCodes(cd.getConcept()); 337 } 338 } 339 } 340 341}