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
032import java.util.ArrayList;
033import java.util.Calendar;
034import java.util.Collections;
035import java.util.Comparator;
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",
079        "Indicates that the code is abstract - only intended to be used as a selector for other concepts",
080        PropertyType.BOOLEAN);
081  }
082
083  public enum ConceptStatus {
084    Active, Experimental, Deprecated, Retired;
085
086    public String toCode() {
087      switch (this) {
088      case Active:
089        return "active";
090      case Experimental:
091        return "experimental";
092      case Deprecated:
093        return "deprecated";
094      case Retired:
095        return "retired";
096      default:
097        return null;
098      }
099    }
100  }
101
102  public static void setStatus(CodeSystem cs, ConceptDefinitionComponent concept, ConceptStatus status)
103      throws FHIRFormatError {
104    defineStatusProperty(cs);
105    ConceptPropertyComponent p = getProperty(concept, "status");
106    if (p != null)
107      p.setValue(new CodeType(status.toCode()));
108    else
109      concept.addProperty().setCode("status").setValue(new CodeType(status.toCode()));
110  }
111
112  public static void defineStatusProperty(CodeSystem cs) {
113    defineCodeSystemProperty(cs, "status",
114        "A property that indicates the status of the concept. One of active, experimental, deprecated, retired",
115        PropertyType.CODE);
116  }
117
118  private static void defineDeprecatedProperty(CodeSystem cs) {
119    defineCodeSystemProperty(cs, "deprecationDate",
120        "The date at which a concept was deprecated. Concepts that are deprecated but not inactive can still be used, but their use is discouraged",
121        PropertyType.DATETIME);
122  }
123
124  public static void defineParentProperty(CodeSystem cs) {
125    defineCodeSystemProperty(cs, "parent",
126        "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",
127        PropertyType.CODE);
128  }
129
130  public static void defineChildProperty(CodeSystem cs) {
131    defineCodeSystemProperty(cs, "child",
132        "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",
133        PropertyType.CODE);
134  }
135
136  public static boolean isDeprecated(CodeSystem cs, ConceptDefinitionComponent def) {
137    try {
138      for (ConceptPropertyComponent p : def.getProperty()) {
139        if (p.getCode().equals("status") && p.hasValue() && p.hasValueCodeType()
140            && p.getValueCodeType().getCode().equals("deprecated"))
141          return true;
142        // this, though status should also be set
143        if (p.getCode().equals("deprecationDate") && p.hasValue() && p.getValue() instanceof DateTimeType)
144          return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance()));
145        // legacy
146        if (p.getCode().equals("deprecated") && p.hasValue() && p.getValue() instanceof BooleanType)
147          return ((BooleanType) p.getValue()).getValue();
148      }
149      return false;
150    } catch (FHIRException e) {
151      return false;
152    }
153  }
154
155  public static void setDeprecated(CodeSystem cs, ConceptDefinitionComponent concept, DateTimeType date)
156      throws FHIRFormatError {
157    setStatus(cs, concept, ConceptStatus.Deprecated);
158    defineDeprecatedProperty(cs);
159    concept.addProperty().setCode("deprecationDate").setValue(date);
160  }
161
162  public static boolean isInactive(CodeSystem cs, ConceptDefinitionComponent def) throws FHIRException {
163    for (ConceptPropertyComponent p : def.getProperty()) {
164      if (p.getCode().equals("status") && p.hasValueStringType())
165        return "inactive".equals(p.getValueStringType());
166    }
167    return false;
168  }
169
170  public static boolean isInactive(CodeSystem cs, String code) throws FHIRException {
171    ConceptDefinitionComponent def = findCode(cs.getConcept(), code);
172    if (def == null)
173      return true;
174    return isInactive(cs, def);
175  }
176
177  public static PropertyComponent defineCodeSystemProperty(CodeSystem cs, String code, String description,
178      PropertyType type) {
179    for (PropertyComponent p : cs.getProperty()) {
180      if (p.getCode().equals(code))
181        return p;
182    }
183    PropertyComponent p = cs.addProperty();
184    p.setCode(code).setDescription(description).setType(type).setUri("http://hl7.org/fhir/concept-properties#" + code);
185    return p;
186  }
187
188  public static String getCodeDefinition(CodeSystem cs, String code) {
189    return getCodeDefinition(cs.getConcept(), code);
190  }
191
192  private static String getCodeDefinition(List<ConceptDefinitionComponent> list, String code) {
193    for (ConceptDefinitionComponent c : list) {
194      if (c.hasCode() && c.getCode().equals(code))
195        return c.getDefinition();
196      String s = getCodeDefinition(c.getConcept(), code);
197      if (s != null)
198        return s;
199    }
200    return null;
201  }
202
203  public static CodeSystem makeShareable(CodeSystem cs) {
204    if (!cs.hasMeta())
205      cs.setMeta(new Meta());
206    for (UriType t : cs.getMeta().getProfile())
207      if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablecodesystem"))
208        return cs;
209    cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem"));
210    return cs;
211  }
212
213  public static void setOID(CodeSystem cs, String oid) {
214    if (!oid.startsWith("urn:oid:"))
215      oid = "urn:oid:" + oid;
216    if (!cs.hasIdentifier())
217      cs.addIdentifier(new Identifier().setSystem("urn:ietf:rfc:3986").setValue(oid));
218    else if ("urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue()
219        && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:"))
220      cs.getIdentifierFirstRep().setValue(oid);
221    else
222      throw new Error("unable to set OID on code system");
223
224  }
225
226  public static boolean hasOID(CodeSystem cs) {
227    return getOID(cs) != null;
228  }
229
230  public static String getOID(CodeSystem cs) {
231    if (cs.hasIdentifier() && "urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem())
232        && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:"))
233      return cs.getIdentifierFirstRep().getValue().substring(8);
234    return null;
235  }
236
237  private static ConceptDefinitionComponent findCode(List<ConceptDefinitionComponent> list, String code) {
238    for (ConceptDefinitionComponent c : list) {
239      if (c.getCode().equals(code))
240        return c;
241      ConceptDefinitionComponent s = findCode(c.getConcept(), code);
242      if (s != null)
243        return s;
244    }
245    return null;
246  }
247
248  public static void markStatus(CodeSystem cs, String wg, StandardsStatus status, String pckage, String fmm,
249      String normativeVersion) throws FHIRException {
250    if (wg != null) {
251      if (!ToolingExtensions.hasExtension(cs, ToolingExtensions.EXT_WORKGROUP)
252          || (Utilities.existsInList(ToolingExtensions.readStringExtension(cs, ToolingExtensions.EXT_WORKGROUP), "fhir",
253              "vocab") && !Utilities.existsInList(wg, "fhir", "vocab"))) {
254        ToolingExtensions.setCodeExtension(cs, ToolingExtensions.EXT_WORKGROUP, wg);
255      }
256    }
257    if (status != null) {
258      StandardsStatus ss = ToolingExtensions.getStandardsStatus(cs);
259      if (ss == null || ss.isLowerThan(status))
260        ToolingExtensions.setStandardsStatus(cs, status, normativeVersion);
261      if (pckage != null) {
262        if (!cs.hasUserData("ballot.package"))
263          cs.setUserData("ballot.package", pckage);
264        else if (!pckage.equals(cs.getUserString("ballot.package")))
265          if (!"infrastructure".equals(cs.getUserString("ballot.package")))
266            System.out.println("Code System " + cs.getUrl() + ": ownership clash " + pckage + " vs "
267                + cs.getUserString("ballot.package"));
268      }
269      if (status == StandardsStatus.NORMATIVE) {
270        cs.setExperimental(false);
271        cs.setStatus(PublicationStatus.ACTIVE);
272      }
273    }
274    if (fmm != null) {
275      String sfmm = ToolingExtensions.readStringExtension(cs, ToolingExtensions.EXT_FMM_LEVEL);
276      if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm))
277        ToolingExtensions.setIntegerExtension(cs, ToolingExtensions.EXT_FMM_LEVEL, Integer.parseInt(fmm));
278    }
279  }
280
281  public static Type readProperty(ConceptDefinitionComponent concept, String code) {
282    for (ConceptPropertyComponent p : concept.getProperty())
283      if (p.getCode().equals(code))
284        return p.getValue();
285    return null;
286  }
287
288  public static ConceptPropertyComponent getProperty(ConceptDefinitionComponent concept, String code) {
289    for (ConceptPropertyComponent p : concept.getProperty())
290      if (p.getCode().equals(code))
291        return p;
292    return null;
293  }
294
295  // see http://hl7.org/fhir/R4/codesystem.html#hierachy
296  // returns additional parents not in the heirarchy
297  public static List<String> getOtherChildren(CodeSystem cs, ConceptDefinitionComponent c) {
298    List<String> res = new ArrayList<String>();
299    for (ConceptPropertyComponent p : c.getProperty()) {
300      if ("parent".equals(p.getCode())) {
301        res.add(p.getValue().primitiveValue());
302      }
303    }
304    return res;
305  }
306
307  // see http://hl7.org/fhir/R4/codesystem.html#hierachy
308  public static void addOtherChild(CodeSystem cs, ConceptDefinitionComponent owner, String code) {
309    defineChildProperty(cs);
310    owner.addProperty().setCode("child").setValue(new CodeType(code));
311  }
312
313  public static class ConceptDefinitionComponentSorter implements Comparator<ConceptDefinitionComponent> {
314
315    @Override
316    public int compare(ConceptDefinitionComponent o1, ConceptDefinitionComponent o2) {
317      return o1.getCode().compareToIgnoreCase(o2.getCode());
318    }
319
320  }
321
322  public static void sortAllCodes(CodeSystem cs) {
323    sortAllCodes(cs.getConcept());
324  }
325
326  public static void sortAllCodes(List<ConceptDefinitionComponent> list) {
327    Collections.sort(list, new ConceptDefinitionComponentSorter());
328    for (ConceptDefinitionComponent cd : list) {
329      if (cd.hasConcept()) {
330        sortAllCodes(cd.getConcept());
331      }
332    }
333  }
334
335}