001package org.hl7.fhir.common.hapi.validation.support;
002
003import ca.uhn.fhir.i18n.Msg;
004import ca.uhn.fhir.context.FhirContext;
005import ca.uhn.fhir.context.FhirVersionEnum;
006import ca.uhn.fhir.context.support.ConceptValidationOptions;
007import ca.uhn.fhir.context.support.IValidationSupport;
008import ca.uhn.fhir.context.support.ValidationSupportContext;
009import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
010import ca.uhn.fhir.parser.IParser;
011import ca.uhn.fhir.util.FhirVersionIndependentConcept;
012import org.apache.commons.lang3.Validate;
013import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_10_50;
014import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_30_50;
015import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50;
016import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50;
017import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50;
018import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
019import org.hl7.fhir.dstu2.model.ValueSet;
020import org.hl7.fhir.instance.model.api.IBaseResource;
021import org.hl7.fhir.instance.model.api.IPrimitiveType;
022import org.hl7.fhir.r5.model.CanonicalType;
023import org.hl7.fhir.r5.model.CodeSystem;
024import org.hl7.fhir.utilities.validation.ValidationMessage;
025
026import javax.annotation.Nonnull;
027import javax.annotation.Nullable;
028import java.util.ArrayList;
029import java.util.Collections;
030import java.util.HashSet;
031import java.util.List;
032import java.util.Objects;
033import java.util.Optional;
034import java.util.Set;
035import java.util.function.Consumer;
036import java.util.function.Function;
037import java.util.stream.Collectors;
038
039import static org.apache.commons.lang3.StringUtils.defaultString;
040import static org.apache.commons.lang3.StringUtils.isBlank;
041import static org.apache.commons.lang3.StringUtils.isNotBlank;
042
043/**
044 * This class is a basic in-memory terminology service, designed to expand ValueSets and validate codes
045 * completely in-memory. It is suitable for runtime validation purposes where no dedicated terminology
046 * service exists (either an internal one such as the HAPI FHIR JPA terminology service, or an
047 * external term service API)
048 */
049public class InMemoryTerminologyServerValidationSupport implements IValidationSupport {
050        private final FhirContext myCtx;
051
052        public InMemoryTerminologyServerValidationSupport(FhirContext theCtx) {
053                Validate.notNull(theCtx, "theCtx must not be null");
054                myCtx = theCtx;
055        }
056
057        @Override
058        public FhirContext getFhirContext() {
059                return myCtx;
060        }
061
062        @Override
063        public ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, @Nonnull IBaseResource theValueSetToExpand) {
064                return expandValueSet(theValidationSupportContext, theExpansionOptions, theValueSetToExpand, null, null);
065        }
066
067        private ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, String theWantSystemAndVersion, String theWantCode) {
068                org.hl7.fhir.r5.model.ValueSet expansionR5;
069                try {
070                        expansionR5 = expandValueSetToCanonical(theValidationSupportContext, theValueSetToExpand, theWantSystemAndVersion, theWantCode);
071                } catch (ExpansionCouldNotBeCompletedInternallyException e) {
072                        return new ValueSetExpansionOutcome(e.getMessage());
073                }
074                if (expansionR5 == null) {
075                        return null;
076                }
077
078                IBaseResource expansion;
079                switch (myCtx.getVersion().getVersion()) {
080                        case DSTU2_HL7ORG: {
081                                expansion = VersionConvertorFactory_10_50.convertResource(expansionR5, new BaseAdvisor_10_50(false));
082                                break;
083                        }
084                        case DSTU3: {
085                                expansion = VersionConvertorFactory_30_50.convertResource(expansionR5, new BaseAdvisor_30_50(false));
086                                break;
087                        }
088                        case R4: {
089                                expansion = VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false));
090                                break;
091                        }
092                        case R5: {
093                                expansion = expansionR5;
094                                break;
095                        }
096                        case DSTU2:
097                        case DSTU2_1:
098                        default:
099                                throw new IllegalArgumentException(Msg.code(697) + "Can not handle version: " + myCtx.getVersion().getVersion());
100                }
101
102                return new ValueSetExpansionOutcome(expansion);
103        }
104
105        private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical(ValidationSupportContext theValidationSupportContext, IBaseResource theValueSetToExpand, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
106                org.hl7.fhir.r5.model.ValueSet expansionR5;
107                switch (theValueSetToExpand.getStructureFhirVersionEnum()) {
108                        case DSTU2: {
109                                expansionR5 = expandValueSetDstu2(theValidationSupportContext, (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode);
110                                break;
111                        }
112                        case DSTU2_HL7ORG: {
113                                expansionR5 = expandValueSetDstu2Hl7Org(theValidationSupportContext, (ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode);
114                                break;
115                        }
116                        case DSTU3: {
117                                expansionR5 = expandValueSetDstu3(theValidationSupportContext, (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode);
118                                break;
119                        }
120                        case R4: {
121                                expansionR5 = expandValueSetR4(theValidationSupportContext, (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode);
122                                break;
123                        }
124                        case R5: {
125                                expansionR5 = expandValueSetR5(theValidationSupportContext, (org.hl7.fhir.r5.model.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode);
126                                break;
127                        }
128                        case DSTU2_1:
129                        default:
130                                throw new IllegalArgumentException(Msg.code(698) + "Can not handle version: " + myCtx.getVersion().getVersion());
131                }
132
133                return expansionR5;
134        }
135
136        @Override
137        public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemUrlAndVersion, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
138                org.hl7.fhir.r5.model.ValueSet expansion;
139                String vsUrl = CommonCodeSystemsTerminologyService.getValueSetUrl(theValueSet);
140                try {
141                        expansion = expandValueSetToCanonical(theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode);
142                } catch (ExpansionCouldNotBeCompletedInternallyException e) {
143                        CodeValidationResult codeValidationResult = new CodeValidationResult();
144                        codeValidationResult.setSeverityCode("error");
145
146                        String msg = "Failed to expand ValueSet '" + vsUrl + "' (in-memory). Could not validate code " + theCodeSystemUrlAndVersion + "#" + theCode;
147                        if (e.getMessage() != null) {
148                                msg += ". Error was: " + e.getMessage();
149                        }
150
151                        codeValidationResult.setMessage(msg);
152                        return codeValidationResult;
153                }
154
155                if (expansion == null) {
156                        return null;
157                }
158
159                return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystemUrlAndVersion, theCode, theDisplay, expansion, vsUrl);
160        }
161
162
163        @Override
164        @Nullable
165        public CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
166                IBaseResource vs;
167                if (isNotBlank(theValueSetUrl)) {
168                        vs = theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl);
169                        if (vs == null) {
170                                return null;
171                        }
172                } else {
173                        String codeSystemUrl;
174                        String codeSystemVersion = null;
175                        int codeSystemVersionIndex = theCodeSystem.indexOf("|");
176                        if (codeSystemVersionIndex > -1) {
177                                codeSystemUrl = theCodeSystem.substring(0, codeSystemVersionIndex);
178                                codeSystemVersion = theCodeSystem.substring(codeSystemVersionIndex + 1);
179                        } else {
180                                codeSystemUrl = theCodeSystem;
181                        }
182                        switch (myCtx.getVersion().getVersion()) {
183                                case DSTU2_HL7ORG:
184                                        vs = new org.hl7.fhir.dstu2.model.ValueSet()
185                                                .setCompose(new org.hl7.fhir.dstu2.model.ValueSet.ValueSetComposeComponent()
186                                                        .addInclude(new org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem)));
187                                        break;
188                                case DSTU3:
189                                        if (codeSystemVersion != null) {
190                                                vs = new org.hl7.fhir.dstu3.model.ValueSet()
191                                                        .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent()
192                                                                .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion)));
193                                        } else {
194                                                vs = new org.hl7.fhir.dstu3.model.ValueSet()
195                                                        .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent()
196                                                                .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem)));
197                                        }
198                                        break;
199                                case R4:
200                                        if (codeSystemVersion != null) {
201                                                vs = new org.hl7.fhir.r4.model.ValueSet()
202                                                        .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent()
203                                                                .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion)));
204                                        } else {
205                                                vs = new org.hl7.fhir.r4.model.ValueSet()
206                                                        .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent()
207                                                                .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem)));
208                                        }
209                                        break;
210                                case R5:
211                                        if (codeSystemVersion != null) {
212                                                vs = new org.hl7.fhir.r5.model.ValueSet()
213                                                        .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent()
214                                                                .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion)));
215                                        } else {
216                                                vs = new org.hl7.fhir.r5.model.ValueSet()
217                                                        .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent()
218                                                                .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem)));
219                                        }
220                                        break;
221                                case DSTU2:
222                                case DSTU2_1:
223                                default:
224                                        throw new IllegalArgumentException(Msg.code(699) + "Can not handle version: " + myCtx.getVersion().getVersion());
225                        }
226                }
227
228                ValueSetExpansionOutcome valueSetExpansionOutcome = expandValueSet(theValidationSupportContext, null, vs, theCodeSystem, theCode);
229                if (valueSetExpansionOutcome == null) {
230                        return null;
231                }
232
233                if (valueSetExpansionOutcome.getError() != null) {
234                        return new CodeValidationResult()
235                                .setSeverity(IssueSeverity.ERROR)
236                                .setMessage(valueSetExpansionOutcome.getError());
237                }
238
239                IBaseResource expansion = valueSetExpansionOutcome.getValueSet();
240                return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion, theValueSetUrl);
241        }
242
243        private CodeValidationResult validateCodeInExpandedValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemUrlAndVersionToValidate, String theCodeToValidate, String theDisplayToValidate, IBaseResource theExpansion, String theValueSetUrl) {
244                assert theExpansion != null;
245
246                boolean caseSensitive = true;
247                IBaseResource codeSystemToValidateResource = null;
248                if (!theOptions.isInferSystem() && isNotBlank(theCodeSystemUrlAndVersionToValidate)) {
249                        codeSystemToValidateResource = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theCodeSystemUrlAndVersionToValidate);
250                }
251
252                List<FhirVersionIndependentConcept> codes = new ArrayList<>();
253                switch (theExpansion.getStructureFhirVersionEnum()) {
254                        case DSTU2_HL7ORG: {
255                                ValueSet expansionVs = (ValueSet) theExpansion;
256                                List<ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains();
257                                flattenAndConvertCodesDstu2(contains, codes);
258                                break;
259                        }
260                        case DSTU3: {
261                                org.hl7.fhir.dstu3.model.ValueSet expansionVs = (org.hl7.fhir.dstu3.model.ValueSet) theExpansion;
262                                List<org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains();
263                                flattenAndConvertCodesDstu3(contains, codes);
264                                break;
265                        }
266                        case R4: {
267                                org.hl7.fhir.r4.model.ValueSet expansionVs = (org.hl7.fhir.r4.model.ValueSet) theExpansion;
268                                List<org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains();
269                                flattenAndConvertCodesR4(contains, codes);
270                                break;
271                        }
272                        case R5: {
273                                org.hl7.fhir.r5.model.ValueSet expansionVs = (org.hl7.fhir.r5.model.ValueSet) theExpansion;
274                                List<org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains();
275                                flattenAndConvertCodesR5(contains, codes);
276                                break;
277                        }
278                        case DSTU2:
279                        case DSTU2_1:
280                        default:
281                                throw new IllegalArgumentException(Msg.code(700) + "Can not handle version: " + myCtx.getVersion().getVersion());
282                }
283
284                String codeSystemResourceName = null;
285                String codeSystemResourceVersion = null;
286                String codeSystemResourceContentMode = null;
287                if (codeSystemToValidateResource != null) {
288                        switch (codeSystemToValidateResource.getStructureFhirVersionEnum()) {
289                                case DSTU2_HL7ORG: {
290                                        caseSensitive = true;
291                                        break;
292                                }
293                                case DSTU3: {
294                                        org.hl7.fhir.dstu3.model.CodeSystem systemDstu3 = (org.hl7.fhir.dstu3.model.CodeSystem) codeSystemToValidateResource;
295                                        caseSensitive = systemDstu3.getCaseSensitive();
296                                        codeSystemResourceName = systemDstu3.getName();
297                                        codeSystemResourceVersion = systemDstu3.getVersion();
298                                        codeSystemResourceContentMode = systemDstu3.getContentElement().getValueAsString();
299                                        break;
300                                }
301                                case R4: {
302                                        org.hl7.fhir.r4.model.CodeSystem systemR4 = (org.hl7.fhir.r4.model.CodeSystem) codeSystemToValidateResource;
303                                        caseSensitive = systemR4.getCaseSensitive();
304                                        codeSystemResourceName = systemR4.getName();
305                                        codeSystemResourceVersion = systemR4.getVersion();
306                                        codeSystemResourceContentMode = systemR4.getContentElement().getValueAsString();
307                                        break;
308                                }
309                                case R5: {
310                                        CodeSystem systemR5 = (CodeSystem) codeSystemToValidateResource;
311                                        caseSensitive = systemR5.getCaseSensitive();
312                                        codeSystemResourceName = systemR5.getName();
313                                        codeSystemResourceVersion = systemR5.getVersion();
314                                        codeSystemResourceContentMode = systemR5.getContentElement().getValueAsString();
315                                        break;
316                                }
317                                case DSTU2:
318                                case DSTU2_1:
319                                default:
320                                        throw new IllegalArgumentException(Msg.code(701) + "Can not handle version: " + myCtx.getVersion().getVersion());
321                        }
322                }
323
324                String codeSystemUrlToValidate = null;
325                String codeSystemVersionToValidate = null;
326                if (theCodeSystemUrlAndVersionToValidate != null) {
327                        int versionIndex = theCodeSystemUrlAndVersionToValidate.indexOf("|");
328                        if (versionIndex > -1) {
329                                codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate.substring(0, versionIndex);
330                                codeSystemVersionToValidate = theCodeSystemUrlAndVersionToValidate.substring(versionIndex + 1);
331                        } else {
332                                codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate;
333                        }
334                }
335                for (FhirVersionIndependentConcept nextExpansionCode : codes) {
336
337                        boolean codeMatches;
338                        if (caseSensitive) {
339                                codeMatches = defaultString(theCodeToValidate).equals(nextExpansionCode.getCode());
340                        } else {
341                                codeMatches = defaultString(theCodeToValidate).equalsIgnoreCase(nextExpansionCode.getCode());
342                        }
343                        if (codeMatches) {
344                                if (theOptions.isInferSystem() || (nextExpansionCode.getSystem().equals(codeSystemUrlToValidate) && (codeSystemVersionToValidate == null || codeSystemVersionToValidate.equals(nextExpansionCode.getSystemVersion())))) {
345                                        String csVersion = codeSystemResourceVersion;
346                                        if (isNotBlank(nextExpansionCode.getSystemVersion())) {
347                                                csVersion = nextExpansionCode.getSystemVersion();
348                                        }
349                                        if (!theOptions.isValidateDisplay() || (isBlank(nextExpansionCode.getDisplay()) || isBlank(theDisplayToValidate) || nextExpansionCode.getDisplay().equals(theDisplayToValidate))) {
350                                                CodeValidationResult codeValidationResult = new CodeValidationResult()
351                                                        .setCode(theCodeToValidate)
352                                                        .setDisplay(nextExpansionCode.getDisplay())
353                                                        .setCodeSystemName(codeSystemResourceName)
354                                                        .setCodeSystemVersion(csVersion);
355                                                if (isNotBlank(theValueSetUrl)) {
356                                                        codeValidationResult.setMessage("Code was validated against in-memory expansion of ValueSet: " + theValueSetUrl);
357                                                }
358                                                return codeValidationResult;
359                                        } else {
360                                                String message = "Concept Display \"" + theDisplayToValidate + "\" does not match expected \"" + nextExpansionCode.getDisplay() + "\"";
361                                                if (isNotBlank(theValueSetUrl)) {
362                                                        message += " for in-memory expansion of ValueSet: " + theValueSetUrl;
363                                                }
364                                                return new CodeValidationResult()
365                                                        .setSeverity(IssueSeverity.ERROR)
366                                                        .setDisplay(nextExpansionCode.getDisplay())
367                                                        .setMessage(message)
368                                                        .setCodeSystemName(codeSystemResourceName)
369                                                        .setCodeSystemVersion(csVersion);
370                                        }
371                                }
372                        }
373                }
374
375                ValidationMessage.IssueSeverity severity;
376                String message;
377                if ("fragment".equals(codeSystemResourceContentMode)) {
378                        severity = ValidationMessage.IssueSeverity.WARNING;
379                        message = "Unknown code in fragment CodeSystem '" + (isNotBlank(theCodeSystemUrlAndVersionToValidate) ? theCodeSystemUrlAndVersionToValidate + "#" : "") + theCodeToValidate + "'";
380                } else {
381                        severity = ValidationMessage.IssueSeverity.ERROR;
382                        message = "Unknown code '" + (isNotBlank(theCodeSystemUrlAndVersionToValidate) ? theCodeSystemUrlAndVersionToValidate + "#" : "") + theCodeToValidate + "'";
383                }
384                if (isNotBlank(theValueSetUrl)) {
385                        message += " for in-memory expansion of ValueSet '" + theValueSetUrl + "'";
386                }
387
388                return new CodeValidationResult()
389                        .setSeverityCode(severity.toCode())
390                        .setMessage(message);
391        }
392
393        @Override
394        public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theDisplayLanguage) {
395                CodeValidationResult codeValidationResult = validateCode(theValidationSupportContext, new ConceptValidationOptions(), theSystem, theCode, null, null);
396                if (codeValidationResult == null) {
397                        return null;
398                }
399                return codeValidationResult.asLookupCodeResult(theSystem, theCode);
400        }
401
402        @Nullable
403        private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org(ValidationSupportContext theValidationSupportContext, ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
404                org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(theInput, new BaseAdvisor_10_50(false));
405                return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode));
406        }
407
408        @Nullable
409        private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2(ValidationSupportContext theValidationSupportContext, ca.uhn.fhir.model.dstu2.resource.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
410                IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser();
411                IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser();
412
413                org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(theInput));
414                org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false));
415                return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode));
416        }
417
418        @Override
419        public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
420                if (isBlank(theSystem)) {
421                        return false;
422                }
423
424                IBaseResource cs = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theSystem);
425
426                if (!myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU2_1)) {
427                        return cs != null;
428                }
429
430                if (cs != null) {
431                        IPrimitiveType<?> content = getFhirContext().newTerser().getSingleValueOrNull(cs, "content", IPrimitiveType.class);
432                        return !"not-present".equals(content.getValueAsString());
433                }
434
435                return false;
436        }
437
438        @Override
439        public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
440                return isNotBlank(theValueSetUrl) && theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl) != null;
441        }
442
443
444        private void addCodesDstu2Hl7Org(List<ValueSet.ConceptDefinitionComponent> theSourceList, List<CodeSystem.ConceptDefinitionComponent> theTargetList) {
445                for (ValueSet.ConceptDefinitionComponent nextSource : theSourceList) {
446                        CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent().setCode(nextSource.getCode()).setDisplay(nextSource.getDisplay());
447                        theTargetList.add(targetConcept);
448                        addCodesDstu2Hl7Org(nextSource.getConcept(), targetConcept.getConcept());
449                }
450        }
451
452        private void addCodesDstu2(List<ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept> theSourceList, List<CodeSystem.ConceptDefinitionComponent> theTargetList) {
453                for (ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept nextSource : theSourceList) {
454                        CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent().setCode(nextSource.getCode()).setDisplay(nextSource.getDisplay());
455                        theTargetList.add(targetConcept);
456                        addCodesDstu2(nextSource.getConcept(), targetConcept.getConcept());
457                }
458        }
459
460        @Nullable
461        private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.dstu3.model.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
462                org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_30_50.convertResource(theInput, new BaseAdvisor_30_50(false));
463                return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode));
464        }
465
466        @Nullable
467        private org.hl7.fhir.r5.model.ValueSet expandValueSetR4(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r4.model.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
468                org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_40_50.convertResource(theInput, new BaseAdvisor_40_50(false));
469                return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode);
470        }
471
472        @Nullable
473        private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput) throws ExpansionCouldNotBeCompletedInternallyException {
474                return expandValueSetR5(theValidationSupportContext, theInput, null, null);
475        }
476
477        @Nullable
478        private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
479                Set<FhirVersionIndependentConcept> concepts = new HashSet<>();
480
481                expandValueSetR5IncludeOrExcludes(theValidationSupportContext, concepts, theInput.getCompose().getInclude(), true, theWantSystemUrlAndVersion, theWantCode);
482                expandValueSetR5IncludeOrExcludes(theValidationSupportContext, concepts, theInput.getCompose().getExclude(), false, theWantSystemUrlAndVersion, theWantCode);
483
484                org.hl7.fhir.r5.model.ValueSet retVal = new org.hl7.fhir.r5.model.ValueSet();
485                for (FhirVersionIndependentConcept next : concepts) {
486                        org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent contains = retVal.getExpansion().addContains();
487                        contains.setSystem(next.getSystem());
488                        contains.setCode(next.getCode());
489                        contains.setDisplay(next.getDisplay());
490                        contains.setVersion(next.getSystemVersion());
491                }
492
493                return retVal;
494        }
495
496        /**
497         * Use with caution - this is not a stable API
498         *
499         * @since 5.6.0
500         */
501        public void expandValueSetIncludeOrExclude(ValidationSupportContext theValidationSupportContext, Consumer<FhirVersionIndependentConcept> theConsumer, org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theIncludeOrExclude) throws ExpansionCouldNotBeCompletedInternallyException {
502                expandValueSetR5IncludeOrExclude(theValidationSupportContext, theConsumer, null, null, theIncludeOrExclude);
503        }
504
505
506        private void expandValueSetR5IncludeOrExcludes(ValidationSupportContext theValidationSupportContext, Set<FhirVersionIndependentConcept> theConcepts, List<org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent> theComposeList, boolean theComposeListIsInclude, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
507                Consumer<FhirVersionIndependentConcept> consumer = c -> {
508                        if (theComposeListIsInclude) {
509                                theConcepts.add(c);
510                        } else {
511                                theConcepts.remove(c);
512                        }
513                };
514                expandValueSetR5IncludeOrExcludes(theValidationSupportContext, consumer, theComposeList, theWantSystemUrlAndVersion, theWantCode);
515        }
516
517
518        private void expandValueSetR5IncludeOrExcludes(ValidationSupportContext theValidationSupportContext, Consumer<FhirVersionIndependentConcept> theConsumer, List<org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent> theComposeList, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
519                ExpansionCouldNotBeCompletedInternallyException caughtException = null;
520                for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) {
521                        try {
522                                boolean outcome = expandValueSetR5IncludeOrExclude(theValidationSupportContext, theConsumer, theWantSystemUrlAndVersion, theWantCode, nextInclude);
523                                if (isNotBlank(theWantCode)) {
524                                        if (outcome) {
525                                                return;
526                                        }
527                                }
528                        } catch (ExpansionCouldNotBeCompletedInternallyException e) {
529                                if (isBlank(theWantCode)) {
530                                        throw e;
531                                } else {
532                                        caughtException = e;
533                                }
534                        }
535                }
536                if (caughtException != null) {
537                        throw caughtException;
538                }
539        }
540
541        /**
542         * Returns <code>true</code> if at least one code was addded
543         */
544        private boolean expandValueSetR5IncludeOrExclude(ValidationSupportContext theValidationSupportContext, Consumer<FhirVersionIndependentConcept> theConsumer, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode, org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theInclude) throws ExpansionCouldNotBeCompletedInternallyException {
545                String wantSystemUrl = null;
546                String wantSystemVersion = null;
547                if (theWantSystemUrlAndVersion != null) {
548                        int versionIndex = theWantSystemUrlAndVersion.indexOf("|");
549                        if (versionIndex > -1) {
550                                wantSystemUrl = theWantSystemUrlAndVersion.substring(0, versionIndex);
551                                wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex + 1);
552                        } else {
553                                wantSystemUrl = theWantSystemUrlAndVersion;
554                        }
555                }
556
557                Function<String, CodeSystem> codeSystemLoader = newCodeSystemLoader(theValidationSupportContext);
558                Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = newValueSetLoader(theValidationSupportContext);
559
560                List<FhirVersionIndependentConcept> nextCodeList = new ArrayList<>();
561                String includeOrExcludeConceptSystemUrl = theInclude.getSystem();
562                String includeOrExcludeConceptSystemVersion = theInclude.getVersion();
563                CodeSystem includeOrExcludeSystemResource = null;
564                if (isNotBlank(includeOrExcludeConceptSystemUrl)) {
565
566                        if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) {
567                                return false;
568                        }
569
570                        if (wantSystemVersion != null && !wantSystemVersion.equals(includeOrExcludeConceptSystemVersion)) {
571                                return false;
572                        }
573
574                        String loadedCodeSystemUrl;
575                        if (includeOrExcludeConceptSystemVersion != null) {
576                                loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl + "|" + includeOrExcludeConceptSystemVersion;
577                        } else {
578                                loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl;
579                        }
580
581                        includeOrExcludeSystemResource = codeSystemLoader.apply(loadedCodeSystemUrl);
582
583                        Set<String> wantCodes;
584                        if (theInclude.getConcept().isEmpty()) {
585                                wantCodes = null;
586                        } else {
587                                wantCodes = theInclude
588                                        .getConcept()
589                                        .stream().map(t -> t.getCode()).collect(Collectors.toSet());
590                        }
591
592                        boolean ableToHandleCode = false;
593                        String failureMessage = null;
594                        FailureType failureType = FailureType.OTHER;
595
596                        if (includeOrExcludeSystemResource == null || includeOrExcludeSystemResource.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) {
597
598                                if (theWantCode != null) {
599                                        if (theValidationSupportContext.getRootValidationSupport().isCodeSystemSupported(theValidationSupportContext, includeOrExcludeConceptSystemUrl)) {
600                                                LookupCodeResult lookup = theValidationSupportContext.getRootValidationSupport().lookupCode(theValidationSupportContext, includeOrExcludeConceptSystemUrl, theWantCode, null);
601                                                if (lookup != null && lookup.isFound()) {
602                                                        CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent()
603                                                                .addConcept()
604                                                                .setCode(theWantCode)
605                                                                .setDisplay(lookup.getCodeDisplay());
606                                                        List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition);
607                                                        addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, codesList, nextCodeList, wantCodes);
608                                                        ableToHandleCode = true;
609                                                }
610                                        } else {
611
612                                                /*
613                                                 * If we're doing an expansion specifically looking for a single code, that means we're validating that code.
614                                                 * In the case where we have a ValueSet that explicitly enumerates a collection of codes
615                                                 * (via ValueSet.compose.include.code) in a code system that is unknown we'll assume the code is valid
616                                                 * even if we can't find the CodeSystem. This is a compromise obviously, since it would be ideal for
617                                                 * CodeSystems to always be known, but realistically there are always going to be CodeSystems that
618                                                 * can't be supplied because of copyright issues, or because they are grammar based. Allowing a VS to
619                                                 * enumerate a set of good codes for them is a nice compromise there.
620                                                 */
621                                                if (Objects.equals(theInclude.getSystem(), theWantSystemUrlAndVersion)) {
622                                                        Optional<org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent> matchingEnumeratedConcept = theInclude.getConcept().stream().filter(t -> Objects.equals(t.getCode(), theWantCode)).findFirst();
623
624                                                        // If the ValueSet.compose.include has no individual concepts in it, and
625                                                        // we can't find the actual referenced CodeSystem, we have no choice
626                                                        // but to fail
627                                                        if (!theInclude.getConcept().isEmpty()) {
628                                                                ableToHandleCode = true;
629                                                        } else {
630                                                                failureMessage = getFailureMessageForMissingOrUnusableCodeSystem(includeOrExcludeSystemResource, loadedCodeSystemUrl);
631                                                        }
632
633                                                        if (matchingEnumeratedConcept.isPresent()) {
634                                                                CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent()
635                                                                        .addConcept()
636                                                                        .setCode(theWantCode)
637                                                                        .setDisplay(matchingEnumeratedConcept.get().getDisplay());
638                                                                List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition);
639                                                                addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, codesList, nextCodeList, wantCodes);
640                                                        }
641                                                }
642
643                                        }
644                                } else {
645                                        if (isNotBlank(theInclude.getSystem()) && !theInclude.getConcept().isEmpty() && theInclude.getFilter().isEmpty() && theInclude.getValueSet().isEmpty()) {
646                                                theInclude
647                                                        .getConcept()
648                                                        .stream()
649                                                        .map(t -> new FhirVersionIndependentConcept(theInclude.getSystem(), t.getCode(), t.getDisplay(), theInclude.getVersion()))
650                                                        .forEach(t -> nextCodeList.add(t));
651                                                ableToHandleCode = true;
652                                        }
653
654                                        if (!ableToHandleCode) {
655                                                failureMessage = getFailureMessageForMissingOrUnusableCodeSystem(includeOrExcludeSystemResource, loadedCodeSystemUrl);
656                                        }
657                                }
658
659                        } else {
660                                ableToHandleCode = true;
661                        }
662
663                        if (!ableToHandleCode) {
664                                if (includeOrExcludeSystemResource == null && failureMessage == null) {
665                                        failureMessage = getFailureMessageForMissingOrUnusableCodeSystem(includeOrExcludeSystemResource, loadedCodeSystemUrl);
666                                }
667
668                                if (includeOrExcludeSystemResource == null) {
669                                        failureType = FailureType.UNKNOWN_CODE_SYSTEM;
670                                }
671
672                                throw new ExpansionCouldNotBeCompletedInternallyException(Msg.code(702) + failureMessage, failureType);
673                        }
674
675                        if (includeOrExcludeSystemResource != null && includeOrExcludeSystemResource.getContent() != CodeSystem.CodeSystemContentMode.NOTPRESENT) {
676                                addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, includeOrExcludeSystemResource.getConcept(), nextCodeList, wantCodes);
677                        }
678
679                }
680
681                for (CanonicalType nextValueSetInclude : theInclude.getValueSet()) {
682                        org.hl7.fhir.r5.model.ValueSet vs = valueSetLoader.apply(nextValueSetInclude.getValueAsString());
683                        if (vs != null) {
684                                org.hl7.fhir.r5.model.ValueSet subExpansion = expandValueSetR5(theValidationSupportContext, vs, theWantSystemUrlAndVersion, theWantCode);
685                                if (subExpansion == null) {
686                                        throw new ExpansionCouldNotBeCompletedInternallyException(Msg.code(703) + "Failed to expand ValueSet: " + nextValueSetInclude.getValueAsString(), FailureType.OTHER);
687                                }
688                                for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : subExpansion.getExpansion().getContains()) {
689                                        nextCodeList.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion()));
690                                }
691                        }
692                }
693
694                boolean retVal = false;
695
696                for (FhirVersionIndependentConcept next : nextCodeList) {
697                        if (includeOrExcludeSystemResource != null && theWantCode != null) {
698                                boolean matches;
699                                if (includeOrExcludeSystemResource.getCaseSensitive()) {
700                                        matches = theWantCode.equals(next.getCode());
701                                } else {
702                                        matches = theWantCode.equalsIgnoreCase(next.getCode());
703                                }
704                                if (!matches) {
705                                        continue;
706                                }
707                        }
708
709                        theConsumer.accept(next);
710                        retVal = true;
711                }
712
713                return retVal;
714        }
715
716        private Function<String, org.hl7.fhir.r5.model.ValueSet> newValueSetLoader(ValidationSupportContext theValidationSupportContext) {
717                switch (myCtx.getVersion().getVersion()) {
718                        case DSTU2:
719                        case DSTU2_HL7ORG:
720                                return t -> {
721                                        IBaseResource vs = theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
722                                        if (vs instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) {
723                                                IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser();
724                                                IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser();
725                                                ca.uhn.fhir.model.dstu2.resource.ValueSet valueSet = (ca.uhn.fhir.model.dstu2.resource.ValueSet) vs;
726                                                org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(valueSet));
727                                                return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false));
728                                        } else {
729                                                org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
730                                                return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(valueSet, new BaseAdvisor_10_50(false));
731                                        }
732                                };
733                        case DSTU3:
734                                return t -> {
735                                        org.hl7.fhir.dstu3.model.ValueSet valueSet = (org.hl7.fhir.dstu3.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
736                                        return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_30_50.convertResource(valueSet, new BaseAdvisor_30_50(false));
737                                };
738                        case R4:
739                                return t -> {
740                                        org.hl7.fhir.r4.model.ValueSet valueSet = (org.hl7.fhir.r4.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
741                                        return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_40_50.convertResource(valueSet, new BaseAdvisor_40_50(false));
742                                };
743                        default:
744                        case DSTU2_1:
745                        case R5:
746                                return t -> (org.hl7.fhir.r5.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
747                }
748        }
749
750        private Function<String, CodeSystem> newCodeSystemLoader(ValidationSupportContext theValidationSupportContext) {
751                switch (myCtx.getVersion().getVersion()) {
752                        case DSTU2:
753                        case DSTU2_HL7ORG:
754                                return t -> {
755                                        IBaseResource codeSystem = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t);
756                                        CodeSystem retVal = null;
757                                        if (codeSystem != null) {
758                                                retVal = new CodeSystem();
759                                                if (codeSystem instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) {
760                                                        ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystemCasted = (ca.uhn.fhir.model.dstu2.resource.ValueSet) codeSystem;
761                                                        retVal.setUrl(codeSystemCasted.getUrl());
762                                                        addCodesDstu2(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept());
763                                                } else {
764                                                        org.hl7.fhir.dstu2.model.ValueSet codeSystemCasted = (org.hl7.fhir.dstu2.model.ValueSet) codeSystem;
765                                                        retVal.setUrl(codeSystemCasted.getUrl());
766                                                        addCodesDstu2Hl7Org(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept());
767                                                }
768                                        }
769                                        return retVal;
770                                };
771                        case DSTU3:
772                                return t -> {
773                                        org.hl7.fhir.dstu3.model.CodeSystem codeSystem = (org.hl7.fhir.dstu3.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t);
774                                        return (CodeSystem) VersionConvertorFactory_30_50.convertResource(codeSystem, new BaseAdvisor_30_50(false));
775                                };
776                        case R4:
777                                return t -> {
778                                        org.hl7.fhir.r4.model.CodeSystem codeSystem = (org.hl7.fhir.r4.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t);
779                                        return (CodeSystem) VersionConvertorFactory_40_50.convertResource(codeSystem, new BaseAdvisor_40_50(false));
780                                };
781                        case DSTU2_1:
782                        case R5:
783                        default:
784                                return t -> (org.hl7.fhir.r5.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t);
785
786                }
787        }
788
789        private String getFailureMessageForMissingOrUnusableCodeSystem(CodeSystem includeOrExcludeSystemResource, String loadedCodeSystemUrl) {
790                String failureMessage;
791                if (includeOrExcludeSystemResource == null) {
792                        failureMessage = "Unable to expand ValueSet because CodeSystem could not be found: " + loadedCodeSystemUrl;
793                } else {
794                        assert includeOrExcludeSystemResource.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT;
795                        failureMessage = "Unable to expand ValueSet because CodeSystem has CodeSystem.content=not-present but contents were not found: " + loadedCodeSystemUrl;
796                }
797                return failureMessage;
798        }
799
800        private void addCodes(String theCodeSystemUrl, String theCodeSystemVersion, List<CodeSystem.ConceptDefinitionComponent> theSource, List<FhirVersionIndependentConcept> theTarget, Set<String> theCodeFilter) {
801                for (CodeSystem.ConceptDefinitionComponent next : theSource) {
802                        if (isNotBlank(next.getCode())) {
803                                if (theCodeFilter == null || theCodeFilter.contains(next.getCode())) {
804                                        theTarget.add(new FhirVersionIndependentConcept(theCodeSystemUrl, next.getCode(), next.getDisplay(), theCodeSystemVersion));
805                                }
806                        }
807                        addCodes(theCodeSystemUrl, theCodeSystemVersion, next.getConcept(), theTarget, theCodeFilter);
808                }
809        }
810
811
812        public enum FailureType {
813
814                UNKNOWN_CODE_SYSTEM,
815                OTHER
816
817        }
818
819        public static class ExpansionCouldNotBeCompletedInternallyException extends Exception {
820
821                private static final long serialVersionUID = -2226561628771483085L;
822                private final FailureType myFailureType;
823
824                public ExpansionCouldNotBeCompletedInternallyException(String theMessage, FailureType theFailureType) {
825                        super(theMessage);
826                        myFailureType = theFailureType;
827                }
828
829                public FailureType getFailureType() {
830                        return myFailureType;
831                }
832        }
833
834        private static void flattenAndConvertCodesDstu2(List<org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) {
835                for (org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) {
836                        theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay()));
837                        flattenAndConvertCodesDstu2(next.getContains(), theFhirVersionIndependentConcepts);
838                }
839        }
840
841        private static void flattenAndConvertCodesDstu3(List<org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) {
842                for (org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) {
843                        theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion()));
844                        flattenAndConvertCodesDstu3(next.getContains(), theFhirVersionIndependentConcepts);
845                }
846        }
847
848        private static void flattenAndConvertCodesR4(List<org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) {
849                for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) {
850                        theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion()));
851                        flattenAndConvertCodesR4(next.getContains(), theFhirVersionIndependentConcepts);
852                }
853        }
854
855        private static void flattenAndConvertCodesR5(List<org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) {
856                for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) {
857                        theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion()));
858                        flattenAndConvertCodesR5(next.getContains(), theFhirVersionIndependentConcepts);
859                }
860        }
861
862}