001package org.hl7.fhir.r5.terminologies;
002
003import java.util.Calendar;
004import java.util.Collections;
005import java.util.Comparator;
006import java.util.HashSet;
007import java.util.List;
008import java.util.Set;
009
010/*
011  Copyright (c) 2011+, HL7, Inc.
012  All rights reserved.
013  
014  Redistribution and use in source and binary forms, with or without modification, 
015  are permitted provided that the following conditions are met:
016    
017   * Redistributions of source code must retain the above copyright notice, this 
018     list of conditions and the following disclaimer.
019   * Redistributions in binary form must reproduce the above copyright notice, 
020     this list of conditions and the following disclaimer in the documentation 
021     and/or other materials provided with the distribution.
022   * Neither the name of HL7 nor the names of its contributors may be used to 
023     endorse or promote products derived from this software without specific 
024     prior written permission.
025  
026  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
027  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
028  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
029  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
030  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
031  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
032  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
033  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
034  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
035  POSSIBILITY OF SUCH DAMAGE.
036  
037 */
038
039
040import lombok.extern.slf4j.Slf4j;
041import org.hl7.fhir.exceptions.FHIRException;
042import org.hl7.fhir.exceptions.FHIRFormatError;
043import org.hl7.fhir.r5.context.IWorkerContext;
044import org.hl7.fhir.r5.model.BooleanType;
045import org.hl7.fhir.r5.model.CanonicalType;
046import org.hl7.fhir.r5.model.CodeSystem;
047import org.hl7.fhir.r5.model.DateTimeType;
048import org.hl7.fhir.r5.model.StringType;
049import org.hl7.fhir.r5.model.IntegerType;
050import org.hl7.fhir.r5.model.Enumerations.FilterOperator;
051import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
052import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
053import org.hl7.fhir.r5.model.Identifier;
054import org.hl7.fhir.r5.model.Meta;
055import org.hl7.fhir.r5.model.Parameters;
056import org.hl7.fhir.r5.model.UriType;
057import org.hl7.fhir.r5.model.ValueSet;
058import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
059import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
060import org.hl7.fhir.r5.model.CodeType;
061import org.hl7.fhir.r5.model.Coding;
062import org.hl7.fhir.r5.model.DataType;
063import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
064import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
065import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent;
066import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
067import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
068import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent;
069import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptDefinitionComponentSorter;
070import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptStatus;
071import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet;
072import org.hl7.fhir.r5.utils.CanonicalResourceUtilities;
073import org.hl7.fhir.r5.utils.ToolingExtensions;
074import org.hl7.fhir.r5.utils.UserDataNames;
075import org.hl7.fhir.utilities.StandardsStatus;
076import org.hl7.fhir.utilities.Utilities;
077import org.hl7.fhir.utilities.VersionUtilities;
078
079@Slf4j
080public class ValueSetUtilities extends TerminologyUtilities {
081
082
083  public static class ValueSetSorter implements Comparator<ValueSet> {
084
085    @Override
086    public int compare(ValueSet o1, ValueSet o2) {
087      String url1 = o1.getUrl();
088      String url2 = o2.getUrl();
089      int c = compareString(url1, url2);
090      if (c == 0) {
091        String ver1 = o1.getVersion();
092        String ver2 = o2.getVersion();
093        c = VersionUtilities.compareVersions(ver1, ver2);
094        if (c == 0) {
095          String d1 = o1.getDateElement().asStringValue();
096          String d2 = o2.getDateElement().asStringValue();
097          c = compareString(url1, url2);
098        }
099      }
100      return c;
101    }
102
103    private int compareString(String s1, String s2) {
104      if (s1 == null) {
105        return s2 == null ? 0 : 1;
106      } else {
107        return s1.compareTo(s2);
108      }
109    }
110
111  }
112
113
114  public static boolean isServerSide(String url) {
115    return Utilities.existsInList(url, "http://hl7.org/fhir/sid/cvx");
116  }
117  
118  public static ValueSet makeShareable(ValueSet vs) {
119    if (!vs.hasExperimental()) {
120      vs.setExperimental(false);
121    }
122    if (!vs.hasMeta())
123      vs.setMeta(new Meta());
124    for (UriType t : vs.getMeta().getProfile()) 
125      if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset"))
126        return vs;
127    vs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablevalueset"));
128    return vs;
129  }
130
131  public static boolean makeVSShareable(ValueSet vs) {
132    if (!vs.hasMeta())
133      vs.setMeta(new Meta());
134    for (UriType t : vs.getMeta().getProfile()) 
135      if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset"))
136        return false;
137    vs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablevalueset"));
138    return true;
139  }
140
141  public static void checkShareable(ValueSet vs) {
142    if (!vs.hasMeta())
143      throw new Error("ValueSet "+vs.getUrl()+" is not shareable");
144    for (UriType t : vs.getMeta().getProfile()) {
145      if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset"))
146        return;
147    }
148    throw new Error("ValueSet "+vs.getUrl()+" is not shareable");    
149  }
150
151  public static boolean hasOID(ValueSet vs) {
152    return getOID(vs) != null;
153  }
154
155  public static String getOID(ValueSet vs) {
156    for (Identifier id : vs.getIdentifier()) {
157      if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:"))
158        return id.getValue().substring(8);
159    }
160    return null;
161  }
162
163  public static void setOID(ValueSet vs, String oid) {
164    if (!oid.startsWith("urn:oid:"))
165      oid = "urn:oid:" + oid;
166    for (Identifier id : vs.getIdentifier()) {
167      if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:")) {
168        id.setValue(oid);
169        return;
170      }
171    }
172    vs.addIdentifier().setSystem("urn:ietf:rfc:3986").setValue(oid);
173  }
174
175  public static void markStatus(ValueSet vs, String wg, StandardsStatus status, String pckage, String fmm, IWorkerContext context, String normativeVersion) throws FHIRException {
176    if (vs.hasUserData(UserDataNames.render_external_link))
177      return;
178    
179    if (wg != null) {
180      if (!ToolingExtensions.hasExtension(vs, ToolingExtensions.EXT_WORKGROUP) || 
181          (!Utilities.existsInList(ToolingExtensions.readStringExtension(vs, ToolingExtensions.EXT_WORKGROUP), "fhir", "vocab") && Utilities.existsInList(wg, "fhir", "vocab"))) {
182        CanonicalResourceUtilities.setHl7WG(vs, wg);
183      }
184    }
185    if (status != null) {
186      StandardsStatus ss = ToolingExtensions.getStandardsStatus(vs);
187      if (ss == null || ss.isLowerThan(status)) 
188        ToolingExtensions.setStandardsStatus(vs, status, normativeVersion);
189      if (pckage != null) {
190        if (!vs.hasUserData(UserDataNames.kindling_ballot_package))        
191          vs.setUserData(UserDataNames.kindling_ballot_package, pckage);
192        else if (!pckage.equals(vs.getUserString(UserDataNames.kindling_ballot_package)))
193          if (!"infrastructure".equals(vs.getUserString(UserDataNames.kindling_ballot_package)))
194          log.warn("Value Set "+vs.getUrl()+": ownership clash "+pckage+" vs "+vs.getUserString(UserDataNames.kindling_ballot_package));
195      }
196      if (status == StandardsStatus.NORMATIVE) {
197        vs.setStatus(PublicationStatus.ACTIVE);
198      }
199    }
200    if (fmm != null) {
201      String sfmm = ToolingExtensions.readStringExtension(vs, ToolingExtensions.EXT_FMM_LEVEL);
202      if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm))  {
203        ToolingExtensions.setIntegerExtension(vs, ToolingExtensions.EXT_FMM_LEVEL, Integer.parseInt(fmm));
204      }
205    }
206    if (vs.hasUserData(UserDataNames.TX_ASSOCIATED_CODESYSTEM))
207      CodeSystemUtilities.markStatus((CodeSystem) vs.getUserData(UserDataNames.TX_ASSOCIATED_CODESYSTEM), wg, status, pckage, fmm, normativeVersion);
208    else if (status == StandardsStatus.NORMATIVE && context != null) {
209      for (ConceptSetComponent csc : vs.getCompose().getInclude()) {
210        if (csc.hasSystem()) {
211          CodeSystem cs = context.fetchCodeSystem(csc.getSystem());
212          if (cs != null) {
213            CodeSystemUtilities.markStatus(cs, wg, status, pckage, fmm, normativeVersion);
214          }
215        }
216      }
217    }
218  }
219
220  private static int ssval(String status) {
221    if ("Draft".equals("status")) 
222      return 1;
223    if ("Informative".equals("status")) 
224      return 2;
225    if ("External".equals("status")) 
226      return 3;
227    if ("Trial Use".equals("status")) 
228      return 3;
229    if ("Normative".equals("status")) 
230      return 4;
231    return -1;
232  }
233
234  public static ValueSet generateImplicitValueSet(String uri) {
235    if (uri.startsWith("http://snomed.info/sct"))
236      return generateImplicitSnomedValueSet(uri);
237    if (uri.startsWith("http://loinc.org/vs"))
238      return generateImplicitLoincValueSet(uri);
239    if (uri.equals("http://hl7.org/fhir/ValueSet/mimetypes")) {
240      return generateImplicitMimetypesValueSet(uri);
241    }
242    return null;
243  }
244
245  private static ValueSet generateImplicitMimetypesValueSet(String theUri) {
246    ValueSet valueSet = new ValueSet();
247    valueSet.setStatus(PublicationStatus.ACTIVE);
248    valueSet.setUrl(theUri);
249    valueSet.setDescription("This value set includes all possible codes from BCP-13 (http://tools.ietf.org/html/bcp13)");
250    valueSet.getCompose()
251      .addInclude().setSystem("urn:ietf:bcp:13");
252    return valueSet;
253  }
254
255  private static ValueSet generateImplicitLoincValueSet(String uri) {
256    if ("http://loinc.org/vs".equals(uri))
257      return makeLoincValueSet();
258    if (uri.startsWith("http://loinc.org/vs/LL"))
259      return makeAnswerList(makeLoincValueSet(), uri);
260    return null;
261  }
262
263  private static ValueSet makeAnswerList(ValueSet vs, String uri) {
264    vs.setUrl(uri);
265    String c = uri.substring(20);
266    vs.setName("LOINCAnswers"+c);
267    vs.setTitle("LOINC Answer Codes for "+c);
268    vs.getCompose().getIncludeFirstRep().addFilter().setProperty("LIST").setOp(FilterOperator.EQUAL).setValue(c);
269    return vs;
270  }
271
272  private static ValueSet makeLoincValueSet() {
273    ValueSet vs = new ValueSet();
274    vs.setUrl("http://loinc.org/vs");
275    vs.setName("LOINCCodes");
276    vs.setTitle("All LOINC codes");
277    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");
278    vs.setStatus(PublicationStatus.ACTIVE);
279    vs.getCompose().addInclude().setSystem("http://loinc.org");
280    return vs;
281  }
282
283  private static ValueSet generateImplicitSnomedValueSet(String uri) {
284    if ("http://snomed.info/sct?fhir_vs".equals(uri))
285      return makeImplicitSnomedValueSet(uri);
286    return null;
287  }
288
289  private static ValueSet makeImplicitSnomedValueSet(String uri) {
290    ValueSet vs = new ValueSet();
291    vs.setUrl(uri);
292    vs.setName("SCTValueSet");
293    vs.setTitle("SCT ValueSet");
294    vs.setDescription("All SNOMED CT Concepts");
295    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");
296    vs.setStatus(PublicationStatus.ACTIVE);
297    vs.getCompose().addInclude().setSystem("http://snomed.info/sct");
298    return vs;
299  }
300
301  public static void setDeprecated(List<ValueSetExpansionPropertyComponent> vsProp,  ValueSetExpansionContainsComponent n) {
302    n.addProperty().setCode("status").setValue(new CodeType("deprecated"));
303    for (ValueSetExpansionPropertyComponent o : vsProp) {
304      if ("status".equals(o.getCode())) {
305        return;
306      }
307    }
308    vsProp.add(new ValueSetExpansionPropertyComponent().setCode("status").setUri("http://hl7.org/fhir/concept-properties#status"));
309  }
310
311
312  public static class ConceptReferenceComponentSorter implements Comparator<ConceptReferenceComponent> {
313
314    @Override
315    public int compare(ConceptReferenceComponent o1, ConceptReferenceComponent o2) {
316      return o1.getCode().compareToIgnoreCase(o2.getCode());
317    }
318  }
319
320
321  public static void sortInclude(ConceptSetComponent inc) {
322    Collections.sort(inc.getConcept(), new ConceptReferenceComponentSorter());
323  }
324
325  public static String getAllCodesSystem(ValueSet vs) {
326    if (vs.hasCompose()) {
327      ValueSetComposeComponent c = vs.getCompose();
328      if (c.getExclude().isEmpty() && c.getInclude().size() == 1) {
329        ConceptSetComponent i = c.getIncludeFirstRep();
330        if (i.hasSystem() && !i.hasValueSet() && !i.hasConcept() && !i.hasFilter()) {
331          return i.getSystem();
332        }
333      }
334    }
335    return null;
336  }
337
338  public static boolean isDeprecated(ValueSet vs, ValueSetExpansionContainsComponent c) {
339    try {
340      for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p : c.getProperty()) {
341        if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "deprecated".equals(p.getValueCodeType().getCode())) {
342          return true;
343        }      
344        // this, though status should also be set
345        if ("deprecationDate".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof DateTimeType) 
346          return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance()));
347        // legacy  
348        if ("deprecated".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 
349          return ((BooleanType) p.getValue()).getValue();
350      }
351      StandardsStatus ss = ToolingExtensions.getStandardsStatus(c);
352      if (ss == StandardsStatus.DEPRECATED) {
353        return true;
354      }
355      return false;
356    } catch (FHIRException e) {
357      return false;
358    }  
359  }
360
361  public static boolean hasCodeInExpansion(ValueSet vs, Coding code) {
362    return hasCodeInExpansion(vs.getExpansion().getContains(), code);
363  }
364
365  private static boolean hasCodeInExpansion(List<ValueSetExpansionContainsComponent> list, Coding code) {
366    for (ValueSetExpansionContainsComponent c : list) {
367      if (c.getSystem().equals(code.getSystem()) && c.getCode().equals(code.getCode())) {
368        return true;
369      }
370      if (hasCodeInExpansion(c.getContains(), code)) {
371        return true;
372      }
373    }
374    return false;
375  }
376
377  public static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, String value) {
378    if (value != null) {
379      return addProperty(vs, ctxt, url, code, new StringType(value));
380    } else {
381      return null;
382    }
383  }
384
385  public static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, Integer value) {
386    if (value != null) {
387      return addProperty(vs, ctxt, url, code, new IntegerType(value));
388    } else {
389      return null;
390    }
391  }
392
393  public static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, DataType value) {
394    code = defineProperty(vs, url, code);
395    org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p = getProperty(ctxt.getProperty(),  code);
396    if (p != null) {
397      p.setValue(value);
398    } else {
399      p = ctxt.addProperty().setCode(code).setValue(value);
400    }
401    return p;
402  }
403
404  private static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent getProperty(List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> list, String code) {
405    for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent t : list) {
406      if (code.equals(t.getCode())) {
407        return t;
408      }
409    }
410    return null;
411  }
412
413  private static String defineProperty(ValueSet vs, String url, String code) {
414    for (ValueSetExpansionPropertyComponent p : vs.getExpansion().getProperty()) {
415      if (p.hasUri() && p.getUri().equals(url)) {
416        return p.getCode();
417      }
418    }
419    for (ValueSetExpansionPropertyComponent p : vs.getExpansion().getProperty()) {
420      if (p.hasCode() && p.getCode().equals(code)) {
421        p.setUri(url);
422        return code;
423      }
424    }
425    ValueSetExpansionPropertyComponent p = vs.getExpansion().addProperty();
426    p.setUri(url);
427    p.setCode(code);
428    return code;  
429  }
430
431  public static int countExpansion(ValueSet valueset) {
432    int i = valueset.getExpansion().getContains().size();
433    for (ValueSetExpansionContainsComponent t : valueset.getExpansion().getContains()) {
434      i = i + countExpansion(t);
435    }
436    return i;
437  }
438
439  public static int countExpansion(List<ValueSetExpansionContainsComponent> list) {
440    int i = list.size();
441    for (ValueSetExpansionContainsComponent t : list) {
442      i = i + countExpansion(t);
443    }
444    return i;
445  }
446
447  private static int countExpansion(ValueSetExpansionContainsComponent c) {
448    int i = c.getContains().size();
449    for (ValueSetExpansionContainsComponent t : c.getContains()) {
450      i = i + countExpansion(t);
451    }
452    return i;
453  }
454
455  public static Set<String> listSystems(IWorkerContext ctxt, ValueSet vs) {
456    Set<String> systems = new HashSet<>();
457    for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
458      for (CanonicalType ct : inc.getValueSet()) {
459        ValueSet vsr = ctxt.findTxResource(ValueSet.class, ct.asStringValue(), vs);
460        if (vsr != null) {
461          systems.addAll(listSystems(ctxt, vsr));
462        }
463      }
464      if (inc.hasSystem()) {
465        systems.add(inc.getSystem());
466      }
467    }
468    return systems;
469  }
470  
471
472  public static boolean isIncompleteExpansion(ValueSet valueSet) {
473    if (valueSet.hasExpansion()) {
474      ValueSetExpansionComponent exp = valueSet.getExpansion();
475      if (exp.hasTotal()) {
476        if (exp.getTotal() != countExpansion(exp.getContains())) {
477          return true;
478        }
479      }
480    }
481    return false;
482  }
483
484
485  public static Set<String> codes(ValueSet vs, CodeSystem cs) {
486    Set<String> res = new HashSet<>();
487    for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
488      if (inc.getSystem().equals(cs.getUrl())) {
489        addCodes(res, inc, cs.getConcept());
490      }
491    }
492    return res;
493  }
494
495  private static void addCodes(Set<String> res, ConceptSetComponent inc, List<ConceptDefinitionComponent> list) {
496    for (ConceptDefinitionComponent cd : list) {
497      if (cd.hasCode() && (!inc.hasConcept() || inc.hasConcept(cd.getCode()))) {
498        res.add(cd.getCode());
499      }
500      if (cd.hasConcept()) {
501        addCodes(res, inc, cd.getConcept());
502      }
503    }    
504  }
505  
506  public static String versionFromExpansionParams(Parameters expParameters, String system, String defaultVersion) {
507    if (expParameters != null) {
508      for (ParametersParameterComponent p : expParameters.getParameter()) {
509        if ("system-version".equals(p.getName()) || "force-system-version".equals(p.getName())) {
510          String v = p.getValue().primitiveValue();
511          if (v.startsWith(system+"|")) {
512            String ver = v.substring(v.indexOf("|")+1);
513            if (defaultVersion == null || ver.startsWith(defaultVersion) || "force-system-version".equals(p.getName())) {
514              return ver;
515            }
516          }
517        }
518      }
519    }
520    return defaultVersion;
521  }
522
523  public static boolean isImplicitLoincValueSet(String url) {
524    return url.startsWith("http://loinc.org/vs");    
525  }
526
527  public static boolean isImplicitSCTValueSet(String url) {
528    return url.startsWith("http://snomed.info/sct") && url.contains("?fhir_vs");
529  }
530
531  public static ValueSet makeImplicitValueSet(String url, String version) {
532    if (url.startsWith("http://snomed.info/sct")) {
533      return makeImplicitSCTVS(url, version);
534    } else if (url.startsWith("http://loinc.org/vs")) {
535      return makeImplicitLoincVS(url, version);
536    } else {
537      throw new FHIRException("Unknown implicit value set URL "+url);
538    }
539  }
540
541  private static ValueSet makeImplicitSCTVS(String url, String version) {
542    String query = url.substring(url.indexOf("?")+1);
543    if ("fhir_vs".equals(query)) {
544      ValueSet vs = new ValueSet();
545      vs.setUrl(url);
546      vs.setVersion(version);
547      vs.getCompose().addInclude().setSystem("http://snomed.info/sct");
548      return vs;
549    } else if (query.startsWith("fhir_vs=isa/")) {
550      ValueSet vs = new ValueSet();
551      vs.setUrl(url);
552      vs.setVersion(version);
553      vs.getCompose().addInclude().setSystem("http://snomed.info/sct").addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue(query.substring(12));
554      return vs;
555    } else if (query.equals("fhir_vs=refset")) {
556      ValueSet vs = new ValueSet();
557      vs.setUrl(url);
558      vs.setVersion(version);
559      vs.getCompose().addInclude().setSystem("http://snomed.info/sct").addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("refset-base");
560      return vs;      
561    } else if (query.startsWith("fhir_vs=refset/")) {
562      ValueSet vs = new ValueSet();
563      vs.setUrl(url);
564      vs.setVersion(version);
565      vs.getCompose().addInclude().setSystem("http://snomed.info/sct").addFilter().setProperty("concept").setOp(FilterOperator.IN).setValue(query.substring(15));
566      return vs;      
567    } else {
568      throw new FHIRException("Unknown implicit SNOMED CT value set URL "+url);
569    }
570  }
571
572  private static ValueSet makeImplicitLoincVS(String url, String version) {
573    if (url.equals("http://loinc.org/vs")) {
574      ValueSet vs = new ValueSet();
575      vs.setUrl(url);
576      vs.setVersion(version);
577      vs.getCompose().addInclude().setSystem("http://loinc.org");
578      return vs;
579    } else if (url.startsWith("http://loinc.org/vs/LP")) {
580      ValueSet vs = new ValueSet();
581      vs.setUrl(url);
582      vs.setVersion(version);
583      vs.getCompose().addInclude().setSystem("http://loinc.org").addFilter().setProperty("ancestor").setOp(FilterOperator.EQUAL).setValue(url.substring("http://loinc.org/vs/".length()));
584      return vs;      
585    } else if (url.startsWith("http://loinc.org/vs/LL")) {
586      ValueSet vs = new ValueSet();
587      vs.setUrl(url);
588      vs.setVersion(version);
589      // this isn't the actual definition, but it won't matter to us internally
590      vs.getCompose().addInclude().setSystem("http://loinc.org").addFilter().setProperty("answer-list").setOp(FilterOperator.EQUAL).setValue(url.substring("http://loinc.org/vs/".length()));      
591      return vs;
592    } else {
593      throw new FHIRException("Unknown implicit LOINC value set URL "+url);
594    }
595  }
596
597  public static Set<String> checkExpansionSubset(ValueSet vs1, ValueSet vs2) {
598    Set<String> codes = new HashSet<>();
599    checkCodes(codes, vs2.getExpansion().getContains(), vs1.getExpansion().getContains());
600    return codes;
601  }
602
603  private static void checkCodes(Set<String> codes, List<ValueSetExpansionContainsComponent> listS, List<ValueSetExpansionContainsComponent> listT) {
604    for (ValueSetExpansionContainsComponent c : listS) {
605      ValueSetExpansionContainsComponent t = findContained(c, listT);
606      if (t == null) {
607        codes.add(c.getCode());
608      }
609      if (c.hasContains()) {
610        checkCodes(codes, c.getContains(), listT);
611      }
612    }
613    
614  }
615
616  private static ValueSetExpansionContainsComponent findContained(ValueSetExpansionContainsComponent c, List<ValueSetExpansionContainsComponent> listT) {
617    for (ValueSetExpansionContainsComponent t : listT) {
618      if (t.getSystem().equals(c.getSystem()) && t.getCode().equals(c.getCode())) {
619        return t;
620      }
621      if (t.hasContains()) {
622        ValueSetExpansionContainsComponent tt = findContained(c, t.getContains());
623        if (tt != null) {
624          return tt;
625        }
626      }
627    }
628    return null;
629  }
630
631  
632}