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}