001/*
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2025 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.util;
021
022import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
023import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
024import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
025import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
026import ca.uhn.fhir.context.ConfigurationException;
027import ca.uhn.fhir.context.FhirContext;
028import ca.uhn.fhir.context.FhirVersionEnum;
029import ca.uhn.fhir.context.RuntimeChildChoiceDefinition;
030import ca.uhn.fhir.context.RuntimeChildDirectResource;
031import ca.uhn.fhir.context.RuntimeExtensionDtDefinition;
032import ca.uhn.fhir.context.RuntimeResourceDefinition;
033import ca.uhn.fhir.context.RuntimeSearchParam;
034import ca.uhn.fhir.i18n.Msg;
035import ca.uhn.fhir.interceptor.auth.CompartmentSearchParameterModifications;
036import ca.uhn.fhir.model.api.ExtensionDt;
037import ca.uhn.fhir.model.api.IIdentifiableElement;
038import ca.uhn.fhir.model.api.IResource;
039import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
040import ca.uhn.fhir.model.base.composite.BaseContainedDt;
041import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
042import ca.uhn.fhir.model.primitive.StringDt;
043import ca.uhn.fhir.parser.DataFormatException;
044import com.google.common.collect.Lists;
045import jakarta.annotation.Nonnull;
046import jakarta.annotation.Nullable;
047import org.apache.commons.lang3.StringUtils;
048import org.apache.commons.lang3.Validate;
049import org.hl7.fhir.instance.model.api.IBase;
050import org.hl7.fhir.instance.model.api.IBaseElement;
051import org.hl7.fhir.instance.model.api.IBaseExtension;
052import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
053import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
054import org.hl7.fhir.instance.model.api.IBaseReference;
055import org.hl7.fhir.instance.model.api.IBaseResource;
056import org.hl7.fhir.instance.model.api.IDomainResource;
057import org.hl7.fhir.instance.model.api.IIdType;
058import org.hl7.fhir.instance.model.api.IPrimitiveType;
059import org.slf4j.Logger;
060import org.slf4j.LoggerFactory;
061
062import java.util.ArrayList;
063import java.util.Arrays;
064import java.util.Collection;
065import java.util.Collections;
066import java.util.Comparator;
067import java.util.HashMap;
068import java.util.HashSet;
069import java.util.IdentityHashMap;
070import java.util.Iterator;
071import java.util.List;
072import java.util.Map;
073import java.util.Objects;
074import java.util.Optional;
075import java.util.Set;
076import java.util.UUID;
077import java.util.regex.Matcher;
078import java.util.regex.Pattern;
079import java.util.stream.Collectors;
080import java.util.stream.Stream;
081
082import static org.apache.commons.lang3.StringUtils.defaultString;
083import static org.apache.commons.lang3.StringUtils.isBlank;
084import static org.apache.commons.lang3.StringUtils.isEmpty;
085import static org.apache.commons.lang3.StringUtils.isNotBlank;
086import static org.apache.commons.lang3.function.Consumers.nop;
087
088public class FhirTerser {
089
090        private static final Pattern COMPARTMENT_MATCHER_PATH =
091                        Pattern.compile("([a-zA-Z.]+)\\.where\\(resolve\\(\\) is ([a-zA-Z]+)\\)");
092
093        private static final String USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED =
094                        FhirTerser.class.getName() + "_CONTAIN_RESOURCES_COMPLETED";
095
096        private final FhirContext myContext;
097        private static final Logger ourLog = LoggerFactory.getLogger(FhirTerser.class);
098
099        /**
100         * This comparator sorts IBaseReferences, and places any that are missing an ID at the end. Those with an ID go to the front.
101         */
102        private static final Comparator<IBaseReference> REFERENCES_WITH_IDS_FIRST =
103                        Comparator.nullsLast(Comparator.comparing(ref -> {
104                                if (ref.getResource() == null) return true;
105                                if (ref.getResource().getIdElement() == null) return true;
106                                if (ref.getResource().getIdElement().getValue() == null) return true;
107                                return false;
108                        }));
109
110        public FhirTerser(FhirContext theContext) {
111                super();
112                myContext = theContext;
113        }
114
115        private List<String> addNameToList(List<String> theCurrentList, BaseRuntimeChildDefinition theChildDefinition) {
116                if (theChildDefinition == null) return null;
117                if (theCurrentList == null || theCurrentList.isEmpty())
118                        return new ArrayList<>(Collections.singletonList(theChildDefinition.getElementName()));
119                List<String> newList = new ArrayList<>(theCurrentList);
120                newList.add(theChildDefinition.getElementName());
121                return newList;
122        }
123
124        private ExtensionDt createEmptyExtensionDt(IBaseExtension<?, ?> theBaseExtension, String theUrl) {
125                return createEmptyExtensionDt(theBaseExtension, false, theUrl);
126        }
127
128        @SuppressWarnings("unchecked")
129        private ExtensionDt createEmptyExtensionDt(IBaseExtension theBaseExtension, boolean theIsModifier, String theUrl) {
130                ExtensionDt retVal = new ExtensionDt(theIsModifier, theUrl);
131                theBaseExtension.getExtension().add(retVal);
132                return retVal;
133        }
134
135        private ExtensionDt createEmptyExtensionDt(
136                        ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, String theUrl) {
137                return createEmptyExtensionDt(theSupportsUndeclaredExtensions, false, theUrl);
138        }
139
140        private ExtensionDt createEmptyExtensionDt(
141                        ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, boolean theIsModifier, String theUrl) {
142                return theSupportsUndeclaredExtensions.addUndeclaredExtension(theIsModifier, theUrl);
143        }
144
145        private IBaseExtension<?, ?> createEmptyExtension(IBaseHasExtensions theBaseHasExtensions, String theUrl) {
146                return (IBaseExtension<?, ?>) theBaseHasExtensions.addExtension().setUrl(theUrl);
147        }
148
149        private IBaseExtension<?, ?> createEmptyModifierExtension(
150                        IBaseHasModifierExtensions theBaseHasModifierExtensions, String theUrl) {
151                return (IBaseExtension<?, ?>)
152                                theBaseHasModifierExtensions.addModifierExtension().setUrl(theUrl);
153        }
154
155        private ExtensionDt createEmptyModifierExtensionDt(
156                        ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, String theUrl) {
157                return createEmptyExtensionDt(theSupportsUndeclaredExtensions, true, theUrl);
158        }
159
160        /**
161         * Clones all values from a source object into the equivalent fields in a target object
162         *
163         * @param theSource              The source object (must not be null)
164         * @param theTarget              The target object to copy values into (must not be null)
165         * @param theIgnoreMissingFields The ignore fields in the target which do not exist (if false, an exception will be thrown if the target is unable to accept a value from the source)
166         * @return Returns the target (which will be the same object that was passed into theTarget) for easy chaining
167         */
168        public IBase cloneInto(IBase theSource, IBase theTarget, boolean theIgnoreMissingFields) {
169                Validate.notNull(theSource, "theSource must not be null");
170                Validate.notNull(theTarget, "theTarget must not be null");
171
172                // DSTU3+
173                if (theSource instanceof IBaseElement) {
174                        IBaseElement source = (IBaseElement) theSource;
175                        IBaseElement target = (IBaseElement) theTarget;
176                        target.setId(source.getId());
177                }
178
179                // DSTU2 only
180                if (theSource instanceof IIdentifiableElement) {
181                        IIdentifiableElement source = (IIdentifiableElement) theSource;
182                        IIdentifiableElement target = (IIdentifiableElement) theTarget;
183                        target.setElementSpecificId(source.getElementSpecificId());
184                }
185
186                // DSTU2 only
187                if (theSource instanceof IResource) {
188                        IResource source = (IResource) theSource;
189                        IResource target = (IResource) theTarget;
190                        target.setId(source.getId());
191                        target.getResourceMetadata().putAll(source.getResourceMetadata());
192                }
193
194                if (theSource instanceof IPrimitiveType<?>) {
195                        if (theTarget instanceof IPrimitiveType<?>) {
196                                String valueAsString = ((IPrimitiveType<?>) theSource).getValueAsString();
197                                if (isNotBlank(valueAsString)) {
198                                        ((IPrimitiveType<?>) theTarget).setValueAsString(valueAsString);
199                                }
200                                if (theSource instanceof IBaseHasExtensions && theTarget instanceof IBaseHasExtensions) {
201                                        List<? extends IBaseExtension<?, ?>> extensions = ((IBaseHasExtensions) theSource).getExtension();
202                                        for (IBaseExtension<?, ?> nextSource : extensions) {
203                                                IBaseExtension<?, ?> nextTarget = ((IBaseHasExtensions) theTarget).addExtension();
204                                                cloneInto(nextSource, nextTarget, theIgnoreMissingFields);
205                                        }
206                                }
207                                return theSource;
208                        }
209                        if (theIgnoreMissingFields) {
210                                return theSource;
211                        }
212                        throw new DataFormatException(Msg.code(1788) + "Can not copy value from primitive of type "
213                                        + theSource.getClass().getName() + " into type "
214                                        + theTarget.getClass().getName());
215                }
216
217                if (theSource instanceof IBaseReference && theTarget instanceof IBaseReference) {
218                        IBaseReference sourceReference = (IBaseReference) theSource;
219                        IBaseReference targetReference = (IBaseReference) theTarget;
220                        if (sourceReference.getResource() != null) {
221                                targetReference.setResource(sourceReference.getResource());
222                        }
223                }
224
225                BaseRuntimeElementCompositeDefinition<?> sourceDef =
226                                (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theSource.getClass());
227                BaseRuntimeElementCompositeDefinition<?> targetDef =
228                                (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theTarget.getClass());
229
230                List<BaseRuntimeChildDefinition> children = sourceDef.getChildren();
231                if (sourceDef instanceof RuntimeExtensionDtDefinition) {
232                        children = ((RuntimeExtensionDtDefinition) sourceDef).getChildrenIncludingUrl();
233                }
234
235                for (BaseRuntimeChildDefinition nextChild : children)
236                        for (IBase nextValue : nextChild.getAccessor().getValues(theSource)) {
237                                Class<? extends IBase> valueType = nextValue.getClass();
238                                String elementName = nextChild.getChildNameByDatatype(valueType);
239                                BaseRuntimeChildDefinition targetChild = targetDef.getChildByName(elementName);
240                                if (targetChild == null) {
241                                        if (theIgnoreMissingFields) {
242                                                continue;
243                                        }
244                                        throw new DataFormatException(Msg.code(1789) + "Type "
245                                                        + theTarget.getClass().getName() + " does not have a child with name " + elementName);
246                                }
247
248                                BaseRuntimeElementDefinition<?> element = myContext.getElementDefinition(valueType);
249                                Object instanceConstructorArg = targetChild.getInstanceConstructorArguments();
250                                IBase target;
251                                if (element == null && BaseContainedDt.class.isAssignableFrom(valueType)) {
252                                        /*
253                                         * This is a hack for DSTU2 - The way we did contained resources in
254                                         * the DSTU2 model was weird, since the element isn't actually a FHIR type.
255                                         * This is fixed in DSTU3+ so this hack only applies there.
256                                         */
257                                        BaseContainedDt containedTarget = (BaseContainedDt) ReflectionUtil.newInstance(valueType);
258                                        BaseContainedDt containedSource = (BaseContainedDt) nextValue;
259                                        for (IResource next : containedSource.getContainedResources()) {
260                                                List containedResources = containedTarget.getContainedResources();
261                                                containedResources.add(next);
262                                        }
263                                        targetChild.getMutator().addValue(theTarget, containedTarget);
264                                        continue;
265                                } else if (instanceConstructorArg != null) {
266                                        target = element.newInstance(instanceConstructorArg);
267                                } else {
268                                        target = element.newInstance();
269                                }
270
271                                targetChild.getMutator().addValue(theTarget, target);
272                                cloneInto(nextValue, target, theIgnoreMissingFields);
273                        }
274
275                return theTarget;
276        }
277
278        /**
279         * Returns a list containing all child elements (including the resource itself) which are <b>non-empty</b> and are either of the exact type specified, or are a subclass of that type.
280         * <p>
281         * For example, specifying a type of {@link StringDt} would return all non-empty string instances within the message. Specifying a type of {@link IResource} would return the resource itself, as
282         * well as any contained resources.
283         * </p>
284         * <p>
285         * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g.
286         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
287         * </p>
288         *
289         * @param theResource The resource instance to search. Must not be null.
290         * @param theType     The type to search for. Must not be null.
291         * @return Returns a list of all matching elements
292         */
293        public <T extends IBase> List<T> getAllPopulatedChildElementsOfType(
294                        IBaseResource theResource, final Class<T> theType) {
295                final ArrayList<T> retVal = new ArrayList<>();
296                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
297                visit(newMap(), theResource, theResource, null, null, def, new IModelVisitor() {
298                        @SuppressWarnings("unchecked")
299                        @Override
300                        public void acceptElement(
301                                        IBaseResource theOuterResource,
302                                        IBase theElement,
303                                        List<String> thePathToElement,
304                                        BaseRuntimeChildDefinition theChildDefinition,
305                                        BaseRuntimeElementDefinition<?> theDefinition) {
306                                if (theElement == null || theElement.isEmpty()) {
307                                        return;
308                                }
309
310                                if (theType.isAssignableFrom(theElement.getClass())) {
311                                        retVal.add((T) theElement);
312                                }
313                        }
314                });
315                return retVal;
316        }
317
318        /**
319         * Extracts all outbound references from a resource
320         *
321         * @param theResource the resource to be analyzed
322         * @return a list of references to other resources
323         */
324        public List<ResourceReferenceInfo> getAllResourceReferences(final IBaseResource theResource) {
325                return getAllResourceReferencesExcluding(theResource, Lists.newArrayList());
326        }
327
328        /**
329         * Extracts all outbound references from a resource, excluding any that are located on black-listed parts of the
330         * resource
331         *
332         * @param theResource       the resource to be analyzed
333         * @param thePathsToExclude a list of dot-delimited paths not to include in the result
334         * @return a list of references to other resources
335         */
336        public List<ResourceReferenceInfo> getAllResourceReferencesExcluding(
337                        final IBaseResource theResource, List<String> thePathsToExclude) {
338                final ArrayList<ResourceReferenceInfo> retVal = new ArrayList<>();
339                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
340                List<List<String>> tokenizedPathsToExclude = thePathsToExclude.stream()
341                                .map(path -> StringUtils.split(path, "."))
342                                .map(Lists::newArrayList)
343                                .collect(Collectors.toList());
344
345                visit(newMap(), theResource, theResource, null, null, def, new IModelVisitor() {
346                        @Override
347                        public void acceptElement(
348                                        IBaseResource theOuterResource,
349                                        IBase theElement,
350                                        List<String> thePathToElement,
351                                        BaseRuntimeChildDefinition theChildDefinition,
352                                        BaseRuntimeElementDefinition<?> theDefinition) {
353                                if (theElement == null || theElement.isEmpty()) {
354                                        return;
355                                }
356
357                                if (thePathToElement != null && pathShouldBeExcluded(tokenizedPathsToExclude, thePathToElement)) {
358                                        return;
359                                }
360                                if (IBaseReference.class.isAssignableFrom(theElement.getClass())) {
361                                        retVal.add(new ResourceReferenceInfo(
362                                                        myContext, theOuterResource, thePathToElement, (IBaseReference) theElement));
363                                }
364                        }
365                });
366                return retVal;
367        }
368
369        private boolean pathShouldBeExcluded(List<List<String>> theTokenizedPathsToExclude, List<String> thePathToElement) {
370                return theTokenizedPathsToExclude.stream().anyMatch(p -> {
371                        // Check whether the path to the element starts with the path to be excluded
372                        if (p.size() > thePathToElement.size()) {
373                                return false;
374                        }
375
376                        List<String> prefix = thePathToElement.subList(0, p.size());
377
378                        return Objects.equals(p, prefix);
379                });
380        }
381
382        private BaseRuntimeChildDefinition getDefinition(
383                        BaseRuntimeElementCompositeDefinition<?> theCurrentDef, List<String> theSubList) {
384                BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(theSubList.get(0));
385
386                if (theSubList.size() == 1) {
387                        return nextDef;
388                }
389                BaseRuntimeElementCompositeDefinition<?> cmp =
390                                (BaseRuntimeElementCompositeDefinition<?>) nextDef.getChildByName(theSubList.get(0));
391                return getDefinition(cmp, theSubList.subList(1, theSubList.size()));
392        }
393
394        public BaseRuntimeChildDefinition getDefinition(Class<? extends IBaseResource> theResourceType, String thePath) {
395                RuntimeResourceDefinition def = myContext.getResourceDefinition(theResourceType);
396
397                List<String> parts = Arrays.asList(thePath.split("\\."));
398                List<String> subList = parts.subList(1, parts.size());
399                if (subList.size() < 1) {
400                        throw new ConfigurationException(Msg.code(1790) + "Invalid path: " + thePath);
401                }
402                return getDefinition(def, subList);
403        }
404
405        public Object getSingleValueOrNull(IBase theTarget, String thePath) {
406                Class<IBase> wantedType = IBase.class;
407
408                return getSingleValueOrNull(theTarget, thePath, wantedType);
409        }
410
411        public <T extends IBase> T getSingleValueOrNull(IBase theTarget, String thePath, Class<T> theWantedType) {
412                Validate.notNull(theTarget, "theTarget must not be null");
413                Validate.notBlank(thePath, "thePath must not be empty");
414
415                BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theTarget.getClass());
416                if (!(def instanceof BaseRuntimeElementCompositeDefinition)) {
417                        throw new IllegalArgumentException(Msg.code(1791) + "Target is not a composite type: "
418                                        + theTarget.getClass().getName());
419                }
420
421                BaseRuntimeElementCompositeDefinition<?> currentDef = (BaseRuntimeElementCompositeDefinition<?>) def;
422
423                List<String> parts = parsePath(currentDef, thePath);
424
425                List<T> retVal = getValues(currentDef, theTarget, parts, theWantedType);
426                if (retVal.isEmpty()) {
427                        return null;
428                }
429                return retVal.get(0);
430        }
431
432        public Optional<String> getSinglePrimitiveValue(IBase theTarget, String thePath) {
433                return getSingleValue(theTarget, thePath, IPrimitiveType.class).map(t -> t.getValueAsString());
434        }
435
436        public String getSinglePrimitiveValueOrNull(IBase theTarget, String thePath) {
437                return getSingleValue(theTarget, thePath, IPrimitiveType.class)
438                                .map(IPrimitiveType::getValueAsString)
439                                .orElse(null);
440        }
441
442        public <T extends IBase> Optional<T> getSingleValue(IBase theTarget, String thePath, Class<T> theWantedType) {
443                return Optional.ofNullable(getSingleValueOrNull(theTarget, thePath, theWantedType));
444        }
445
446        private <T extends IBase> List<T> getValues(
447                        BaseRuntimeElementCompositeDefinition<?> theCurrentDef,
448                        IBase theCurrentObj,
449                        List<String> theSubList,
450                        Class<T> theWantedClass) {
451                return getValues(theCurrentDef, theCurrentObj, theSubList, theWantedClass, false, false);
452        }
453
454        @SuppressWarnings("unchecked")
455        private <T extends IBase> List<T> getValues(
456                        BaseRuntimeElementCompositeDefinition<?> theCurrentDef,
457                        IBase theCurrentObj,
458                        List<String> theSubList,
459                        Class<T> theWantedClass,
460                        boolean theCreate,
461                        boolean theAddExtension) {
462                if (theSubList.isEmpty()) {
463                        return Collections.emptyList();
464                }
465
466                String name = theSubList.get(0);
467                List<T> retVal = new ArrayList<>();
468
469                if (name.startsWith("extension('")) {
470                        String extensionUrl = name.substring("extension('".length());
471                        int endIndex = extensionUrl.indexOf('\'');
472                        if (endIndex != -1) {
473                                extensionUrl = extensionUrl.substring(0, endIndex);
474                        }
475
476                        if (myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
477                                // DTSU2
478                                final String extensionDtUrlForLambda = extensionUrl;
479                                List<ExtensionDt> extensionDts = Collections.emptyList();
480                                if (theCurrentObj instanceof ISupportsUndeclaredExtensions) {
481                                        extensionDts = ((ISupportsUndeclaredExtensions) theCurrentObj)
482                                                        .getUndeclaredExtensions().stream()
483                                                                        .filter(t -> t.getUrl().equals(extensionDtUrlForLambda))
484                                                                        .collect(Collectors.toList());
485
486                                        if (theAddExtension
487                                                        && (!(theCurrentObj instanceof IBaseExtension)
488                                                                        || (extensionDts.isEmpty() && theSubList.size() == 1))) {
489                                                extensionDts.add(
490                                                                createEmptyExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl));
491                                        }
492
493                                        if (extensionDts.isEmpty() && theCreate) {
494                                                extensionDts.add(
495                                                                createEmptyExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl));
496                                        }
497
498                                } else if (theCurrentObj instanceof IBaseExtension) {
499                                        extensionDts = ((IBaseExtension) theCurrentObj).getExtension();
500
501                                        if (theAddExtension && (extensionDts.isEmpty() && theSubList.size() == 1)) {
502                                                extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl));
503                                        }
504
505                                        if (extensionDts.isEmpty() && theCreate) {
506                                                extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl));
507                                        }
508                                }
509
510                                for (ExtensionDt next : extensionDts) {
511                                        if (theWantedClass.isAssignableFrom(next.getClass())) {
512                                                retVal.add((T) next);
513                                        }
514                                }
515                        } else {
516                                // DSTU3+
517                                final String extensionUrlForLambda = extensionUrl;
518                                List<IBaseExtension<?, ?>> extensions = Collections.emptyList();
519                                if (theCurrentObj instanceof IBaseHasExtensions) {
520                                        extensions = ((IBaseHasExtensions) theCurrentObj)
521                                                        .getExtension().stream()
522                                                                        .filter(t -> t.getUrl().equals(extensionUrlForLambda))
523                                                                        .collect(Collectors.toList());
524
525                                        if (theAddExtension
526                                                        && (!(theCurrentObj instanceof IBaseExtension)
527                                                                        || (extensions.isEmpty() && theSubList.size() == 1))) {
528                                                extensions.add(createEmptyExtension((IBaseHasExtensions) theCurrentObj, extensionUrl));
529                                        }
530
531                                        if (extensions.isEmpty() && theCreate) {
532                                                extensions.add(createEmptyExtension((IBaseHasExtensions) theCurrentObj, extensionUrl));
533                                        }
534                                }
535
536                                for (IBaseExtension<?, ?> next : extensions) {
537                                        if (theWantedClass.isAssignableFrom(next.getClass())) {
538                                                retVal.add((T) next);
539                                        }
540                                }
541                        }
542
543                        if (theSubList.size() > 1) {
544                                List<T> values = retVal;
545                                retVal = new ArrayList<>();
546                                for (T nextElement : values) {
547                                        BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>)
548                                                        myContext.getElementDefinition(nextElement.getClass());
549                                        List<T> foundValues = getValues(
550                                                        nextChildDef,
551                                                        nextElement,
552                                                        theSubList.subList(1, theSubList.size()),
553                                                        theWantedClass,
554                                                        theCreate,
555                                                        theAddExtension);
556                                        retVal.addAll(foundValues);
557                                }
558                        }
559
560                        return retVal;
561                }
562
563                if (name.startsWith("modifierExtension('")) {
564                        String extensionUrl = name.substring("modifierExtension('".length());
565                        int endIndex = extensionUrl.indexOf('\'');
566                        if (endIndex != -1) {
567                                extensionUrl = extensionUrl.substring(0, endIndex);
568                        }
569
570                        if (myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
571                                // DSTU2
572                                final String extensionDtUrlForLambda = extensionUrl;
573                                List<ExtensionDt> extensionDts = Collections.emptyList();
574                                if (theCurrentObj instanceof ISupportsUndeclaredExtensions) {
575                                        extensionDts = ((ISupportsUndeclaredExtensions) theCurrentObj)
576                                                        .getUndeclaredModifierExtensions().stream()
577                                                                        .filter(t -> t.getUrl().equals(extensionDtUrlForLambda))
578                                                                        .collect(Collectors.toList());
579
580                                        if (theAddExtension
581                                                        && (!(theCurrentObj instanceof IBaseExtension)
582                                                                        || (extensionDts.isEmpty() && theSubList.size() == 1))) {
583                                                extensionDts.add(createEmptyModifierExtensionDt(
584                                                                (ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl));
585                                        }
586
587                                        if (extensionDts.isEmpty() && theCreate) {
588                                                extensionDts.add(createEmptyModifierExtensionDt(
589                                                                (ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl));
590                                        }
591
592                                } else if (theCurrentObj instanceof IBaseExtension) {
593                                        extensionDts = ((IBaseExtension) theCurrentObj).getExtension();
594
595                                        if (theAddExtension && (extensionDts.isEmpty() && theSubList.size() == 1)) {
596                                                extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl));
597                                        }
598
599                                        if (extensionDts.isEmpty() && theCreate) {
600                                                extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl));
601                                        }
602                                }
603
604                                for (ExtensionDt next : extensionDts) {
605                                        if (theWantedClass.isAssignableFrom(next.getClass())) {
606                                                retVal.add((T) next);
607                                        }
608                                }
609                        } else {
610                                // DSTU3+
611                                final String extensionUrlForLambda = extensionUrl;
612                                List<IBaseExtension<?, ?>> extensions = Collections.emptyList();
613
614                                if (theCurrentObj instanceof IBaseHasModifierExtensions) {
615                                        extensions = ((IBaseHasModifierExtensions) theCurrentObj)
616                                                        .getModifierExtension().stream()
617                                                                        .filter(t -> t.getUrl().equals(extensionUrlForLambda))
618                                                                        .collect(Collectors.toList());
619
620                                        if (theAddExtension
621                                                        && (!(theCurrentObj instanceof IBaseExtension)
622                                                                        || (extensions.isEmpty() && theSubList.size() == 1))) {
623                                                extensions.add(
624                                                                createEmptyModifierExtension((IBaseHasModifierExtensions) theCurrentObj, extensionUrl));
625                                        }
626
627                                        if (extensions.isEmpty() && theCreate) {
628                                                extensions.add(
629                                                                createEmptyModifierExtension((IBaseHasModifierExtensions) theCurrentObj, extensionUrl));
630                                        }
631                                }
632
633                                for (IBaseExtension<?, ?> next : extensions) {
634                                        if (theWantedClass.isAssignableFrom(next.getClass())) {
635                                                retVal.add((T) next);
636                                        }
637                                }
638                        }
639
640                        if (theSubList.size() > 1) {
641                                List<T> values = retVal;
642                                retVal = new ArrayList<>();
643                                for (T nextElement : values) {
644                                        BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>)
645                                                        myContext.getElementDefinition(nextElement.getClass());
646                                        List<T> foundValues = getValues(
647                                                        nextChildDef,
648                                                        nextElement,
649                                                        theSubList.subList(1, theSubList.size()),
650                                                        theWantedClass,
651                                                        theCreate,
652                                                        theAddExtension);
653                                        retVal.addAll(foundValues);
654                                }
655                        }
656
657                        return retVal;
658                }
659
660                BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(name);
661                List<? extends IBase> values = nextDef.getAccessor().getValues(theCurrentObj);
662
663                if (values.isEmpty() && theCreate) {
664                        BaseRuntimeElementDefinition<?> childByName = nextDef.getChildByName(name);
665                        Object arg = nextDef.getInstanceConstructorArguments();
666                        IBase value;
667                        if (arg != null) {
668                                value = childByName.newInstance(arg);
669                        } else {
670                                value = childByName.newInstance();
671                        }
672                        nextDef.getMutator().addValue(theCurrentObj, value);
673                        List<IBase> list = new ArrayList<>();
674                        list.add(value);
675                        values = list;
676                }
677
678                if (theSubList.size() == 1) {
679                        if (nextDef instanceof RuntimeChildChoiceDefinition) {
680                                for (IBase next : values) {
681                                        if (next != null) {
682                                                if (name.endsWith("[x]")) {
683                                                        if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) {
684                                                                retVal.add((T) next);
685                                                        }
686                                                } else {
687                                                        String childName = nextDef.getChildNameByDatatype(next.getClass());
688                                                        if (theSubList.get(0).equals(childName)) {
689                                                                if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) {
690                                                                        retVal.add((T) next);
691                                                                }
692                                                        }
693                                                }
694                                        }
695                                }
696                        } else {
697                                for (IBase next : values) {
698                                        if (next != null) {
699                                                if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) {
700                                                        retVal.add((T) next);
701                                                }
702                                        }
703                                }
704                        }
705                } else {
706                        for (IBase nextElement : values) {
707                                // We can only continue iterating if the current element is composite.
708                                // If we haven't reached the end of the path, and we have already reached an element
709                                // with a primitive data type, we did not find a match.
710                                if (myContext.getElementDefinition(nextElement.getClass())
711                                                instanceof BaseRuntimeElementCompositeDefinition<?> nextChildDef) {
712                                        List<T> foundValues = getValues(
713                                                        nextChildDef,
714                                                        nextElement,
715                                                        theSubList.subList(1, theSubList.size()),
716                                                        theWantedClass,
717                                                        theCreate,
718                                                        theAddExtension);
719                                        retVal.addAll(foundValues);
720                                }
721                        }
722                }
723                return retVal;
724        }
725
726        /**
727         * Returns values stored in an element identified by its path. The list of values is of
728         * type {@link Object}.
729         *
730         * @param theElement The element to be accessed. Must not be null.
731         * @param thePath    The path for the element to be accessed.@param theElement The resource instance to be accessed. Must not be null.
732         * @return A list of values of type {@link Object}.
733         */
734        public List<IBase> getValues(IBase theElement, String thePath) {
735                Class<IBase> wantedClass = IBase.class;
736
737                return getValues(theElement, thePath, wantedClass);
738        }
739
740        /**
741         * Returns values stored in an element identified by its path. The list of values is of
742         * type {@link Object}.
743         *
744         * @param theElement The element to be accessed. Must not be null.
745         * @param thePath    The path for the element to be accessed.
746         * @param theCreate  When set to <code>true</code>, the terser will create a null-valued element where none exists.
747         * @return A list of values of type {@link Object}.
748         */
749        public List<IBase> getValues(IBase theElement, String thePath, boolean theCreate) {
750                Class<IBase> wantedClass = IBase.class;
751
752                return getValues(theElement, thePath, wantedClass, theCreate);
753        }
754
755        /**
756         * Returns values stored in an element identified by its path. The list of values is of
757         * type {@link Object}.
758         *
759         * @param theElement      The element to be accessed. Must not be null.
760         * @param thePath         The path for the element to be accessed.
761         * @param theCreate       When set to <code>true</code>, the terser will create a null-valued element where none exists.
762         * @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist.
763         * @return A list of values of type {@link Object}.
764         */
765        public List<IBase> getValues(IBase theElement, String thePath, boolean theCreate, boolean theAddExtension) {
766                Class<IBase> wantedClass = IBase.class;
767
768                return getValues(theElement, thePath, wantedClass, theCreate, theAddExtension);
769        }
770
771        /**
772         * Returns values stored in an element identified by its path. The list of values is of
773         * type <code>theWantedClass</code>.
774         *
775         * @param theElement     The element to be accessed. Must not be null.
776         * @param thePath        The path for the element to be accessed.
777         * @param theWantedClass The desired class to be returned in a list.
778         * @param <T>            Type declared by <code>theWantedClass</code>
779         * @return A list of values of type <code>theWantedClass</code>.
780         */
781        public <T extends IBase> List<T> getValues(IBase theElement, String thePath, Class<T> theWantedClass) {
782                BaseRuntimeElementCompositeDefinition<?> def =
783                                (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass());
784                List<String> parts = parsePath(def, thePath);
785                return getValues(def, theElement, parts, theWantedClass);
786        }
787
788        /**
789         * Returns values stored in an element identified by its path. The list of values is of
790         * type <code>theWantedClass</code>.
791         *
792         * @param theElement     The element to be accessed. Must not be null.
793         * @param thePath        The path for the element to be accessed.
794         * @param theWantedClass The desired class to be returned in a list.
795         * @param theCreate      When set to <code>true</code>, the terser will create a null-valued element where none exists.
796         * @param <T>            Type declared by <code>theWantedClass</code>
797         * @return A list of values of type <code>theWantedClass</code>.
798         */
799        public <T extends IBase> List<T> getValues(
800                        IBase theElement, String thePath, Class<T> theWantedClass, boolean theCreate) {
801                BaseRuntimeElementCompositeDefinition<?> def =
802                                (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass());
803                List<String> parts = parsePath(def, thePath);
804                return getValues(def, theElement, parts, theWantedClass, theCreate, false);
805        }
806
807        /**
808         * Returns values stored in an element identified by its path. The list of values is of
809         * type <code>theWantedClass</code>.
810         *
811         * @param theElement      The element to be accessed. Must not be null.
812         * @param thePath         The path for the element to be accessed.
813         * @param theWantedClass  The desired class to be returned in a list.
814         * @param theCreate       When set to <code>true</code>, the terser will create a null-valued element where none exists.
815         * @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist.
816         * @param <T>             Type declared by <code>theWantedClass</code>
817         * @return A list of values of type <code>theWantedClass</code>.
818         */
819        public <T extends IBase> List<T> getValues(
820                        IBase theElement, String thePath, Class<T> theWantedClass, boolean theCreate, boolean theAddExtension) {
821                BaseRuntimeElementCompositeDefinition<?> def =
822                                (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass());
823                List<String> parts = parsePath(def, thePath);
824                return getValues(def, theElement, parts, theWantedClass, theCreate, theAddExtension);
825        }
826
827        private List<String> parsePath(BaseRuntimeElementCompositeDefinition<?> theElementDef, String thePath) {
828                List<String> parts = new ArrayList<>();
829
830                int currentStart = 0;
831                boolean inSingleQuote = false;
832                for (int i = 0; i < thePath.length(); i++) {
833                        switch (thePath.charAt(i)) {
834                                case '\'':
835                                        inSingleQuote = !inSingleQuote;
836                                        break;
837                                case '.':
838                                        if (!inSingleQuote) {
839                                                parts.add(thePath.substring(currentStart, i));
840                                                currentStart = i + 1;
841                                        }
842                                        break;
843                        }
844                }
845
846                parts.add(thePath.substring(currentStart));
847
848                String firstPart = parts.get(0);
849                if (Character.isUpperCase(firstPart.charAt(0)) && theElementDef instanceof RuntimeResourceDefinition) {
850                        if (firstPart.equals(theElementDef.getName())) {
851                                parts = parts.subList(1, parts.size());
852                        } else {
853                                parts = Collections.emptyList();
854                                return parts;
855                        }
856                } else if (firstPart.equals(theElementDef.getName())) {
857                        parts = parts.subList(1, parts.size());
858                }
859
860                if (parts.size() < 1) {
861                        throw new ConfigurationException(Msg.code(1792) + "Invalid path: " + thePath);
862                }
863                return parts;
864        }
865
866        /**
867         * Returns <code>true</code> if <code>theSource</code> is in the compartment named <code>theCompartmentName</code>
868         * belonging to resource <code>theTarget</code>
869         *
870         * @param theCompartmentName The name of the compartment
871         * @param theSource          The potential member of the compartment
872         * @param theTarget          The owner of the compartment. Note that both the resource type and ID must be filled in on this IIdType or the method will throw an {@link IllegalArgumentException}
873         * @return <code>true</code> if <code>theSource</code> is in the compartment
874         * @throws IllegalArgumentException If theTarget does not contain both a resource type and ID
875         */
876        public boolean isSourceInCompartmentForTarget(
877                        String theCompartmentName, IBaseResource theSource, IIdType theTarget) {
878                return isSourceInCompartmentForTarget(
879                                theCompartmentName, theSource, theTarget, new CompartmentSearchParameterModifications());
880        }
881
882        @Deprecated
883        public boolean isSourceInCompartmentForTarget(
884                        String theCompartmentName,
885                        IBaseResource theSource,
886                        IIdType theTarget,
887                        @Nullable Set<String> theAdditionalCompartmentParamNames) {
888                return isSourceInCompartmentForTarget(
889                                theCompartmentName,
890                                theSource,
891                                theTarget,
892                                CompartmentSearchParameterModifications.fromAdditionalCompartmentParamNames(
893                                                myContext.getResourceType(theSource),
894                                                theAdditionalCompartmentParamNames == null ? Set.of() : theAdditionalCompartmentParamNames));
895        }
896
897        /**
898         * Returns <code>true</code> if <code>theSource</code> is in the compartment named <code>theCompartmentName</code>
899         * belonging to resource <code>theTarget</code>
900         *
901         * @param theCompartmentName                 The name of the compartment
902         * @param theSource                          The potential member of the compartment
903         * @param theTarget                          The owner of the compartment. Note that both the resource type and ID must be filled in on this IIdType or the method will throw an {@link IllegalArgumentException}
904         * @param theModifications If provided, SP modifications to the compartment
905         * @return <code>true</code> if <code>theSource</code> is in the compartment or one of the additional parameters matched.
906         * @throws IllegalArgumentException If theTarget does not contain both a resource type and ID
907         */
908        public boolean isSourceInCompartmentForTarget(
909                        String theCompartmentName,
910                        IBaseResource theSource,
911                        IIdType theTarget,
912                        CompartmentSearchParameterModifications theModifications) {
913                Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank");
914                Validate.notNull(theSource, "theSource must not be null");
915                Validate.notNull(theTarget, "theTarget must not be null");
916                Validate.notBlank(
917                                defaultString(theTarget.getResourceType()),
918                                "theTarget must have a populated resource type (theTarget.getResourceType() does not return a value)");
919                Validate.notBlank(
920                                defaultString(theTarget.getIdPart()),
921                                "theTarget must have a populated ID (theTarget.getIdPart() does not return a value)");
922
923                String wantRef = theTarget.toUnqualifiedVersionless().getValue();
924
925                RuntimeResourceDefinition sourceDef = myContext.getResourceDefinition(theSource);
926                if (theSource.getIdElement().hasIdPart()) {
927                        if (wantRef.equals(
928                                        sourceDef.getName() + '/' + theSource.getIdElement().getIdPart())) {
929                                return true;
930                        }
931                }
932
933                class CompartmentOwnerVisitor implements ICompartmentOwnerVisitor {
934
935                        private final String myWantRef;
936
937                        public boolean isFound() {
938                                return myFound;
939                        }
940
941                        private boolean myFound;
942
943                        public CompartmentOwnerVisitor(String theWantRef) {
944                                myWantRef = theWantRef;
945                        }
946
947                        @Override
948                        public boolean consume(IIdType theCompartmentOwner) {
949                                if (myWantRef.equals(
950                                                theCompartmentOwner.toUnqualifiedVersionless().getValue())) {
951                                        myFound = true;
952                                }
953                                return !myFound;
954                        }
955                }
956
957                CompartmentOwnerVisitor consumer = new CompartmentOwnerVisitor(wantRef);
958                visitCompartmentOwnersForResource(theCompartmentName, theSource, theModifications, consumer);
959                return consumer.isFound();
960        }
961
962        public List<IIdType> getCompartmentOwnersForResource(
963                        String theCompartmentName,
964                        IBaseResource theSource,
965                        @Nullable Set<String> theAdditionalCompartmentParamNames) {
966                return getCompartmentOwnersForResource(
967                                theCompartmentName,
968                                theSource,
969                                CompartmentSearchParameterModifications.fromAdditionalCompartmentParamNames(
970                                                myContext.getResourceType(theCompartmentName),
971                                                theAdditionalCompartmentParamNames == null ? Set.of() : theAdditionalCompartmentParamNames));
972        }
973
974        /**
975         * Returns the owners of the compartment in <code>theSource</code> is in the compartment named <code>theCompartmentName</code>.
976         *
977         * @param theCompartmentName                 The name of the compartment
978         * @param theSource                          The potential member of the compartment
979         * @param theModifications If provided, defines compartment modifications to be considered
980         */
981        @Nonnull
982        public List<IIdType> getCompartmentOwnersForResource(
983                        String theCompartmentName,
984                        IBaseResource theSource,
985                        CompartmentSearchParameterModifications theModifications) {
986                Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank");
987                Validate.notNull(theSource, "theSource must not be null");
988
989                class CompartmentOwnerVisitor implements ICompartmentOwnerVisitor {
990
991                        private final Set<String> myOwnersAdded = new HashSet<>();
992                        private final List<IIdType> myOwners = new ArrayList<>(2);
993
994                        public List<IIdType> getOwners() {
995                                return myOwners;
996                        }
997
998                        @Override
999                        public boolean consume(IIdType theCompartmentOwner) {
1000                                if (myOwnersAdded.add(theCompartmentOwner.getValue())) {
1001                                        myOwners.add(theCompartmentOwner);
1002                                }
1003                                return true;
1004                        }
1005                }
1006
1007                CompartmentOwnerVisitor consumer = new CompartmentOwnerVisitor();
1008                visitCompartmentOwnersForResource(theCompartmentName, theSource, theModifications, consumer);
1009                return consumer.getOwners();
1010        }
1011
1012        public Stream<IBaseReference> getCompartmentReferencesForResource(
1013                        String theCompartmentName,
1014                        IBaseResource theSource,
1015                        @Nullable Set<String> theAdditionalCompartmentParamNames) {
1016                return getCompartmentReferencesForResource(
1017                                theCompartmentName,
1018                                theSource,
1019                                CompartmentSearchParameterModifications.fromAdditionalCompartmentParamNames(
1020                                                myContext.getResourceType(theCompartmentName),
1021                                                theAdditionalCompartmentParamNames == null ? Set.of() : theAdditionalCompartmentParamNames));
1022        }
1023
1024        @Nonnull
1025        public Stream<IBaseReference> getCompartmentReferencesForResource(
1026                        String theCompartmentName,
1027                        IBaseResource theSource,
1028                        @Nullable CompartmentSearchParameterModifications theModifications) {
1029                Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank");
1030                Validate.notNull(theSource, "theSource must not be null");
1031                Set<String> additionalSPNames;
1032                Set<String> omittedSPNames;
1033                if (theModifications != null) {
1034                        String resourceType = myContext.getResourceType(theSource);
1035                        additionalSPNames = theModifications.getAdditionalSearchParamNamesForResourceType(resourceType);
1036                        omittedSPNames = theModifications.getOmittedSPNamesForResourceType(resourceType);
1037                } else {
1038                        additionalSPNames = new HashSet<>();
1039                        omittedSPNames = new HashSet<>();
1040                }
1041
1042                RuntimeResourceDefinition sourceDef = myContext.getResourceDefinition(theSource);
1043                List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(theCompartmentName).stream()
1044                                .filter(p -> !omittedSPNames.contains(p.getName()))
1045                                .collect(Collectors.toList());
1046
1047                additionalSPNames.stream()
1048                                .map(sourceDef::getSearchParam)
1049                                .filter(Objects::nonNull)
1050                                .forEach(params::add);
1051
1052                return params.stream()
1053                                .flatMap(nextParam -> nextParam.getPathsSplit().stream())
1054                                .filter(StringUtils::isNotBlank)
1055                                .flatMap(nextPath -> {
1056
1057                                        /*
1058                                         * DSTU3 and before just defined compartments as being (e.g.) named
1059                                         * Patient with a path like CarePlan.subject
1060                                         *
1061                                         * R4 uses a fancier format like CarePlan.subject.where(resolve() is Patient)
1062                                         *
1063                                         * The following Regex is a hack to make that efficient at runtime.
1064                                         */
1065                                        String wantType;
1066                                        Matcher matcher = COMPARTMENT_MATCHER_PATH.matcher(nextPath);
1067                                        if (matcher.matches()) {
1068                                                nextPath = matcher.group(1);
1069                                                wantType = matcher.group(2);
1070                                        } else {
1071                                                wantType = null;
1072                                        }
1073
1074                                        return getValues(theSource, nextPath, IBaseReference.class).stream()
1075                                                        .filter(nextValue -> isEmpty(wantType) || wantType.equals(getTypeFromReference(nextValue)));
1076                                });
1077        }
1078
1079        private String getTypeFromReference(IBaseReference theReference) {
1080
1081                IIdType nextTargetId = theReference.getReferenceElement().toUnqualifiedVersionless();
1082
1083                if (isNotBlank(nextTargetId.getResourceType())) {
1084                        return nextTargetId.getResourceType();
1085                } else if (theReference.getResource() != null) {
1086                        /*
1087                         * If the reference isn't an explicit resource ID, but instead is just
1088                         * a resource object, we'll use that type.
1089                         */
1090                        return myContext.getResourceType(theReference.getResource());
1091                } else {
1092                        return "No type on reference";
1093                }
1094        }
1095
1096        private void visitCompartmentOwnersForResource(
1097                        String theCompartmentName,
1098                        IBaseResource theSource,
1099                        CompartmentSearchParameterModifications theCompartmentModifications,
1100                        ICompartmentOwnerVisitor theConsumer) {
1101
1102                getCompartmentReferencesForResource(theCompartmentName, theSource, theCompartmentModifications)
1103                                .flatMap(nextValue -> {
1104                                        IIdType nextTargetId = nextValue.getReferenceElement().toUnqualifiedVersionless();
1105
1106                                        /*
1107                                         * If the reference isn't an explicit resource ID, but instead is just
1108                                         * a resource object, we'll calculate its ID and treat the target
1109                                         * as that.
1110                                         */
1111                                        if (isBlank(nextTargetId.getValue()) && nextValue.getResource() != null) {
1112                                                IBaseResource nextTarget = nextValue.getResource();
1113                                                nextTargetId = nextTarget.getIdElement().toUnqualifiedVersionless();
1114                                                if (!nextTargetId.hasResourceType()) {
1115                                                        String resourceType = myContext.getResourceType(nextTarget);
1116                                                        nextTargetId.setParts(null, resourceType, nextTargetId.getIdPart(), null);
1117                                                }
1118                                        }
1119                                        if (isNotBlank(nextTargetId.getValue())) {
1120                                                return Stream.of(nextTargetId);
1121                                        } else {
1122                                                return Stream.empty();
1123                                        }
1124                                })
1125                                .takeWhile(theConsumer::consume)
1126                                .forEach(nop());
1127        }
1128
1129        private void visit(
1130                        IBase theElement,
1131                        BaseRuntimeChildDefinition theChildDefinition,
1132                        BaseRuntimeElementDefinition<?> theDefinition,
1133                        IModelVisitor2 theCallback,
1134                        List<IBase> theContainingElementPath,
1135                        List<BaseRuntimeChildDefinition> theChildDefinitionPath,
1136                        List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
1137                if (theChildDefinition != null) {
1138                        theChildDefinitionPath.add(theChildDefinition);
1139                }
1140                theContainingElementPath.add(theElement);
1141                theElementDefinitionPath.add(theDefinition);
1142
1143                boolean recurse = theCallback.acceptElement(
1144                                theElement,
1145                                Collections.unmodifiableList(theContainingElementPath),
1146                                Collections.unmodifiableList(theChildDefinitionPath),
1147                                Collections.unmodifiableList(theElementDefinitionPath));
1148                if (recurse) {
1149
1150                        /*
1151                         * Visit undeclared extensions
1152                         */
1153                        if (theElement instanceof ISupportsUndeclaredExtensions) {
1154                                ISupportsUndeclaredExtensions containingElement = (ISupportsUndeclaredExtensions) theElement;
1155                                for (ExtensionDt nextExt : containingElement.getUndeclaredExtensions()) {
1156                                        theContainingElementPath.add(nextExt);
1157                                        theCallback.acceptUndeclaredExtension(
1158                                                        nextExt, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
1159                                        theContainingElementPath.remove(theContainingElementPath.size() - 1);
1160                                }
1161                        }
1162
1163                        /*
1164                         * Now visit the children of the given element
1165                         */
1166                        switch (theDefinition.getChildType()) {
1167                                case ID_DATATYPE:
1168                                case PRIMITIVE_XHTML_HL7ORG:
1169                                case PRIMITIVE_XHTML:
1170                                case PRIMITIVE_DATATYPE:
1171                                        // These are primitive types, so we don't need to visit their children
1172                                        break;
1173                                case RESOURCE:
1174                                case RESOURCE_BLOCK:
1175                                case COMPOSITE_DATATYPE: {
1176                                        BaseRuntimeElementCompositeDefinition<?> childDef =
1177                                                        (BaseRuntimeElementCompositeDefinition<?>) theDefinition;
1178                                        for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
1179                                                List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
1180                                                if (values != null) {
1181                                                        for (IBase nextValue : values) {
1182                                                                if (nextValue == null) {
1183                                                                        continue;
1184                                                                }
1185                                                                if (nextValue.isEmpty()) {
1186                                                                        continue;
1187                                                                }
1188                                                                BaseRuntimeElementDefinition<?> childElementDef;
1189                                                                Class<? extends IBase> valueType = nextValue.getClass();
1190                                                                childElementDef = nextChild.getChildElementDefinitionByDatatype(valueType);
1191                                                                while (childElementDef == null && IBase.class.isAssignableFrom(valueType)) {
1192                                                                        childElementDef = nextChild.getChildElementDefinitionByDatatype(valueType);
1193                                                                        valueType = (Class<? extends IBase>) valueType.getSuperclass();
1194                                                                }
1195
1196                                                                Class<? extends IBase> typeClass = nextValue.getClass();
1197                                                                while (childElementDef == null && IBase.class.isAssignableFrom(typeClass)) {
1198                                                                        //noinspection unchecked
1199                                                                        typeClass = (Class<? extends IBase>) typeClass.getSuperclass();
1200                                                                        childElementDef = nextChild.getChildElementDefinitionByDatatype(typeClass);
1201                                                                }
1202
1203                                                                Validate.notNull(
1204                                                                                childElementDef,
1205                                                                                "Found value of type[%s] which is not valid for field[%s] in %s",
1206                                                                                nextValue.getClass(),
1207                                                                                nextChild.getElementName(),
1208                                                                                childDef.getName());
1209
1210                                                                visit(
1211                                                                                nextValue,
1212                                                                                nextChild,
1213                                                                                childElementDef,
1214                                                                                theCallback,
1215                                                                                theContainingElementPath,
1216                                                                                theChildDefinitionPath,
1217                                                                                theElementDefinitionPath);
1218                                                        }
1219                                                }
1220                                        }
1221                                        break;
1222                                }
1223                                case CONTAINED_RESOURCES: {
1224                                        BaseContainedDt value = (BaseContainedDt) theElement;
1225                                        for (IResource next : value.getContainedResources()) {
1226                                                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(next);
1227                                                visit(
1228                                                                next,
1229                                                                null,
1230                                                                def,
1231                                                                theCallback,
1232                                                                theContainingElementPath,
1233                                                                theChildDefinitionPath,
1234                                                                theElementDefinitionPath);
1235                                        }
1236                                        break;
1237                                }
1238                                case EXTENSION_DECLARED:
1239                                case UNDECL_EXT: {
1240                                        throw new IllegalStateException(
1241                                                        Msg.code(1793) + "state should not happen: " + theDefinition.getChildType());
1242                                }
1243                                case CONTAINED_RESOURCE_LIST: {
1244                                        if (theElement != null) {
1245                                                BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass());
1246                                                visit(
1247                                                                theElement,
1248                                                                null,
1249                                                                def,
1250                                                                theCallback,
1251                                                                theContainingElementPath,
1252                                                                theChildDefinitionPath,
1253                                                                theElementDefinitionPath);
1254                                        }
1255                                        break;
1256                                }
1257                        }
1258                }
1259
1260                if (theChildDefinition != null) {
1261                        theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1);
1262                }
1263                theContainingElementPath.remove(theContainingElementPath.size() - 1);
1264                theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1);
1265        }
1266
1267        /**
1268         * Visit all elements in a given resource
1269         *
1270         * <p>
1271         * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g.
1272         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
1273         * </p>
1274         *
1275         * @param theResource The resource to visit
1276         * @param theVisitor  The visitor
1277         */
1278        public void visit(IBaseResource theResource, IModelVisitor theVisitor) {
1279                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
1280                visit(newMap(), theResource, theResource, null, null, def, theVisitor);
1281        }
1282
1283        public Map<Object, Object> newMap() {
1284                return new IdentityHashMap<>();
1285        }
1286
1287        /**
1288         * Visit all elements in a given resource or element
1289         * <p>
1290         * <b>THIS ALTERNATE METHOD IS STILL EXPERIMENTAL! USE WITH CAUTION</b>
1291         * </p>
1292         * <p>
1293         * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g.
1294         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
1295         * </p>
1296         *
1297         * @param theElement The element to visit
1298         * @param theVisitor The visitor
1299         */
1300        public void visit(IBase theElement, IModelVisitor2 theVisitor) {
1301                BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass());
1302                if (def instanceof BaseRuntimeElementCompositeDefinition) {
1303                        visit(theElement, null, def, theVisitor, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
1304                } else if (theElement instanceof IBaseExtension) {
1305                        theVisitor.acceptUndeclaredExtension(
1306                                        (IBaseExtension<?, ?>) theElement,
1307                                        Collections.emptyList(),
1308                                        Collections.emptyList(),
1309                                        Collections.emptyList());
1310                } else {
1311                        theVisitor.acceptElement(
1312                                        theElement, Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
1313                }
1314        }
1315
1316        private void visit(
1317                        Map<Object, Object> theStack,
1318                        IBaseResource theResource,
1319                        IBase theElement,
1320                        List<String> thePathToElement,
1321                        BaseRuntimeChildDefinition theChildDefinition,
1322                        BaseRuntimeElementDefinition<?> theDefinition,
1323                        IModelVisitor theCallback) {
1324                List<String> pathToElement = addNameToList(thePathToElement, theChildDefinition);
1325
1326                if (theStack.put(theElement, theElement) != null) {
1327                        return;
1328                }
1329
1330                theCallback.acceptElement(theResource, theElement, pathToElement, theChildDefinition, theDefinition);
1331
1332                BaseRuntimeElementDefinition<?> def = theDefinition;
1333                if (def.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) {
1334                        Class<? extends IBase> clazz = theElement.getClass();
1335                        def = myContext.getElementDefinition(clazz);
1336                        Validate.notNull(def, "Unable to find element definition for class: %s", clazz);
1337                }
1338
1339                if (theElement instanceof IBaseReference) {
1340                        IBaseResource target = ((IBaseReference) theElement).getResource();
1341                        if (target != null) {
1342                                if (target.getIdElement().hasIdPart() == false
1343                                                || target.getIdElement().isLocal()) {
1344                                        RuntimeResourceDefinition targetDef = myContext.getResourceDefinition(target);
1345                                        visit(theStack, target, target, pathToElement, null, targetDef, theCallback);
1346                                }
1347                        }
1348                }
1349
1350                switch (def.getChildType()) {
1351                        case ID_DATATYPE:
1352                        case PRIMITIVE_XHTML_HL7ORG:
1353                        case PRIMITIVE_XHTML:
1354                        case PRIMITIVE_DATATYPE:
1355                                // These are primitive types
1356                                break;
1357                        case RESOURCE:
1358                        case RESOURCE_BLOCK:
1359                        case COMPOSITE_DATATYPE: {
1360                                BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) def;
1361                                List<BaseRuntimeChildDefinition> childrenAndExtensionDefs = childDef.getChildrenAndExtension();
1362                                for (BaseRuntimeChildDefinition nextChild : childrenAndExtensionDefs) {
1363
1364                                        List<?> values = nextChild.getAccessor().getValues(theElement);
1365
1366                                        if (values != null) {
1367                                                for (Object nextValueObject : values) {
1368                                                        IBase nextValue;
1369                                                        try {
1370                                                                nextValue = (IBase) nextValueObject;
1371                                                        } catch (ClassCastException e) {
1372                                                                String s = "Found instance of " + nextValueObject.getClass()
1373                                                                                + " - Did you set a field value to the incorrect type? Expected "
1374                                                                                + IBase.class.getName();
1375                                                                throw new ClassCastException(Msg.code(1794) + s);
1376                                                        }
1377                                                        if (nextValue == null) {
1378                                                                continue;
1379                                                        }
1380                                                        if (nextValue.isEmpty()) {
1381                                                                continue;
1382                                                        }
1383                                                        BaseRuntimeElementDefinition<?> childElementDef;
1384                                                        Class<? extends IBase> clazz = nextValue.getClass();
1385                                                        childElementDef = nextChild.getChildElementDefinitionByDatatype(clazz);
1386
1387                                                        if (childElementDef == null) {
1388                                                                childElementDef = myContext.getElementDefinition(clazz);
1389                                                                Validate.notNull(
1390                                                                                childElementDef, "Unable to find element definition for class: %s", clazz);
1391                                                        }
1392
1393                                                        if (nextChild instanceof RuntimeChildDirectResource) {
1394                                                                // Don't descend into embedded resources
1395                                                                theCallback.acceptElement(theResource, nextValue, null, nextChild, childElementDef);
1396                                                        } else {
1397                                                                visit(
1398                                                                                theStack,
1399                                                                                theResource,
1400                                                                                nextValue,
1401                                                                                pathToElement,
1402                                                                                nextChild,
1403                                                                                childElementDef,
1404                                                                                theCallback);
1405                                                        }
1406                                                }
1407                                        }
1408                                }
1409                                break;
1410                        }
1411                        case CONTAINED_RESOURCES: {
1412                                BaseContainedDt value = (BaseContainedDt) theElement;
1413                                for (IResource next : value.getContainedResources()) {
1414                                        def = myContext.getResourceDefinition(next);
1415                                        visit(theStack, next, next, pathToElement, null, def, theCallback);
1416                                }
1417                                break;
1418                        }
1419                        case CONTAINED_RESOURCE_LIST:
1420                        case EXTENSION_DECLARED:
1421                        case UNDECL_EXT: {
1422                                throw new IllegalStateException(Msg.code(1795) + "state should not happen: " + def.getChildType());
1423                        }
1424                }
1425
1426                theStack.remove(theElement);
1427        }
1428
1429        /**
1430         * Returns all embedded resources that are found embedded within <code>theResource</code>.
1431         * An embedded resource is a resource that can be found as a direct child within a resource,
1432         * as opposed to being referenced by the resource.
1433         * <p>
1434         * Examples include resources found within <code>Bundle.entry.resource</code>
1435         * and <code>Parameters.parameter.resource</code>, as well as contained resources
1436         * found within <code>Resource.contained</code>
1437         * </p>
1438         *
1439         * @param theRecurse Should embedded resources be recursively scanned for further embedded
1440         *                   resources
1441         * @return A collection containing the embedded resources. Order is arbitrary.
1442         */
1443        public Collection<IBaseResource> getAllEmbeddedResources(IBaseResource theResource, boolean theRecurse) {
1444                Validate.notNull(theResource, "theResource must not be null");
1445                ArrayList<IBaseResource> retVal = new ArrayList<>();
1446
1447                visit(theResource, new IModelVisitor2() {
1448                        @Override
1449                        public boolean acceptElement(
1450                                        IBase theElement,
1451                                        List<IBase> theContainingElementPath,
1452                                        List<BaseRuntimeChildDefinition> theChildDefinitionPath,
1453                                        List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
1454                                if (theElement == theResource) {
1455                                        return true;
1456                                }
1457                                if (theElement instanceof IBaseResource) {
1458                                        retVal.add((IBaseResource) theElement);
1459                                        return theRecurse;
1460                                }
1461                                return true;
1462                        }
1463
1464                        @Override
1465                        public boolean acceptUndeclaredExtension(
1466                                        IBaseExtension<?, ?> theNextExt,
1467                                        List<IBase> theContainingElementPath,
1468                                        List<BaseRuntimeChildDefinition> theChildDefinitionPath,
1469                                        List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
1470                                return true;
1471                        }
1472                });
1473
1474                return retVal;
1475        }
1476
1477        /**
1478         * Clear all content on an element
1479         *
1480         * @return Returns <code>true</code> if any content was actually cleared
1481         * @since 8.6.0
1482         */
1483        public boolean clear(IBase theInput) {
1484                ClearingModelVisitor visitor = new ClearingModelVisitor();
1485                visit(theInput, visitor);
1486                return visitor.myFoundContent;
1487        }
1488
1489        /**
1490         * Clear all content on a resource.
1491         *
1492         * @return Returns <code>true</code> if any content was actually cleared
1493         * @see #clear(IBase) This method is a synonym for {@link #clear(IBase)}, provided for historical reasons.
1494         */
1495        public boolean clear(IBaseResource theInput) {
1496                ClearingModelVisitor visitor = new ClearingModelVisitor();
1497                visit(theInput, visitor);
1498                return visitor.myFoundContent;
1499        }
1500
1501        private void containResourcesForEncoding(ContainedResources theContained, IBaseResource theResource) {
1502                List<IBaseReference> allReferences = getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
1503
1504                // Note that we process all contained resources that have arrived here with an ID contained resources first, so
1505                // that we don't accidentally auto-assign an ID
1506                // which may collide with a resource we have yet to process.
1507                // See: https://github.com/hapifhir/hapi-fhir/issues/6403
1508                allReferences.sort(REFERENCES_WITH_IDS_FIRST);
1509
1510                for (IBaseReference next : allReferences) {
1511                        IBaseResource resource = next.getResource();
1512                        if (resource == null && next.getReferenceElement().isLocal()) {
1513                                if (theContained.hasExistingIdToContainedResource()) {
1514                                        IBaseResource potentialTarget = theContained
1515                                                        .getExistingIdToContainedResource()
1516                                                        .remove(next.getReferenceElement().getValue());
1517                                        if (potentialTarget != null) {
1518                                                theContained.addContained(next.getReferenceElement(), potentialTarget);
1519                                                containResourcesForEncoding(theContained, potentialTarget);
1520                                        }
1521                                }
1522                        }
1523                }
1524
1525                for (IBaseReference next : allReferences) {
1526                        IBaseResource resource = next.getResource();
1527                        if (resource != null) {
1528                                if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) {
1529
1530                                        IIdType id = theContained.addContained(resource);
1531                                        if (id == null) {
1532                                                continue;
1533                                        }
1534                                        getContainedResourceList(theResource).add(resource);
1535
1536                                        String idString = id.getValue();
1537                                        if (!idString.startsWith("#")) {
1538                                                idString = "#" + idString;
1539                                        }
1540
1541                                        next.setReference(idString);
1542                                        next.setResource(null);
1543                                        if (resource.getIdElement().isLocal() && theContained.hasExistingIdToContainedResource()) {
1544                                                theContained
1545                                                                .getExistingIdToContainedResource()
1546                                                                .remove(resource.getIdElement().getValue());
1547                                        }
1548                                } else {
1549                                        IIdType previouslyContainedResourceId = theContained.getPreviouslyContainedResourceId(resource);
1550                                        if (previouslyContainedResourceId != null) {
1551                                                if (theContained.getResourceId(resource) == null) {
1552                                                        theContained.addContained(previouslyContainedResourceId, resource);
1553                                                        getContainedResourceList(theResource).add(resource);
1554                                                }
1555                                        }
1556                                }
1557                        }
1558                }
1559        }
1560
1561        /**
1562         * Iterate through the whole resource and identify any contained resources. Optionally this method
1563         * can also assign IDs and modify references where the resource link has been specified but not the
1564         * reference text.
1565         *
1566         * @since 5.4.0
1567         */
1568        public ContainedResources containResources(
1569                        IBaseResource theResource, ContainedResources theParentContainedResources, boolean theStoreResults) {
1570
1571                if (theStoreResults) {
1572                        Object cachedValue = theResource.getUserData(USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED);
1573                        if (cachedValue != null) {
1574                                return (ContainedResources) cachedValue;
1575                        }
1576                }
1577
1578                ContainedResources contained = new ContainedResources();
1579
1580                List<? extends IBaseResource> containedResources = getContainedResourceList(theResource);
1581                for (IBaseResource next : containedResources) {
1582                        String nextId = next.getIdElement().getValue();
1583                        if (StringUtils.isNotBlank(nextId)) {
1584                                while (nextId.startsWith("#")) {
1585                                        ourLog.warn(
1586                                                        "Found contained resource with ID starting with # character ({}). This form of ID is deprecated and will be dropped in a future release, preventing the current code from working correctly.",
1587                                                        nextId);
1588                                        nextId = nextId.substring(1);
1589                                }
1590                                next.getIdElement().setValue(nextId);
1591                        }
1592                        contained.addContained(next);
1593                }
1594
1595                if (myContext.getParserOptions().isAutoContainReferenceTargetsWithNoId()) {
1596                        if (theParentContainedResources != null) {
1597                                if (theParentContainedResources.hasResourceToIdValues()) {
1598                                        contained
1599                                                        .getPreviouslyContainedResourceToIdMap()
1600                                                        .putAll(theParentContainedResources.getResourceToIdMap());
1601                                }
1602                                if (theParentContainedResources.hasPreviouslyContainedResourceToIdValues()) {
1603                                        contained
1604                                                        .getPreviouslyContainedResourceToIdMap()
1605                                                        .putAll(theParentContainedResources.getPreviouslyContainedResourceToIdMap());
1606                                }
1607                        }
1608
1609                        containResourcesForEncoding(contained, theResource);
1610                }
1611
1612                if (theStoreResults) {
1613                        theResource.setUserData(USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED, contained);
1614                }
1615
1616                return contained;
1617        }
1618
1619        @SuppressWarnings("unchecked")
1620        private <T extends IBaseResource> List<T> getContainedResourceList(T theResource) {
1621                List<T> containedResources = Collections.emptyList();
1622                if (theResource instanceof IResource) {
1623                        containedResources =
1624                                        (List<T>) ((IResource) theResource).getContained().getContainedResources();
1625                } else if (theResource instanceof IDomainResource) {
1626                        containedResources = (List<T>) ((IDomainResource) theResource).getContained();
1627                }
1628                return containedResources;
1629        }
1630
1631        /**
1632         * Adds and returns a new element at the given path within the given structure. The paths used here
1633         * are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions.
1634         * <p>
1635         * Only the last entry in the path is always created, existing repetitions of elements before
1636         * the final dot are returned if they exists (although they are created if they do not). For example,
1637         * given the path <code>Patient.name.given</code>, a new repetition of <code>given</code> is always
1638         * added to the first (index 0) repetition of the name. If an index-0 repetition of <code>name</code>
1639         * already exists, it is added to. If one does not exist, it if created and then added to.
1640         * </p>
1641         * <p>
1642         * If the last element in the path refers to a non-repeatable element that is already present and
1643         * is not empty, a {@link DataFormatException} error will be thrown.
1644         * </p>
1645         *
1646         * @param theTarget The element to add to. This will often be a {@link IBaseResource resource}
1647         *                  instance, but does not need to be.
1648         * @param thePath   The path.
1649         * @return The newly added element
1650         * @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or
1651         *                             an element that is non-repeatable but not already populated.
1652         */
1653        @SuppressWarnings("unchecked")
1654        @Nonnull
1655        public <T extends IBase> T addElement(@Nonnull IBase theTarget, @Nonnull String thePath) {
1656                return (T) doAddElement(theTarget, thePath, 1).get(0);
1657        }
1658
1659        @SuppressWarnings("unchecked")
1660        private <T extends IBase> List<T> doAddElement(IBase theTarget, String thePath, int theElementsToAdd) {
1661                if (theElementsToAdd == 0) {
1662                        return Collections.emptyList();
1663                }
1664
1665                IBase target = theTarget;
1666                BaseRuntimeElementCompositeDefinition<?> def =
1667                                (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(target.getClass());
1668                List<String> parts = parsePath(def, thePath);
1669
1670                for (int i = 0, partsSize = parts.size(); ; i++) {
1671                        String nextPart = parts.get(i);
1672                        boolean lastPart = i == partsSize - 1;
1673
1674                        BaseRuntimeChildDefinition nextChild = def.getChildByName(nextPart);
1675                        if (nextChild == null) {
1676                                throw new DataFormatException(Msg.code(1796) + "Invalid path " + thePath + ": Element of type "
1677                                                + def.getName() + " has no child named " + nextPart + ". Valid names: "
1678                                                + def.getChildrenAndExtension().stream()
1679                                                                .map(BaseRuntimeChildDefinition::getElementName)
1680                                                                .sorted()
1681                                                                .collect(Collectors.joining(", ")));
1682                        }
1683
1684                        List<IBase> childValues = nextChild.getAccessor().getValues(target);
1685                        IBase childValue;
1686                        if (childValues.size() > 0 && !lastPart) {
1687                                childValue = childValues.get(0);
1688                        } else {
1689
1690                                if (lastPart) {
1691                                        if (!childValues.isEmpty()) {
1692                                                if (theElementsToAdd == -1) {
1693                                                        return (List<T>) Collections.singletonList(childValues.get(0));
1694                                                } else if (nextChild.getMax() == 1
1695                                                                && !childValues.get(0).isEmpty()) {
1696                                                        throw new DataFormatException(
1697                                                                        Msg.code(1797) + "Element at path " + thePath + " is not repeatable and not empty");
1698                                                } else if (nextChild.getMax() == 1 && childValues.get(0).isEmpty()) {
1699                                                        return (List<T>) Collections.singletonList(childValues.get(0));
1700                                                }
1701                                        }
1702                                }
1703
1704                                BaseRuntimeElementDefinition<?> elementDef = nextChild.getChildByName(nextPart);
1705                                childValue = elementDef.newInstance(nextChild.getInstanceConstructorArguments());
1706                                nextChild.getMutator().addValue(target, childValue);
1707
1708                                if (lastPart) {
1709                                        if (theElementsToAdd == 1 || theElementsToAdd == -1) {
1710                                                return (List<T>) Collections.singletonList(childValue);
1711                                        } else {
1712                                                if (nextChild.getMax() == 1) {
1713                                                        throw new DataFormatException(Msg.code(1798) + "Can not add multiple values at path "
1714                                                                        + thePath + ": Element does not repeat");
1715                                                }
1716
1717                                                List<T> values = (List<T>) Lists.newArrayList(childValue);
1718                                                for (int j = 1; j < theElementsToAdd; j++) {
1719                                                        childValue = elementDef.newInstance(nextChild.getInstanceConstructorArguments());
1720                                                        nextChild.getMutator().addValue(target, childValue);
1721                                                        values.add((T) childValue);
1722                                                }
1723
1724                                                return values;
1725                                        }
1726                                }
1727                        }
1728
1729                        target = childValue;
1730
1731                        if (!lastPart) {
1732                                BaseRuntimeElementDefinition<?> nextDef = myContext.getElementDefinition(target.getClass());
1733                                if (!(nextDef instanceof BaseRuntimeElementCompositeDefinition)) {
1734                                        throw new DataFormatException(Msg.code(1799) + "Invalid path " + thePath + ": Element of type "
1735                                                        + def.getName() + " has no child named " + nextPart + " (this is a primitive type)");
1736                                }
1737                                def = (BaseRuntimeElementCompositeDefinition<?>) nextDef;
1738                        }
1739                }
1740        }
1741
1742        /**
1743         * Adds and returns a new element at the given path within the given structure. The paths used here
1744         * are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions.
1745         * <p>
1746         * This method follows all of the same semantics as {@link #addElement(IBase, String)} but it
1747         * requires the path to point to an element with a primitive datatype and set the value of
1748         * the datatype to the given value.
1749         * </p>
1750         *
1751         * @param theTarget The element to add to. This will often be a {@link IBaseResource resource}
1752         *                  instance, but does not need to be.
1753         * @param thePath   The path.
1754         * @param theValue  The value to set, or <code>null</code>.
1755         * @return The newly added element
1756         * @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or
1757         *                             an element that is non-repeatable but not already populated.
1758         */
1759        @SuppressWarnings("unchecked")
1760        @Nonnull
1761        public <T extends IBase> T addElement(
1762                        @Nonnull IBase theTarget, @Nonnull String thePath, @Nullable String theValue) {
1763                T value = (T) doAddElement(theTarget, thePath, 1).get(0);
1764                if (!(value instanceof IPrimitiveType)) {
1765                        throw new DataFormatException(
1766                                        Msg.code(1800) + "Element at path " + thePath + " is not a primitive datatype. Found: "
1767                                                        + myContext.getElementDefinition(value.getClass()).getName());
1768                }
1769
1770                ((IPrimitiveType<?>) value).setValueAsString(theValue);
1771
1772                return value;
1773        }
1774
1775        /**
1776         * Adds and returns a new element at the given path within the given structure. The paths used here
1777         * are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions.
1778         * <p>
1779         * This method follows all of the same semantics as {@link #addElement(IBase, String)} but it
1780         * requires the path to point to an element with a primitive datatype and set the value of
1781         * the datatype to the given value.
1782         * </p>
1783         *
1784         * @param theTarget The element to add to. This will often be a {@link IBaseResource resource}
1785         *                  instance, but does not need to be.
1786         * @param thePath   The path.
1787         * @param theValue  The value to set, or <code>null</code>.
1788         * @return The newly added element
1789         * @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or
1790         *                             an element that is non-repeatable but not already populated.
1791         */
1792        @SuppressWarnings("unchecked")
1793        @Nonnull
1794        public <T extends IBase> T setElement(
1795                        @Nonnull IBase theTarget, @Nonnull String thePath, @Nullable String theValue) {
1796                T value = (T) doAddElement(theTarget, thePath, -1).get(0);
1797                if (!(value instanceof IPrimitiveType)) {
1798                        throw new DataFormatException(
1799                                        Msg.code(1801) + "Element at path " + thePath + " is not a primitive datatype. Found: "
1800                                                        + myContext.getElementDefinition(value.getClass()).getName());
1801                }
1802
1803                ((IPrimitiveType<?>) value).setValueAsString(theValue);
1804
1805                return value;
1806        }
1807
1808        /**
1809         * This method has the same semantics as {@link #addElement(IBase, String, String)} but adds
1810         * a collection of primitives instead of a single one.
1811         *
1812         * @param theTarget The element to add to. This will often be a {@link IBaseResource resource}
1813         *                  instance, but does not need to be.
1814         * @param thePath   The path.
1815         * @param theValues The values to set, or <code>null</code>.
1816         */
1817        public void addElements(IBase theTarget, String thePath, Collection<String> theValues) {
1818                List<IBase> targets = doAddElement(theTarget, thePath, theValues.size());
1819                Iterator<String> valuesIter = theValues.iterator();
1820                for (IBase target : targets) {
1821
1822                        if (!(target instanceof IPrimitiveType)) {
1823                                throw new DataFormatException(Msg.code(1802) + "Element at path " + thePath
1824                                                + " is not a primitive datatype. Found: "
1825                                                + myContext.getElementDefinition(target.getClass()).getName());
1826                        }
1827
1828                        ((IPrimitiveType<?>) target).setValueAsString(valuesIter.next());
1829                }
1830        }
1831
1832        /**
1833         * Checks if the field exists on the resource
1834         *
1835         * @param theFieldName   Name of the field to check
1836         * @param theResource    Resource instance to check
1837         * @return Returns true if resource definition has a child with the specified name and false otherwise
1838         */
1839        public boolean fieldExists(String theFieldName, IBaseResource theResource) {
1840                return myContext.getResourceDefinition(theResource).getChildByName(theFieldName) != null;
1841        }
1842
1843        /**
1844         * Clones a resource object, copying all data elements from theSource into a new copy of the same type.
1845         * <p>
1846         * Note that:
1847         * <ul>
1848         *    <li>Only FHIR data elements are copied (i.e. user data maps are not copied)</li>
1849         *    <li>If a class extending a HAPI FHIR type (e.g. an instance of a class extending the Patient class) is supplied, an instance of the base type will be returned.</li>
1850         * </ul>
1851         *
1852         * @param theSource The source resource
1853         * @return A copy of the source resource
1854         * @since 5.6.0
1855         */
1856        @SuppressWarnings("unchecked")
1857        public <T extends IBaseResource> T clone(T theSource) {
1858                Validate.notNull(theSource, "theSource must not be null");
1859                T target = (T) myContext.getResourceDefinition(theSource).newInstance();
1860                cloneInto(theSource, target, false);
1861                return target;
1862        }
1863
1864        @FunctionalInterface
1865        private interface ICompartmentOwnerVisitor {
1866
1867                /**
1868                 * @return Returns true if we should keep looking for more
1869                 */
1870                boolean consume(IIdType theCompartmentOwner);
1871        }
1872
1873        public static class ContainedResources {
1874                private List<IBaseResource> myResourceList;
1875                private IdentityHashMap<IBaseResource, IIdType> myResourceToIdMap;
1876                private IdentityHashMap<IBaseResource, IIdType> myPreviouslyContainedResourceToIdMap;
1877                private Map<String, IBaseResource> myExistingIdToContainedResourceMap;
1878
1879                public ContainedResources() {
1880                        super();
1881                }
1882
1883                public Map<String, IBaseResource> getExistingIdToContainedResource() {
1884                        if (myExistingIdToContainedResourceMap == null) {
1885                                myExistingIdToContainedResourceMap = new HashMap<>();
1886                        }
1887                        return myExistingIdToContainedResourceMap;
1888                }
1889
1890                public boolean referenceMatchesAContainedResource(IIdType theRefId) {
1891                        assert theRefId.getValue().startsWith("#");
1892
1893                        String expectedResourceId = theRefId.getValue().substring(1);
1894                        return this.getContainedResources().stream()
1895                                        .anyMatch(res -> res.getIdElement().getIdPart().equals(expectedResourceId));
1896                }
1897
1898                public IIdType addContained(IBaseResource theResource) {
1899                        if (this.getResourceId(theResource) != null) {
1900                                // Prevent infinite recursion if there are circular loops in the contained resources
1901                                return null;
1902                        }
1903
1904                        IIdType existing = getResourceToIdMap().get(theResource);
1905                        if (existing != null) {
1906                                return existing;
1907                        }
1908
1909                        IIdType newId = theResource.getIdElement();
1910                        if (isBlank(newId.getValue())) {
1911                                newId.setValue(UUID.randomUUID().toString());
1912                        } else if (newId.getValue().startsWith("#")) {
1913                                newId.setValue(newId.getValueAsString().substring(1));
1914                        }
1915
1916                        getResourceToIdMap().put(theResource, newId);
1917                        getOrCreateResourceList().add(theResource);
1918                        return newId;
1919                }
1920
1921                public void addContained(IIdType theId, IBaseResource theResource) {
1922                        if (!getResourceToIdMap().containsKey(theResource)) {
1923                                getResourceToIdMap().put(theResource, theId);
1924                                getOrCreateResourceList().add(theResource);
1925                        }
1926                }
1927
1928                public List<IBaseResource> getContainedResources() {
1929                        if (getResourceToIdMap() == null) {
1930                                return Collections.emptyList();
1931                        }
1932                        return getOrCreateResourceList();
1933                }
1934
1935                public IIdType getResourceId(IBaseResource theNext) {
1936                        if (getResourceToIdMap() == null) {
1937                                return null;
1938                        }
1939
1940                        var idFromMap = getResourceToIdMap().get(theNext);
1941                        if (idFromMap != null) {
1942                                return idFromMap;
1943                        } else if (theNext.getIdElement().getIdPart() != null) {
1944                                return getResourceToIdMap().values().stream()
1945                                                .filter(id -> theNext.getIdElement().getIdPart().equals(id.getIdPart()))
1946                                                .findAny()
1947                                                .orElse(null);
1948                        } else {
1949                                return null;
1950                        }
1951                }
1952
1953                private List<IBaseResource> getOrCreateResourceList() {
1954                        if (myResourceList == null) {
1955                                myResourceList = new ArrayList<>();
1956                        }
1957                        return myResourceList;
1958                }
1959
1960                private boolean hasResourceToIdValues() {
1961                        return myResourceToIdMap != null && !myResourceToIdMap.isEmpty();
1962                }
1963
1964                private IdentityHashMap<IBaseResource, IIdType> getResourceToIdMap() {
1965                        if (myResourceToIdMap == null) {
1966                                myResourceToIdMap = new IdentityHashMap<>();
1967                        }
1968                        return myResourceToIdMap;
1969                }
1970
1971                public boolean isEmpty() {
1972                        if (myResourceToIdMap == null) {
1973                                return true;
1974                        }
1975                        return myResourceToIdMap.isEmpty();
1976                }
1977
1978                public boolean hasExistingIdToContainedResource() {
1979                        return myExistingIdToContainedResourceMap != null;
1980                }
1981
1982                public IdentityHashMap<IBaseResource, IIdType> getPreviouslyContainedResourceToIdMap() {
1983                        if (myPreviouslyContainedResourceToIdMap == null) {
1984                                myPreviouslyContainedResourceToIdMap = new IdentityHashMap<>();
1985                        }
1986                        return myPreviouslyContainedResourceToIdMap;
1987                }
1988
1989                public boolean hasPreviouslyContainedResourceToIdValues() {
1990                        return myPreviouslyContainedResourceToIdMap != null && !myPreviouslyContainedResourceToIdMap.isEmpty();
1991                }
1992
1993                public IIdType getPreviouslyContainedResourceId(IBaseResource theResource) {
1994                        if (hasPreviouslyContainedResourceToIdValues()) {
1995                                return getPreviouslyContainedResourceToIdMap().get(theResource);
1996                        }
1997                        return null;
1998                }
1999        }
2000
2001        private static class ClearingModelVisitor implements IModelVisitor2 {
2002
2003                private boolean myFoundContent;
2004
2005                @Override
2006                public boolean acceptElement(
2007                                IBase theElement,
2008                                List<IBase> theContainingElementPath,
2009                                List<BaseRuntimeChildDefinition> theChildDefinitionPath,
2010                                List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
2011                        if (theElement instanceof IPrimitiveType<?> type) {
2012                                if (type.getValueAsString() != null) {
2013                                        myFoundContent = true;
2014                                        type.setValueAsString(null);
2015                                }
2016                        }
2017                        return true;
2018                }
2019
2020                @Override
2021                public boolean acceptUndeclaredExtension(
2022                                IBaseExtension<?, ?> theNextExt,
2023                                List<IBase> theContainingElementPath,
2024                                List<BaseRuntimeChildDefinition> theChildDefinitionPath,
2025                                List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
2026                        if (theNextExt.getUrl() != null) {
2027                                theNextExt.setUrl(null);
2028                                myFoundContent = true;
2029                        }
2030                        if (theNextExt.getValue() != null) {
2031                                myFoundContent = true;
2032                                theNextExt.setValue(null);
2033                        }
2034                        if (!theNextExt.getExtension().isEmpty()) {
2035                                theNextExt.getExtension().clear();
2036                                myFoundContent = true;
2037                        }
2038                        return true;
2039                }
2040        }
2041}