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}