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