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