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 static org.apache.commons.lang3.StringUtils.isNotBlank;
035
036import java.io.FileNotFoundException;
037import java.io.IOException;
038
039/*
040 * Copyright (c) 2011+, HL7, Inc
041 * All rights reserved.
042 * 
043 * Redistribution and use in source and binary forms, with or without modification,
044 * are permitted provided that the following conditions are met:
045 * 
046 * Redistributions of source code must retain the above copyright notice, this
047 * list of conditions and the following disclaimer.
048 * Redistributions in binary form must reproduce the above copyright notice,
049 * this list of conditions and the following disclaimer in the documentation
050 * and/or other materials provided with the distribution.
051 * Neither the name of HL7 nor the names of its contributors may be used to
052 * endorse or promote products derived from this software without specific
053 * prior written permission.
054 * 
055 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
056 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
057 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
058 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
059 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
060 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
061 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
062 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
063 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
064 * POSSIBILITY OF SUCH DAMAGE.
065 * 
066 */
067
068import java.util.ArrayList;
069import java.util.HashMap;
070import java.util.HashSet;
071import java.util.List;
072import java.util.Map;
073import java.util.Set;
074
075import org.apache.commons.lang3.NotImplementedException;
076import org.hl7.fhir.exceptions.FHIRException;
077import org.hl7.fhir.exceptions.FHIRFormatError;
078import org.hl7.fhir.exceptions.NoTerminologyServiceException;
079import org.hl7.fhir.exceptions.TerminologyServiceException;
080import org.hl7.fhir.r4.context.IWorkerContext;
081import org.hl7.fhir.r4.model.CodeSystem;
082import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode;
083import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
084import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionDesignationComponent;
085import org.hl7.fhir.r4.model.DateTimeType;
086import org.hl7.fhir.r4.model.Factory;
087import org.hl7.fhir.r4.model.Parameters;
088import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
089import org.hl7.fhir.r4.model.PrimitiveType;
090import org.hl7.fhir.r4.model.Type;
091import org.hl7.fhir.r4.model.UriType;
092import org.hl7.fhir.r4.model.ValueSet;
093import org.hl7.fhir.r4.model.ValueSet.ConceptReferenceComponent;
094import org.hl7.fhir.r4.model.ValueSet.ConceptReferenceDesignationComponent;
095import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent;
096import org.hl7.fhir.r4.model.ValueSet.ConceptSetFilterComponent;
097import org.hl7.fhir.r4.model.ValueSet.FilterOperator;
098import org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent;
099import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent;
100import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
101import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionParameterComponent;
102import org.hl7.fhir.r4.utils.ToolingExtensions;
103import org.hl7.fhir.utilities.Utilities;
104
105public class ValueSetExpanderSimple implements ValueSetExpander {
106
107  private List<ValueSetExpansionContainsComponent> codes = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>();
108  private List<ValueSetExpansionContainsComponent> roots = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>();
109  private Map<String, ValueSetExpansionContainsComponent> map = new HashMap<String, ValueSet.ValueSetExpansionContainsComponent>();
110  private IWorkerContext context;
111  private boolean canBeHeirarchy = true;
112  private Set<String> excludeKeys = new HashSet<String>();
113  private Set<String> excludeSystems = new HashSet<String>();
114  private ValueSet focus;
115  private int maxExpansionSize = 500;
116
117  private int total;
118
119  public ValueSetExpanderSimple(IWorkerContext context) {
120    super();
121    this.context = context;
122  }
123
124  public void setMaxExpansionSize(int theMaxExpansionSize) {
125    maxExpansionSize = theMaxExpansionSize;
126  }
127
128  private ValueSetExpansionContainsComponent addCode(String system, String code, String display, ValueSetExpansionContainsComponent parent, List<ConceptDefinitionDesignationComponent> designations, Parameters expParams, boolean isAbstract, boolean inactive, List<ValueSet> filters) {
129 
130    if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code))
131      return null;
132    ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
133    n.setSystem(system);
134    n.setCode(code);
135    if (isAbstract)
136      n.setAbstract(true);
137    if (inactive)
138      n.setInactive(true);
139
140    if (expParams.getParameterBool("includeDesignations") && designations != null) {
141      for (ConceptDefinitionDesignationComponent t : designations) {
142        ToolingExtensions.addLanguageTranslation(n, t.getLanguage(), t.getValue());
143      }
144    }
145    ConceptDefinitionDesignationComponent t = expParams.hasLanguage() ? getMatchingLang(designations, expParams.getLanguage()) : null;
146    if (t == null)
147      n.setDisplay(display);
148    else
149      n.setDisplay(t.getValue());
150
151    String s = key(n);
152    if (map.containsKey(s) || excludeKeys.contains(s)) {
153      canBeHeirarchy = false;
154    } else {
155      codes.add(n);
156      map.put(s, n);
157      total++;
158    }
159    if (canBeHeirarchy && parent != null) {
160      parent.getContains().add(n);
161    } else {
162      roots.add(n);
163    }
164    return n;
165  }
166
167  private boolean filterContainsCode(List<ValueSet> filters, String system, String code) {
168    for (ValueSet vse : filters)
169      if (expansionContainsCode(vse.getExpansion().getContains(), system, code))
170        return true;
171    return false;
172  }
173
174  private boolean expansionContainsCode(List<ValueSetExpansionContainsComponent> contains, String system, String code) {
175    for (ValueSetExpansionContainsComponent cc : contains) {
176      if (system.equals(cc.getSystem()) && code.equals(cc.getCode()))
177        return true;
178      if (expansionContainsCode(cc.getContains(), system, code))
179        return true;
180    }
181    return false;
182  }
183
184  private ConceptDefinitionDesignationComponent getMatchingLang(List<ConceptDefinitionDesignationComponent> list, String lang) {
185    for (ConceptDefinitionDesignationComponent t : list)
186      if (t.getLanguage().equals(lang))
187        return t;
188    for (ConceptDefinitionDesignationComponent t : list)
189      if (t.getLanguage().startsWith(lang))
190        return t;
191    return null;
192  }
193
194  private void addCodeAndDescendents(ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters)  throws FHIRException {
195    focus.checkNoModifiers("Expansion.contains", "expanding");
196    ValueSetExpansionContainsComponent np = addCode(focus.getSystem(), focus.getCode(), focus.getDisplay(), parent, 
197         convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), filters);
198    for (ValueSetExpansionContainsComponent c : focus.getContains())
199      addCodeAndDescendents(focus, np, expParams, filters);
200  }
201  
202  private List<ConceptDefinitionDesignationComponent> convert(List<ConceptReferenceDesignationComponent> designations) {
203    List<ConceptDefinitionDesignationComponent> list = new ArrayList<ConceptDefinitionDesignationComponent>();
204    for (ConceptReferenceDesignationComponent d : designations) {
205      ConceptDefinitionDesignationComponent n = new ConceptDefinitionDesignationComponent();
206      n.setLanguage(d.getLanguage());
207      n.setUse(d.getUse());
208      n.setValue(d.getValue());
209      list.add(n);
210    }
211    return list;
212  }
213
214  private void addCodeAndDescendents(CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, ConceptDefinitionComponent exclusion)  throws FHIRException {
215    def.checkNoModifiers("Code in Code System", "expanding");
216    if (exclusion != null) {
217      if (exclusion.getCode().equals(def.getCode()))
218        return; // excluded.
219    }
220    if (!CodeSystemUtilities.isDeprecated(cs, def)) {
221      ValueSetExpansionContainsComponent np = null;
222      boolean abs = CodeSystemUtilities.isNotSelectable(cs, def);
223      boolean inc = CodeSystemUtilities.isInactive(cs, def);
224      if (canBeHeirarchy || !abs)
225        np = addCode(system, def.getCode(), def.getDisplay(), parent, def.getDesignation(), expParams, abs, inc, filters);
226      for (ConceptDefinitionComponent c : def.getConcept())
227        addCodeAndDescendents(cs, system, c, np, expParams, filters, exclusion);
228    } else {
229      for (ConceptDefinitionComponent c : def.getConcept())
230        addCodeAndDescendents(cs, system, c, null, expParams, filters, exclusion);
231    }
232
233  }
234
235  private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params, Parameters expParams, List<ValueSet> filters) throws ETooCostly, FHIRException {
236    if (expand != null) {
237      if (expand.getContains().size() > maxExpansionSize)
238        throw new ETooCostly("Too many codes to display (>" + Integer.toString(expand.getContains().size()) + ")");
239      for (ValueSetExpansionParameterComponent p : expand.getParameter()) {
240        if (!existsInParams(params, p.getName(), p.getValue()))
241          params.add(p);
242      }
243
244      copyImportContains(expand.getContains(), null, expParams, filters);
245    }
246  }
247
248  private void excludeCode(String theSystem, String theCode) {
249    ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
250    n.setSystem(theSystem);
251    n.setCode(theCode);
252    String s = key(n);
253    excludeKeys.add(s);
254  }
255
256  private void excludeCodes(ConceptSetComponent exc, List<ValueSetExpansionParameterComponent> params, String ctxt) throws FHIRException {
257    exc.checkNoModifiers("Compose.exclude", "expanding");
258    if (exc.hasSystem() && exc.getConcept().size() == 0 && exc.getFilter().size() == 0) {
259      excludeSystems.add(exc.getSystem());
260    }
261
262    if (exc.hasValueSet())
263      throw new Error("Processing Value set references in exclude is not yet done in "+ctxt);
264    // importValueSet(imp.getValue(), params, expParams);
265
266    CodeSystem cs = context.fetchCodeSystem(exc.getSystem());
267    if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem())) {
268      ValueSetExpansionOutcome vse = context.expandVS(exc, false);
269      ValueSet valueset = vse.getValueset();
270      if (valueset == null)
271        throw new TerminologyServiceException("Error Expanding ValueSet: "+vse.getError());
272      excludeCodes(valueset.getExpansion(), params);
273      return;
274    }
275
276    for (ConceptReferenceComponent c : exc.getConcept()) {
277      excludeCode(exc.getSystem(), c.getCode());
278    }
279
280    if (exc.getFilter().size() > 0)
281      throw new NotImplementedException("not done yet");
282  }
283
284  private void excludeCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params) {
285    for (ValueSetExpansionContainsComponent c : expand.getContains()) {
286      excludeCode(c.getSystem(), c.getCode());
287    }
288  }
289
290  private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, Type value) {
291    for (ValueSetExpansionParameterComponent p : params) {
292      if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false))
293        return true;
294    }
295    return false;
296  }
297
298  @Override
299  public ValueSetExpansionOutcome expand(ValueSet source, Parameters expParams) {
300    try {
301      return doExpand(source, expParams);
302    } catch (NoTerminologyServiceException e) {
303      // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
304      // that might fail too, but it might not, later.
305      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.NOSERVICE);
306    } catch (RuntimeException e) {
307      // TODO: we should put something more specific instead of just Exception below, since
308      // it swallows bugs.. what would be expected to be caught there?
309      throw e;
310    } catch (Exception e) {
311      // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
312      // that might fail too, but it might not, later.
313      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN);
314    }
315  }
316
317  public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams) throws FHIRException, ETooCostly, FileNotFoundException, IOException {
318    if (expParams == null)
319      expParams = makeDefaultExpansion();
320    source.checkNoModifiers("ValueSet", "expanding");
321    focus = source.copy();
322    focus.setExpansion(new ValueSet.ValueSetExpansionComponent());
323    focus.getExpansion().setTimestampElement(DateTimeType.now());
324    focus.getExpansion().setIdentifier(Factory.createUUID());
325    for (ParametersParameterComponent p : expParams.getParameter()) {
326      if (Utilities.existsInList(p.getName(), "includeDesignations", "excludeNested"))
327        focus.getExpansion().addParameter().setName(p.getName()).setValue(p.getValue());
328    }
329
330    if (source.hasCompose())
331      handleCompose(source.getCompose(), focus.getExpansion().getParameter(), expParams, source.getUrl());
332
333    if (canBeHeirarchy) {
334      for (ValueSetExpansionContainsComponent c : roots) {
335        focus.getExpansion().getContains().add(c);
336      }
337    } else {
338      for (ValueSetExpansionContainsComponent c : codes) {
339        if (map.containsKey(key(c)) && !c.getAbstract()) { // we may have added abstract codes earlier while we still thought it might be heirarchical, but later we gave up, so now ignore them
340          focus.getExpansion().getContains().add(c);
341          c.getContains().clear(); // make sure any heirarchy is wiped
342        }
343      }
344    }
345
346    if (total > 0) {
347      focus.getExpansion().setTotal(total);
348    }
349
350    return new ValueSetExpansionOutcome(focus);
351  }
352
353  private Parameters makeDefaultExpansion() {
354    Parameters res = new Parameters();
355    res.addParameter("excludeNested", true);
356    res.addParameter("includeDesignations", false);
357    return res;
358  }
359
360  private void addToHeirarchy(List<ValueSetExpansionContainsComponent> target, List<ValueSetExpansionContainsComponent> source) {
361    for (ValueSetExpansionContainsComponent s : source) {
362      target.add(s);
363    }
364  }
365
366  private String getCodeDisplay(CodeSystem cs, String code) throws TerminologyServiceException {
367    ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), code);
368    if (def == null)
369      throw new TerminologyServiceException("Unable to find code '" + code + "' in code system " + cs.getUrl());
370    return def.getDisplay();
371  }
372
373  private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) {
374    for (ConceptDefinitionComponent c : clist) {
375      if (code.equals(c.getCode()))
376        return c;
377      ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code);
378      if (v != null)
379        return v;
380    }
381    return null;
382  }
383
384  private void handleCompose(ValueSetComposeComponent compose, List<ValueSetExpansionParameterComponent> params, Parameters expParams, String ctxt)
385      throws ETooCostly, FileNotFoundException, IOException, FHIRException {
386    compose.checkNoModifiers("ValueSet.compose", "expanding");
387    // Exclude comes first because we build up a map of things to exclude
388    for (ConceptSetComponent inc : compose.getExclude())
389      excludeCodes(inc, params, ctxt);
390    canBeHeirarchy = !expParams.getParameterBool("excludeNested") && excludeKeys.isEmpty() && excludeSystems.isEmpty();
391    boolean first = true;
392    for (ConceptSetComponent inc : compose.getInclude()) {
393      if (first == true)
394        first = false;
395      else
396        canBeHeirarchy = false;
397      includeCodes(inc, params, expParams, canBeHeirarchy);
398    }
399
400  }
401
402  private ValueSet importValueSet(String value, List<ValueSetExpansionParameterComponent> params, Parameters expParams) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError {
403    if (value == null)
404      throw new TerminologyServiceException("unable to find value set with no identity");
405    ValueSet vs = context.fetchResource(ValueSet.class, value);
406    if (vs == null)
407      throw new TerminologyServiceException("Unable to find imported value set " + value);
408    ValueSetExpansionOutcome vso = new ValueSetExpanderSimple(context).expand(vs, expParams);
409    if (vso.getError() != null)
410      throw new TerminologyServiceException("Unable to expand imported value set: " + vso.getError());
411    if (vs.hasVersion())
412      if (!existsInParams(params, "version", new UriType(vs.getUrl() + "|" + vs.getVersion())))
413        params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl() + "|" + vs.getVersion())));
414    for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) {
415      if (!existsInParams(params, p.getName(), p.getValue()))
416        params.add(p);
417    }
418    canBeHeirarchy = false; // if we're importing a value set, we have to be combining, so we won't try for a heirarchy
419    return vso.getValueset();
420  }
421
422  private void copyImportContains(List<ValueSetExpansionContainsComponent> list, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filter) throws FHIRException {
423    for (ValueSetExpansionContainsComponent c : list) {
424      c.checkNoModifiers("Imported Expansion in Code System", "expanding");
425      ValueSetExpansionContainsComponent np = addCode(c.getSystem(), c.getCode(), c.getDisplay(), parent, null, expParams, c.getAbstract(), c.getInactive(), filter);
426      copyImportContains(c.getContains(), np, expParams, filter);
427    }
428  }
429
430  private void includeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params, Parameters expParams, boolean heirarchical) throws ETooCostly, FileNotFoundException, IOException, FHIRException {
431    inc.checkNoModifiers("Compose.include", "expanding");
432    List<ValueSet> imports = new ArrayList<ValueSet>();
433    for (UriType imp : inc.getValueSet()) {
434      imports.add(importValueSet(imp.getValue(), params, expParams));
435    }
436
437    if (!inc.hasSystem()) {
438      if (imports.isEmpty()) // though this is not supposed to be the case
439        return;
440      ValueSet base = imports.get(0);
441      imports.remove(0);
442      base.checkNoModifiers("Imported ValueSet", "expanding");
443      copyImportContains(base.getExpansion().getContains(), null, expParams, imports);
444    } else {
445      CodeSystem cs = context.fetchCodeSystem(inc.getSystem());
446      if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE)) {
447        doServerIncludeCodes(inc, heirarchical, params, imports, expParams);
448      } else {
449        doInternalIncludeCodes(inc, params, expParams, imports, cs);
450      }
451    }
452  }
453
454  private void doServerIncludeCodes(ConceptSetComponent inc, boolean heirarchical, List<ValueSetExpansionParameterComponent> params, List<ValueSet> imports, Parameters expParams) throws FHIRException {
455    ValueSetExpansionOutcome vso = context.expandVS(inc, heirarchical);
456    if (vso.getError() != null)
457      throw new TerminologyServiceException("Unable to expand imported value set: " + vso.getError());
458    ValueSet vs = vso.getValueset();
459    if (vs.hasVersion())
460      if (!existsInParams(params, "version", new UriType(vs.getUrl() + "|" + vs.getVersion())))
461        params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl() + "|" + vs.getVersion())));
462    for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) {
463      if (!existsInParams(params, p.getName(), p.getValue()))
464        params.add(p);
465    }
466    for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) {
467      addCodeAndDescendents(cc, null, expParams, imports);
468    }
469  }
470
471  public void doInternalIncludeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params, Parameters expParams, List<ValueSet> imports,
472      CodeSystem cs) throws NoTerminologyServiceException, TerminologyServiceException, FHIRException {
473    if (cs == null) {
474      if (context.isNoTerminologyServer())
475        throw new NoTerminologyServiceException("unable to find code system " + inc.getSystem().toString());
476      else
477        throw new TerminologyServiceException("unable to find code system " + inc.getSystem().toString());
478    }
479    cs.checkNoModifiers("Code System", "expanding");
480    if (cs.getContent() != CodeSystemContentMode.COMPLETE)
481      throw new TerminologyServiceException("Code system " + inc.getSystem().toString() + " is incomplete");
482    if (cs.hasVersion())
483      if (!existsInParams(params, "version", new UriType(cs.getUrl() + "|" + cs.getVersion())))
484        params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(cs.getUrl() + "|" + cs.getVersion())));
485
486    if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
487      // special case - add all the code system
488      for (ConceptDefinitionComponent def : cs.getConcept()) {
489        addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, null);
490      }
491    }
492
493    if (!inc.getConcept().isEmpty()) {
494      canBeHeirarchy = false;
495      for (ConceptReferenceComponent c : inc.getConcept()) {
496        c.checkNoModifiers("Code in Code System", "expanding");
497        addCode(inc.getSystem(), c.getCode(), Utilities.noString(c.getDisplay()) ? getCodeDisplay(cs, c.getCode()) : c.getDisplay(), null, convertDesignations(c.getDesignation()), expParams, false,
498            CodeSystemUtilities.isInactive(cs, c.getCode()), imports);
499      }
500    }
501    if (inc.getFilter().size() > 1) {
502      canBeHeirarchy = false; // which will bt the case if we get around to supporting this
503      throw new TerminologyServiceException("Multiple filters not handled yet"); // need to and them, and this isn't done yet. But this shouldn't arise in non loinc and snomed value sets
504    }
505    if (inc.getFilter().size() == 1) {
506      ConceptSetFilterComponent fc = inc.getFilter().get(0);
507      if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) {
508        // special: all codes in the target code system under the value
509        ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
510        if (def == null)
511          throw new TerminologyServiceException("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
512        addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, null);
513      } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISNOTA) {
514        // special: all codes in the target code system that are not under the value
515        ConceptDefinitionComponent defEx = getConceptForCode(cs.getConcept(), fc.getValue());
516        if (defEx == null)
517          throw new TerminologyServiceException("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
518        for (ConceptDefinitionComponent def : cs.getConcept()) {
519          addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, defEx);
520        }
521      } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.DESCENDENTOF) {
522        // special: all codes in the target code system under the value
523        ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
524        if (def == null)
525          throw new TerminologyServiceException("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
526        for (ConceptDefinitionComponent c : def.getConcept())
527          addCodeAndDescendents(cs, inc.getSystem(), c, null, expParams, imports, null);
528      } else if ("display".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) {
529        // gg; note: wtf is this: if the filter is display=v, look up the code 'v', and see if it's diplsay is 'v'?
530        canBeHeirarchy = false;
531        ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
532        if (def != null) {
533          if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) {
534            if (def.getDisplay().contains(fc.getValue())) {
535              addCode(inc.getSystem(), def.getCode(), def.getDisplay(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def),
536                  imports);
537            }
538          }
539        }
540      } else
541        throw new NotImplementedException("Search by property[" + fc.getProperty() + "] and op[" + fc.getOp() + "] is not supported yet");
542    }
543  }
544
545  private List<ConceptDefinitionDesignationComponent> convertDesignations(List<ConceptReferenceDesignationComponent> list) {
546    List<ConceptDefinitionDesignationComponent> res = new ArrayList<CodeSystem.ConceptDefinitionDesignationComponent>();
547    for (ConceptReferenceDesignationComponent t : list) {
548      ConceptDefinitionDesignationComponent c = new ConceptDefinitionDesignationComponent();
549      c.setLanguage(t.getLanguage());
550      c.setUse(t.getUse());
551      c.setValue(t.getValue());
552    }
553    return res;
554  }
555
556  private String key(String uri, String code) {
557    return "{" + uri + "}" + code;
558  }
559
560  private String key(ValueSetExpansionContainsComponent c) {
561    return key(c.getSystem(), c.getCode());
562  }
563
564}