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;
038import java.text.MessageFormat;
039
040/*
041 * Copyright (c) 2011+, HL7, Inc
042 * All rights reserved.
043 * 
044 * Redistribution and use in source and binary forms, with or without modification,
045 * are permitted provided that the following conditions are met:
046 * 
047 * Redistributions of source code must retain the above copyright notice, this
048 * list of conditions and the following disclaimer.
049 * Redistributions in binary form must reproduce the above copyright notice,
050 * this list of conditions and the following disclaimer in the documentation
051 * and/or other materials provided with the distribution.
052 * Neither the name of HL7 nor the names of its contributors may be used to
053 * endorse or promote products derived from this software without specific
054 * prior written permission.
055 * 
056 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
057 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
058 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
059 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
060 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
061 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
062 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
063 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
064 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
065 * POSSIBILITY OF SUCH DAMAGE.
066 * 
067 */
068
069import java.util.ArrayList;
070import java.util.Calendar;
071import java.util.Collection;
072import java.util.GregorianCalendar;
073import java.util.HashSet;
074import java.util.List;
075import java.util.Set;
076
077import org.hl7.fhir.exceptions.FHIRException;
078import org.hl7.fhir.exceptions.FHIRFormatError;
079import org.hl7.fhir.exceptions.NoTerminologyServiceException;
080import org.hl7.fhir.exceptions.TerminologyServiceException;
081import org.hl7.fhir.r5.context.IWorkerContext;
082import org.hl7.fhir.r5.elementmodel.LanguageUtils;
083import org.hl7.fhir.r5.extensions.ExtensionConstants;
084import org.hl7.fhir.r5.extensions.ExtensionsUtils;
085import org.hl7.fhir.r5.model.BooleanType;
086import org.hl7.fhir.r5.model.CanonicalType;
087import org.hl7.fhir.r5.model.CodeSystem;
088import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
089import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent;
090import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
091import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
092import org.hl7.fhir.r5.model.CodeType;
093import org.hl7.fhir.r5.model.Coding;
094import org.hl7.fhir.r5.model.DataType;
095import org.hl7.fhir.r5.model.DateTimeType;
096import org.hl7.fhir.r5.model.DecimalType;
097import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
098import org.hl7.fhir.r5.model.Enumerations.FilterOperator;
099import org.hl7.fhir.r5.model.OperationOutcome.IssueType;
100import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
101import org.hl7.fhir.r5.model.Extension;
102import org.hl7.fhir.r5.model.Factory;
103import org.hl7.fhir.r5.model.IntegerType;
104import org.hl7.fhir.r5.model.PackageInformation;
105import org.hl7.fhir.r5.model.Parameters;
106import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
107import org.hl7.fhir.r5.model.PrimitiveType;
108import org.hl7.fhir.r5.model.Resource;
109import org.hl7.fhir.r5.model.StringType;
110import org.hl7.fhir.r5.model.UriType;
111import org.hl7.fhir.r5.model.ValueSet;
112import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
113import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent;
114import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
115import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent;
116import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent;
117import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
118import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
119import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent;
120import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent;
121import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
122import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
123import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpander.UnknownValueSetException;
124import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider;
125import org.hl7.fhir.r5.terminologies.providers.CodeSystemProviderExtension;
126import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext;
127import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.TerminologyServiceProtectionException;
128import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase.OpIssueCode;
129import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase.TerminologyOperationDetails;
130import org.hl7.fhir.r5.terminologies.validation.VSCheckerException;
131import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
132import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase;
133import org.hl7.fhir.r5.utils.ToolingExtensions;
134import org.hl7.fhir.r5.utils.UserDataNames;
135import org.hl7.fhir.r5.utils.client.EFhirClientException;
136import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
137import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
138import org.hl7.fhir.utilities.Utilities;
139import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader;
140import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader.LanguagePreference;
141import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
142import org.hl7.fhir.utilities.i18n.I18nConstants;
143
144@MarkedToMoveToAdjunctPackage
145public class ValueSetExpander extends ValueSetProcessBase {
146
147
148  public class UnknownValueSetException extends FHIRException {
149
150    protected UnknownValueSetException() {
151      super();
152    }
153
154    protected UnknownValueSetException(String message, Throwable cause) {
155      super(message, cause);
156    }
157    
158    protected UnknownValueSetException(String message) {
159      super(message);
160    }
161
162    protected UnknownValueSetException(Throwable cause) {
163      super(cause);
164    }
165
166  }
167
168  public class Token {
169    private String system;
170    private String code;
171    public Token(String system, String code) {
172      super();
173      this.system = system;
174      this.code = code;
175    }
176    public String getSystem() {
177      return system;
178    }
179    public String getCode() {
180      return code;
181    }
182    public boolean matches(Coding use) {
183      return system.equals(use.getSystem()) && code.equals(use.getCode());
184    }
185    public boolean matchesLang(String language) {
186      return system.equals("urn:ietf:bcp:47") && code.equals(language);
187    }
188  }
189
190  private static final boolean REPORT_VERSION_ANYWAY = true;
191
192  private static final String VS_EXP_IMPORT_ERROR_TOO_COSTLY = null;
193  
194  private ValueSet focus;
195  private List<String> allErrors = new ArrayList<>();
196  private int maxExpansionSize = 1000;
197  private WorkingContext dwc = new WorkingContext();
198  
199  private boolean checkCodesWhenExpanding;
200  private boolean includeAbstract = true;
201  private boolean debug;
202  private Set<String> sources = new HashSet<>();
203
204  private AcceptLanguageHeader langs;
205  private List<Token> designations = new ArrayList<>();
206
207  public ValueSetExpander(IWorkerContext context, TerminologyOperationContext opContext) {
208    super(context, opContext);
209  }
210
211  public ValueSetExpander(IWorkerContext context, TerminologyOperationContext opContext, List<String> allErrors) {
212    super(context, opContext);
213    this.allErrors = allErrors;
214  }
215
216  public void setMaxExpansionSize(int theMaxExpansionSize) {
217    maxExpansionSize = theMaxExpansionSize;
218  }
219  
220  private ValueSetExpansionContainsComponent addCode(WorkingContext wc, String system, String code, String display, String dispLang, ValueSetExpansionContainsComponent parent, List<ConceptDefinitionDesignationComponent> designations, Parameters expParams, 
221      boolean isAbstract, boolean inactive, List<ValueSet> filters, boolean noInactive, boolean deprecated, List<ValueSetExpansionPropertyComponent> vsProp, 
222      List<ConceptPropertyComponent> csProps, CodeSystem cs, List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> expProps, List<Extension> csExtList, List<Extension> vsExtList, ValueSetExpansionComponent exp) throws ETooCostly {
223    opContext.deadCheck("addCode"+code);
224    
225    if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code, exp))
226      return null;
227    if (noInactive && inactive) {
228      return null;
229    }
230    
231    ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
232    n.setSystem(system);
233    n.setCode(code);
234    if (isAbstract) {
235      n.setAbstract(true);
236    }
237    if (inactive) {
238      n.setInactive(true);
239    }
240    if (deprecated) {
241      ValueSetUtilities.setDeprecated(vsProp, n);
242    }
243    if (ExtensionsUtils.hasExtension(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-label")) {
244      ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#label", "label", ExtensionsUtils.getExtensionValue(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-label"));
245    }
246    if (ExtensionsUtils.hasExtension(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-label")) {
247      ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#label", "label", ExtensionsUtils.getExtensionValue(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-label"));
248    }
249    if (ExtensionsUtils.hasExtension(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-conceptOrder")) {
250      ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#order", "order", convertToDecimal(ExtensionsUtils.getExtensionValue(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-conceptOrder")));
251    }
252    if (ExtensionsUtils.hasExtension(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-conceptOrder")) {
253      ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#order", "order", convertToDecimal(ExtensionsUtils.getExtensionValue(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-conceptOrder")));
254    }
255    if (ExtensionsUtils.hasExtension(csExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight")) {
256      ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#itemWeight", "weight", ExtensionsUtils.getExtensionValue(csExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight"));
257    }
258    if (ExtensionsUtils.hasExtension(vsExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight")) {
259      ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#itemWeight", "weight", ExtensionsUtils.getExtensionValue(vsExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight"));
260    }
261    ExtensionsUtils.copyExtensions(csExtList, n.getExtension(), 
262        "http://hl7.org/fhir/StructureDefinition/coding-sctdescid", 
263        "http://hl7.org/fhir/StructureDefinition/rendering-style", 
264        "http://hl7.org/fhir/StructureDefinition/rendering-xhtml",
265        "http://hl7.org/fhir/StructureDefinition/codesystem-alternate");
266    
267    ExtensionsUtils.copyExtensions(vsExtList, n.getExtension(), 
268        "http://hl7.org/fhir/StructureDefinition/valueset-supplement", 
269        "http://hl7.org/fhir/StructureDefinition/valueset-deprecated",
270        "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition",
271        "http://hl7.org/fhir/StructureDefinition/coding-sctdescid", 
272        "http://hl7.org/fhir/StructureDefinition/rendering-style", 
273        "http://hl7.org/fhir/StructureDefinition/rendering-xhtml");
274    
275    // display and designations
276    ConceptDefinitionDesignationComponent pref = null;
277    if (langs == null) {
278      n.setDisplay(display);
279    } else {
280      if (designations == null) {
281        designations = new ArrayList<>();
282      }
283      designations.add(new ConceptDefinitionDesignationComponent().setLanguage(dispLang).setValue(display).setUse(new Coding().setSystem("http://terminology.hl7.org/CodeSystem/designation-usage").setCode("display")));
284      pref = findMatchingDesignation(designations);
285      if (pref != null) {
286        n.setDisplay(pref.getValue());
287      }
288    }
289    if (!n.hasDisplay() && display != null && langs != null && (langs.matches(dispLang) || Utilities.existsInList(langs.getSource(), "en", "en-US"))) {
290      n.setDisplay(display);      
291    }
292
293    if (expParams.getParameterBool("includeDesignations") && designations != null) {
294      
295      for (ConceptDefinitionDesignationComponent t : designations) {
296        if (t != pref && (t.hasLanguage() || t.hasUse()) && t.getValue() != null && passesDesignationFilter(t)) {
297          ConceptReferenceDesignationComponent d = n.addDesignation();
298          if (t.getLanguage() != null) {
299            d.setLanguage(t.getLanguage().trim());
300          }
301          if (t.getValue() != null) {
302            d.setValue(t.getValue().trim());
303          }
304          if (t.getUse() != null) {
305            d.setUse(t.getUse());
306          }
307          for (Extension ext : t.getExtension()) {
308            if (Utilities.existsInList(ext.getUrl(), "http://hl7.org/fhir/StructureDefinition/coding-sctdescid")) {
309              d.addExtension(ext);
310            }
311          }
312        }
313      }
314    }
315    for (ParametersParameterComponent p : expParams.getParameter()) {
316      if ("property".equals(p.getName())) {
317        if (csProps != null && p.hasValue()) {
318          for (ConceptPropertyComponent cp : csProps) {
319            if (p.getValue().primitiveValue().equals(cp.getCode())) {
320              PropertyComponent pd = cs.getProperty(cp.getCode());
321              String url = pd == null ? null : pd.getUri();
322              if (url == null) {
323                if ("definition".equals(cp.getCode())) {
324                  url = "http://hl7.org/fhir/concept-properties#definition";
325                } else {
326                  // ??
327                }
328              }
329              ValueSetUtilities.addProperty(focus, n, url, cp.getCode(), cp.getValue()).copyExtensions(cp, "http://hl7.org/fhir/StructureDefinition/alternate-code-use", "http://hl7.org/fhir/StructureDefinition/alternate-code-status");
330            }
331          }
332        }
333        if (expProps != null && p.hasValue()) {
334          for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent cp : expProps) {
335            if (p.getValue().primitiveValue().equals(cp.getCode())) {
336              String url = null;
337              for (ValueSetExpansionPropertyComponent t : vsProp) {
338                if (t.hasCode() && t.getCode().equals(cp.getCode())) {
339                  url = t.getUri();
340                }
341              }
342              if (url == null) {
343                if ("definition".equals(cp.getCode())) {
344                  url = "http://hl7.org/fhir/concept-properties#definition";
345                } else {
346                  // TODO: try looking it up from the code system
347                }
348              }
349              ValueSetUtilities.addProperty(focus, n, url, cp.getCode(), cp.getValue()).copyExtensions(cp, "http://hl7.org/fhir/StructureDefinition/alternate-code-use", "http://hl7.org/fhir/StructureDefinition/alternate-code-status");
350            }
351          }
352        }        
353      }
354    }
355
356    String s = key(n);
357    if (wc.getExcludeKeys().contains(s)) {
358      return null;
359    } else if (wc.getMap().containsKey(s)) {
360      wc.setCanBeHierarchy(false);
361    } else {
362      wc.getCodes().add(n);
363      wc.getMap().put(s, n);
364//      if (wc == dwc && wc.getTotal() > maxExpansionSize) {
365//        if (wc.getOffset()+wc.getCount() > 0 && wc.getTotal() > wc.getOffset()+wc.getCount()) {
366//          wc.setTotal(-1);
367//          throw new EFinished();
368//        }
369//        throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, focus.getUrl(), ">" + Integer.toString(maxExpansionSize)));
370//      }
371    }
372    if (wc.isCanBeHierarchy() && parent != null) {
373      parent.getContains().add(n);
374    } else if (!wc.getRootMap().containsKey(s)) {
375      wc.getRootMap().put(s, n);
376      wc.getRoots().add(n);
377    }
378    return n;
379  }
380
381  private String findParamValue(List<ValueSetExpansionParameterComponent> list, String name) {
382    for (ValueSetExpansionParameterComponent p : list) {
383      if (name.equals(p.getName())) {
384        return p.getValue().primitiveValue();
385      }
386    }
387    return null;
388  }
389
390  private DataType convertToDecimal(DataType v) {
391    if (v == null) {
392      return null;
393    } 
394    if (v instanceof DecimalType) {
395      return v;
396    } 
397    if (v instanceof IntegerType) {
398      return new DecimalType(((IntegerType) v).asStringValue());
399    }
400    return null;
401  }
402
403  private boolean passesDesignationFilter(ConceptDefinitionDesignationComponent d) {
404    if (designations.isEmpty()) {
405      return true;
406    }
407    for (Token t : designations) {
408      if ((d.hasUse() && t.matches(d.getUse())) || t.matchesLang(d.getLanguage())) {
409        return true;
410      }
411      for (Coding c : d.getAdditionalUse()) {
412        if (t.matches(c)) {
413          return true;
414        }
415      }
416    }
417    return false;
418  }
419
420  private ConceptDefinitionDesignationComponent findMatchingDesignation(List<ConceptDefinitionDesignationComponent> designations) {
421    if (langs == null) {
422      return null;
423    }
424    // we have a list of languages in priority order 
425    // we have a list of designations in no order 
426    // language exact match is preferred
427    // display is always preferred
428    
429    for (LanguagePreference lang : langs.getLangs()) {
430      if (lang.getValue() > 0) {
431        for (ConceptDefinitionDesignationComponent cd : designations) {
432          if (isDisplay(cd, false) && LanguageUtils.langsMatchExact(cd.getLanguage(), lang.getLang())) {
433            return cd;
434          }
435        }
436        for (ConceptDefinitionDesignationComponent cd : designations) {
437          if (isDisplay(cd, false) && LanguageUtils.langsMatch(lang.getLang(), cd.getLanguage())) {
438            return cd;
439          }
440        }
441        for (ConceptDefinitionDesignationComponent cd : designations) {
442          if (isDisplay(cd, false) && LanguageUtils.langsMatch(cd.getLanguage(), lang.getLang())) {
443            return cd;
444          }
445        }
446        for (ConceptDefinitionDesignationComponent cd : designations) {
447          if (isDisplay(cd, true) && LanguageUtils.langsMatchExact(cd.getLanguage(), lang.getLang())) {
448            return cd;
449          }
450        }
451        for (ConceptDefinitionDesignationComponent cd : designations) {
452          if (isDisplay(cd, true) && LanguageUtils.langsMatch(cd.getLanguage(), lang.getLang())) {
453            return cd;
454          }
455        }
456        for (ConceptDefinitionDesignationComponent cd : designations) {
457          if (isDisplay(cd, true) && LanguageUtils.langsMatch(lang.getLang(), cd.getLanguage())) {
458            return cd;
459          }
460        }
461        for (ConceptDefinitionDesignationComponent cd : designations) {
462          if (LanguageUtils.langsMatchExact(cd.getLanguage(), lang.getLang())) {
463            return cd;
464          }
465        }
466        for (ConceptDefinitionDesignationComponent cd : designations) {
467          if (LanguageUtils.langsMatch(cd.getLanguage(), lang.getLang())) {
468            return cd;
469          }
470        }
471        for (ConceptDefinitionDesignationComponent cd : designations) {
472          if (LanguageUtils.langsMatch(lang.getLang(), cd.getLanguage())) {
473            return cd;
474          }
475        }
476      }
477    }
478    return null;
479  }
480
481  private boolean isDisplay(ConceptDefinitionDesignationComponent cd, boolean def) {
482    return (def && !cd.hasUse()) || (cd.hasUse() && cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display"));
483  }
484
485  private boolean filterContainsCode(List<ValueSet> filters, String system, String code, ValueSetExpansionComponent exp) {
486    for (ValueSet vse : filters) {
487      checkCanonical(exp, vse, focus);
488      if (expansionContainsCode(vse.getExpansion().getContains(), system, code))
489        return true;
490    }
491    return false;
492  }
493
494  private boolean expansionContainsCode(List<ValueSetExpansionContainsComponent> contains, String system, String code) {
495    for (ValueSetExpansionContainsComponent cc : contains) {
496      if (system.equals(cc.getSystem()) && code.equals(cc.getCode()))
497        return true;
498      if (expansionContainsCode(cc.getContains(), system, code))
499        return true;
500    }
501    return false;
502  }
503
504  private ConceptDefinitionDesignationComponent getMatchingLang(List<ConceptDefinitionDesignationComponent> list, AcceptLanguageHeader langs) {
505    for (ConceptDefinitionDesignationComponent t : list) {
506      if (LanguageUtils.langsMatchExact(langs, t.getLanguage())) {
507        return t;
508      }
509    }
510    for (ConceptDefinitionDesignationComponent t : list) {
511      if (LanguageUtils.langsMatch(langs, t.getLanguage())) {
512        return t;
513      }
514    }
515    return null;
516  }
517
518  private void addCodeAndDescendents(WorkingContext wc, ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp, String langDisplay)  throws FHIRException, ETooCostly {
519    opContext.deadCheck("addCodeAndDescendents");
520    focus.checkNoModifiers("Expansion.contains", "expanding");
521    ValueSetExpansionContainsComponent np = null;
522    for (String code : getCodesForConcept(focus, expParams)) {
523      ValueSetExpansionContainsComponent t = addCode(wc, focus.getSystem(), code, focus.getDisplay(), langDisplay, parent, 
524           convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), filters, noInactive, false, vsProps, makeCSProps(focus.getExtensionString(ToolingExtensions.EXT_DEFINITION), null), null, focus.getProperty(), null, focus.getExtension(), exp);
525      if (np == null) {
526        np = t;
527      }
528    }
529    for (ValueSetExpansionContainsComponent c : focus.getContains())
530      addCodeAndDescendents(wc, c, np, expParams, filters, noInactive, vsProps, vsSrc, exp, langDisplay);
531  }
532  
533  private String getLang(ValueSet vsSrc) {
534    if (vsSrc.hasLanguage()) {
535      return vsSrc.getLanguage();
536    }
537    for (ValueSetExpansionParameterComponent p : vsSrc.getExpansion().getParameter()) {
538      if ("displayLanguage".equals(p.getName())) {
539        return p.getValue().primitiveValue();
540      }
541    }
542    return null;
543  }
544
545  private List<ConceptPropertyComponent> makeCSProps(String definition, List<ConceptPropertyComponent> list) {
546    List<ConceptPropertyComponent> res = new ArrayList<>();
547    if (!Utilities.noString(definition)) {
548      res.add(new ConceptPropertyComponent("definition", new StringType(definition)));
549    }
550    if (list != null) {
551      res.addAll(list);
552    }
553    return res;
554  }
555
556  private List<String> getCodesForConcept(ValueSetExpansionContainsComponent focus, Parameters expParams) {
557    List<String> codes = new ArrayList<>();
558    codes.add(focus.getCode());
559    for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p : focus.getProperty()) {
560      if ("alternateCode".equals(p.getCode()) && (altCodeParams.passes(p.getExtension())) && p.getValue().isPrimitive()) {
561        codes.add(p.getValue().primitiveValue());        
562      }
563    }
564    return codes;
565  }
566
567  private List<ConceptDefinitionDesignationComponent> convert(List<ConceptReferenceDesignationComponent> designations) {
568    List<ConceptDefinitionDesignationComponent> list = new ArrayList<ConceptDefinitionDesignationComponent>();
569    for (ConceptReferenceDesignationComponent d : designations) {
570      ConceptDefinitionDesignationComponent n = new ConceptDefinitionDesignationComponent();
571      n.setLanguage(d.getLanguage());
572      if (d.hasUse()) {
573        n.setUse(d.getUse());
574      }
575      n.setValue(d.getValue());
576      list.add(n);
577    }
578    return list;
579  }
580
581  private void addCodeAndDescendents(WorkingContext wc, CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, 
582        ConceptDefinitionComponent exclusion, ConceptFilter filterFunc, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, List<WorkingContext> otherFilters, ValueSetExpansionComponent exp)  throws FHIRException, ETooCostly {
583    opContext.deadCheck("addCodeAndDescendents");
584    def.checkNoModifiers("Code in Code System", "expanding");
585    if (exclusion != null) {
586      if (exclusion.getCode().equals(def.getCode()))
587        return; // excluded.
588    }
589    ValueSetExpansionContainsComponent np = null;
590    boolean abs = CodeSystemUtilities.isNotSelectable(cs, def);
591    boolean inc = CodeSystemUtilities.isInactive(cs, def);
592    boolean dep = CodeSystemUtilities.isDeprecated(cs, def, false);
593    if ((includeAbstract || !abs)  && filterFunc.includeConcept(cs, def) && passesOtherFilters(otherFilters, cs, def.getCode())) {
594      for (String code : getCodesForConcept(def, expParams)) {
595        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()), cs, null, def.getExtension(), null, exp);
596        if (np == null) {
597          np = t;
598        }
599      }
600    }
601    for (ConceptDefinitionComponent c : def.getConcept()) {
602      addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters, exp);
603    }
604    if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) {
605      List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK);
606      for (ConceptDefinitionComponent c : children)
607        addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters, exp);
608    }
609  }
610
611
612  private void excludeCodeAndDescendents(WorkingContext wc, CodeSystem cs, String system, ConceptDefinitionComponent def, Parameters expParams, List<ValueSet> filters, 
613      ConceptDefinitionComponent exclusion, ConceptFilter filterFunc, List<WorkingContext> otherFilters, ValueSetExpansionComponent exp)  throws FHIRException, ETooCostly {
614    opContext.deadCheck("excludeCodeAndDescendents");
615    def.checkNoModifiers("Code in Code System", "expanding");
616    if (exclusion != null) {
617      if (exclusion.getCode().equals(def.getCode()))
618        return; // excluded.
619    }
620    boolean abs = CodeSystemUtilities.isNotSelectable(cs, def);
621    if ((includeAbstract || !abs)  && filterFunc.includeConcept(cs, def) && passesOtherFilters(otherFilters, cs, def.getCode())) {
622      for (String code : getCodesForConcept(def, expParams)) {
623        if (!(filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code, exp)))
624          excludeCode(wc, system, code);
625      }
626    }
627    for (ConceptDefinitionComponent c : def.getConcept()) {
628      excludeCodeAndDescendents(wc, cs, system, c, expParams, filters, exclusion, filterFunc, otherFilters, exp);
629    }
630    if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) {
631      List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK);
632      for (ConceptDefinitionComponent c : children)
633        excludeCodeAndDescendents(wc, cs, system, c, expParams, filters, exclusion, filterFunc, otherFilters, exp);
634    }
635  }
636
637
638  private List<String> getCodesForConcept(ConceptDefinitionComponent focus, Parameters expParams) {
639    List<String> codes = new ArrayList<>();
640    codes.add(focus.getCode());
641    for (ConceptPropertyComponent p : focus.getProperty()) {
642      if ("alternateCode".equals(p.getCode()) && (altCodeParams.passes(p.getExtension())) && p.getValue().isPrimitive()) {
643        codes.add(p.getValue().primitiveValue());        
644      }
645    }
646    return codes;
647  }
648
649  private static boolean hasUse(ConceptPropertyComponent p, List<String> uses) {
650    for (Extension ext : p.getExtensionsByUrl(ToolingExtensions.EXT_CS_ALTERNATE_USE)) {
651      if (ext.hasValueCoding() && Utilities.existsInList(ext.getValueCoding().getCode(), uses)) {
652        return true;
653      }
654    }
655    return false;
656  }
657
658  
659
660  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 {
661    if (expand != null) {
662      if (expand.getContains().size() > maxExpansionSize)
663        throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, vsSrc.getUrl(), ">" + Integer.toString(expand.getContains().size())));
664      for (ValueSetExpansionParameterComponent p : expand.getParameter()) {
665        if (!existsInParams(params, p.getName(), p.getValue()))
666          params.add(p);
667      }
668      
669      copyImportContains(expand.getContains(), null, expParams, filters, noInactive, vsProps, vsSrc, exp);
670    }
671  }
672
673  private void excludeCode(WorkingContext wc, String theSystem, String theCode) {
674    ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
675    n.setSystem(theSystem);
676    n.setCode(theCode);
677    String s = key(n);
678    wc.getExcludeKeys().add(s);
679  }
680
681  private void excludeCodes(WorkingContext wc, ConceptSetComponent exc, Parameters expParams, ValueSetExpansionComponent exp, ValueSet vs, String vspath) throws FHIRException, FileNotFoundException, ETooCostly, IOException {
682    opContext.deadCheck("excludeCodes");
683    exc.checkNoModifiers("Compose.exclude", "expanding");
684    if (exc.hasSystem() && exc.getConcept().size() == 0 && exc.getFilter().size() == 0) {
685      wc.getExcludeSystems().add(exc.getSystem());
686    }
687
688    for (UriType imp : exc.getValueSet()) {
689      excludeCodes(wc, importValueSetForExclude(wc, imp.getValue(), exp, expParams, false, vs).getExpansion());
690    }
691    
692    if (exc.hasSystem()) {
693      String sv = exc.getSystem()+(exc.hasVersion() ? "#"+exc.getVersion(): "");
694      if (dwc.getCountIncompleteSystems().contains(sv)) {
695        dwc.setNoTotal(true);
696      }
697
698      CodeSystem cs = context.fetchSupplementedCodeSystem(exc.getSystem());
699      if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem(), opContext.getOptions().getFhirVersion())) {
700        ValueSetExpansionOutcome vse = context.expandVS(new TerminologyOperationDetails(requiredSupplements), exc, false, false);
701        ValueSet valueset = vse.getValueset();
702        if (valueset.hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) {
703          sources.add(valueset.getUserString(UserDataNames.VS_EXPANSION_SOURCE));
704        }
705        if (valueset == null)
706          throw failTSE("Error Expanding ValueSet: "+vse.getError());
707        excludeCodes(wc, valueset.getExpansion());
708        return;
709      }
710
711      for (ConceptReferenceComponent c : exc.getConcept()) {
712        excludeCode(wc, exc.getSystem(), c.getCode());
713      }
714
715      if (exc.getFilter().size() > 0) {
716        if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
717          addFragmentWarning(exp, cs);
718        }
719        List<WorkingContext> filters = new ArrayList<>();
720        for (int i = 1; i < exc.getFilter().size(); i++) {
721          WorkingContext wc1 = new WorkingContext();
722          filters.add(wc1);
723          processFilter(exc, exp, expParams, null, cs, false, exc.getFilter().get(i), wc1, null, true, vspath+".filter["+i+"]");
724        }
725        ConceptSetFilterComponent fc = exc.getFilter().get(0);
726        WorkingContext wc1 = dwc;
727        processFilter(exc, exp, expParams, null, cs, false, fc, wc1, filters, true, vspath+".filter[0]");
728      }
729    }
730  }
731
732  private void excludeCodes(WorkingContext wc, ValueSetExpansionComponent expand) {
733    opContext.deadCheck("excludeCodes");
734    for (ValueSetExpansionContainsComponent c : expand.getContains()) {
735      excludeCode(wc, c.getSystem(), c.getCode());
736    }
737  }
738
739  private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, DataType value) {
740    for (ValueSetExpansionParameterComponent p : params) {
741      if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false)) {
742        return true;
743      }
744    }
745    return false;
746  }
747
748  public ValueSetExpansionOutcome expand(ValueSet source, Parameters expParams) {
749    allErrors.clear();
750    try {
751      opContext.seeContext(source.getVersionedUrl());
752      
753      return expandInternal(source, expParams);
754    } catch (NoTerminologyServiceException e) {
755      // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
756      // that might fail too, but it might not, later.
757      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.NOSERVICE, allErrors, false);
758    } catch (CodeSystemProviderExtension e) {
759      // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
760      // that might fail too, but it might not, later.
761      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.INTERNAL_ERROR, allErrors, false);
762    } catch (TerminologyServiceProtectionException e) {
763      if (opContext.isOriginal()) {
764        return new ValueSetExpansionOutcome(e.getMessage(), e.getError(), allErrors, false);
765      } else {
766        throw e;
767      }
768    } catch (ETooCostly e) {
769      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.TOO_COSTLY, allErrors, false);
770    } catch (UnknownValueSetException e) {
771      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.VALUESET_UNKNOWN, allErrors, false);
772    } catch (VSCheckerException e) {
773      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, allErrors, e.getIssues());      
774    } catch (Exception e) {
775      if (debug) {
776        e.printStackTrace();
777      }
778      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, allErrors, e instanceof EFhirClientException || e instanceof TerminologyServiceException);
779    }
780  }
781
782  public ValueSetExpansionOutcome expandInternal(ValueSet source, Parameters expParams) throws FHIRException, FileNotFoundException, ETooCostly, IOException, CodeSystemProviderExtension {
783    if (expParams == null)
784      expParams = makeDefaultExpansion();
785    altCodeParams.seeParameters(expParams);
786    altCodeParams.seeValueSet(source);
787    source.checkNoModifiers("ValueSet", "expanding");
788    focus = source.copy();
789    focus.setIdBase(null);
790    focus.setExpansion(new ValueSet.ValueSetExpansionComponent());
791    focus.getExpansion().setTimestampElement(DateTimeType.now());
792    focus.getExpansion().setIdentifier(Factory.createUUID()); 
793    checkCanonical(focus.getExpansion(), focus, focus);
794    for (Extension ext : focus.getCompose().getExtensionsByUrl("http://hl7.org/fhir/tools/StructureDefinition/valueset-expansion-parameter")) {
795      processParameter(ext.getExtensionString("name"), ext.getExtensionByUrl("value").getValue());
796    }
797    for (ParametersParameterComponent p : expParams.getParameter()) {
798      processParameter(p.getName(), p.getValue());
799    }
800    for (Extension s : focus.getExtensionsByUrl(ExtensionConstants.EXT_VSSUPPLEMENT)) {
801      requiredSupplements.add(s.getValue().primitiveValue());
802    }
803    if (langs == null && focus.hasLanguage()) {
804      langs = new AcceptLanguageHeader(focus.getLanguage(), true);
805    }
806
807    try {
808      if (source.hasCompose()) {
809        //        ExtensionsUtils.stripExtensions(focus.getCompose()); - disabled 23/05/2023 GDG - why was this ever thought to be a good idea?
810        handleCompose(source.getCompose(), focus.getExpansion(), expParams, source.getUrl(), focus.getExpansion().getExtension(), source);
811      }
812    } catch (EFinished e) {
813      // nothing - we intended to trap this here
814    }
815
816    if (dwc.getCount() > maxExpansionSize && dwc.getOffsetParam() + dwc.getCountParam() == 0) {
817      if (dwc.isNoTotal()) {
818        throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize)));        
819      } else {
820        throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY_COUNT, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize), MessageFormat.format("{0,number,#}", dwc.getCount())));
821      }
822    } else if (dwc.isCanBeHierarchy() && ((dwc.getCountParam() == 0) || dwc.getCountParam() > dwc.getCodes().size())) {
823      for (ValueSetExpansionContainsComponent c : dwc.getRoots()) {
824        focus.getExpansion().getContains().add(c);
825      }
826    } else {
827      int i = 0;
828      int cc = 0;
829      for (ValueSetExpansionContainsComponent c : dwc.getCodes()) {
830        c.getContains().clear(); // make sure any hierarchy is wiped
831        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
832          if (dwc.getOffsetParam() == 0 || i >= dwc.getOffsetParam()) {
833            focus.getExpansion().getContains().add(c);
834            cc++;
835            if (cc == dwc.getCountParam()) {
836              break;
837            }
838          }
839          i++;
840        }
841      }
842    }
843
844    if (dwc.hasOffsetParam()) {
845      focus.getExpansion().setOffset(dwc.getOffsetParam());
846    }
847    if (!dwc.isNoTotal()) {
848      focus.getExpansion().setTotal(dwc.getStatedTotal());
849    }
850    if (!requiredSupplements.isEmpty()) {      
851      return new ValueSetExpansionOutcome(context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements)), TerminologyServiceErrorClass.BUSINESS_RULE, allErrors, false);
852    }
853    if (!expParams.hasParameter("includeDefinition") || !expParams.getParameterBool("includeDefinition")) {
854      focus.setCompose(null);
855      focus.getExtension().clear();
856      focus.setPublisher(null);
857      focus.setDescription(null);
858      focus.setPurpose(null);
859      focus.getContact().clear();
860      focus.setCopyright(null);
861      focus.setText(null);
862    }
863    return new ValueSetExpansionOutcome(focus);
864  }
865
866  private void processParameter(String name, DataType value) {
867    if (Utilities.existsInList(name, "includeDesignations", "excludeNested", "activeOnly", "offset", "count")) {
868      focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
869      focus.getExpansion().addParameter().setName(name).setValue(value);
870    }
871    if ("displayLanguage".equals(name)) {
872      this.langs = new AcceptLanguageHeader(value.primitiveValue(), true);
873      focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
874      focus.getExpansion().addParameter().setName(name).setValue(new CodeType(value.primitiveValue()));
875    }
876    if ("designation".equals(name)) {
877      String[] v = value.primitiveValue().split("\\|");
878      if (v.length != 2 || !Utilities.isAbsoluteUrl(v[0]) || Utilities.noString(v[1])) {
879        throw new NoTerminologyServiceException("Unable to understand designation parameter "+value.primitiveValue());
880      }
881      this.designations.add(new Token(v[0], v[1]));
882      focus.getExpansion().addParameter().setName(name).setValue(new StringType(value.primitiveValue()));
883    }
884    if ("offset".equals(name) && value instanceof IntegerType) {
885      focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
886      focus.getExpansion().addParameter().setName(name).setValue(value);
887      dwc.setOffsetParam(((IntegerType) value).getValue());
888      if (dwc.getOffsetParam() < 0) {
889        dwc.setOffsetParam(0);
890      }
891    }
892    if ("count".equals(name)) {
893      focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
894      focus.getExpansion().addParameter().setName(name).setValue(value);
895      dwc.setCountParam(((IntegerType) value).getValue());
896      if (dwc.getCountParam() < 0) {
897        dwc.setCountParam(0);
898      }
899    }
900  }
901  
902  public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams) throws FHIRException, ETooCostly, FileNotFoundException, IOException, CodeSystemProviderExtension {
903    if (expParams == null)
904      expParams = makeDefaultExpansion();
905    altCodeParams.seeParameters(expParams);
906    altCodeParams.seeValueSet(source);
907    source.checkNoModifiers("ValueSet", "expanding");
908    focus = source.copy();
909    focus.setIdBase(null);
910    focus.setExpansion(new ValueSet.ValueSetExpansionComponent());
911    focus.getExpansion().setTimestampElement(DateTimeType.now());
912    focus.getExpansion().setIdentifier(Factory.createUUID()); 
913    checkCanonical(focus.getExpansion(), focus, focus);
914    for (Extension ext : focus.getCompose().getExtensionsByUrl("http://hl7.org/fhir/tools/StructureDefinition/valueset-expansion-parameter")) {
915      processParameter(ext.getExtensionString("name"), ext.getExtensionByUrl("value").getValue());
916    }
917    for (ParametersParameterComponent p : expParams.getParameter()) {
918      processParameter(p.getName(), p.getValue());
919    }
920    for (Extension s : focus.getExtensionsByUrl(ExtensionConstants.EXT_VSSUPPLEMENT)) {
921      requiredSupplements.add(s.getValue().primitiveValue());
922    }
923    if (langs == null && focus.hasLanguage()) {
924      langs = new AcceptLanguageHeader(focus.getLanguage(), true);
925    }
926
927    try {
928      if (source.hasCompose()) {
929//        ExtensionsUtils.stripExtensions(focus.getCompose()); - disabled 23/05/2023 GDG - why was this ever thought to be a good idea?
930        handleCompose(source.getCompose(), focus.getExpansion(), expParams, source.getUrl(), focus.getExpansion().getExtension(), source);
931      }
932    } catch (EFinished e) {
933      // nothing - we intended to trap this here
934    }
935
936    if (dwc.getCount() > maxExpansionSize && dwc.getOffsetParam() + dwc.getCountParam() == 0) {
937      if (dwc.isNoTotal()) {
938        throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize)));        
939      } else {
940        throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY_COUNT, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize), MessageFormat.format("{0,number,#}", dwc.getCount())));
941      }
942    } else if (dwc.isCanBeHierarchy() && ((dwc.getCountParam() == 0) || dwc.getCountParam() > dwc.getCodes().size())) {
943      for (ValueSetExpansionContainsComponent c : dwc.getRoots()) {
944        focus.getExpansion().getContains().add(c);
945      }
946    } else {
947      int i = 0;
948      int cc = 0;
949      for (ValueSetExpansionContainsComponent c : dwc.getCodes()) {
950        c.getContains().clear(); // make sure any hierarchy is wiped
951        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
952          if (dwc.getOffsetParam() == 0 || i >= dwc.getOffsetParam()) {
953            focus.getExpansion().getContains().add(c);
954            cc++;
955            if (cc == dwc.getCountParam()) {
956              break;
957            }
958          }
959          i++;
960        }
961      }
962    }
963
964    if (dwc.hasOffsetParam()) {
965      focus.getExpansion().setOffset(dwc.getOffsetParam());
966    }
967    if (!dwc.isNoTotal()) {
968      focus.getExpansion().setTotal(dwc.getStatedTotal());
969    }
970    if (!requiredSupplements.isEmpty()) {      
971      return new ValueSetExpansionOutcome(context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements)), TerminologyServiceErrorClass.BUSINESS_RULE, allErrors, false);
972    }
973    if (!expParams.hasParameter("includeDefinition") || !expParams.getParameterBool("includeDefinition")) {
974      focus.setCompose(null);
975      focus.getExtension().clear();
976      focus.setPublisher(null);
977      focus.setDescription(null);
978      focus.setPurpose(null);
979      focus.getContact().clear();
980      focus.setCopyright(null);
981      focus.setText(null);
982    }
983    return new ValueSetExpansionOutcome(focus);
984  }
985
986
987  private Parameters makeDefaultExpansion() {
988    Parameters res = new Parameters();
989    res.addParameter("excludeNested", true);
990    res.addParameter("includeDesignations", false);
991    return res;
992  }
993
994  private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) {
995    for (ConceptDefinitionComponent c : clist) {
996      if (code.equals(c.getCode()))
997        return c;
998      ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code);
999      if (v != null)
1000        return v;
1001    }
1002    return null;
1003  }
1004
1005  private void handleCompose(ValueSetComposeComponent compose, ValueSetExpansionComponent exp, Parameters expParams, String ctxt, List<Extension> extensions, ValueSet valueSet)
1006      throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension {
1007    compose.checkNoModifiers("ValueSet.compose", "expanding");
1008    String vspath = "ValueSet["+valueSet.getVersionedUrl()+"].compose";
1009    
1010    // Exclude comes first because we build up a map of things to exclude
1011    int c = 0;
1012    for (ConceptSetComponent inc : compose.getExclude()) {
1013      excludeCodes(dwc, inc, expParams, exp, valueSet, vspath+".include["+c+"]");
1014      c++;
1015    }
1016    dwc.setCanBeHierarchy(!expParams.getParameterBool("excludeNested") && dwc.getExcludeKeys().isEmpty() && dwc.getExcludeSystems().isEmpty() && dwc.getOffsetParam() == 0);
1017    includeAbstract = !expParams.getParameterBool("excludeNotForUI");
1018    boolean first = true;
1019    c = 0;
1020    for (ConceptSetComponent inc : compose.getInclude()) {
1021      if (first == true)
1022        first = false;
1023      else
1024        dwc.setCanBeHierarchy(false);
1025      includeCodes(inc, exp, expParams, dwc.isCanBeHierarchy(), compose.hasInactive() ? !compose.getInactive() : checkNoInActiveFromParam(expParams), extensions, valueSet, vspath+".include["+c+"]");
1026      c++;
1027    }
1028  }
1029
1030  /**
1031   * returns true if activeOnly = true 
1032   * @param expParams
1033   * @return
1034   */
1035  private boolean checkNoInActiveFromParam(Parameters expParams) {
1036    for (ParametersParameterComponent p : expParams.getParameter()) {
1037      if (p.getName().equals("activeOnly")) {
1038        return p.getValueBooleanType().getValue();
1039      }
1040    }
1041    return false;
1042  }
1043
1044  private ValueSet importValueSet(WorkingContext wc, String value, ValueSetExpansionComponent exp, Parameters expParams, boolean noInactive, ValueSet valueSet) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError {
1045    if (value == null)
1046      throw fail(I18nConstants.VS_EXP_IMPORT_NULL, true);
1047    String url = getCu().pinValueSet(value, expParams);
1048    ValueSet vs = context.findTxResource(ValueSet.class, url, valueSet);
1049    if (vs == null) {
1050      boolean pinned = !url.equals(value);
1051      String ver = pinned ? url.substring(value.length()+1) : null;
1052      if (context.fetchResource(CodeSystem.class, url, valueSet) != null) {
1053        throw failNotFound(pinned ? I18nConstants.VS_EXP_IMPORT_CS_PINNED : I18nConstants.VS_EXP_IMPORT_CS, true, value, ver);
1054      } else  {
1055        throw failNotFound(pinned ? I18nConstants.VS_EXP_IMPORT_UNK_PINNED : I18nConstants.VS_EXP_IMPORT_UNK, true, value, ver);
1056      }
1057    }
1058    checkCanonical(exp, vs, focus);
1059    if (noInactive) {
1060      expParams = expParams.copy();
1061      expParams.addParameter("activeOnly", true);
1062    }
1063    ValueSetExpander expander = new ValueSetExpander(context, opContext.copy(), allErrors);
1064    ValueSetExpansionOutcome vso = expander.expand(vs, expParams);
1065    if (vso.getError() != null) {
1066      addErrors(vso.getAllErrors());
1067      if (vso.getErrorClass() == TerminologyServiceErrorClass.VALUESET_UNKNOWN) {  
1068        throw failUnk(I18nConstants.VS_EXP_IMPORT_ERROR, true, vs.getUrl(), vso.getError());
1069      } else {
1070        throw fail(I18nConstants.VS_EXP_IMPORT_ERROR, true, vs.getUrl(), vso.getError());
1071      }
1072    } else if (vso.getValueset() == null) {
1073      throw fail(I18nConstants.VS_EXP_IMPORT_FAIL, true, vs.getUrl());      
1074    }
1075    sources.addAll(expander.sources);
1076    if (vs.hasVersion() || REPORT_VERSION_ANYWAY) {
1077      UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : ""));
1078      if (!existsInParams(exp.getParameter(), "used-valueset", u))
1079        exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u));
1080    }
1081    ValueSetExpansionComponent evs = vso.getValueset().getExpansion();
1082    for (Extension ex : evs.getExtension()) {
1083      if (ex.getUrl().equals(ToolingExtensions.EXT_EXP_TOOCOSTLY)) {
1084        if (ex.getValue() instanceof BooleanType) {
1085          exp.getExtension().add(new Extension(ToolingExtensions.EXT_EXP_TOOCOSTLY).setValue(new CanonicalType(value)));
1086        } else {
1087          exp.getExtension().add(ex);
1088        }
1089      } 
1090    }
1091    if (!evs.hasTotal()) {
1092      // because if there's no total, we can't know if we got everything
1093      dwc.setNoTotal(true);
1094    }
1095    for (ValueSetExpansionParameterComponent p : evs.getParameter()) {
1096      if (!existsInParams(exp.getParameter(), p.getName(), p.getValue()))
1097        exp.getParameter().add(p);
1098    }
1099    if (isValueSetUnionImports(valueSet)) {
1100      copyExpansion(wc, evs.getContains());
1101    }
1102    wc.setCanBeHierarchy(false); // if we're importing a value set, we have to be combining, so we won't try for a hierarchy
1103    return vso.getValueset();
1104  }
1105  
1106
1107
1108  private ValueSet importValueSetForExclude(WorkingContext wc, String value, ValueSetExpansionComponent exp, Parameters expParams, boolean noInactive, ValueSet valueSet) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError {
1109    if (value == null)
1110      throw fail(I18nConstants.VS_EXP_IMPORT_NULL_X, true);  
1111    String url = getCu().pinValueSet(value, expParams);
1112    ValueSet vs = context.findTxResource(ValueSet.class, url, valueSet);
1113    if (vs == null) {
1114      boolean pinned = !url.equals(value);
1115      String ver = pinned ? url.substring(value.length()+1) : null;
1116      if (context.fetchResource(CodeSystem.class, url, valueSet) != null) {
1117        throw fail(pinned ? I18nConstants.VS_EXP_IMPORT_CS_PINNED_X : I18nConstants.VS_EXP_IMPORT_CS_X, true, value, ver);
1118      } else  {
1119        throw fail(pinned ? I18nConstants.VS_EXP_IMPORT_UNK_PINNED_X : I18nConstants.VS_EXP_IMPORT_UNK_X, true, value, ver);
1120      }
1121    }
1122    checkCanonical(exp, vs, focus);
1123    if (noInactive) {
1124      expParams = expParams.copy();
1125      expParams.addParameter("activeOnly", true);
1126    }
1127    ValueSetExpander expander = new ValueSetExpander(context, opContext.copy(), allErrors);
1128    ValueSetExpansionOutcome vso = expander.expand(vs, expParams);
1129    sources.addAll(expander.sources);
1130    if (vso.getError() != null) {
1131      addErrors(vso.getAllErrors());
1132      throw fail(I18nConstants.VS_EXP_IMPORT_ERROR_X, true, vs.getUrl(), vso.getError());
1133    } else if (vso.getValueset() == null) {
1134      throw fail(I18nConstants.VS_EXP_IMPORT_FAIL_X, true, vs.getUrl());      
1135    }
1136    
1137    if (vs.hasVersion() || REPORT_VERSION_ANYWAY) {
1138      UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : ""));
1139      if (!existsInParams(exp.getParameter(), "used-valueset", u))
1140        exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u));
1141    }
1142    for (Extension ex : vso.getValueset().getExpansion().getExtension()) {
1143      if (ex.getUrl().equals(ToolingExtensions.EXT_EXP_TOOCOSTLY)) {
1144        throw fail(VS_EXP_IMPORT_ERROR_TOO_COSTLY, true, vs.getUrl());
1145      } 
1146    }
1147    return vso.getValueset();
1148  }
1149  
1150  protected boolean isValueSetUnionImports(ValueSet valueSet) {
1151    PackageInformation p = valueSet.getSourcePackage();
1152    if (p != null) {
1153      return p.getDate().before(new GregorianCalendar(2022, Calendar.MARCH, 31).getTime());
1154    } else {
1155      return false;
1156    }
1157  }
1158
1159  public void copyExpansion(WorkingContext wc,List<ValueSetExpansionContainsComponent> list) {
1160    opContext.deadCheck("copyExpansion");
1161    for (ValueSetExpansionContainsComponent cc : list) {
1162       ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
1163       n.setSystem(cc.getSystem());
1164       n.setCode(cc.getCode());
1165       n.setAbstract(cc.getAbstract());
1166       n.setInactive(cc.getInactive());
1167       n.setDisplay(cc.getDisplay());
1168       n.getDesignation().addAll(cc.getDesignation());
1169
1170       String s = key(n);
1171       if (!wc.getMap().containsKey(s) && !wc.getExcludeKeys().contains(s)) {
1172         wc.getCodes().add(n);
1173         wc.getMap().put(s, n);
1174       }
1175       copyExpansion(wc, cc.getContains());
1176    }
1177  }
1178
1179  private void addErrors(List<String> errs) {
1180    for (String s : errs) {
1181      if (!allErrors.contains(s)) {
1182        allErrors.add(s);
1183      }
1184    }
1185  }
1186
1187  private int copyImportContains(List<ValueSetExpansionContainsComponent> list, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filter, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly {
1188    int count = 0;
1189    opContext.deadCheck("copyImportContains");
1190    
1191    String lang = vsSrc.getLanguage();
1192    if (lang == null) { 
1193      lang = findParamValue(vsSrc.getExpansion().getParameter(), "displayLanguage");
1194    }
1195    for (ValueSetExpansionContainsComponent c : list) {
1196      c.checkNoModifiers("Imported Expansion in Code System", "expanding");
1197      ValueSetExpansionContainsComponent np = addCode(dwc, c.getSystem(), c.getCode(), c.getDisplay(), lang, parent, translateDesignations(c), expParams, c.getAbstract(), c.getInactive(), 
1198          filter, noInactive, false, vsProps, makeCSProps(c.getExtensionString(ToolingExtensions.EXT_DEFINITION), null), null, c.getProperty(), null, c.getExtension(), exp);
1199      if (np != null) {
1200        count++;
1201      }
1202      count = count + copyImportContains(c.getContains(), np, expParams, filter, noInactive, vsProps, vsSrc, exp);
1203    }
1204    return count;
1205  }
1206
1207  private List<ConceptDefinitionDesignationComponent> translateDesignations(ValueSetExpansionContainsComponent c) {
1208    if (!c.hasDesignation()) {
1209      return null;
1210    }
1211    List<ConceptDefinitionDesignationComponent> list = new ArrayList<>();
1212    for (ConceptReferenceDesignationComponent d : c.getDesignation()) {
1213      ConceptDefinitionDesignationComponent d2 = new ConceptDefinitionDesignationComponent();
1214      d2.setLanguage(d.getLanguage());
1215      d2.setUse(d.getUse());
1216      d2.setAdditionalUse(d.getAdditionalUse());
1217      d2.setValue(d.getValue());
1218      list.add(d2);
1219    }
1220    return list;
1221  }
1222
1223  private void includeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, boolean heirarchical, boolean noInactive, List<Extension> extensions, ValueSet valueSet, String vspath) throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension {
1224    opContext.deadCheck("includeCodes");
1225    inc.checkNoModifiers("Compose.include", "expanding");
1226    List<ValueSet> imports = new ArrayList<ValueSet>();
1227    for (CanonicalType imp : inc.getValueSet()) {
1228      imports.add(importValueSet(dwc, imp.getValue(), exp, expParams, noInactive, valueSet));
1229    }
1230
1231    if (!inc.hasSystem()) {
1232      if (imports.isEmpty()) // though this is not supposed to be the case
1233        return;
1234      ValueSet base = imports.get(0);
1235      checkCanonical(exp, base, focus);
1236      imports.remove(0);
1237      base.checkNoModifiers("Imported ValueSet", "expanding");
1238      copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive, base.getExpansion().getProperty(), base, exp);
1239    } else {
1240      String sv = inc.getSystem()+(inc.hasVersion() ? "#"+inc.getVersion(): "");
1241      if (dwc.getCountIncompleteSystems().contains(sv)) {
1242        dwc.setNoTotal(true);
1243      }
1244      CodeSystem cs = context.fetchSupplementedCodeSystem(inc.getSystem());
1245      if (ValueSetUtilities.isServerSide(inc.getSystem()) || (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT))) {
1246        doServerIncludeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, valueSet.getExpansion().getProperty());
1247      } else {
1248        if (cs.hasUserData(UserDataNames.tx_known_supplements)) {
1249          for (String s : cs.getUserString(UserDataNames.tx_known_supplements).split("\\,")) {
1250            requiredSupplements.remove(s);
1251          }
1252        }
1253        doInternalIncludeCodes(inc, exp, expParams, imports, cs, noInactive, valueSet, vspath);
1254      }
1255    }
1256  }
1257
1258  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 {
1259    opContext.deadCheck("doServerIncludeCodes");
1260    CodeSystemProvider csp = CodeSystemProvider.factory(inc.getSystem());
1261    if (csp != null) {
1262      csp.includeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, vsProps);
1263      return;
1264    }
1265    
1266    ValueSetExpansionOutcome vso = context.expandVS(new TerminologyOperationDetails(requiredSupplements), inc, heirarchical, noInactive);
1267    if (vso.getError() != null) {
1268      throw failTSE("Unable to expand imported value set: " + vso.getError());
1269    }
1270    ValueSet vs = vso.getValueset();
1271    if (vs.hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) {
1272      sources.add(vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE));
1273    }
1274    if (vs.hasVersion() || REPORT_VERSION_ANYWAY) {
1275      UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : ""));
1276      if (!existsInParams(exp.getParameter(), "used-valueset", u)) {
1277        exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u));
1278      }
1279    }
1280    if (vs.getExpansion().hasTotal()) {
1281      dwc.incExtraCount(vs.getExpansion().getTotal() - countContains(vs.getExpansion().getContains()));
1282      dwc.getCountIncompleteSystems().add(inc.getSystem()+(inc.hasVersion() ? "#"+inc.getVersion(): ""));
1283    } else {
1284      dwc.setNoTotal(true);
1285    }
1286    for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) {
1287      if (!existsInParams(exp.getParameter(), p.getName(), p.getValue())) {
1288        exp.getParameter().add(p);
1289      }
1290    }
1291    for (Extension ex : vs.getExpansion().getExtension()) {
1292      if (Utilities.existsInList(ex.getUrl(), ToolingExtensions.EXT_EXP_TOOCOSTLY, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed")) {
1293        if (!ExtensionsUtils.hasExtension(extensions, ex.getUrl())) {
1294          extensions.add(ex);
1295        }
1296      }
1297    }
1298    for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) {
1299      addCodeAndDescendents(dwc, cc, null, expParams, imports, noInactive, vsProps, vs, exp, getLang(vs));
1300    }
1301  }
1302
1303
1304  private int countContains(List<ValueSetExpansionContainsComponent> contains) {
1305    int count = contains.size();
1306    for (ValueSetExpansionContainsComponent cc : contains) {
1307      if (cc.hasContains()) {
1308        count += countContains(cc.getContains());
1309      }
1310    }
1311    return count;
1312  }
1313
1314  public void doInternalIncludeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List<ValueSet> imports, CodeSystem cs, boolean noInactive, Resource vsSrc, String vspath) throws NoTerminologyServiceException, TerminologyServiceException, FHIRException, ETooCostly {
1315    opContext.deadCheck("doInternalIncludeCodes");
1316    if (cs == null) {
1317      if (context.isNoTerminologyServer())
1318        throw failTSE("Unable to find code system " + inc.getSystem().toString());
1319      else
1320        throw failTSE("Unable to find code system " + inc.getSystem().toString());
1321    }
1322    checkCanonical(exp, cs, focus);
1323    cs.checkNoModifiers("Code System", "expanding");
1324    if (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT)
1325      throw failTSE("Code system " + inc.getSystem().toString() + " is incomplete");
1326    if (cs.hasVersion() || REPORT_VERSION_ANYWAY) {
1327      UriType u = new UriType(cs.getUrl() + (cs.hasVersion() ? "|"+cs.getVersion() : ""));
1328      if (!existsInParams(exp.getParameter(), "used-codesystem", u))
1329        exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-codesystem").setValue(u));
1330      if (cs.hasUserData(UserDataNames.tx_known_supplements)) {
1331        for (String s : cs.getUserString(UserDataNames.tx_known_supplements).split("\\,")) {
1332          u = new UriType(s);
1333          if (!existsInParams(exp.getParameter(), "used-supplement", u)) {
1334            exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-supplement").setValue(u));
1335          }
1336        }
1337      }
1338    }
1339    if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
1340      // special case - add all the code system
1341      for (ConceptDefinitionComponent def : cs.getConcept()) {
1342        addCodeAndDescendents(dwc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), null, exp);
1343      }
1344      if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
1345        addFragmentWarning(exp, cs);
1346      }
1347      if (cs.getContent() == CodeSystemContentMode.EXAMPLE) {
1348        addExampleWarning(exp, cs);
1349      }      
1350    }
1351
1352    if (!inc.getConcept().isEmpty()) {
1353      dwc.setCanBeHierarchy(false);
1354      for (ConceptReferenceComponent c : inc.getConcept()) {
1355        c.checkNoModifiers("Code in Value Set", "expanding");
1356        ConceptDefinitionComponent def = CodeSystemUtilities.findCodeOrAltCode(cs.getConcept(), c.getCode(), null);
1357        boolean inactive = false; // default is true if we're a fragment and  
1358        boolean isAbstract = false;
1359        if (def == null) {
1360          if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
1361            addFragmentWarning(exp, cs);
1362          } else if (cs.getContent() == CodeSystemContentMode.EXAMPLE) {
1363              addExampleWarning(exp, cs);
1364          } else {
1365            if (checkCodesWhenExpanding) {
1366              throw failTSE("Unable to find code '" + c.getCode() + "' in code system " + cs.getUrl());
1367            }
1368          }
1369        } else {
1370          def.checkNoModifiers("Code in Code System", "expanding");
1371          inactive = CodeSystemUtilities.isInactive(cs, def);
1372          isAbstract = CodeSystemUtilities.isNotSelectable(cs, def);
1373          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())), 
1374              expParams, isAbstract, inactive, imports, noInactive, false, exp.getProperty(), makeCSProps(def.getDefinition(), def.getProperty()), cs, null, def.getExtension(), c.getExtension(), exp);
1375        }
1376      }
1377    }
1378    if (inc.getFilter().size() > 0) {
1379      if (inc.getFilter().size() > 1) {
1380        dwc.setCanBeHierarchy(false); // which will be the case if we get around to supporting this
1381      }
1382      if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
1383        addFragmentWarning(exp, cs);
1384      }
1385      List<WorkingContext> filters = new ArrayList<>();
1386      for (int i = 1; i < inc.getFilter().size(); i++) {
1387        WorkingContext wc = new WorkingContext();
1388        filters.add(wc);
1389        processFilter(inc, exp, expParams, imports, cs, noInactive, inc.getFilter().get(i), wc, null, false, vspath+".filter["+i+"]");
1390      }
1391      ConceptSetFilterComponent fc = inc.getFilter().get(0);
1392      WorkingContext wc = dwc;
1393      processFilter(inc, exp, expParams, imports, cs, noInactive, fc, wc, filters, false, vspath+".filter[0]");
1394    }
1395  }
1396
1397  private void processFilter(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List<ValueSet> imports, CodeSystem cs, boolean noInactive, 
1398      ConceptSetFilterComponent fc, WorkingContext wc, List<WorkingContext> filters, boolean exclude, String vspath)
1399      throws ETooCostly {
1400    
1401    if (!fc.hasValue() || fc.getValue() == null) {
1402      List<OperationOutcomeIssueComponent> issues = new ArrayList<>();
1403      issues.addAll(makeIssue(IssueSeverity.ERROR, IssueType.INVALID, vspath+".value", context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM_FILTER_WITH_NO_VALUE, cs.getUrl(), fc.getProperty(), fc.getOp().toCode()), OpIssueCode.VSProcessing, null)); 
1404      throw new VSCheckerException(context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM_FILTER_WITH_NO_VALUE, cs.getUrl(), fc.getProperty(), fc.getOp().toCode()), issues, TerminologyServiceErrorClass.INTERNAL_ERROR);
1405    }
1406    opContext.deadCheck("processFilter");
1407    if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) {
1408      // special: all codes in the target code system under the value
1409      ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
1410      if (def == null)
1411        throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
1412      if (exclude) {
1413        excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, new AllConceptsFilter(allErrors), filters, exp);
1414      } else {
1415        addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
1416      }
1417    } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISNOTA) {
1418      // special: all codes in the target code system that are not under the value
1419      ConceptDefinitionComponent defEx = getConceptForCode(cs.getConcept(), fc.getValue());
1420      if (defEx == null)
1421        throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
1422      for (ConceptDefinitionComponent def : cs.getConcept()) {
1423        if (exclude) {
1424          excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, defEx, new AllConceptsFilter(allErrors), filters, exp);
1425        } else {
1426          addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, defEx, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
1427        }
1428      }
1429    } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.DESCENDENTOF) {
1430      // special: all codes in the target code system under the value
1431      ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
1432      if (def == null)
1433        throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
1434      for (ConceptDefinitionComponent c : def.getConcept())
1435        if (exclude) {
1436          excludeCodeAndDescendents(wc, cs, inc.getSystem(), c, null, imports, null, new AllConceptsFilter(allErrors), filters, exp);
1437        } else {
1438          addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
1439        }
1440      if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) {
1441        List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK);
1442        for (ConceptDefinitionComponent c : children) {
1443          if (exclude) {
1444            excludeCodeAndDescendents(wc, cs, inc.getSystem(), c, null, imports, null, new AllConceptsFilter(allErrors), filters, exp);
1445          } else {
1446            addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
1447          }
1448        }
1449      }
1450
1451    } else if ("display".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) {
1452      // gg; note: wtf is this: if the filter is display=v, look up the code 'v', and see if it's display is 'v'?
1453      dwc.setCanBeHierarchy(false);
1454      ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
1455      if (def != null) {
1456        if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) {
1457          if (def.getDisplay().contains(fc.getValue()) && passesOtherFilters(filters, cs, def.getCode())) {
1458            for (String code : getCodesForConcept(def, expParams)) {
1459              opContext.deadCheck("processFilter2");
1460              if (exclude) {
1461                excludeCode(wc, inc.getSystem(), code);
1462              } else {
1463                addCode(wc, inc.getSystem(), code, def.getDisplay(), cs.getLanguage(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def),
1464                  imports, noInactive, false, exp.getProperty(), makeCSProps(def.getDefinition(), def.getProperty()), cs, null, def.getExtension(), null, exp);
1465              }
1466            }
1467          }
1468        }
1469      }
1470    } else if (CodeSystemUtilities.isDefinedProperty(cs, fc.getProperty())) {
1471      for (ConceptDefinitionComponent def : cs.getConcept()) {
1472        PropertyFilter pf = new PropertyFilter(allErrors, fc, CodeSystemUtilities.getPropertyDefinition(cs, fc.getProperty()));
1473        if (exclude) {
1474          excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, pf, filters, exp);
1475        } else {
1476          addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, pf, noInactive, exp.getProperty(), filters, exp);
1477        }
1478      }
1479    } else if (isKnownProperty(fc.getProperty(), cs)) {
1480      for (ConceptDefinitionComponent def : cs.getConcept()) {
1481        KnownPropertyFilter pf = new KnownPropertyFilter(allErrors, fc, fc.getProperty());
1482        if (exclude) {
1483          excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, pf, filters, exp);
1484        } else {
1485          addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, pf, noInactive, exp.getProperty(), filters, exp);
1486        }
1487      }
1488    } else if ("code".equals(fc.getProperty()) && fc.getOp() == FilterOperator.REGEX) {
1489      for (ConceptDefinitionComponent def : cs.getConcept()) {
1490        if (exclude) {
1491          excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, new RegexFilter(allErrors, fc.getValue()), filters, exp);
1492        } else {
1493          addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new RegexFilter(allErrors, fc.getValue()), noInactive, exp.getProperty(), filters, exp);
1494        }
1495      }
1496    } else {
1497      throw fail(I18nConstants.VS_EXP_FILTER_UNK, true, focus.getVersionedUrl(), fc.getProperty(), fc.getOp());
1498    }
1499  }
1500
1501  private boolean isKnownProperty(String property, CodeSystem cs) {
1502    return Utilities.existsInList(property, "notSelectable");
1503  }
1504
1505  private List<ConceptDefinitionDesignationComponent> mergeDesignations(ConceptDefinitionComponent def,
1506      List<ConceptDefinitionDesignationComponent> list) {
1507    List<ConceptDefinitionDesignationComponent> res = new ArrayList<>();
1508    if (def != null) {
1509      res.addAll(def.getDesignation());
1510    }
1511    res.addAll(list);
1512    return res;
1513  }
1514
1515 
1516
1517  private void addFragmentWarning(ValueSetExpansionComponent exp, CodeSystem cs) {
1518    String url = cs.getVersionedUrl();
1519    for (ValueSetExpansionParameterComponent p : exp.getParameter()) {
1520      if ("fragment".equals(p.getName()) && p.hasValueUriType() && url.equals(p.getValue().primitiveValue())) { 
1521        return;
1522      }     
1523    }
1524    exp.addParameter().setName("fragment").setValue(new CanonicalType(url));
1525  }
1526
1527  private void addExampleWarning(ValueSetExpansionComponent exp, CodeSystem cs) {
1528    String url = cs.getVersionedUrl();
1529    for (ValueSetExpansionParameterComponent p : exp.getParameter()) {
1530      if ("example".equals(p.getName()) && p.hasValueUriType() && url.equals(p.getValue().primitiveValue())) { 
1531        return;
1532      }     
1533    }
1534    exp.addParameter().setName("example").setValue(new CanonicalType(url));
1535  }
1536  
1537  private List<ConceptDefinitionDesignationComponent> convertDesignations(List<ConceptReferenceDesignationComponent> list) {
1538    List<ConceptDefinitionDesignationComponent> res = new ArrayList<CodeSystem.ConceptDefinitionDesignationComponent>();
1539    for (ConceptReferenceDesignationComponent t : list) {
1540      ConceptDefinitionDesignationComponent c = new ConceptDefinitionDesignationComponent();
1541      c.setLanguage(t.getLanguage());
1542      if (t.hasUse()) {
1543        c.setUse(t.getUse());
1544      }
1545      c.setValue(t.getValue());
1546      c.getExtension().addAll(t.getExtension());
1547      res.add(c);
1548    }
1549    return res;
1550  }
1551
1552  private String key(String uri, String code) {
1553    return "{" + uri + "}" + code;
1554  }
1555
1556  private String key(ValueSetExpansionContainsComponent c) {
1557    return key(c.getSystem(), c.getCode());
1558  }
1559
1560  private FHIRException fail(String msgId, boolean check, Object... params) {
1561    String msg = context.formatMessage(msgId, params);
1562    allErrors.add(msg);
1563    return new FHIRException(msg);
1564  }
1565
1566  private UnknownValueSetException failUnk(String msgId, boolean check, Object... params) {
1567    String msg = context.formatMessage(msgId, params);
1568    allErrors.add(msg);
1569    return new UnknownValueSetException(msg);
1570  }
1571
1572  private UnknownValueSetException failNotFound(String msgId, boolean check, Object... params) {
1573    String msg = context.formatMessage(msgId, params);
1574    allErrors.add(msg);
1575    return new UnknownValueSetException(msg);
1576  }
1577
1578  private ETooCostly failCostly(String msg) {
1579    allErrors.add(msg);
1580    return new ETooCostly(msg);
1581  }
1582
1583  private TerminologyServiceException failTSE(String msg) {
1584    allErrors.add(msg);
1585    return new TerminologyServiceException(msg);
1586  }
1587
1588  public Collection<? extends String> getAllErrors() {
1589    return allErrors;
1590  }
1591
1592  public boolean isCheckCodesWhenExpanding() {
1593    return checkCodesWhenExpanding;
1594  }
1595
1596  public void setCheckCodesWhenExpanding(boolean checkCodesWhenExpanding) {
1597    this.checkCodesWhenExpanding = checkCodesWhenExpanding;
1598  }
1599
1600  private boolean passesOtherFilters(List<WorkingContext> otherFilters, CodeSystem cs, String code) {
1601    if (otherFilters == null) {
1602      return true;
1603    }
1604    String key = key(cs.getUrl(), code);
1605    for (WorkingContext wc : otherFilters) {
1606      if (!wc.getMap().containsKey(key)) {
1607        return false;
1608      }
1609    }
1610    return true;
1611  }
1612
1613  public boolean isDebug() {
1614    return debug;
1615  }
1616
1617  public ValueSetExpander setDebug(boolean debug) {
1618    this.debug = debug;
1619    return this;
1620  }
1621
1622  public String getSource() {
1623    if (sources.isEmpty()) {
1624      return "internal";
1625    } else {
1626      return CommaSeparatedStringBuilder.join(", ", Utilities.sorted(sources));
1627    }
1628  }
1629  
1630  
1631}