001package org.hl7.fhir.r5.terminologies.expansion;
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.Calendar;
070import java.util.Collection;
071import java.util.GregorianCalendar;
072import java.util.List;
073
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.r5.context.IWorkerContext;
079import org.hl7.fhir.r5.elementmodel.LanguageUtils;
080import org.hl7.fhir.r5.extensions.ExtensionConstants;
081import org.hl7.fhir.r5.extensions.Extensions;
082import org.hl7.fhir.r5.extensions.ExtensionsUtils;
083import org.hl7.fhir.r5.model.BooleanType;
084import org.hl7.fhir.r5.model.CodeSystem;
085import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
086import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
087import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent;
088import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
089import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
090import org.hl7.fhir.r5.model.CodeType;
091import org.hl7.fhir.r5.model.Coding;
092import org.hl7.fhir.r5.model.DataType;
093import org.hl7.fhir.r5.model.DateTimeType;
094import org.hl7.fhir.r5.model.Enumerations.FilterOperator;
095import org.hl7.fhir.r5.model.Extension;
096import org.hl7.fhir.r5.model.Factory;
097import org.hl7.fhir.r5.model.IntegerType;
098import org.hl7.fhir.r5.model.PackageInformation;
099import org.hl7.fhir.r5.model.Parameters;
100import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
101import org.hl7.fhir.r5.model.PrimitiveType;
102import org.hl7.fhir.r5.model.Resource;
103import org.hl7.fhir.r5.model.StringType;
104import org.hl7.fhir.r5.model.CanonicalType;
105import org.hl7.fhir.r5.model.UriType;
106import org.hl7.fhir.r5.model.ValueSet;
107import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
108import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent;
109import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
110import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent;
111import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent;
112import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
113import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
114import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent;
115import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent;
116import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
117import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
118import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpander.Token;
119import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider;
120import org.hl7.fhir.r5.terminologies.providers.CodeSystemProviderExtension;
121import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext;
122import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.TerminologyServiceProtectionException;
123import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
124import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase;
125import org.hl7.fhir.r5.utils.ToolingExtensions;
126import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
127import org.hl7.fhir.utilities.Utilities;
128import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader;
129import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader.LanguagePreference;
130import org.hl7.fhir.utilities.i18n.I18nConstants;
131
132public class ValueSetExpander extends ValueSetProcessBase {
133
134
135  public class Token {
136    private String system;
137    private String code;
138    public Token(String system, String code) {
139      super();
140      this.system = system;
141      this.code = code;
142    }
143    public String getSystem() {
144      return system;
145    }
146    public String getCode() {
147      return code;
148    }
149    public boolean matches(Coding use) {
150      return system.equals(use.getSystem()) && code.equals(use.getCode());
151    }
152    public boolean matchesLang(String language) {
153      return system.equals("urn:ietf:bcp:47") && code.equals(language);
154    }
155  }
156
157  private static final boolean REPORT_VERSION_ANYWAY = true;
158  
159  private ValueSet focus;
160  private List<String> allErrors = new ArrayList<>();
161  private int maxExpansionSize = 1000;
162  private WorkingContext dwc = new WorkingContext();
163  
164  private boolean checkCodesWhenExpanding;
165  private boolean includeAbstract = true;
166
167  private AcceptLanguageHeader langs;
168  private List<Token> designations = new ArrayList<>();
169
170  public ValueSetExpander(IWorkerContext context, TerminologyOperationContext opContext) {
171    super(context, opContext);
172  }
173
174  public ValueSetExpander(IWorkerContext context, TerminologyOperationContext opContext, List<String> allErrors) {
175    super(context, opContext);
176    this.allErrors = allErrors;
177  }
178
179  public void setMaxExpansionSize(int theMaxExpansionSize) {
180    maxExpansionSize = theMaxExpansionSize;
181  }
182  
183  private ValueSetExpansionContainsComponent addCode(WorkingContext wc, String system, String code, String display, String dispLang, ValueSetExpansionContainsComponent parent, List<ConceptDefinitionDesignationComponent> designations, Parameters expParams, 
184      boolean isAbstract, boolean inactive, List<ValueSet> filters, boolean noInactive, boolean deprecated, List<ValueSetExpansionPropertyComponent> vsProp, 
185      List<ConceptPropertyComponent> csProps, List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> expProps, List<Extension> csExtList, List<Extension> vsExtList, ValueSetExpansionComponent exp) throws ETooCostly {
186    opContext.deadCheck();
187    
188    if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code, exp))
189      return null;
190    if (noInactive && inactive) {
191      return null;
192    }
193    
194    ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
195    n.setSystem(system);
196    n.setCode(code);
197    if (isAbstract)
198      n.setAbstract(true);
199    if (inactive)
200      n.setInactive(true);
201    if (deprecated) {
202      ValueSetUtilities.setDeprecated(vsProp, n);
203    }
204    if (ExtensionsUtils.hasExtension(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-label")) {
205      ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#label", "label", ExtensionsUtils.getExtensionValue(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-label"));
206    }
207    if (ExtensionsUtils.hasExtension(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-label")) {
208      ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#label", "label", ExtensionsUtils.getExtensionValue(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-label"));
209    }
210    if (ExtensionsUtils.hasExtension(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-conceptOrder")) {
211      ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#order", "order", ExtensionsUtils.getExtensionValue(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-conceptOrder"));
212    }
213    if (ExtensionsUtils.hasExtension(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-conceptOrder")) {
214      ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#order", "order", ExtensionsUtils.getExtensionValue(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-conceptOrder"));
215    }
216    if (ExtensionsUtils.hasExtension(csExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight")) {
217      ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#itemWeight", "weight", ExtensionsUtils.getExtensionValue(csExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight"));
218    }
219    if (ExtensionsUtils.hasExtension(vsExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight")) {
220      ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#itemWeight", "weight", ExtensionsUtils.getExtensionValue(vsExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight"));
221    }
222    ExtensionsUtils.copyExtensions(csExtList, n.getExtension(), 
223        "http://hl7.org/fhir/StructureDefinition/coding-sctdescid", 
224        "http://hl7.org/fhir/StructureDefinition/rendering-style", 
225        "http://hl7.org/fhir/StructureDefinition/rendering-xhtml",
226        "http://hl7.org/fhir/StructureDefinition/codesystem-alternate");
227    
228    ExtensionsUtils.copyExtensions(vsExtList, n.getExtension(), 
229        "http://hl7.org/fhir/StructureDefinition/valueset-supplement", 
230        "http://hl7.org/fhir/StructureDefinition/valueset-deprecated",
231        "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition",
232        "http://hl7.org/fhir/StructureDefinition/coding-sctdescid", 
233        "http://hl7.org/fhir/StructureDefinition/rendering-style", 
234        "http://hl7.org/fhir/StructureDefinition/rendering-xhtml");
235    
236    // display and designations
237    ConceptDefinitionDesignationComponent pref = null;
238    if (langs == null) {
239      n.setDisplay(display);
240    } else {
241      if (designations == null) {
242        designations = new ArrayList<>();
243      }
244      designations.add(new ConceptDefinitionDesignationComponent().setLanguage(dispLang).setValue(display).setUse(new Coding().setSystem("http://terminology.hl7.org/CodeSystem/designation-usage").setCode("display")));
245      pref = findMatchingDesignation(designations);
246      if (pref != null) {
247        n.setDisplay(pref.getValue());
248      }
249    }
250
251    if (expParams.getParameterBool("includeDesignations")) {
252      
253      for (ConceptDefinitionDesignationComponent t : designations) {
254        if (t != pref && (t.hasLanguage() || t.hasUse()) && t.getValue() != null && passesDesignationFilter(t)) {
255          ConceptReferenceDesignationComponent d = n.addDesignation();
256          if (t.getLanguage() != null) {
257            d.setLanguage(t.getLanguage().trim());
258          }
259          if (t.getValue() != null) {
260            d.setValue(t.getValue().trim());
261          }
262          if (t.getUse() != null) {
263            d.setUse(t.getUse());
264          }
265          for (Extension ext : t.getExtension()) {
266            if (Utilities.existsInList(ext.getUrl(), "http://hl7.org/fhir/StructureDefinition/coding-sctdescid")) {
267              d.addExtension(ext);
268            }
269          }
270        }
271      }
272    }
273    for (ParametersParameterComponent p : expParams.getParameter()) {
274      if ("property".equals(p.getName())) {
275        if (csProps != null && p.hasValue()) {
276          for (ConceptPropertyComponent cp : csProps) {
277            if (p.getValue().primitiveValue().equals(cp.getCode())) {
278              n.addProperty().setCode(cp.getCode()).setValue(cp.getValue()).copyExtensions(cp, "http://hl7.org/fhir/StructureDefinition/alternate-code-use", "http://hl7.org/fhir/StructureDefinition/alternate-code-status");
279            }
280          }
281        }
282        if (expProps != null && p.hasValue()) {
283          for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent cp : expProps) {
284            if (p.getValue().primitiveValue().equals(cp.getCode())) {
285              n.addProperty(cp).copyExtensions(cp, "http://hl7.org/fhir/StructureDefinition/alternate-code-use", "http://hl7.org/fhir/StructureDefinition/alternate-code-status");
286            }
287          }
288        }        
289      }
290    }
291
292    String s = key(n);
293    if (wc.getMap().containsKey(s) || wc.getExcludeKeys().contains(s)) {
294      wc.setCanBeHeirarchy(false);
295    } else {
296      wc.getCodes().add(n);
297      wc.getMap().put(s, n);
298      wc.incTotal();
299      if (wc == dwc && wc.getTotal() > maxExpansionSize) {
300        if (wc.getOffset()+wc.getCount() > 0 && wc.getTotal() > wc.getOffset()+wc.getCount()) {
301          wc.setTotal(-1);
302          throw new EFinished();
303        }
304        throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, focus.getUrl(), ">" + Integer.toString(maxExpansionSize)));
305      }
306    }
307    if (wc.isCanBeHeirarchy() && parent != null) {
308      parent.getContains().add(n);
309    } else {
310      wc.getRoots().add(n);
311    }
312    return n;
313  }
314
315  private boolean passesDesignationFilter(ConceptDefinitionDesignationComponent d) {
316    if (designations.isEmpty()) {
317      return true;
318    }
319    for (Token t : designations) {
320      if (t.matches(d.getUse()) || t.matchesLang(d.getLanguage())) {
321        return true;
322      }
323      for (Coding c : d.getAdditionalUse()) {
324        if (t.matches(c)) {
325          return true;
326        }
327      }
328    }
329    return false;
330  }
331
332  private ConceptDefinitionDesignationComponent findMatchingDesignation(List<ConceptDefinitionDesignationComponent> designations) {
333    if (langs == null) {
334      return null;
335    }
336    // we have a list of languages in priority order 
337    // we have a list of designations in no order 
338    // language exact match is preferred
339    // display is always preferred
340    
341    for (LanguagePreference lang : langs.getLangs()) {
342      if (lang.getValue() > 0) {
343        for (ConceptDefinitionDesignationComponent cd : designations) {
344          if (isDisplay(cd) && LanguageUtils.langsMatchExact(cd.getLanguage(), lang.getLang())) {
345            return cd;
346          }
347        }
348        for (ConceptDefinitionDesignationComponent cd : designations) {
349          if (isDisplay(cd) && LanguageUtils.langsMatch(cd.getLanguage(), lang.getLang())) {
350            return cd;
351          }
352        }
353        for (ConceptDefinitionDesignationComponent cd : designations) {
354          if (LanguageUtils.langsMatchExact(cd.getLanguage(), lang.getLang())) {
355            return cd;
356          }
357        }
358        for (ConceptDefinitionDesignationComponent cd : designations) {
359          if (LanguageUtils.langsMatch(cd.getLanguage(), lang.getLang())) {
360            return cd;
361          }
362        }
363      }
364    }
365    return null;
366  }
367
368  private boolean isDisplay(ConceptDefinitionDesignationComponent cd) {
369    return cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display");
370  }
371
372  private boolean filterContainsCode(List<ValueSet> filters, String system, String code, ValueSetExpansionComponent exp) {
373    for (ValueSet vse : filters) {
374      checkCanonical(exp, vse, focus);
375      if (expansionContainsCode(vse.getExpansion().getContains(), system, code))
376        return true;
377    }
378    return false;
379  }
380
381  private boolean expansionContainsCode(List<ValueSetExpansionContainsComponent> contains, String system, String code) {
382    for (ValueSetExpansionContainsComponent cc : contains) {
383      if (system.equals(cc.getSystem()) && code.equals(cc.getCode()))
384        return true;
385      if (expansionContainsCode(cc.getContains(), system, code))
386        return true;
387    }
388    return false;
389  }
390
391  private ConceptDefinitionDesignationComponent getMatchingLang(List<ConceptDefinitionDesignationComponent> list, AcceptLanguageHeader langs) {
392    for (ConceptDefinitionDesignationComponent t : list) {
393      if (LanguageUtils.langsMatchExact(langs, t.getLanguage())) {
394        return t;
395      }
396    }
397    for (ConceptDefinitionDesignationComponent t : list) {
398      if (LanguageUtils.langsMatch(langs, t.getLanguage())) {
399        return t;
400      }
401    }
402    return null;
403  }
404
405  private void addCodeAndDescendents(WorkingContext wc, ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp)  throws FHIRException, ETooCostly {
406    opContext.deadCheck();
407    focus.checkNoModifiers("Expansion.contains", "expanding");
408    ValueSetExpansionContainsComponent np = null;
409    for (String code : getCodesForConcept(focus, expParams)) {
410      ValueSetExpansionContainsComponent t = addCode(wc, focus.getSystem(), code, focus.getDisplay(), vsSrc.getLanguage(), parent, 
411           convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), filters, noInactive, false, vsProps, makeCSProps(focus.getExtensionString(ToolingExtensions.EXT_DEFINITION), null), focus.getProperty(), null, focus.getExtension(), exp);
412      if (np == null) {
413        np = t;
414      }
415    }
416    for (ValueSetExpansionContainsComponent c : focus.getContains())
417      addCodeAndDescendents(wc, c, np, expParams, filters, noInactive, vsProps, vsSrc, exp);
418  }
419  
420  private List<ConceptPropertyComponent> makeCSProps(String definition, List<ConceptPropertyComponent> list) {
421    List<ConceptPropertyComponent> res = new ArrayList<>();
422    if (!Utilities.noString(definition)) {
423      res.add(new ConceptPropertyComponent("definition", new StringType(definition)));
424    }
425    if (list != null) {
426      res.addAll(list);
427    }
428    return res;
429  }
430
431  private List<String> getCodesForConcept(ValueSetExpansionContainsComponent focus, Parameters expParams) {
432    List<String> codes = new ArrayList<>();
433    codes.add(focus.getCode());
434    for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p : focus.getProperty()) {
435      if ("alternateCode".equals(p.getCode()) && (altCodeParams.passes(p.getExtension())) && p.getValue().isPrimitive()) {
436        codes.add(p.getValue().primitiveValue());        
437      }
438    }
439    return codes;
440  }
441
442  private List<ConceptDefinitionDesignationComponent> convert(List<ConceptReferenceDesignationComponent> designations) {
443    List<ConceptDefinitionDesignationComponent> list = new ArrayList<ConceptDefinitionDesignationComponent>();
444    for (ConceptReferenceDesignationComponent d : designations) {
445      ConceptDefinitionDesignationComponent n = new ConceptDefinitionDesignationComponent();
446      n.setLanguage(d.getLanguage());
447      n.setUse(d.getUse());
448      n.setValue(d.getValue());
449      list.add(n);
450    }
451    return list;
452  }
453
454  private void addCodeAndDescendents(WorkingContext wc, CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, 
455        ConceptDefinitionComponent exclusion, ConceptFilter filterFunc, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, List<WorkingContext> otherFilters, ValueSetExpansionComponent exp)  throws FHIRException, ETooCostly {
456    opContext.deadCheck();
457    def.checkNoModifiers("Code in Code System", "expanding");
458    if (exclusion != null) {
459      if (exclusion.getCode().equals(def.getCode()))
460        return; // excluded.
461    }
462    ValueSetExpansionContainsComponent np = null;
463    boolean abs = CodeSystemUtilities.isNotSelectable(cs, def);
464    boolean inc = CodeSystemUtilities.isInactive(cs, def);
465    boolean dep = CodeSystemUtilities.isDeprecated(cs, def, false);
466    if ((includeAbstract || !abs)  && filterFunc.includeConcept(cs, def) && passesOtherFilters(otherFilters, cs, def.getCode())) {
467      for (String code : getCodesForConcept(def, expParams)) {
468        ValueSetExpansionContainsComponent t = addCode(wc, system, code, def.getDisplay(), cs.getLanguage(), parent, def.getDesignation(), expParams, abs, inc, filters, noInactive, dep, vsProps, makeCSProps(def.getDefinition(), def.getProperty()), null, def.getExtension(), null, exp);
469        if (np == null) {
470          np = t;
471        }
472      }
473    }
474    for (ConceptDefinitionComponent c : def.getConcept()) {
475      addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters, exp);
476    }
477    if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) {
478      List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK);
479      for (ConceptDefinitionComponent c : children)
480        addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters, exp);
481    }
482  }
483
484
485  private List<String> getCodesForConcept(ConceptDefinitionComponent focus, Parameters expParams) {
486    List<String> codes = new ArrayList<>();
487    codes.add(focus.getCode());
488    for (ConceptPropertyComponent p : focus.getProperty()) {
489      if ("alternateCode".equals(p.getCode()) && (altCodeParams.passes(p.getExtension())) && p.getValue().isPrimitive()) {
490        codes.add(p.getValue().primitiveValue());        
491      }
492    }
493    return codes;
494  }
495
496  private static boolean hasUse(ConceptPropertyComponent p, List<String> uses) {
497    for (Extension ext : p.getExtensionsByUrl(ToolingExtensions.EXT_CS_ALTERNATE_USE)) {
498      if (ext.hasValueCoding() && Utilities.existsInList(ext.getValueCoding().getCode(), uses)) {
499        return true;
500      }
501    }
502    return false;
503  }
504
505  
506
507  private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws ETooCostly, FHIRException {
508    if (expand != null) {
509      if (expand.getContains().size() > maxExpansionSize)
510        throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, vsSrc.getUrl(), ">" + Integer.toString(expand.getContains().size())));
511      for (ValueSetExpansionParameterComponent p : expand.getParameter()) {
512        if (!existsInParams(params, p.getName(), p.getValue()))
513          params.add(p);
514      }
515
516      copyImportContains(expand.getContains(), null, expParams, filters, noInactive, vsProps, vsSrc, exp);
517    }
518  }
519
520  private void excludeCode(WorkingContext wc, String theSystem, String theCode) {
521    ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
522    n.setSystem(theSystem);
523    n.setCode(theCode);
524    String s = key(n);
525    wc.getExcludeKeys().add(s);
526  }
527
528  private void excludeCodes(WorkingContext wc, ConceptSetComponent exc, List<ValueSetExpansionParameterComponent> params, String ctxt) throws FHIRException {
529    opContext.deadCheck();
530    exc.checkNoModifiers("Compose.exclude", "expanding");
531    if (exc.hasSystem() && exc.getConcept().size() == 0 && exc.getFilter().size() == 0) {
532      wc.getExcludeSystems().add(exc.getSystem());
533    }
534
535    if (exc.hasValueSet())
536      throw fail("Processing Value set references in exclude is not yet done in "+ctxt);
537    // importValueSet(imp.getValue(), params, expParams);
538
539    CodeSystem cs = context.fetchSupplementedCodeSystem(exc.getSystem());
540    if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem())) {
541      ValueSetExpansionOutcome vse = context.expandVS(exc, false, false);
542      ValueSet valueset = vse.getValueset();
543      if (valueset == null)
544        throw failTSE("Error Expanding ValueSet: "+vse.getError());
545      excludeCodes(wc, valueset.getExpansion(), params);
546      return;
547    }
548
549    for (ConceptReferenceComponent c : exc.getConcept()) {
550      excludeCode(wc, exc.getSystem(), c.getCode());
551    }
552
553    if (exc.getFilter().size() > 0)
554      throw fail("not done yet - multiple filters");
555  }
556
557  private void excludeCodes(WorkingContext wc, ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params) {
558    opContext.deadCheck();
559    for (ValueSetExpansionContainsComponent c : expand.getContains()) {
560      excludeCode(wc, c.getSystem(), c.getCode());
561    }
562  }
563
564  private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, DataType value) {
565    for (ValueSetExpansionParameterComponent p : params) {
566      if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false)) {
567        return true;
568      }
569    }
570    return false;
571  }
572
573  public ValueSetExpansionOutcome expand(ValueSet source, Parameters expParams) {
574    
575    allErrors.clear();
576    try {
577      opContext.seeContext(source.getVersionedUrl());
578      
579      return expandInternal(source, expParams);
580    } catch (NoTerminologyServiceException e) {
581      // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
582      // that might fail too, but it might not, later.
583      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.NOSERVICE, allErrors);
584    } catch (CodeSystemProviderExtension e) {
585      // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
586      // that might fail too, but it might not, later.
587      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.INTERNAL_ERROR, allErrors);
588    } catch (TerminologyServiceProtectionException e) {
589      if (opContext.isOriginal()) {
590        return new ValueSetExpansionOutcome(e.getMessage(), e.getError(), allErrors);
591      } else {
592        throw e;
593      }
594    } catch (ETooCostly e) {
595      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.TOO_COSTLY, allErrors);
596    } catch (Exception e) {
597      // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
598      // that might fail too, but it might not, later.
599      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, allErrors);
600    }
601  }
602  
603  public ValueSetExpansionOutcome expandInternal(ValueSet source, Parameters expParams) throws FHIRException, FileNotFoundException, ETooCostly, IOException, CodeSystemProviderExtension {
604      return doExpand(source, expParams);
605  }
606
607  private void processParameter(String name, DataType value) {
608    if (Utilities.existsInList(name, "includeDesignations", "excludeNested", "activeOnly", "offset", "count")) {
609      focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
610      focus.getExpansion().addParameter().setName(name).setValue(value);
611    }
612    if ("displayLanguage".equals(name)) {
613      this.langs = new AcceptLanguageHeader(value.primitiveValue(), true);
614      focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
615      focus.getExpansion().addParameter().setName(name).setValue(new CodeType(value.primitiveValue()));
616    }
617    if ("designation".equals(name)) {
618      String[] v = value.primitiveValue().split("\\|");
619      if (v.length != 2 || !Utilities.isAbsoluteUrl(v[0]) || Utilities.noString(v[1])) {
620        throw new NoTerminologyServiceException("Unable to understand designation parameter "+value.primitiveValue());
621      }
622      this.designations.add(new Token(v[0], v[1]));
623      focus.getExpansion().addParameter().setName(name).setValue(new StringType(value.primitiveValue()));
624    }
625    if ("offset".equals(name) && value instanceof IntegerType) {
626      focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
627      focus.getExpansion().addParameter().setName(name).setValue(value);
628      dwc.setOffset(((IntegerType) value).getValue());
629      if (dwc.getOffset() < 0) {
630        dwc.setOffset(0);
631      }
632    }
633    if ("count".equals(name)) {
634      focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
635      focus.getExpansion().addParameter().setName(name).setValue(value);
636      dwc.setCount(((IntegerType) value).getValue());
637      if (dwc.getCount() < 0) {
638        dwc.setCount(0);
639      }
640    }
641  }
642  
643  public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams) throws FHIRException, ETooCostly, FileNotFoundException, IOException, CodeSystemProviderExtension {
644    if (expParams == null)
645      expParams = makeDefaultExpansion();
646    altCodeParams.seeParameters(expParams);
647    altCodeParams.seeValueSet(source);
648    
649    source.checkNoModifiers("ValueSet", "expanding");
650    focus = source.copy();
651    focus.setIdBase(null);
652    focus.setExpansion(new ValueSet.ValueSetExpansionComponent());
653    focus.getExpansion().setTimestampElement(DateTimeType.now());
654    focus.getExpansion().setIdentifier(Factory.createUUID()); 
655    checkCanonical(focus.getExpansion(), focus, focus);
656    for (Extension ext : focus.getCompose().getExtensionsByUrl("http://hl7.org/fhir/tools/StructureDefinion/valueset-expansion-param")) {
657      processParameter(ext.getExtensionString("name"), ext.getExtensionByUrl("value").getValue());
658    }
659    for (ParametersParameterComponent p : expParams.getParameter()) {
660      processParameter(p.getName(), p.getValue());
661    }
662    for (Extension s : focus.getExtensionsByUrl(ExtensionConstants.EXT_VSSUPPLEMENT)) {
663      requiredSupplements.add(s.getValue().primitiveValue());
664    }
665    if (langs == null && focus.hasLanguage()) {
666      langs = new AcceptLanguageHeader(focus.getLanguage(), true);
667    } else if (langs != null && langs.hasChosen()) {
668      focus.setLanguage(langs.getChosen());
669    }
670
671    try {
672      if (source.hasCompose()) {
673//        ExtensionsUtils.stripExtensions(focus.getCompose()); - disabled 23/05/2023 GDG - why was this ever thought to be a good idea?
674        handleCompose(source.getCompose(), focus.getExpansion(), expParams, source.getUrl(), focus.getExpansion().getExtension(), source);
675      }
676    } catch (EFinished e) {
677      // nothing - we intended to trap this here
678    }
679
680    if (dwc.isCanBeHeirarchy()) {
681      for (ValueSetExpansionContainsComponent c : dwc.getRoots()) {
682        focus.getExpansion().getContains().add(c);
683      }
684    } else {
685      int i = 0;
686      int cc = 0;
687      for (ValueSetExpansionContainsComponent c : dwc.getCodes()) {
688        if (dwc.getMap().containsKey(key(c)) && (includeAbstract || !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
689          if (dwc.getOffset() == 0 || i >= dwc.getOffset()) {
690            focus.getExpansion().getContains().add(c);
691            c.getContains().clear(); // make sure any heirarchy is wiped
692            cc++;
693            if (cc == dwc.getCount()) {
694              break;
695            }
696          }
697          i++;
698        }
699      }
700    }
701
702    if (dwc.getTotal() >= 0) {
703      focus.getExpansion().setTotal(dwc.getTotal());
704    }
705    if (!requiredSupplements.isEmpty()) {      
706      return new ValueSetExpansionOutcome(context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements)), TerminologyServiceErrorClass.BUSINESS_RULE, allErrors);
707    }
708    if (!expParams.hasParameter("includeDefinition") || !expParams.getParameterBool("includeDefinition")) {
709      focus.setCompose(null);
710      focus.getExtension().clear();
711      focus.setPublisher(null);
712      focus.setDescription(null);
713      focus.setPurpose(null);
714      focus.getContact().clear();
715      focus.setCopyright(null);
716      focus.setText(null);
717    }
718    return new ValueSetExpansionOutcome(focus);
719  }
720
721
722  private Parameters makeDefaultExpansion() {
723    Parameters res = new Parameters();
724    res.addParameter("excludeNested", true);
725    res.addParameter("includeDesignations", false);
726    return res;
727  }
728
729  private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) {
730    for (ConceptDefinitionComponent c : clist) {
731      if (code.equals(c.getCode()))
732        return c;
733      ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code);
734      if (v != null)
735        return v;
736    }
737    return null;
738  }
739
740  private void handleCompose(ValueSetComposeComponent compose, ValueSetExpansionComponent exp, Parameters expParams, String ctxt, List<Extension> extensions, ValueSet valueSet)
741      throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension {
742    compose.checkNoModifiers("ValueSet.compose", "expanding");
743    // Exclude comes first because we build up a map of things to exclude
744    for (ConceptSetComponent inc : compose.getExclude())
745      excludeCodes(dwc, inc, exp.getParameter(), ctxt);
746    dwc.setCanBeHeirarchy(!expParams.getParameterBool("excludeNested") && dwc.getExcludeKeys().isEmpty() && dwc.getExcludeSystems().isEmpty() && dwc.getOffset()+dwc.getCount() == 0);
747    includeAbstract = !expParams.getParameterBool("excludeNotForUI");
748    boolean first = true;
749    for (ConceptSetComponent inc : compose.getInclude()) {
750      if (first == true)
751        first = false;
752      else
753        dwc.setCanBeHeirarchy(false);
754      includeCodes(inc, exp, expParams, dwc.isCanBeHeirarchy(), compose.hasInactive() ? !compose.getInactive() : checkNoInActiveFromParam(expParams), extensions, valueSet);
755    }
756  }
757
758  /**
759   * returns true if activeOnly = true 
760   * @param expParams
761   * @return
762   */
763  private boolean checkNoInActiveFromParam(Parameters expParams) {
764    for (ParametersParameterComponent p : expParams.getParameter()) {
765      if (p.getName().equals("activeOnly")) {
766        return p.getValueBooleanType().getValue();
767      }
768    }
769    return false;
770  }
771
772  private ValueSet importValueSet(WorkingContext wc, String value, ValueSetExpansionComponent exp, Parameters expParams, boolean noInactive, ValueSet valueSet) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError {
773    if (value == null)
774      throw fail("unable to find value set with no identity");
775    ValueSet vs = context.fetchResource(ValueSet.class, value, valueSet);
776    if (vs == null) {
777      if (context.fetchResource(CodeSystem.class, value, valueSet) != null) {
778        throw fail("Cannot include value set "+value+" because it's actually a code system");
779      } else {
780        throw fail("Unable to find imported value set " + value);
781      }
782    }
783    checkCanonical(exp, vs, focus);
784    if (noInactive) {
785      expParams = expParams.copy();
786      expParams.addParameter("activeOnly", true);
787    }
788    ValueSetExpansionOutcome vso = new ValueSetExpander(context, opContext.copy(), allErrors).expand(vs, expParams);
789    if (vso.getError() != null) {
790      addErrors(vso.getAllErrors());
791      throw fail("Unable to expand imported value set "+vs.getUrl()+": " + vso.getError());
792    }
793    if (vs.hasVersion() || REPORT_VERSION_ANYWAY) {
794      UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : ""));
795      if (!existsInParams(exp.getParameter(), "used-valueset", u))
796        exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u));
797    }
798    for (Extension ex : vso.getValueset().getExpansion().getExtension()) {
799      if (ex.getUrl().equals(ToolingExtensions.EXT_EXP_TOOCOSTLY)) {
800        if (ex.getValue() instanceof BooleanType) {
801          exp.getExtension().add(new Extension(ToolingExtensions.EXT_EXP_TOOCOSTLY).setValue(new CanonicalType(value)));
802        } else {
803          exp.getExtension().add(ex);
804        }
805      } 
806    }
807    for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) {
808      if (!existsInParams(exp.getParameter(), p.getName(), p.getValue()))
809        exp.getParameter().add(p);
810    }
811    if (isValueSetUnionImports(valueSet)) {
812      copyExpansion(wc, vso.getValueset().getExpansion().getContains());
813    }
814    wc.setCanBeHeirarchy(false); // if we're importing a value set, we have to be combining, so we won't try for a heirarchy
815    return vso.getValueset();
816  }
817  
818
819  protected boolean isValueSetUnionImports(ValueSet valueSet) {
820    PackageInformation p = valueSet.getSourcePackage();
821    if (p != null) {
822      return p.getDate().before(new GregorianCalendar(2022, Calendar.MARCH, 31).getTime());
823    } else {
824      return false;
825    }
826  }
827
828  public void copyExpansion(WorkingContext wc,List<ValueSetExpansionContainsComponent> list) {
829    opContext.deadCheck();
830    for (ValueSetExpansionContainsComponent cc : list) {
831       ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
832       n.setSystem(cc.getSystem());
833       n.setCode(cc.getCode());
834       n.setAbstract(cc.getAbstract());
835       n.setInactive(cc.getInactive());
836       n.setDisplay(cc.getDisplay());
837       n.getDesignation().addAll(cc.getDesignation());
838
839       String s = key(n);
840       if (!wc.getMap().containsKey(s) && !wc.getExcludeKeys().contains(s)) {
841         wc.getCodes().add(n);
842         wc.getMap().put(s, n);
843         wc.incTotal();
844       }
845       copyExpansion(wc, cc.getContains());
846    }
847  }
848
849  private void addErrors(List<String> errs) {
850    for (String s : errs) {
851      if (!allErrors.contains(s)) {
852        allErrors.add(s);
853      }
854    }
855  }
856
857  private void copyImportContains(List<ValueSetExpansionContainsComponent> list, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filter, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly {
858    opContext.deadCheck();
859    for (ValueSetExpansionContainsComponent c : list) {
860      c.checkNoModifiers("Imported Expansion in Code System", "expanding");
861      ValueSetExpansionContainsComponent np = addCode(dwc, c.getSystem(), c.getCode(), c.getDisplay(), vsSrc.getLanguage(), parent, null, expParams, c.getAbstract(), c.getInactive(), 
862          filter, noInactive, false, vsProps, makeCSProps(c.getExtensionString(ToolingExtensions.EXT_DEFINITION), null), c.getProperty(), null, c.getExtension(), exp);
863      copyImportContains(c.getContains(), np, expParams, filter, noInactive, vsProps, vsSrc, exp);
864    }
865  }
866
867  private void includeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, boolean heirarchical, boolean noInactive, List<Extension> extensions, ValueSet valueSet) throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension {
868    opContext.deadCheck();
869    inc.checkNoModifiers("Compose.include", "expanding");
870    List<ValueSet> imports = new ArrayList<ValueSet>();
871    for (CanonicalType imp : inc.getValueSet()) {
872      imports.add(importValueSet(dwc, imp.getValue(), exp, expParams, noInactive, valueSet));
873    }
874
875    if (!inc.hasSystem()) {
876      if (imports.isEmpty()) // though this is not supposed to be the case
877        return;
878      ValueSet base = imports.get(0);
879      checkCanonical(exp, base, focus);
880      imports.remove(0);
881      base.checkNoModifiers("Imported ValueSet", "expanding");
882      copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive, base.getExpansion().getProperty(), base, exp);
883    } else {
884      CodeSystem cs = context.fetchSupplementedCodeSystem(inc.getSystem());
885      if (ValueSetUtilities.isServerSide(inc.getSystem()) || (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT))) {
886        doServerIncludeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, valueSet.getExpansion().getProperty());
887      } else {
888        if (cs.hasUserData("supplements.installed")) {
889          for (String s : cs.getUserString("supplements.installed").split("\\,")) {
890            requiredSupplements.remove(s);
891          }
892        }
893        doInternalIncludeCodes(inc, exp, expParams, imports, cs, noInactive, valueSet);
894      }
895    }
896  }
897
898  private void doServerIncludeCodes(ConceptSetComponent inc, boolean heirarchical, ValueSetExpansionComponent exp, List<ValueSet> imports, Parameters expParams, List<Extension> extensions, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps) throws FHIRException, CodeSystemProviderExtension, ETooCostly {
899    opContext.deadCheck();
900    CodeSystemProvider csp = CodeSystemProvider.factory(inc.getSystem());
901    if (csp != null) {
902      csp.includeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, vsProps);
903      return;
904    }
905    
906    ValueSetExpansionOutcome vso = context.expandVS(inc, heirarchical, noInactive);
907    if (vso.getError() != null) {
908      throw failTSE("Unable to expand imported value set: " + vso.getError());
909    }
910    ValueSet vs = vso.getValueset();
911    if (vs.hasVersion() || REPORT_VERSION_ANYWAY) {
912      UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : ""));
913      if (!existsInParams(exp.getParameter(), "used-valueset", u)) {
914        exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u));
915      }
916    }
917    for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) {
918      if (!existsInParams(exp.getParameter(), p.getName(), p.getValue())) {
919        exp.getParameter().add(p);
920      }
921    }
922    for (Extension ex : vs.getExpansion().getExtension()) {
923      if (Utilities.existsInList(ex.getUrl(), ToolingExtensions.EXT_EXP_TOOCOSTLY, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed")) {
924        if (!ExtensionsUtils.hasExtension(extensions, ex.getUrl())) {
925          extensions.add(ex);
926        }
927      }
928    }
929    for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) {
930      addCodeAndDescendents(dwc, cc, null, expParams, imports, noInactive, vsProps, vs, exp);
931    }
932  }
933
934
935  public void doInternalIncludeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List<ValueSet> imports, CodeSystem cs, boolean noInactive, Resource vsSrc) throws NoTerminologyServiceException, TerminologyServiceException, FHIRException, ETooCostly {
936    opContext.deadCheck();
937    if (cs == null) {
938      if (context.isNoTerminologyServer())
939        throw failTSE("Unable to find code system " + inc.getSystem().toString());
940      else
941        throw failTSE("Unable to find code system " + inc.getSystem().toString());
942    }
943    checkCanonical(exp, cs, focus);
944    cs.checkNoModifiers("Code System", "expanding");
945    if (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT)
946      throw failTSE("Code system " + inc.getSystem().toString() + " is incomplete");
947    if (cs.hasVersion() || REPORT_VERSION_ANYWAY) {
948      UriType u = new UriType(cs.getUrl() + (cs.hasVersion() ? "|"+cs.getVersion() : ""));
949      if (!existsInParams(exp.getParameter(), "used-codesystem", u))
950        exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-codesystem").setValue(u));
951      if (cs.hasUserData("supplements.installed")) {
952        for (String s : cs.getUserString("supplements.installed").split("\\,")) {
953          u = new UriType(s);
954          if (!existsInParams(exp.getParameter(), "used-supplement", u)) {
955            exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-supplement").setValue(u));
956          }
957        }
958      }
959    }
960    if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
961      // special case - add all the code system
962      for (ConceptDefinitionComponent def : cs.getConcept()) {
963        addCodeAndDescendents(dwc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), null, exp);
964      }
965      if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
966        addFragmentWarning(exp, cs);
967      }
968      if (cs.getContent() == CodeSystemContentMode.EXAMPLE) {
969        addExampleWarning(exp, cs);
970      }      
971    }
972
973    if (!inc.getConcept().isEmpty()) {
974      dwc.setCanBeHeirarchy(false);
975      for (ConceptReferenceComponent c : inc.getConcept()) {
976        c.checkNoModifiers("Code in Value Set", "expanding");
977        ConceptDefinitionComponent def = CodeSystemUtilities.findCodeOrAltCode(cs.getConcept(), c.getCode(), null);
978        boolean inactive = false; // default is true if we're a fragment and  
979        boolean isAbstract = false;
980        if (def == null) {
981          if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
982            addFragmentWarning(exp, cs);
983          } else if (cs.getContent() == CodeSystemContentMode.EXAMPLE) {
984              addExampleWarning(exp, cs);
985          } else {
986            if (checkCodesWhenExpanding) {
987              throw failTSE("Unable to find code '" + c.getCode() + "' in code system " + cs.getUrl());
988            }
989          }
990        } else {
991          def.checkNoModifiers("Code in Code System", "expanding");
992          inactive = CodeSystemUtilities.isInactive(cs, def);
993          isAbstract = CodeSystemUtilities.isNotSelectable(cs, def);
994          addCode(dwc, inc.getSystem(), c.getCode(), !Utilities.noString(c.getDisplay()) ? c.getDisplay() : def.getDisplay(), c.hasDisplay() ? vsSrc.getLanguage() : cs.getLanguage(), null, mergeDesignations(def, convertDesignations(c.getDesignation())), 
995              expParams, isAbstract, inactive, imports, noInactive, false, exp.getProperty(), makeCSProps(def.getDefinition(), def.getProperty()), null, def.getExtension(), c.getExtension(), exp);
996        }
997      }
998    }
999    if (inc.getFilter().size() > 0) {
1000      if (inc.getFilter().size() > 1) {
1001        dwc.setCanBeHeirarchy(false); // which will be the case if we get around to supporting this
1002      }
1003      if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
1004        addFragmentWarning(exp, cs);
1005      }
1006      List<WorkingContext> filters = new ArrayList<>();
1007      for (int i = 1; i < inc.getFilter().size(); i++) {
1008        WorkingContext wc = new WorkingContext();
1009        filters.add(wc);
1010        processFilter(inc, exp, expParams, imports, cs, noInactive, inc.getFilter().get(i), wc, null);
1011      }
1012      ConceptSetFilterComponent fc = inc.getFilter().get(0);
1013      WorkingContext wc = dwc;
1014      processFilter(inc, exp, expParams, imports, cs, noInactive, fc, wc, filters);
1015    }
1016  }
1017
1018  private void processFilter(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams,
1019      List<ValueSet> imports, CodeSystem cs, boolean noInactive, ConceptSetFilterComponent fc, WorkingContext wc, List<WorkingContext> filters)
1020      throws ETooCostly {
1021    opContext.deadCheck();
1022    if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) {
1023      // special: all codes in the target code system under the value
1024      ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
1025      if (def == null)
1026        throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
1027      addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
1028    } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISNOTA) {
1029      // special: all codes in the target code system that are not under the value
1030      ConceptDefinitionComponent defEx = getConceptForCode(cs.getConcept(), fc.getValue());
1031      if (defEx == null)
1032        throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
1033      for (ConceptDefinitionComponent def : cs.getConcept()) {
1034        addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, defEx, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
1035      }
1036    } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.DESCENDENTOF) {
1037      // special: all codes in the target code system under the value
1038      ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
1039      if (def == null)
1040        throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
1041      for (ConceptDefinitionComponent c : def.getConcept())
1042        addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
1043      if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) {
1044        List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK);
1045        for (ConceptDefinitionComponent c : children)
1046          addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
1047      }
1048
1049    } else if ("display".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) {
1050      // gg; note: wtf is this: if the filter is display=v, look up the code 'v', and see if it's display is 'v'?
1051      dwc.setCanBeHeirarchy(false);
1052      ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
1053      if (def != null) {
1054        if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) {
1055          if (def.getDisplay().contains(fc.getValue()) && passesOtherFilters(filters, cs, def.getCode())) {
1056            for (String code : getCodesForConcept(def, expParams)) {
1057              opContext.deadCheck();
1058              ValueSetExpansionContainsComponent t = addCode(wc, inc.getSystem(), code, def.getDisplay(), cs.getLanguage(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def),
1059                  imports, noInactive, false, exp.getProperty(), makeCSProps(def.getDefinition(), def.getProperty()), null, def.getExtension(), null, exp);
1060            }
1061          }
1062        }
1063      }
1064    } else if (isDefinedProperty(cs, fc.getProperty())) {
1065      for (ConceptDefinitionComponent def : cs.getConcept()) {
1066        addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new PropertyFilter(allErrors, fc, getPropertyDefinition(cs, fc.getProperty())), noInactive, exp.getProperty(), filters, exp);
1067      }
1068    } else if ("code".equals(fc.getProperty()) && fc.getOp() == FilterOperator.REGEX) {
1069      for (ConceptDefinitionComponent def : cs.getConcept()) {
1070        addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new RegexFilter(allErrors, fc.getValue()), noInactive, exp.getProperty(), filters, exp);
1071      }
1072    } else {
1073      throw fail("Filter by property[" + fc.getProperty() + "] and op[" + fc.getOp() + "] is not supported yet");
1074    }
1075  }
1076
1077  private List<ConceptDefinitionDesignationComponent> mergeDesignations(ConceptDefinitionComponent def,
1078      List<ConceptDefinitionDesignationComponent> list) {
1079    List<ConceptDefinitionDesignationComponent> res = new ArrayList<>();
1080    if (def != null) {
1081      res.addAll(def.getDesignation());
1082    }
1083    res.addAll(list);
1084    return res;
1085  }
1086
1087  private PropertyComponent getPropertyDefinition(CodeSystem cs, String property) {
1088    for (PropertyComponent cp : cs.getProperty()) {
1089      if (cp.getCode().equals(property)) {
1090        return cp;
1091      }
1092    }
1093    return null;
1094  }
1095
1096  private boolean isDefinedProperty(CodeSystem cs, String property) {
1097    for (PropertyComponent cp : cs.getProperty()) {
1098      if (cp.getCode().equals(property)) {
1099        return true;
1100      }
1101    }
1102    return false;
1103  }
1104
1105  private void addFragmentWarning(ValueSetExpansionComponent exp, CodeSystem cs) {
1106    String url = cs.getVersionedUrl();
1107    for (ValueSetExpansionParameterComponent p : exp.getParameter()) {
1108      if ("fragment".equals(p.getName()) && p.hasValueUriType() && url.equals(p.getValue().primitiveValue())) { 
1109        return;
1110      }     
1111    }
1112    exp.addParameter().setName("fragment").setValue(new CanonicalType(url));
1113  }
1114
1115  private void addExampleWarning(ValueSetExpansionComponent exp, CodeSystem cs) {
1116    String url = cs.getVersionedUrl();
1117    for (ValueSetExpansionParameterComponent p : exp.getParameter()) {
1118      if ("example".equals(p.getName()) && p.hasValueUriType() && url.equals(p.getValue().primitiveValue())) { 
1119        return;
1120      }     
1121    }
1122    exp.addParameter().setName("example").setValue(new CanonicalType(url));
1123  }
1124  
1125  private List<ConceptDefinitionDesignationComponent> convertDesignations(List<ConceptReferenceDesignationComponent> list) {
1126    List<ConceptDefinitionDesignationComponent> res = new ArrayList<CodeSystem.ConceptDefinitionDesignationComponent>();
1127    for (ConceptReferenceDesignationComponent t : list) {
1128      ConceptDefinitionDesignationComponent c = new ConceptDefinitionDesignationComponent();
1129      c.setLanguage(t.getLanguage());
1130      c.setUse(t.getUse());
1131      c.setValue(t.getValue());
1132      c.getExtension().addAll(t.getExtension());
1133      res.add(c);
1134    }
1135    return res;
1136  }
1137
1138  private String key(String uri, String code) {
1139    return "{" + uri + "}" + code;
1140  }
1141
1142  private String key(ValueSetExpansionContainsComponent c) {
1143    return key(c.getSystem(), c.getCode());
1144  }
1145
1146  private FHIRException fail(String msg) {
1147    allErrors.add(msg);
1148    return new FHIRException(msg);
1149  }
1150
1151  private ETooCostly failCostly(String msg) {
1152    allErrors.add(msg);
1153    return new ETooCostly(msg);
1154  }
1155
1156  private TerminologyServiceException failTSE(String msg) {
1157    allErrors.add(msg);
1158    return new TerminologyServiceException(msg);
1159  }
1160
1161  public Collection<? extends String> getAllErrors() {
1162    return allErrors;
1163  }
1164
1165  public boolean isCheckCodesWhenExpanding() {
1166    return checkCodesWhenExpanding;
1167  }
1168
1169  public void setCheckCodesWhenExpanding(boolean checkCodesWhenExpanding) {
1170    this.checkCodesWhenExpanding = checkCodesWhenExpanding;
1171  }
1172
1173  private boolean passesOtherFilters(List<WorkingContext> otherFilters, CodeSystem cs, String code) {
1174    if (otherFilters == null) {
1175      return true;
1176    }
1177    String key = key(cs.getUrl(), code);
1178    for (WorkingContext wc : otherFilters) {
1179      if (!wc.getMap().containsKey(key)) {
1180        return false;
1181      }
1182    }
1183    return true;
1184  }
1185}