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