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