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