001package org.hl7.fhir.r4.terminologies;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032
033
034import java.util.ArrayList;
035import java.util.Calendar;
036import java.util.List;
037
038import org.hl7.fhir.exceptions.FHIRException;
039import org.hl7.fhir.exceptions.FHIRFormatError;
040import org.hl7.fhir.r4.model.BooleanType;
041import org.hl7.fhir.r4.model.CanonicalType;
042import org.hl7.fhir.r4.model.CodeSystem;
043import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
044import org.hl7.fhir.r4.model.CodeSystem.ConceptPropertyComponent;
045import org.hl7.fhir.r4.model.CodeSystem.PropertyComponent;
046import org.hl7.fhir.r4.model.CodeSystem.PropertyType;
047import org.hl7.fhir.r4.model.CodeType;
048import org.hl7.fhir.r4.model.DateTimeType;
049import org.hl7.fhir.r4.model.Enumerations.PublicationStatus;
050import org.hl7.fhir.r4.model.Identifier;
051import org.hl7.fhir.r4.model.Meta;
052import org.hl7.fhir.r4.model.Type;
053import org.hl7.fhir.r4.model.UriType;
054import org.hl7.fhir.r4.utils.ToolingExtensions;
055import org.hl7.fhir.utilities.StandardsStatus;
056import org.hl7.fhir.utilities.Utilities;
057
058public class CodeSystemUtilities {
059
060  public static boolean isNotSelectable(CodeSystem cs, ConceptDefinitionComponent def) {
061    for (ConceptPropertyComponent p : def.getProperty()) {
062      if (p.getCode().equals("notSelectable") && p.hasValue() && p.getValue() instanceof BooleanType) 
063        return ((BooleanType) p.getValue()).getValue();
064    }
065    return false;
066  }
067
068  public static void setNotSelectable(CodeSystem cs, ConceptDefinitionComponent concept) throws FHIRFormatError {
069    defineNotSelectableProperty(cs);
070    ConceptPropertyComponent p = getProperty(concept, "abstract");
071    if (p != null)
072      p.setValue(new BooleanType(true));
073    else
074      concept.addProperty().setCode("notSelectable").setValue(new BooleanType(true));    
075  }
076
077  public static void defineNotSelectableProperty(CodeSystem cs) {
078    defineCodeSystemProperty(cs, "notSelectable", "Indicates that the code is abstract - only intended to be used as a selector for other concepts", PropertyType.BOOLEAN);
079  }
080
081
082  public enum ConceptStatus {
083    Active, Experimental, Deprecated, Retired;
084
085    public String toCode() {
086      switch (this) {
087      case Active: return "active";
088      case Experimental: return "experimental";
089      case Deprecated: return "deprecated";
090      case Retired: return "retired";
091      default: return null;
092      }
093    }
094  }
095
096  public static void setStatus(CodeSystem cs, ConceptDefinitionComponent concept, ConceptStatus status) throws FHIRFormatError {
097    defineStatusProperty(cs);
098    ConceptPropertyComponent p = getProperty(concept, "status");
099    if (p != null)
100      p.setValue(new CodeType(status.toCode()));
101    else
102      concept.addProperty().setCode("status").setValue(new CodeType(status.toCode()));    
103  }
104
105  public static void defineStatusProperty(CodeSystem cs) {
106    defineCodeSystemProperty(cs, "status", "A property that indicates the status of the concept. One of active, experimental, deprecated, retired", PropertyType.CODE);
107  }
108
109  private static void defineDeprecatedProperty(CodeSystem cs) {
110    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);
111  }
112
113  public static void defineParentProperty(CodeSystem cs) {
114    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);
115  }
116
117  public static void defineChildProperty(CodeSystem cs) {
118    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);
119  }
120
121  public static boolean isDeprecated(CodeSystem cs, ConceptDefinitionComponent def)  {
122    try {
123      for (ConceptPropertyComponent p : def.getProperty()) {
124        if (p.getCode().equals("status") && p.hasValue() && p.hasValueCodeType() && p.getValueCodeType().getCode().equals("deprecated"))
125          return true;
126        // this, though status should also be set
127        if (p.getCode().equals("deprecationDate") && p.hasValue() && p.getValue() instanceof DateTimeType) 
128          return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance()));
129        // legacy  
130        if (p.getCode().equals("deprecated") && p.hasValue() && p.getValue() instanceof BooleanType) 
131          return ((BooleanType) p.getValue()).getValue();
132      }
133      return false;
134    } catch (FHIRException e) {
135      return false;
136    }
137  }
138
139  public static void setDeprecated(CodeSystem cs, ConceptDefinitionComponent concept, DateTimeType date) throws FHIRFormatError {
140    setStatus(cs, concept, ConceptStatus.Deprecated);
141    defineDeprecatedProperty(cs);
142    concept.addProperty().setCode("deprecationDate").setValue(date);    
143  }
144  
145  public static boolean isInactive(CodeSystem cs, ConceptDefinitionComponent def) throws FHIRException {
146    for (ConceptPropertyComponent p : def.getProperty()) {
147      if (p.getCode().equals("status") && p.hasValueStringType()) 
148        return "inactive".equals(p.getValueStringType());
149    }
150    return false;
151  }
152  
153  public static boolean isInactive(CodeSystem cs, String code) throws FHIRException {
154    ConceptDefinitionComponent def = findCode(cs.getConcept(), code);
155    if (def == null)
156      return true;
157    return isInactive(cs, def);
158  }
159
160  public static PropertyComponent defineCodeSystemProperty(CodeSystem cs, String code, String description, PropertyType type) {
161    for (PropertyComponent p : cs.getProperty()) {
162      if (p.getCode().equals(code))
163        return p;
164    }
165    PropertyComponent p = cs.addProperty();
166    p.setCode(code).setDescription(description).setType(type).setUri("http://hl7.org/fhir/concept-properties#"+code);
167    return p;
168  }
169
170  public static String getCodeDefinition(CodeSystem cs, String code) {
171    return getCodeDefinition(cs.getConcept(), code);
172  }
173
174  private static String getCodeDefinition(List<ConceptDefinitionComponent> list, String code) {
175    for (ConceptDefinitionComponent c : list) {
176      if (c.hasCode() &&  c.getCode().equals(code))
177        return c.getDefinition();
178      String s = getCodeDefinition(c.getConcept(), code);
179      if (s != null)
180        return s;
181    }
182    return null;
183  }
184
185  public static CodeSystem makeShareable(CodeSystem cs) {
186    if (!cs.hasMeta())
187      cs.setMeta(new Meta());
188    for (UriType t : cs.getMeta().getProfile()) 
189      if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablecodesystem"))
190        return cs;
191    cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem"));
192    return cs;
193  }
194
195  public static void setOID(CodeSystem cs, String oid) {
196    if (!oid.startsWith("urn:oid:"))
197       oid = "urn:oid:" + oid;
198    if (!cs.hasIdentifier())
199      cs.addIdentifier(new Identifier().setSystem("urn:ietf:rfc:3986").setValue(oid));
200    else if ("urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:"))
201      cs.getIdentifierFirstRep().setValue(oid);
202    else
203      throw new Error("unable to set OID on code system");
204    
205  }
206
207  public static boolean hasOID(CodeSystem cs) {
208    return getOID(cs) != null;
209  }
210
211  public static String getOID(CodeSystem cs) {
212    if (cs.hasIdentifier() && "urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:"))
213        return cs.getIdentifierFirstRep().getValue().substring(8);
214    return null;
215  }
216
217  private static ConceptDefinitionComponent findCode(List<ConceptDefinitionComponent> list, String code) {
218    for (ConceptDefinitionComponent c : list) {
219      if (c.getCode().equals(code))
220        return c;
221      ConceptDefinitionComponent s = findCode(c.getConcept(), code);
222      if (s != null)
223        return s;
224    }
225    return null;
226  }
227
228  public static void markStatus(CodeSystem cs, String wg, StandardsStatus status, String pckage, String fmm, String normativeVersion) throws FHIRException {
229    if (wg != null) {
230      if (!ToolingExtensions.hasExtension(cs, ToolingExtensions.EXT_WORKGROUP) || 
231          (Utilities.existsInList(ToolingExtensions.readStringExtension(cs, ToolingExtensions.EXT_WORKGROUP), "fhir", "vocab") && !Utilities.existsInList(wg, "fhir", "vocab"))) {
232        ToolingExtensions.setCodeExtension(cs, ToolingExtensions.EXT_WORKGROUP, wg);
233      }
234    }
235    if (status != null) {
236      StandardsStatus ss = ToolingExtensions.getStandardsStatus(cs);
237      if (ss == null || ss.isLowerThan(status)) 
238        ToolingExtensions.setStandardsStatus(cs, status, normativeVersion);
239      if (pckage != null) {
240        if (!cs.hasUserData("ballot.package"))
241          cs.setUserData("ballot.package", pckage);
242        else if (!pckage.equals(cs.getUserString("ballot.package")))
243          if (!"infrastructure".equals(cs.getUserString("ballot.package")))
244            System.out.println("Code System "+cs.getUrl()+": ownership clash "+pckage+" vs "+cs.getUserString("ballot.package"));
245      }
246      if (status == StandardsStatus.NORMATIVE) {
247        cs.setExperimental(false);
248        cs.setStatus(PublicationStatus.ACTIVE);
249      }
250    }
251    if (fmm != null) {
252      String sfmm = ToolingExtensions.readStringExtension(cs, ToolingExtensions.EXT_FMM_LEVEL);
253      if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm)) 
254        ToolingExtensions.setIntegerExtension(cs, ToolingExtensions.EXT_FMM_LEVEL, Integer.parseInt(fmm));
255    }
256  }
257
258 
259  public static Type readProperty(ConceptDefinitionComponent concept, String code) {
260    for (ConceptPropertyComponent p : concept.getProperty())
261      if (p.getCode().equals(code))
262        return p.getValue(); 
263    return null;
264  }
265
266  public static ConceptPropertyComponent getProperty(ConceptDefinitionComponent concept, String code) {
267    for (ConceptPropertyComponent p : concept.getProperty())
268      if (p.getCode().equals(code))
269        return p; 
270    return null;
271  }
272
273  // see http://hl7.org/fhir/R4/codesystem.html#hierachy
274  // returns additional parents not in the heirarchy
275  public static List<String> getOtherChildren(CodeSystem cs, ConceptDefinitionComponent c) {
276    List<String> res = new ArrayList<String>();
277    for (ConceptPropertyComponent p : c.getProperty()) {
278      if ("parent".equals(p.getCode())) {
279        res.add(p.getValue().primitiveValue());
280      }
281    }
282    return res;
283  }
284
285  // see http://hl7.org/fhir/R4/codesystem.html#hierachy
286  public static void addOtherChild(CodeSystem cs, ConceptDefinitionComponent owner, String code) {
287    defineChildProperty(cs);
288    owner.addProperty().setCode("child").setValue(new CodeType(code));
289  }
290
291}