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