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