001package org.hl7.fhir.r5.terminologies;
002
003import java.util.Calendar;
004import java.util.Collections;
005import java.util.Comparator;
006import java.util.List;
007
008/*
009  Copyright (c) 2011+, HL7, Inc.
010  All rights reserved.
011  
012  Redistribution and use in source and binary forms, with or without modification, 
013  are permitted provided that the following conditions are met:
014    
015   * Redistributions of source code must retain the above copyright notice, this 
016     list of conditions and the following disclaimer.
017   * Redistributions in binary form must reproduce the above copyright notice, 
018     this list of conditions and the following disclaimer in the documentation 
019     and/or other materials provided with the distribution.
020   * Neither the name of HL7 nor the names of its contributors may be used to 
021     endorse or promote products derived from this software without specific 
022     prior written permission.
023  
024  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
025  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
026  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
027  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
028  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
029  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
030  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
031  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
032  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
033  POSSIBILITY OF SUCH DAMAGE.
034  
035 */
036
037
038import org.hl7.fhir.exceptions.FHIRException;
039import org.hl7.fhir.exceptions.FHIRFormatError;
040import org.hl7.fhir.r5.context.IWorkerContext;
041import org.hl7.fhir.r5.model.BooleanType;
042import org.hl7.fhir.r5.model.CanonicalType;
043import org.hl7.fhir.r5.model.CodeSystem;
044import org.hl7.fhir.r5.model.DateTimeType;
045import org.hl7.fhir.r5.model.StringType;
046import org.hl7.fhir.r5.model.IntegerType;
047import org.hl7.fhir.r5.model.Enumerations.FilterOperator;
048import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
049import org.hl7.fhir.r5.model.Identifier;
050import org.hl7.fhir.r5.model.Meta;
051import org.hl7.fhir.r5.model.UriType;
052import org.hl7.fhir.r5.model.ValueSet;
053import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
054import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
055import org.hl7.fhir.r5.model.CodeType;
056import org.hl7.fhir.r5.model.Coding;
057import org.hl7.fhir.r5.model.DataType;
058import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
059import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
060import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent;
061import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
062import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent;
063import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptDefinitionComponentSorter;
064import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptStatus;
065import org.hl7.fhir.r5.utils.ToolingExtensions;
066import org.hl7.fhir.utilities.StandardsStatus;
067import org.hl7.fhir.utilities.Utilities;
068
069public class ValueSetUtilities {
070
071
072  public static boolean isServerSide(String url) {
073    return Utilities.existsInList(url, "http://hl7.org/fhir/sid/cvx");
074  }
075  
076  public static ValueSet makeShareable(ValueSet vs) {
077    if (!vs.hasExperimental()) {
078      vs.setExperimental(false);
079    }
080    if (!vs.hasMeta())
081      vs.setMeta(new Meta());
082    for (UriType t : vs.getMeta().getProfile()) 
083      if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset"))
084        return vs;
085    vs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablevalueset"));
086    return vs;
087  }
088
089  public static boolean makeVSShareable(ValueSet vs) {
090    if (!vs.hasMeta())
091      vs.setMeta(new Meta());
092    for (UriType t : vs.getMeta().getProfile()) 
093      if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset"))
094        return false;
095    vs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablevalueset"));
096    return true;
097  }
098
099  public static void checkShareable(ValueSet vs) {
100    if (!vs.hasMeta())
101      throw new Error("ValueSet "+vs.getUrl()+" is not shareable");
102    for (UriType t : vs.getMeta().getProfile()) {
103      if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset"))
104        return;
105    }
106    throw new Error("ValueSet "+vs.getUrl()+" is not shareable");    
107  }
108
109  public static boolean hasOID(ValueSet vs) {
110    return getOID(vs) != null;
111  }
112
113  public static String getOID(ValueSet vs) {
114    for (Identifier id : vs.getIdentifier()) {
115      if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:"))
116        return id.getValue().substring(8);
117    }
118    return null;
119  }
120
121  public static void setOID(ValueSet vs, String oid) {
122    if (!oid.startsWith("urn:oid:"))
123      oid = "urn:oid:" + oid;
124    for (Identifier id : vs.getIdentifier()) {
125      if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:")) {
126        id.setValue(oid);
127        return;
128      }
129    }
130    vs.addIdentifier().setSystem("urn:ietf:rfc:3986").setValue(oid);
131  }
132
133  public static void markStatus(ValueSet vs, String wg, StandardsStatus status, String pckage, String fmm, IWorkerContext context, String normativeVersion) throws FHIRException {
134    if (vs.hasUserData("external.url"))
135      return;
136    
137    if (wg != null) {
138      if (!ToolingExtensions.hasExtension(vs, ToolingExtensions.EXT_WORKGROUP) || 
139          (!Utilities.existsInList(ToolingExtensions.readStringExtension(vs, ToolingExtensions.EXT_WORKGROUP), "fhir", "vocab") && Utilities.existsInList(wg, "fhir", "vocab"))) {
140        ToolingExtensions.setCodeExtension(vs, ToolingExtensions.EXT_WORKGROUP, wg);
141      }
142    }
143    if (status != null) {
144      StandardsStatus ss = ToolingExtensions.getStandardsStatus(vs);
145      if (ss == null || ss.isLowerThan(status)) 
146        ToolingExtensions.setStandardsStatus(vs, status, normativeVersion);
147      if (pckage != null) {
148        if (!vs.hasUserData("ballot.package"))        
149          vs.setUserData("ballot.package", pckage);
150        else if (!pckage.equals(vs.getUserString("ballot.package")))
151          if (!"infrastructure".equals(vs.getUserString("ballot.package")))
152          System.out.println("Value Set "+vs.getUrl()+": ownership clash "+pckage+" vs "+vs.getUserString("ballot.package"));
153      }
154      if (status == StandardsStatus.NORMATIVE) {
155        vs.setExperimental(false);
156        vs.setStatus(PublicationStatus.ACTIVE);
157      }
158    }
159    if (fmm != null) {
160      String sfmm = ToolingExtensions.readStringExtension(vs, ToolingExtensions.EXT_FMM_LEVEL);
161      if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm))  {
162        ToolingExtensions.setIntegerExtension(vs, ToolingExtensions.EXT_FMM_LEVEL, Integer.parseInt(fmm));
163      }
164      if (Integer.parseInt(fmm) <= 1) {
165        vs.setExperimental(true);
166      }
167    }
168    if (vs.hasUserData("cs"))
169      CodeSystemUtilities.markStatus((CodeSystem) vs.getUserData("cs"), wg, status, pckage, fmm, normativeVersion);
170    else if (status == StandardsStatus.NORMATIVE && context != null) {
171      for (ConceptSetComponent csc : vs.getCompose().getInclude()) {
172        if (csc.hasSystem()) {
173          CodeSystem cs = context.fetchCodeSystem(csc.getSystem());
174          if (cs != null) {
175            CodeSystemUtilities.markStatus(cs, wg, status, pckage, fmm, normativeVersion);
176          }
177        }
178      }
179    }
180  }
181
182  private static int ssval(String status) {
183    if ("Draft".equals("status")) 
184      return 1;
185    if ("Informative".equals("status")) 
186      return 2;
187    if ("External".equals("status")) 
188      return 3;
189    if ("Trial Use".equals("status")) 
190      return 3;
191    if ("Normative".equals("status")) 
192      return 4;
193    return -1;
194  }
195
196  public static ValueSet generateImplicitValueSet(String uri) {
197    if (uri.startsWith("http://snomed.info/sct"))
198      return generateImplicitSnomedValueSet(uri);
199    if (uri.startsWith("http://loinc.org/vs"))
200      return generateImplicitLoincValueSet(uri);
201    if (uri.equals("http://hl7.org/fhir/ValueSet/mimetypes")) {
202      return generateImplicitMimetypesValueSet(uri);
203    }
204    return null;
205  }
206
207  private static ValueSet generateImplicitMimetypesValueSet(String theUri) {
208    ValueSet valueSet = new ValueSet();
209    valueSet.setStatus(PublicationStatus.ACTIVE);
210    valueSet.setUrl(theUri);
211    valueSet.setDescription("This value set includes all possible codes from BCP-13 (http://tools.ietf.org/html/bcp13)");
212    valueSet.getCompose()
213      .addInclude().setSystem("urn:ietf:bcp:13");
214    return valueSet;
215  }
216
217  private static ValueSet generateImplicitLoincValueSet(String uri) {
218    if ("http://loinc.org/vs".equals(uri))
219      return makeLoincValueSet();
220    if (uri.startsWith("http://loinc.org/vs/LL"))
221      return makeAnswerList(makeLoincValueSet(), uri);
222    return null;
223  }
224
225  private static ValueSet makeAnswerList(ValueSet vs, String uri) {
226    vs.setUrl(uri);
227    String c = uri.substring(20);
228    vs.setName("LOINCAnswers"+c);
229    vs.setTitle("LOINC Answer Codes for "+c);
230    vs.getCompose().getIncludeFirstRep().addFilter().setProperty("LIST").setOp(FilterOperator.EQUAL).setValue(c);
231    return vs;
232  }
233
234  private static ValueSet makeLoincValueSet() {
235    ValueSet vs = new ValueSet();
236    vs.setUrl("http://loinc.org/vs");
237    vs.setName("LOINCCodes");
238    vs.setTitle("All LOINC codes");
239    vs.setCopyright("This content LOINC® is copyright © 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at http://loinc.org/terms-of-use");
240    vs.setStatus(PublicationStatus.ACTIVE);
241    vs.getCompose().addInclude().setSystem("http://loinc.org");
242    return vs;
243  }
244
245  private static ValueSet generateImplicitSnomedValueSet(String uri) {
246    if ("http://snomed.info/sct?fhir_vs".equals(uri))
247      return makeImplicitSnomedValueSet(uri);
248    return null;
249  }
250
251  private static ValueSet makeImplicitSnomedValueSet(String uri) {
252    ValueSet vs = new ValueSet();
253    vs.setUrl(uri);
254    vs.setName("SCTValueSet");
255    vs.setTitle("SCT ValueSet");
256    vs.setDescription("All SNOMED CT Concepts");
257    vs.setCopyright("This value set includes content from SNOMED CT, which is copyright © 2002+ International Health Terminology Standards Development Organisation (SNOMED International), and distributed by agreement between SNOMED International and HL7. Implementer use of SNOMED CT is not covered by this agreement");
258    vs.setStatus(PublicationStatus.ACTIVE);
259    vs.getCompose().addInclude().setSystem("http://snomed.info/sct");
260    return vs;
261  }
262
263  public static void setDeprecated(List<ValueSetExpansionPropertyComponent> vsProp,  ValueSetExpansionContainsComponent n) {
264    n.addProperty().setCode("status").setValue(new CodeType("deprecated"));
265    for (ValueSetExpansionPropertyComponent o : vsProp) {
266      if ("status".equals(o.getCode())) {
267        return;
268      }
269    }
270    vsProp.add(new ValueSetExpansionPropertyComponent().setCode("status").setUri("http://hl7.org/fhir/concept-properties#status"));
271  }
272
273
274  public static class ConceptReferenceComponentSorter implements Comparator<ConceptReferenceComponent> {
275
276    @Override
277    public int compare(ConceptReferenceComponent o1, ConceptReferenceComponent o2) {
278      return o1.getCode().compareToIgnoreCase(o2.getCode());
279    }
280  }
281
282
283  public static void sortInclude(ConceptSetComponent inc) {
284    Collections.sort(inc.getConcept(), new ConceptReferenceComponentSorter());
285  }
286
287  public static String getAllCodesSystem(ValueSet vs) {
288    if (vs.hasCompose()) {
289      ValueSetComposeComponent c = vs.getCompose();
290      if (c.getExclude().isEmpty() && c.getInclude().size() == 1) {
291        ConceptSetComponent i = c.getIncludeFirstRep();
292        if (i.hasSystem() && !i.hasValueSet() && !i.hasConcept() && !i.hasFilter()) {
293          return i.getSystem();
294        }
295      }
296    }
297    return null;
298  }
299
300  public static boolean isDeprecated(ValueSet vs, ValueSetExpansionContainsComponent c) {
301    try {
302      for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p : c.getProperty()) {
303        if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "deprecated".equals(p.getValueCodeType().getCode())) {
304          return true;
305        }      
306        // this, though status should also be set
307        if ("deprecationDate".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof DateTimeType) 
308          return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance()));
309        // legacy  
310        if ("deprecated".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 
311          return ((BooleanType) p.getValue()).getValue();
312      }
313      StandardsStatus ss = ToolingExtensions.getStandardsStatus(c);
314      if (ss == StandardsStatus.DEPRECATED) {
315        return true;
316      }
317      return false;
318    } catch (FHIRException e) {
319      return false;
320    }  
321  }
322
323  public static boolean hasCodeInExpansion(ValueSet vs, Coding code) {
324    return hasCodeInExpansion(vs.getExpansion().getContains(), code);
325  }
326
327  private static boolean hasCodeInExpansion(List<ValueSetExpansionContainsComponent> list, Coding code) {
328    for (ValueSetExpansionContainsComponent c : list) {
329      if (c.getSystem().equals(code.getSystem()) && c.getCode().equals(code.getCode())) {
330        return true;
331      }
332      if (hasCodeInExpansion(c.getContains(), code)) {
333        return true;
334      }
335    }
336    return false;
337  }
338
339  public static void addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, String value) {
340    if (value != null) {
341      addProperty(vs, ctxt, url, code, new StringType(value));
342    }
343  }
344
345  public static void addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, Integer value) {
346    if (value != null) {
347      addProperty(vs, ctxt, url, code, new IntegerType(value));
348    }
349  }
350
351  public static void addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, DataType value) {
352    code = defineProperty(vs, url, code);
353    org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p = getProperty(ctxt.getProperty(),  code);
354    if (p != null)
355      p.setValue(value);
356    else
357      ctxt.addProperty().setCode(code).setValue(value);    
358
359  }
360
361  private static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent getProperty(List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> list, String code) {
362    for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent t : list) {
363      if (code.equals(t.getCode())) {
364        return t;
365      }
366    }
367    return null;
368  }
369
370  private static String defineProperty(ValueSet vs, String url, String code) {
371    for (ValueSetExpansionPropertyComponent p : vs.getExpansion().getProperty()) {
372      if (p.hasUri() && p.getUri().equals(url)) {
373        return p.getCode();
374      }
375    }
376    for (ValueSetExpansionPropertyComponent p : vs.getExpansion().getProperty()) {
377      if (p.hasCode() && p.getCode().equals(code)) {
378        p.setUri(url);
379        return code;
380      }
381    }
382    ValueSetExpansionPropertyComponent p = vs.getExpansion().addProperty();
383    p.setUri(url);
384    p.setCode(code);
385    return code;  
386  }
387
388  public static int countExpansion(ValueSet valueset) {
389    int i = valueset.getExpansion().getContains().size();
390    for (ValueSetExpansionContainsComponent t : valueset.getExpansion().getContains()) {
391      i = i + countExpansion(t);
392    }
393    return i;
394  }
395
396  private static int countExpansion(ValueSetExpansionContainsComponent c) {
397    int i = c.getContains().size();
398    for (ValueSetExpansionContainsComponent t : c.getContains()) {
399      i = i + countExpansion(t);
400    }
401    return i;
402  }
403
404
405}