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