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