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