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      CodeSystem cs = context.fetchSupplementedCodeSystem(exc.getSystem());
694      if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem(), opContext.getOptions().getFhirVersion())) {
695        ValueSetExpansionOutcome vse = context.expandVS(new TerminologyOperationDetails(requiredSupplements), exc, false, false);
696        ValueSet valueset = vse.getValueset();
697        if (valueset.hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) {
698          sources.add(valueset.getUserString(UserDataNames.VS_EXPANSION_SOURCE));
699        }
700        if (valueset == null)
701          throw failTSE("Error Expanding ValueSet: "+vse.getError());
702        excludeCodes(wc, valueset.getExpansion());
703        return;
704      }
705
706      for (ConceptReferenceComponent c : exc.getConcept()) {
707        excludeCode(wc, exc.getSystem(), c.getCode());
708      }
709
710      if (exc.getFilter().size() > 0) {
711        if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
712          addFragmentWarning(exp, cs);
713        }
714        List<WorkingContext> filters = new ArrayList<>();
715        for (int i = 1; i < exc.getFilter().size(); i++) {
716          WorkingContext wc1 = new WorkingContext();
717          filters.add(wc1);
718          processFilter(exc, exp, expParams, null, cs, false, exc.getFilter().get(i), wc1, null, true, vspath+".filter["+i+"]");
719        }
720        ConceptSetFilterComponent fc = exc.getFilter().get(0);
721        WorkingContext wc1 = dwc;
722        processFilter(exc, exp, expParams, null, cs, false, fc, wc1, filters, true, vspath+".filter[0]");
723      }
724    }
725  }
726
727  private void excludeCodes(WorkingContext wc, ValueSetExpansionComponent expand) {
728    opContext.deadCheck("excludeCodes");
729    for (ValueSetExpansionContainsComponent c : expand.getContains()) {
730      excludeCode(wc, c.getSystem(), c.getCode());
731    }
732  }
733
734  private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, DataType value) {
735    for (ValueSetExpansionParameterComponent p : params) {
736      if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false)) {
737        return true;
738      }
739    }
740    return false;
741  }
742
743  public ValueSetExpansionOutcome expand(ValueSet source, Parameters expParams) {
744    allErrors.clear();
745    try {
746      opContext.seeContext(source.getVersionedUrl());
747      
748      return expandInternal(source, expParams);
749    } catch (NoTerminologyServiceException e) {
750      // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
751      // that might fail too, but it might not, later.
752      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.NOSERVICE, allErrors, false);
753    } catch (CodeSystemProviderExtension e) {
754      // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
755      // that might fail too, but it might not, later.
756      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.INTERNAL_ERROR, allErrors, false);
757    } catch (TerminologyServiceProtectionException e) {
758      if (opContext.isOriginal()) {
759        return new ValueSetExpansionOutcome(e.getMessage(), e.getError(), allErrors, false);
760      } else {
761        throw e;
762      }
763    } catch (ETooCostly e) {
764      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.TOO_COSTLY, allErrors, false);
765    } catch (UnknownValueSetException e) {
766      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.VALUESET_UNKNOWN, allErrors, false);
767    } catch (VSCheckerException e) {
768      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, allErrors, e.getIssues());      
769    } catch (Exception e) {
770      if (debug) {
771        e.printStackTrace();
772      }
773      return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, allErrors, e instanceof EFhirClientException || e instanceof TerminologyServiceException);
774    }
775  }
776
777  public ValueSetExpansionOutcome expandInternal(ValueSet source, Parameters expParams) throws FHIRException, FileNotFoundException, ETooCostly, IOException, CodeSystemProviderExtension {
778    if (expParams == null)
779      expParams = makeDefaultExpansion();
780    altCodeParams.seeParameters(expParams);
781    altCodeParams.seeValueSet(source);
782    source.checkNoModifiers("ValueSet", "expanding");
783    focus = source.copy();
784    focus.setIdBase(null);
785    focus.setExpansion(new ValueSet.ValueSetExpansionComponent());
786    focus.getExpansion().setTimestampElement(DateTimeType.now());
787    focus.getExpansion().setIdentifier(Factory.createUUID()); 
788    checkCanonical(focus.getExpansion(), focus, focus);
789    for (Extension ext : focus.getCompose().getExtensionsByUrl("http://hl7.org/fhir/tools/StructureDefinion/valueset-expansion-param")) {
790      processParameter(ext.getExtensionString("name"), ext.getExtensionByUrl("value").getValue());
791    }
792    for (ParametersParameterComponent p : expParams.getParameter()) {
793      processParameter(p.getName(), p.getValue());
794    }
795    for (Extension s : focus.getExtensionsByUrl(ExtensionConstants.EXT_VSSUPPLEMENT)) {
796      requiredSupplements.add(s.getValue().primitiveValue());
797    }
798    if (langs == null && focus.hasLanguage()) {
799      langs = new AcceptLanguageHeader(focus.getLanguage(), true);
800    }
801
802    try {
803      if (source.hasCompose()) {
804        //        ExtensionsUtils.stripExtensions(focus.getCompose()); - disabled 23/05/2023 GDG - why was this ever thought to be a good idea?
805        handleCompose(source.getCompose(), focus.getExpansion(), expParams, source.getUrl(), focus.getExpansion().getExtension(), source);
806      }
807    } catch (EFinished e) {
808      // nothing - we intended to trap this here
809    }
810
811    if (dwc.getCount() > maxExpansionSize && dwc.getOffsetParam() + dwc.getCountParam() == 0) {
812      if (dwc.isNoTotal()) {
813        throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize)));        
814      } else {
815        throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY_COUNT, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize), MessageFormat.format("{0,number,#}", dwc.getCount())));
816      }
817    } else if (dwc.isCanBeHierarchy() && ((dwc.getCountParam() == 0) || dwc.getCountParam() > dwc.getCodes().size())) {
818      for (ValueSetExpansionContainsComponent c : dwc.getRoots()) {
819        focus.getExpansion().getContains().add(c);
820      }
821    } else {
822      int i = 0;
823      int cc = 0;
824      for (ValueSetExpansionContainsComponent c : dwc.getCodes()) {
825        c.getContains().clear(); // make sure any hierarchy is wiped
826        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
827          if (dwc.getOffsetParam() == 0 || i >= dwc.getOffsetParam()) {
828            focus.getExpansion().getContains().add(c);
829            cc++;
830            if (cc == dwc.getCountParam()) {
831              break;
832            }
833          }
834          i++;
835        }
836      }
837    }
838
839    if (dwc.hasOffsetParam()) {
840      focus.getExpansion().setOffset(dwc.getOffsetParam());
841    }
842    if (!dwc.isNoTotal()) {
843      focus.getExpansion().setTotal(dwc.getStatedTotal());
844    }
845    if (!requiredSupplements.isEmpty()) {      
846      return new ValueSetExpansionOutcome(context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements)), TerminologyServiceErrorClass.BUSINESS_RULE, allErrors, false);
847    }
848    if (!expParams.hasParameter("includeDefinition") || !expParams.getParameterBool("includeDefinition")) {
849      focus.setCompose(null);
850      focus.getExtension().clear();
851      focus.setPublisher(null);
852      focus.setDescription(null);
853      focus.setPurpose(null);
854      focus.getContact().clear();
855      focus.setCopyright(null);
856      focus.setText(null);
857    }
858    return new ValueSetExpansionOutcome(focus);
859  }
860
861  private void processParameter(String name, DataType value) {
862    if (Utilities.existsInList(name, "includeDesignations", "excludeNested", "activeOnly", "offset", "count")) {
863      focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
864      focus.getExpansion().addParameter().setName(name).setValue(value);
865    }
866    if ("displayLanguage".equals(name)) {
867      this.langs = new AcceptLanguageHeader(value.primitiveValue(), true);
868      focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
869      focus.getExpansion().addParameter().setName(name).setValue(new CodeType(value.primitiveValue()));
870    }
871    if ("designation".equals(name)) {
872      String[] v = value.primitiveValue().split("\\|");
873      if (v.length != 2 || !Utilities.isAbsoluteUrl(v[0]) || Utilities.noString(v[1])) {
874        throw new NoTerminologyServiceException("Unable to understand designation parameter "+value.primitiveValue());
875      }
876      this.designations.add(new Token(v[0], v[1]));
877      focus.getExpansion().addParameter().setName(name).setValue(new StringType(value.primitiveValue()));
878    }
879    if ("offset".equals(name) && value instanceof IntegerType) {
880      focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
881      focus.getExpansion().addParameter().setName(name).setValue(value);
882      dwc.setOffsetParam(((IntegerType) value).getValue());
883      if (dwc.getOffsetParam() < 0) {
884        dwc.setOffsetParam(0);
885      }
886    }
887    if ("count".equals(name)) {
888      focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
889      focus.getExpansion().addParameter().setName(name).setValue(value);
890      dwc.setCountParam(((IntegerType) value).getValue());
891      if (dwc.getCountParam() < 0) {
892        dwc.setCountParam(0);
893      }
894    }
895  }
896  
897  public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams) throws FHIRException, ETooCostly, FileNotFoundException, IOException, CodeSystemProviderExtension {
898    if (expParams == null)
899      expParams = makeDefaultExpansion();
900    altCodeParams.seeParameters(expParams);
901    altCodeParams.seeValueSet(source);
902    source.checkNoModifiers("ValueSet", "expanding");
903    focus = source.copy();
904    focus.setIdBase(null);
905    focus.setExpansion(new ValueSet.ValueSetExpansionComponent());
906    focus.getExpansion().setTimestampElement(DateTimeType.now());
907    focus.getExpansion().setIdentifier(Factory.createUUID()); 
908    checkCanonical(focus.getExpansion(), focus, focus);
909    for (Extension ext : focus.getCompose().getExtensionsByUrl("http://hl7.org/fhir/tools/StructureDefinion/valueset-expansion-param")) {
910      processParameter(ext.getExtensionString("name"), ext.getExtensionByUrl("value").getValue());
911    }
912    for (ParametersParameterComponent p : expParams.getParameter()) {
913      processParameter(p.getName(), p.getValue());
914    }
915    for (Extension s : focus.getExtensionsByUrl(ExtensionConstants.EXT_VSSUPPLEMENT)) {
916      requiredSupplements.add(s.getValue().primitiveValue());
917    }
918    if (langs == null && focus.hasLanguage()) {
919      langs = new AcceptLanguageHeader(focus.getLanguage(), true);
920    }
921
922    try {
923      if (source.hasCompose()) {
924//        ExtensionsUtils.stripExtensions(focus.getCompose()); - disabled 23/05/2023 GDG - why was this ever thought to be a good idea?
925        handleCompose(source.getCompose(), focus.getExpansion(), expParams, source.getUrl(), focus.getExpansion().getExtension(), source);
926      }
927    } catch (EFinished e) {
928      // nothing - we intended to trap this here
929    }
930
931    if (dwc.getCount() > maxExpansionSize && dwc.getOffsetParam() + dwc.getCountParam() == 0) {
932      if (dwc.isNoTotal()) {
933        throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize)));        
934      } else {
935        throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY_COUNT, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize), MessageFormat.format("{0,number,#}", dwc.getCount())));
936      }
937    } else if (dwc.isCanBeHierarchy() && ((dwc.getCountParam() == 0) || dwc.getCountParam() > dwc.getCodes().size())) {
938      for (ValueSetExpansionContainsComponent c : dwc.getRoots()) {
939        focus.getExpansion().getContains().add(c);
940      }
941    } else {
942      int i = 0;
943      int cc = 0;
944      for (ValueSetExpansionContainsComponent c : dwc.getCodes()) {
945        c.getContains().clear(); // make sure any hierarchy is wiped
946        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
947          if (dwc.getOffsetParam() == 0 || i >= dwc.getOffsetParam()) {
948            focus.getExpansion().getContains().add(c);
949            cc++;
950            if (cc == dwc.getCountParam()) {
951              break;
952            }
953          }
954          i++;
955        }
956      }
957    }
958
959    if (dwc.hasOffsetParam()) {
960      focus.getExpansion().setOffset(dwc.getOffsetParam());
961    }
962    if (!dwc.isNoTotal()) {
963      focus.getExpansion().setTotal(dwc.getStatedTotal());
964    }
965    if (!requiredSupplements.isEmpty()) {      
966      return new ValueSetExpansionOutcome(context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements)), TerminologyServiceErrorClass.BUSINESS_RULE, allErrors, false);
967    }
968    if (!expParams.hasParameter("includeDefinition") || !expParams.getParameterBool("includeDefinition")) {
969      focus.setCompose(null);
970      focus.getExtension().clear();
971      focus.setPublisher(null);
972      focus.setDescription(null);
973      focus.setPurpose(null);
974      focus.getContact().clear();
975      focus.setCopyright(null);
976      focus.setText(null);
977    }
978    return new ValueSetExpansionOutcome(focus);
979  }
980
981
982  private Parameters makeDefaultExpansion() {
983    Parameters res = new Parameters();
984    res.addParameter("excludeNested", true);
985    res.addParameter("includeDesignations", false);
986    return res;
987  }
988
989  private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) {
990    for (ConceptDefinitionComponent c : clist) {
991      if (code.equals(c.getCode()))
992        return c;
993      ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code);
994      if (v != null)
995        return v;
996    }
997    return null;
998  }
999
1000  private void handleCompose(ValueSetComposeComponent compose, ValueSetExpansionComponent exp, Parameters expParams, String ctxt, List<Extension> extensions, ValueSet valueSet)
1001      throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension {
1002    compose.checkNoModifiers("ValueSet.compose", "expanding");
1003    String vspath = "ValueSet["+valueSet.getVersionedUrl()+"].compose";
1004    
1005    // Exclude comes first because we build up a map of things to exclude
1006    int c = 0;
1007    for (ConceptSetComponent inc : compose.getExclude()) {
1008      excludeCodes(dwc, inc, expParams, exp, valueSet, vspath+".include["+c+"]");
1009      c++;
1010    }
1011    dwc.setCanBeHierarchy(!expParams.getParameterBool("excludeNested") && dwc.getExcludeKeys().isEmpty() && dwc.getExcludeSystems().isEmpty() && dwc.getOffsetParam() == 0);
1012    includeAbstract = !expParams.getParameterBool("excludeNotForUI");
1013    boolean first = true;
1014    c = 0;
1015    for (ConceptSetComponent inc : compose.getInclude()) {
1016      if (first == true)
1017        first = false;
1018      else
1019        dwc.setCanBeHierarchy(false);
1020      includeCodes(inc, exp, expParams, dwc.isCanBeHierarchy(), compose.hasInactive() ? !compose.getInactive() : checkNoInActiveFromParam(expParams), extensions, valueSet, vspath+".include["+c+"]");
1021      c++;
1022    }
1023  }
1024
1025  /**
1026   * returns true if activeOnly = true 
1027   * @param expParams
1028   * @return
1029   */
1030  private boolean checkNoInActiveFromParam(Parameters expParams) {
1031    for (ParametersParameterComponent p : expParams.getParameter()) {
1032      if (p.getName().equals("activeOnly")) {
1033        return p.getValueBooleanType().getValue();
1034      }
1035    }
1036    return false;
1037  }
1038
1039  private ValueSet importValueSet(WorkingContext wc, String value, ValueSetExpansionComponent exp, Parameters expParams, boolean noInactive, ValueSet valueSet) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError {
1040    if (value == null)
1041      throw fail(I18nConstants.VS_EXP_IMPORT_NULL, true);
1042    String url = getCu().pinValueSet(value, expParams);
1043    ValueSet vs = context.findTxResource(ValueSet.class, url, valueSet);
1044    if (vs == null) {
1045      boolean pinned = !url.equals(value);
1046      String ver = pinned ? url.substring(value.length()+1) : null;
1047      if (context.fetchResource(CodeSystem.class, url, valueSet) != null) {
1048        throw failNotFound(pinned ? I18nConstants.VS_EXP_IMPORT_CS_PINNED : I18nConstants.VS_EXP_IMPORT_CS, true, value, ver);
1049      } else  {
1050        throw failNotFound(pinned ? I18nConstants.VS_EXP_IMPORT_UNK_PINNED : I18nConstants.VS_EXP_IMPORT_UNK, true, value, ver);
1051      }
1052    }
1053    checkCanonical(exp, vs, focus);
1054    if (noInactive) {
1055      expParams = expParams.copy();
1056      expParams.addParameter("activeOnly", true);
1057    }
1058    ValueSetExpander expander = new ValueSetExpander(context, opContext.copy(), allErrors);
1059    ValueSetExpansionOutcome vso = expander.expand(vs, expParams);
1060    if (vso.getError() != null) {
1061      addErrors(vso.getAllErrors());
1062      if (vso.getErrorClass() == TerminologyServiceErrorClass.VALUESET_UNKNOWN) {  
1063        throw failUnk(I18nConstants.VS_EXP_IMPORT_ERROR, true, vs.getUrl(), vso.getError());
1064      } else {
1065        throw fail(I18nConstants.VS_EXP_IMPORT_ERROR, true, vs.getUrl(), vso.getError());
1066      }
1067    } else if (vso.getValueset() == null) {
1068      throw fail(I18nConstants.VS_EXP_IMPORT_FAIL, true, vs.getUrl());      
1069    }
1070    sources.addAll(expander.sources);
1071    if (vs.hasVersion() || REPORT_VERSION_ANYWAY) {
1072      UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : ""));
1073      if (!existsInParams(exp.getParameter(), "used-valueset", u))
1074        exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u));
1075    }
1076    ValueSetExpansionComponent evs = vso.getValueset().getExpansion();
1077    for (Extension ex : evs.getExtension()) {
1078      if (ex.getUrl().equals(ToolingExtensions.EXT_EXP_TOOCOSTLY)) {
1079        if (ex.getValue() instanceof BooleanType) {
1080          exp.getExtension().add(new Extension(ToolingExtensions.EXT_EXP_TOOCOSTLY).setValue(new CanonicalType(value)));
1081        } else {
1082          exp.getExtension().add(ex);
1083        }
1084      } 
1085    }
1086    if (!evs.hasTotal()) {
1087      // because if there's no total, we can't know if we got everything
1088      dwc.setNoTotal(true);
1089    }
1090    for (ValueSetExpansionParameterComponent p : evs.getParameter()) {
1091      if (!existsInParams(exp.getParameter(), p.getName(), p.getValue()))
1092        exp.getParameter().add(p);
1093    }
1094    if (isValueSetUnionImports(valueSet)) {
1095      copyExpansion(wc, evs.getContains());
1096    }
1097    wc.setCanBeHierarchy(false); // if we're importing a value set, we have to be combining, so we won't try for a hierarchy
1098    return vso.getValueset();
1099  }
1100  
1101
1102
1103  private ValueSet importValueSetForExclude(WorkingContext wc, String value, ValueSetExpansionComponent exp, Parameters expParams, boolean noInactive, ValueSet valueSet) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError {
1104    if (value == null)
1105      throw fail(I18nConstants.VS_EXP_IMPORT_NULL_X, true);  
1106    String url = getCu().pinValueSet(value, expParams);
1107    ValueSet vs = context.findTxResource(ValueSet.class, url, valueSet);
1108    if (vs == null) {
1109      boolean pinned = !url.equals(value);
1110      String ver = pinned ? url.substring(value.length()+1) : null;
1111      if (context.fetchResource(CodeSystem.class, url, valueSet) != null) {
1112        throw fail(pinned ? I18nConstants.VS_EXP_IMPORT_CS_PINNED_X : I18nConstants.VS_EXP_IMPORT_CS_X, true, value, ver);
1113      } else  {
1114        throw fail(pinned ? I18nConstants.VS_EXP_IMPORT_UNK_PINNED_X : I18nConstants.VS_EXP_IMPORT_UNK_X, true, value, ver);
1115      }
1116    }
1117    checkCanonical(exp, vs, focus);
1118    if (noInactive) {
1119      expParams = expParams.copy();
1120      expParams.addParameter("activeOnly", true);
1121    }
1122    ValueSetExpander expander = new ValueSetExpander(context, opContext.copy(), allErrors);
1123    ValueSetExpansionOutcome vso = expander.expand(vs, expParams);
1124    sources.addAll(expander.sources);
1125    if (vso.getError() != null) {
1126      addErrors(vso.getAllErrors());
1127      throw fail(I18nConstants.VS_EXP_IMPORT_ERROR_X, true, vs.getUrl(), vso.getError());
1128    } else if (vso.getValueset() == null) {
1129      throw fail(I18nConstants.VS_EXP_IMPORT_FAIL_X, true, vs.getUrl());      
1130    }
1131    
1132    if (vs.hasVersion() || REPORT_VERSION_ANYWAY) {
1133      UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : ""));
1134      if (!existsInParams(exp.getParameter(), "used-valueset", u))
1135        exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u));
1136    }
1137    for (Extension ex : vso.getValueset().getExpansion().getExtension()) {
1138      if (ex.getUrl().equals(ToolingExtensions.EXT_EXP_TOOCOSTLY)) {
1139        throw fail(VS_EXP_IMPORT_ERROR_TOO_COSTLY, true, vs.getUrl());
1140      } 
1141    }
1142    return vso.getValueset();
1143  }
1144  
1145  protected boolean isValueSetUnionImports(ValueSet valueSet) {
1146    PackageInformation p = valueSet.getSourcePackage();
1147    if (p != null) {
1148      return p.getDate().before(new GregorianCalendar(2022, Calendar.MARCH, 31).getTime());
1149    } else {
1150      return false;
1151    }
1152  }
1153
1154  public void copyExpansion(WorkingContext wc,List<ValueSetExpansionContainsComponent> list) {
1155    opContext.deadCheck("copyExpansion");
1156    for (ValueSetExpansionContainsComponent cc : list) {
1157       ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
1158       n.setSystem(cc.getSystem());
1159       n.setCode(cc.getCode());
1160       n.setAbstract(cc.getAbstract());
1161       n.setInactive(cc.getInactive());
1162       n.setDisplay(cc.getDisplay());
1163       n.getDesignation().addAll(cc.getDesignation());
1164
1165       String s = key(n);
1166       if (!wc.getMap().containsKey(s) && !wc.getExcludeKeys().contains(s)) {
1167         wc.getCodes().add(n);
1168         wc.getMap().put(s, n);
1169       }
1170       copyExpansion(wc, cc.getContains());
1171    }
1172  }
1173
1174  private void addErrors(List<String> errs) {
1175    for (String s : errs) {
1176      if (!allErrors.contains(s)) {
1177        allErrors.add(s);
1178      }
1179    }
1180  }
1181
1182  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 {
1183    int count = 0;
1184    opContext.deadCheck("copyImportContains");
1185    
1186    String lang = vsSrc.getLanguage();
1187    if (lang == null) { 
1188      lang = findParamValue(vsSrc.getExpansion().getParameter(), "displayLanguage");
1189    }
1190    for (ValueSetExpansionContainsComponent c : list) {
1191      c.checkNoModifiers("Imported Expansion in Code System", "expanding");
1192      ValueSetExpansionContainsComponent np = addCode(dwc, c.getSystem(), c.getCode(), c.getDisplay(), lang, parent, translateDesignations(c), expParams, c.getAbstract(), c.getInactive(), 
1193          filter, noInactive, false, vsProps, makeCSProps(c.getExtensionString(ToolingExtensions.EXT_DEFINITION), null), null, c.getProperty(), null, c.getExtension(), exp);
1194      if (np != null) {
1195        count++;
1196      }
1197      count = count + copyImportContains(c.getContains(), np, expParams, filter, noInactive, vsProps, vsSrc, exp);
1198    }
1199    return count;
1200  }
1201
1202  private List<ConceptDefinitionDesignationComponent> translateDesignations(ValueSetExpansionContainsComponent c) {
1203    if (!c.hasDesignation()) {
1204      return null;
1205    }
1206    List<ConceptDefinitionDesignationComponent> list = new ArrayList<>();
1207    for (ConceptReferenceDesignationComponent d : c.getDesignation()) {
1208      ConceptDefinitionDesignationComponent d2 = new ConceptDefinitionDesignationComponent();
1209      d2.setLanguage(d.getLanguage());
1210      d2.setUse(d.getUse());
1211      d2.setAdditionalUse(d.getAdditionalUse());
1212      d2.setValue(d.getValue());
1213      list.add(d2);
1214    }
1215    return list;
1216  }
1217
1218  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 {
1219    opContext.deadCheck("includeCodes");
1220    inc.checkNoModifiers("Compose.include", "expanding");
1221    List<ValueSet> imports = new ArrayList<ValueSet>();
1222    for (CanonicalType imp : inc.getValueSet()) {
1223      imports.add(importValueSet(dwc, imp.getValue(), exp, expParams, noInactive, valueSet));
1224    }
1225
1226    if (!inc.hasSystem()) {
1227      if (imports.isEmpty()) // though this is not supposed to be the case
1228        return;
1229      ValueSet base = imports.get(0);
1230      checkCanonical(exp, base, focus);
1231      imports.remove(0);
1232      base.checkNoModifiers("Imported ValueSet", "expanding");
1233      copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive, base.getExpansion().getProperty(), base, exp);
1234    } else {
1235      CodeSystem cs = context.fetchSupplementedCodeSystem(inc.getSystem());
1236      if (ValueSetUtilities.isServerSide(inc.getSystem()) || (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT))) {
1237        doServerIncludeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, valueSet.getExpansion().getProperty());
1238      } else {
1239        if (cs.hasUserData(UserDataNames.tx_known_supplements)) {
1240          for (String s : cs.getUserString(UserDataNames.tx_known_supplements).split("\\,")) {
1241            requiredSupplements.remove(s);
1242          }
1243        }
1244        doInternalIncludeCodes(inc, exp, expParams, imports, cs, noInactive, valueSet, vspath);
1245      }
1246    }
1247  }
1248
1249  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 {
1250    opContext.deadCheck("doServerIncludeCodes");
1251    CodeSystemProvider csp = CodeSystemProvider.factory(inc.getSystem());
1252    if (csp != null) {
1253      csp.includeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, vsProps);
1254      return;
1255    }
1256    
1257    ValueSetExpansionOutcome vso = context.expandVS(new TerminologyOperationDetails(requiredSupplements), inc, heirarchical, noInactive);
1258    if (vso.getError() != null) {
1259      throw failTSE("Unable to expand imported value set: " + vso.getError());
1260    }
1261    ValueSet vs = vso.getValueset();
1262    if (vs.hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) {
1263      sources.add(vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE));
1264    }
1265    if (vs.hasVersion() || REPORT_VERSION_ANYWAY) {
1266      UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : ""));
1267      if (!existsInParams(exp.getParameter(), "used-valueset", u)) {
1268        exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u));
1269      }
1270    }
1271    if (vs.getExpansion().hasTotal()) {
1272      // 0 for now... dwc.incExtraCount(!vs.getExpansion().getTotal());
1273    } else {
1274      dwc.setNoTotal(true);
1275    }
1276    for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) {
1277      if (!existsInParams(exp.getParameter(), p.getName(), p.getValue())) {
1278        exp.getParameter().add(p);
1279      }
1280    }
1281    for (Extension ex : vs.getExpansion().getExtension()) {
1282      if (Utilities.existsInList(ex.getUrl(), ToolingExtensions.EXT_EXP_TOOCOSTLY, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed")) {
1283        if (!ExtensionsUtils.hasExtension(extensions, ex.getUrl())) {
1284          extensions.add(ex);
1285        }
1286      }
1287    }
1288    for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) {
1289      addCodeAndDescendents(dwc, cc, null, expParams, imports, noInactive, vsProps, vs, exp, getLang(vs));
1290    }
1291  }
1292
1293
1294  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 {
1295    opContext.deadCheck("doInternalIncludeCodes");
1296    if (cs == null) {
1297      if (context.isNoTerminologyServer())
1298        throw failTSE("Unable to find code system " + inc.getSystem().toString());
1299      else
1300        throw failTSE("Unable to find code system " + inc.getSystem().toString());
1301    }
1302    checkCanonical(exp, cs, focus);
1303    cs.checkNoModifiers("Code System", "expanding");
1304    if (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT)
1305      throw failTSE("Code system " + inc.getSystem().toString() + " is incomplete");
1306    if (cs.hasVersion() || REPORT_VERSION_ANYWAY) {
1307      UriType u = new UriType(cs.getUrl() + (cs.hasVersion() ? "|"+cs.getVersion() : ""));
1308      if (!existsInParams(exp.getParameter(), "used-codesystem", u))
1309        exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-codesystem").setValue(u));
1310      if (cs.hasUserData(UserDataNames.tx_known_supplements)) {
1311        for (String s : cs.getUserString(UserDataNames.tx_known_supplements).split("\\,")) {
1312          u = new UriType(s);
1313          if (!existsInParams(exp.getParameter(), "used-supplement", u)) {
1314            exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-supplement").setValue(u));
1315          }
1316        }
1317      }
1318    }
1319    if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
1320      // special case - add all the code system
1321      for (ConceptDefinitionComponent def : cs.getConcept()) {
1322        addCodeAndDescendents(dwc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), null, exp);
1323      }
1324      if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
1325        addFragmentWarning(exp, cs);
1326      }
1327      if (cs.getContent() == CodeSystemContentMode.EXAMPLE) {
1328        addExampleWarning(exp, cs);
1329      }      
1330    }
1331
1332    if (!inc.getConcept().isEmpty()) {
1333      dwc.setCanBeHierarchy(false);
1334      for (ConceptReferenceComponent c : inc.getConcept()) {
1335        c.checkNoModifiers("Code in Value Set", "expanding");
1336        ConceptDefinitionComponent def = CodeSystemUtilities.findCodeOrAltCode(cs.getConcept(), c.getCode(), null);
1337        boolean inactive = false; // default is true if we're a fragment and  
1338        boolean isAbstract = false;
1339        if (def == null) {
1340          if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
1341            addFragmentWarning(exp, cs);
1342          } else if (cs.getContent() == CodeSystemContentMode.EXAMPLE) {
1343              addExampleWarning(exp, cs);
1344          } else {
1345            if (checkCodesWhenExpanding) {
1346              throw failTSE("Unable to find code '" + c.getCode() + "' in code system " + cs.getUrl());
1347            }
1348          }
1349        } else {
1350          def.checkNoModifiers("Code in Code System", "expanding");
1351          inactive = CodeSystemUtilities.isInactive(cs, def);
1352          isAbstract = CodeSystemUtilities.isNotSelectable(cs, def);
1353          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())), 
1354              expParams, isAbstract, inactive, imports, noInactive, false, exp.getProperty(), makeCSProps(def.getDefinition(), def.getProperty()), cs, null, def.getExtension(), c.getExtension(), exp);
1355        }
1356      }
1357    }
1358    if (inc.getFilter().size() > 0) {
1359      if (inc.getFilter().size() > 1) {
1360        dwc.setCanBeHierarchy(false); // which will be the case if we get around to supporting this
1361      }
1362      if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
1363        addFragmentWarning(exp, cs);
1364      }
1365      List<WorkingContext> filters = new ArrayList<>();
1366      for (int i = 1; i < inc.getFilter().size(); i++) {
1367        WorkingContext wc = new WorkingContext();
1368        filters.add(wc);
1369        processFilter(inc, exp, expParams, imports, cs, noInactive, inc.getFilter().get(i), wc, null, false, vspath+".filter["+i+"]");
1370      }
1371      ConceptSetFilterComponent fc = inc.getFilter().get(0);
1372      WorkingContext wc = dwc;
1373      processFilter(inc, exp, expParams, imports, cs, noInactive, fc, wc, filters, false, vspath+".filter[0]");
1374    }
1375  }
1376
1377  private void processFilter(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List<ValueSet> imports, CodeSystem cs, boolean noInactive, 
1378      ConceptSetFilterComponent fc, WorkingContext wc, List<WorkingContext> filters, boolean exclude, String vspath)
1379      throws ETooCostly {
1380    
1381    if (!fc.hasValue() || fc.getValue() == null) {
1382      List<OperationOutcomeIssueComponent> issues = new ArrayList<>();
1383      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)); 
1384      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);
1385    }
1386    opContext.deadCheck("processFilter");
1387    if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) {
1388      // special: all codes in the target code system under the value
1389      ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
1390      if (def == null)
1391        throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
1392      if (exclude) {
1393        excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, new AllConceptsFilter(allErrors), filters, exp);
1394      } else {
1395        addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
1396      }
1397    } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISNOTA) {
1398      // special: all codes in the target code system that are not under the value
1399      ConceptDefinitionComponent defEx = getConceptForCode(cs.getConcept(), fc.getValue());
1400      if (defEx == null)
1401        throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
1402      for (ConceptDefinitionComponent def : cs.getConcept()) {
1403        if (exclude) {
1404          excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, defEx, new AllConceptsFilter(allErrors), filters, exp);
1405        } else {
1406          addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, defEx, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
1407        }
1408      }
1409    } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.DESCENDENTOF) {
1410      // special: all codes in the target code system under the value
1411      ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
1412      if (def == null)
1413        throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
1414      for (ConceptDefinitionComponent c : def.getConcept())
1415        if (exclude) {
1416          excludeCodeAndDescendents(wc, cs, inc.getSystem(), c, null, imports, null, new AllConceptsFilter(allErrors), filters, exp);
1417        } else {
1418          addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
1419        }
1420      if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) {
1421        List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK);
1422        for (ConceptDefinitionComponent c : children) {
1423          if (exclude) {
1424            excludeCodeAndDescendents(wc, cs, inc.getSystem(), c, null, imports, null, new AllConceptsFilter(allErrors), filters, exp);
1425          } else {
1426            addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
1427          }
1428        }
1429      }
1430
1431    } else if ("display".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) {
1432      // gg; note: wtf is this: if the filter is display=v, look up the code 'v', and see if it's display is 'v'?
1433      dwc.setCanBeHierarchy(false);
1434      ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
1435      if (def != null) {
1436        if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) {
1437          if (def.getDisplay().contains(fc.getValue()) && passesOtherFilters(filters, cs, def.getCode())) {
1438            for (String code : getCodesForConcept(def, expParams)) {
1439              opContext.deadCheck("processFilter2");
1440              if (exclude) {
1441                excludeCode(wc, inc.getSystem(), code);
1442              } else {
1443                addCode(wc, inc.getSystem(), code, def.getDisplay(), cs.getLanguage(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def),
1444                  imports, noInactive, false, exp.getProperty(), makeCSProps(def.getDefinition(), def.getProperty()), cs, null, def.getExtension(), null, exp);
1445              }
1446            }
1447          }
1448        }
1449      }
1450    } else if (CodeSystemUtilities.isDefinedProperty(cs, fc.getProperty())) {
1451      for (ConceptDefinitionComponent def : cs.getConcept()) {
1452        PropertyFilter pf = new PropertyFilter(allErrors, fc, CodeSystemUtilities.getPropertyDefinition(cs, fc.getProperty()));
1453        if (exclude) {
1454          excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, pf, filters, exp);
1455        } else {
1456          addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, pf, noInactive, exp.getProperty(), filters, exp);
1457        }
1458      }
1459    } else if (isKnownProperty(fc.getProperty(), cs)) {
1460      for (ConceptDefinitionComponent def : cs.getConcept()) {
1461        KnownPropertyFilter pf = new KnownPropertyFilter(allErrors, fc, fc.getProperty());
1462        if (exclude) {
1463          excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, pf, filters, exp);
1464        } else {
1465          addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, pf, noInactive, exp.getProperty(), filters, exp);
1466        }
1467      }
1468    } else if ("code".equals(fc.getProperty()) && fc.getOp() == FilterOperator.REGEX) {
1469      for (ConceptDefinitionComponent def : cs.getConcept()) {
1470        if (exclude) {
1471          excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, new RegexFilter(allErrors, fc.getValue()), filters, exp);
1472        } else {
1473          addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new RegexFilter(allErrors, fc.getValue()), noInactive, exp.getProperty(), filters, exp);
1474        }
1475      }
1476    } else {
1477      throw fail(I18nConstants.VS_EXP_FILTER_UNK, true, focus.getVersionedUrl(), fc.getProperty(), fc.getOp());
1478    }
1479  }
1480
1481  private boolean isKnownProperty(String property, CodeSystem cs) {
1482    return Utilities.existsInList(property, "notSelectable");
1483  }
1484
1485  private List<ConceptDefinitionDesignationComponent> mergeDesignations(ConceptDefinitionComponent def,
1486      List<ConceptDefinitionDesignationComponent> list) {
1487    List<ConceptDefinitionDesignationComponent> res = new ArrayList<>();
1488    if (def != null) {
1489      res.addAll(def.getDesignation());
1490    }
1491    res.addAll(list);
1492    return res;
1493  }
1494
1495 
1496
1497  private void addFragmentWarning(ValueSetExpansionComponent exp, CodeSystem cs) {
1498    String url = cs.getVersionedUrl();
1499    for (ValueSetExpansionParameterComponent p : exp.getParameter()) {
1500      if ("fragment".equals(p.getName()) && p.hasValueUriType() && url.equals(p.getValue().primitiveValue())) { 
1501        return;
1502      }     
1503    }
1504    exp.addParameter().setName("fragment").setValue(new CanonicalType(url));
1505  }
1506
1507  private void addExampleWarning(ValueSetExpansionComponent exp, CodeSystem cs) {
1508    String url = cs.getVersionedUrl();
1509    for (ValueSetExpansionParameterComponent p : exp.getParameter()) {
1510      if ("example".equals(p.getName()) && p.hasValueUriType() && url.equals(p.getValue().primitiveValue())) { 
1511        return;
1512      }     
1513    }
1514    exp.addParameter().setName("example").setValue(new CanonicalType(url));
1515  }
1516  
1517  private List<ConceptDefinitionDesignationComponent> convertDesignations(List<ConceptReferenceDesignationComponent> list) {
1518    List<ConceptDefinitionDesignationComponent> res = new ArrayList<CodeSystem.ConceptDefinitionDesignationComponent>();
1519    for (ConceptReferenceDesignationComponent t : list) {
1520      ConceptDefinitionDesignationComponent c = new ConceptDefinitionDesignationComponent();
1521      c.setLanguage(t.getLanguage());
1522      if (t.hasUse()) {
1523        c.setUse(t.getUse());
1524      }
1525      c.setValue(t.getValue());
1526      c.getExtension().addAll(t.getExtension());
1527      res.add(c);
1528    }
1529    return res;
1530  }
1531
1532  private String key(String uri, String code) {
1533    return "{" + uri + "}" + code;
1534  }
1535
1536  private String key(ValueSetExpansionContainsComponent c) {
1537    return key(c.getSystem(), c.getCode());
1538  }
1539
1540  private FHIRException fail(String msgId, boolean check, Object... params) {
1541    String msg = context.formatMessage(msgId, params);
1542    allErrors.add(msg);
1543    return new FHIRException(msg);
1544  }
1545
1546  private UnknownValueSetException failUnk(String msgId, boolean check, Object... params) {
1547    String msg = context.formatMessage(msgId, params);
1548    allErrors.add(msg);
1549    return new UnknownValueSetException(msg);
1550  }
1551
1552  private UnknownValueSetException failNotFound(String msgId, boolean check, Object... params) {
1553    String msg = context.formatMessage(msgId, params);
1554    allErrors.add(msg);
1555    return new UnknownValueSetException(msg);
1556  }
1557
1558  private ETooCostly failCostly(String msg) {
1559    allErrors.add(msg);
1560    return new ETooCostly(msg);
1561  }
1562
1563  private TerminologyServiceException failTSE(String msg) {
1564    allErrors.add(msg);
1565    return new TerminologyServiceException(msg);
1566  }
1567
1568  public Collection<? extends String> getAllErrors() {
1569    return allErrors;
1570  }
1571
1572  public boolean isCheckCodesWhenExpanding() {
1573    return checkCodesWhenExpanding;
1574  }
1575
1576  public void setCheckCodesWhenExpanding(boolean checkCodesWhenExpanding) {
1577    this.checkCodesWhenExpanding = checkCodesWhenExpanding;
1578  }
1579
1580  private boolean passesOtherFilters(List<WorkingContext> otherFilters, CodeSystem cs, String code) {
1581    if (otherFilters == null) {
1582      return true;
1583    }
1584    String key = key(cs.getUrl(), code);
1585    for (WorkingContext wc : otherFilters) {
1586      if (!wc.getMap().containsKey(key)) {
1587        return false;
1588      }
1589    }
1590    return true;
1591  }
1592
1593  public boolean isDebug() {
1594    return debug;
1595  }
1596
1597  public ValueSetExpander setDebug(boolean debug) {
1598    this.debug = debug;
1599    return this;
1600  }
1601
1602  public String getSource() {
1603    if (sources.isEmpty()) {
1604      return "internal";
1605    } else {
1606      return CommaSeparatedStringBuilder.join(", ", Utilities.sorted(sources));
1607    }
1608  }
1609  
1610  
1611}