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