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