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