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