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