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                                List<RuntimeSearchParam> existingParams = params;
760                                params = new ArrayList<>(existingParams.size() + additionalParams.size());
761                                params.addAll(existingParams);
762                                params.addAll(additionalParams);
763                        }
764                }
765
766
767                for (RuntimeSearchParam nextParam : params) {
768                        for (String nextPath : nextParam.getPathsSplit()) {
769
770                                /*
771                                 * DSTU3 and before just defined compartments as being (e.g.) named
772                                 * Patient with a path like CarePlan.subject
773                                 *
774                                 * R4 uses a fancier format like CarePlan.subject.where(resolve() is Patient)
775                                 *
776                                 * The following Regex is a hack to make that efficient at runtime.
777                                 */
778                                String wantType = null;
779                                Pattern pattern = COMPARTMENT_MATCHER_PATH;
780                                Matcher matcher = pattern.matcher(nextPath);
781                                if (matcher.matches()) {
782                                        nextPath = matcher.group(1);
783                                        wantType = matcher.group(2);
784                                }
785
786                                List<IBaseReference> values = getValues(theSource, nextPath, IBaseReference.class);
787                                for (IBaseReference nextValue : values) {
788                                        IIdType nextTargetId = nextValue.getReferenceElement();
789                                        String nextRef = nextTargetId.toUnqualifiedVersionless().getValue();
790
791                                        /*
792                                         * If the reference isn't an explicit resource ID, but instead is just
793                                         * a resource object, we'll calculate its ID and treat the target
794                                         * as that.
795                                         */
796                                        if (isBlank(nextRef) && nextValue.getResource() != null) {
797                                                IBaseResource nextTarget = nextValue.getResource();
798                                                nextTargetId = nextTarget.getIdElement().toUnqualifiedVersionless();
799                                                if (!nextTargetId.hasResourceType()) {
800                                                        String resourceType = myContext.getResourceType(nextTarget);
801                                                        nextTargetId.setParts(null, resourceType, nextTargetId.getIdPart(), null);
802                                                }
803                                                nextRef = nextTargetId.getValue();
804                                        }
805
806                                        if (isNotBlank(wantType)) {
807                                                String nextTargetIdResourceType = nextTargetId.getResourceType();
808                                                if (nextTargetIdResourceType == null || !nextTargetIdResourceType.equals(wantType)) {
809                                                        continue;
810                                                }
811                                        }
812
813                                        if (wantRef.equals(nextRef)) {
814                                                return true;
815                                        }
816                                }
817                        }
818                }
819
820                return false;
821        }
822
823        private void visit(IBase theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor2 theCallback, List<IBase> theContainingElementPath,
824                                                         List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
825                if (theChildDefinition != null) {
826                        theChildDefinitionPath.add(theChildDefinition);
827                }
828                theContainingElementPath.add(theElement);
829                theElementDefinitionPath.add(theDefinition);
830
831                boolean recurse = theCallback.acceptElement(theElement, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath),
832                        Collections.unmodifiableList(theElementDefinitionPath));
833                if (recurse) {
834
835                        /*
836                         * Visit undeclared extensions
837                         */
838                        if (theElement instanceof ISupportsUndeclaredExtensions) {
839                                ISupportsUndeclaredExtensions containingElement = (ISupportsUndeclaredExtensions) theElement;
840                                for (ExtensionDt nextExt : containingElement.getUndeclaredExtensions()) {
841                                        theContainingElementPath.add(nextExt);
842                                        theCallback.acceptUndeclaredExtension(nextExt, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
843                                        theContainingElementPath.remove(theContainingElementPath.size() - 1);
844                                }
845                        }
846
847                        /*
848                         * Now visit the children of the given element
849                         */
850                        switch (theDefinition.getChildType()) {
851                                case ID_DATATYPE:
852                                case PRIMITIVE_XHTML_HL7ORG:
853                                case PRIMITIVE_XHTML:
854                                case PRIMITIVE_DATATYPE:
855                                        // These are primitive types, so we don't need to visit their children
856                                        break;
857                                case RESOURCE:
858                                case RESOURCE_BLOCK:
859                                case COMPOSITE_DATATYPE: {
860                                        BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) theDefinition;
861                                        for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
862                                                List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
863                                                if (values != null) {
864                                                        for (IBase nextValue : values) {
865                                                                if (nextValue == null) {
866                                                                        continue;
867                                                                }
868                                                                if (nextValue.isEmpty()) {
869                                                                        continue;
870                                                                }
871                                                                BaseRuntimeElementDefinition<?> childElementDef;
872                                                                Class<? extends IBase> valueType = nextValue.getClass();
873                                                                childElementDef = nextChild.getChildElementDefinitionByDatatype(valueType);
874                                                                while (childElementDef == null && IBase.class.isAssignableFrom(valueType)) {
875                                                                        childElementDef = nextChild.getChildElementDefinitionByDatatype(valueType);
876                                                                        valueType = (Class<? extends IBase>) valueType.getSuperclass();
877                                                                }
878
879                                                                Class<? extends IBase> typeClass = nextValue.getClass();
880                                                                while (childElementDef == null && IBase.class.isAssignableFrom(typeClass)) {
881                                                                        //noinspection unchecked
882                                                                        typeClass = (Class<? extends IBase>) typeClass.getSuperclass();
883                                                                        childElementDef = nextChild.getChildElementDefinitionByDatatype(typeClass);
884                                                                }
885
886                                                                Validate.notNull(childElementDef, "Found value of type[%s] which is not valid for field[%s] in %s", nextValue.getClass(), nextChild.getElementName(), childDef.getName());
887
888                                                                visit(nextValue, nextChild, childElementDef, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
889                                                        }
890                                                }
891                                        }
892                                        break;
893                                }
894                                case CONTAINED_RESOURCES: {
895                                        BaseContainedDt value = (BaseContainedDt) theElement;
896                                        for (IResource next : value.getContainedResources()) {
897                                                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(next);
898                                                visit(next, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
899                                        }
900                                        break;
901                                }
902                                case EXTENSION_DECLARED:
903                                case UNDECL_EXT: {
904                                        throw new IllegalStateException(Msg.code(1793) + "state should not happen: " + theDefinition.getChildType());
905                                }
906                                case CONTAINED_RESOURCE_LIST: {
907                                        if (theElement != null) {
908                                                BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass());
909                                                visit(theElement, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
910                                        }
911                                        break;
912                                }
913                        }
914
915                }
916
917                if (theChildDefinition != null) {
918                        theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1);
919                }
920                theContainingElementPath.remove(theContainingElementPath.size() - 1);
921                theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1);
922        }
923
924        /**
925         * Visit all elements in a given resource
926         *
927         * <p>
928         * 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.
929         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
930         * </p>
931         *
932         * @param theResource The resource to visit
933         * @param theVisitor  The visitor
934         */
935        public void visit(IBaseResource theResource, IModelVisitor theVisitor) {
936                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
937                visit(newMap(), theResource, theResource, null, null, def, theVisitor);
938        }
939
940        public Map<Object, Object> newMap() {
941                return new IdentityHashMap<>();
942        }
943
944        /**
945         * Visit all elements in a given resource or element
946         * <p>
947         * <b>THIS ALTERNATE METHOD IS STILL EXPERIMENTAL! USE WITH CAUTION</b>
948         * </p>
949         * <p>
950         * 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.
951         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
952         * </p>
953         *
954         * @param theElement The element to visit
955         * @param theVisitor The visitor
956         */
957        public void visit(IBase theElement, IModelVisitor2 theVisitor) {
958                BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass());
959                if (def instanceof BaseRuntimeElementCompositeDefinition) {
960                        BaseRuntimeElementCompositeDefinition<?> defComposite = (BaseRuntimeElementCompositeDefinition<?>) def;
961                        visit(theElement, null, def, theVisitor, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
962                } else if (theElement instanceof IBaseExtension) {
963                        theVisitor.acceptUndeclaredExtension((IBaseExtension<?, ?>) theElement, Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
964                } else {
965                        theVisitor.acceptElement(theElement, Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
966                }
967        }
968
969        private void visit(Map<Object, Object> theStack, IBaseResource theResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition,
970                                                         BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor theCallback) {
971                List<String> pathToElement = addNameToList(thePathToElement, theChildDefinition);
972
973                if (theStack.put(theElement, theElement) != null) {
974                        return;
975                }
976
977                theCallback.acceptElement(theResource, theElement, pathToElement, theChildDefinition, theDefinition);
978
979                BaseRuntimeElementDefinition<?> def = theDefinition;
980                if (def.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) {
981                        Class<? extends IBase> clazz = theElement.getClass();
982                        def = myContext.getElementDefinition(clazz);
983                        Validate.notNull(def, "Unable to find element definition for class: %s", clazz);
984                }
985
986                if (theElement instanceof IBaseReference) {
987                        IBaseResource target = ((IBaseReference) theElement).getResource();
988                        if (target != null) {
989                                if (target.getIdElement().hasIdPart() == false || target.getIdElement().isLocal()) {
990                                        RuntimeResourceDefinition targetDef = myContext.getResourceDefinition(target);
991                                        visit(theStack, target, target, pathToElement, null, targetDef, theCallback);
992                                }
993                        }
994                }
995
996                switch (def.getChildType()) {
997                        case ID_DATATYPE:
998                        case PRIMITIVE_XHTML_HL7ORG:
999                        case PRIMITIVE_XHTML:
1000                        case PRIMITIVE_DATATYPE:
1001                                // These are primitive types
1002                                break;
1003                        case RESOURCE:
1004                        case RESOURCE_BLOCK:
1005                        case COMPOSITE_DATATYPE: {
1006                                BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) def;
1007                                List<BaseRuntimeChildDefinition> childrenAndExtensionDefs = childDef.getChildrenAndExtension();
1008                                for (BaseRuntimeChildDefinition nextChild : childrenAndExtensionDefs) {
1009
1010                                        List<?> values = nextChild.getAccessor().getValues(theElement);
1011
1012                                        if (values != null) {
1013                                                for (Object nextValueObject : values) {
1014                                                        IBase nextValue;
1015                                                        try {
1016                                                                nextValue = (IBase) nextValueObject;
1017                                                        } catch (ClassCastException e) {
1018                                                                String s = "Found instance of " + nextValueObject.getClass() + " - Did you set a field value to the incorrect type? Expected " + IBase.class.getName();
1019                                                                throw new ClassCastException(Msg.code(1794) + s);
1020                                                        }
1021                                                        if (nextValue == null) {
1022                                                                continue;
1023                                                        }
1024                                                        if (nextValue.isEmpty()) {
1025                                                                continue;
1026                                                        }
1027                                                        BaseRuntimeElementDefinition<?> childElementDef;
1028                                                        Class<? extends IBase> clazz = nextValue.getClass();
1029                                                        childElementDef = nextChild.getChildElementDefinitionByDatatype(clazz);
1030
1031                                                        if (childElementDef == null) {
1032                                                                childElementDef = myContext.getElementDefinition(clazz);
1033                                                                Validate.notNull(childElementDef, "Unable to find element definition for class: %s", clazz);
1034                                                        }
1035
1036                                                        if (nextChild instanceof RuntimeChildDirectResource) {
1037                                                                // Don't descend into embedded resources
1038                                                                theCallback.acceptElement(theResource, nextValue, null, nextChild, childElementDef);
1039                                                        } else {
1040                                                                visit(theStack, theResource, nextValue, pathToElement, nextChild, childElementDef, theCallback);
1041                                                        }
1042                                                }
1043                                        }
1044                                }
1045                                break;
1046                        }
1047                        case CONTAINED_RESOURCES: {
1048                                BaseContainedDt value = (BaseContainedDt) theElement;
1049                                for (IResource next : value.getContainedResources()) {
1050                                        def = myContext.getResourceDefinition(next);
1051                                        visit(theStack, next, next, pathToElement, null, def, theCallback);
1052                                }
1053                                break;
1054                        }
1055                        case CONTAINED_RESOURCE_LIST:
1056                        case EXTENSION_DECLARED:
1057                        case UNDECL_EXT: {
1058                                throw new IllegalStateException(Msg.code(1795) + "state should not happen: " + def.getChildType());
1059                        }
1060                }
1061
1062                theStack.remove(theElement);
1063
1064        }
1065
1066        /**
1067         * Returns all embedded resources that are found embedded within <code>theResource</code>.
1068         * An embedded resource is a resource that can be found as a direct child within a resource,
1069         * as opposed to being referenced by the resource.
1070         * <p>
1071         * Examples include resources found within <code>Bundle.entry.resource</code>
1072         * and <code>Parameters.parameter.resource</code>, as well as contained resources
1073         * found within <code>Resource.contained</code>
1074         * </p>
1075         *
1076         * @param theRecurse Should embedded resources be recursively scanned for further embedded
1077         *                   resources
1078         * @return A collection containing the embedded resources. Order is arbitrary.
1079         */
1080        public Collection<IBaseResource> getAllEmbeddedResources(IBaseResource theResource, boolean theRecurse) {
1081                Validate.notNull(theResource, "theResource must not be null");
1082                ArrayList<IBaseResource> retVal = new ArrayList<>();
1083
1084                visit(theResource, new IModelVisitor2() {
1085                        @Override
1086                        public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
1087                                if (theElement == theResource) {
1088                                        return true;
1089                                }
1090                                if (theElement instanceof IBaseResource) {
1091                                        retVal.add((IBaseResource) theElement);
1092                                        return theRecurse;
1093                                }
1094                                return true;
1095                        }
1096
1097                        @Override
1098                        public boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
1099                                return true;
1100                        }
1101                });
1102
1103                return retVal;
1104        }
1105
1106        /**
1107         * Clear all content on a resource
1108         */
1109        public void clear(IBaseResource theInput) {
1110                visit(theInput, new IModelVisitor2() {
1111                        @Override
1112                        public boolean acceptElement(IBase theElement, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
1113                                if (theElement instanceof IPrimitiveType) {
1114                                        ((IPrimitiveType) theElement).setValueAsString(null);
1115                                }
1116                                return true;
1117                        }
1118
1119                        @Override
1120                        public boolean acceptUndeclaredExtension(IBaseExtension<?, ?> theNextExt, List<IBase> theContainingElementPath, List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
1121                                theNextExt.setUrl(null);
1122                                theNextExt.setValue(null);
1123                                return true;
1124                        }
1125
1126                });
1127        }
1128
1129        private void containResourcesForEncoding(ContainedResources theContained, IBaseResource theResource, boolean theModifyResource) {
1130                List<IBaseReference> allReferences = getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
1131                for (IBaseReference next : allReferences) {
1132                        IBaseResource resource = next.getResource();
1133                        if (resource == null && next.getReferenceElement().isLocal()) {
1134                                if (theContained.hasExistingIdToContainedResource()) {
1135                                        IBaseResource potentialTarget = theContained.getExistingIdToContainedResource().remove(next.getReferenceElement().getValue());
1136                                        if (potentialTarget != null) {
1137                                                theContained.addContained(next.getReferenceElement(), potentialTarget);
1138                                                containResourcesForEncoding(theContained, potentialTarget, theModifyResource);
1139                                        }
1140                                }
1141                        }
1142                }
1143
1144                for (IBaseReference next : allReferences) {
1145                        IBaseResource resource = next.getResource();
1146                        if (resource != null) {
1147                                if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) {
1148                                        if (theContained.getResourceId(resource) != null) {
1149                                                // Prevent infinite recursion if there are circular loops in the contained resources
1150                                                continue;
1151                                        }
1152                                        IIdType id = theContained.addContained(resource);
1153                                        if (theModifyResource) {
1154                                                getContainedResourceList(theResource).add(resource);
1155                                                next.setReference(id.getValue());
1156                                        }
1157                                        if (resource.getIdElement().isLocal() && theContained.hasExistingIdToContainedResource()) {
1158                                                theContained.getExistingIdToContainedResource().remove(resource.getIdElement().getValue());
1159                                        }
1160                                }
1161
1162                        }
1163
1164                }
1165
1166        }
1167
1168        /**
1169         * Iterate through the whole resource and identify any contained resources. Optionally this method
1170         * can also assign IDs and modify references where the resource link has been specified but not the
1171         * reference text.
1172         *
1173         * @since 5.4.0
1174         */
1175        public ContainedResources containResources(IBaseResource theResource, OptionsEnum... theOptions) {
1176                boolean storeAndReuse = false;
1177                boolean modifyResource = false;
1178                for (OptionsEnum next : theOptions) {
1179                        switch (next) {
1180                                case MODIFY_RESOURCE:
1181                                        modifyResource = true;
1182                                        break;
1183                                case STORE_AND_REUSE_RESULTS:
1184                                        storeAndReuse = true;
1185                                        break;
1186                        }
1187                }
1188
1189                if (storeAndReuse) {
1190                        Object cachedValue = theResource.getUserData(USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED);
1191                        if (cachedValue != null) {
1192                                return (ContainedResources) cachedValue;
1193                        }
1194                }
1195
1196                ContainedResources contained = new ContainedResources();
1197
1198                List<? extends IBaseResource> containedResources = getContainedResourceList(theResource);
1199                for (IBaseResource next : containedResources) {
1200                        String nextId = next.getIdElement().getValue();
1201                        if (StringUtils.isNotBlank(nextId)) {
1202                                if (!nextId.startsWith("#")) {
1203                                        nextId = '#' + nextId;
1204                                }
1205                                next.getIdElement().setValue(nextId);
1206                        }
1207                        contained.addContained(next);
1208                }
1209
1210                if (myContext.getParserOptions().isAutoContainReferenceTargetsWithNoId()) {
1211                        containResourcesForEncoding(contained, theResource, modifyResource);
1212                }
1213
1214                if (storeAndReuse) {
1215                        theResource.setUserData(USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED, contained);
1216                }
1217
1218                return contained;
1219        }
1220
1221        @SuppressWarnings("unchecked")
1222        private <T extends IBaseResource> List<T> getContainedResourceList(T theResource) {
1223                List<T> containedResources = Collections.emptyList();
1224                if (theResource instanceof IResource) {
1225                        containedResources = (List<T>) ((IResource) theResource).getContained().getContainedResources();
1226                } else if (theResource instanceof IDomainResource) {
1227                        containedResources = (List<T>) ((IDomainResource) theResource).getContained();
1228                }
1229                return containedResources;
1230        }
1231
1232        /**
1233         * Adds and returns a new element at the given path within the given structure. The paths used here
1234         * are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions.
1235         * <p>
1236         * Only the last entry in the path is always created, existing repetitions of elements before
1237         * the final dot are returned if they exists (although they are created if they do not). For example,
1238         * given the path <code>Patient.name.given</code>, a new repetition of <code>given</code> is always
1239         * added to the first (index 0) repetition of the name. If an index-0 repetition of <code>name</code>
1240         * already exists, it is added to. If one does not exist, it if created and then added to.
1241         * </p>
1242         * <p>
1243         * If the last element in the path refers to a non-repeatable element that is already present and
1244         * is not empty, a {@link DataFormatException} error will be thrown.
1245         * </p>
1246         *
1247         * @param theTarget The element to add to. This will often be a {@link IBaseResource resource}
1248         *                  instance, but does not need to be.
1249         * @param thePath   The path.
1250         * @return The newly added element
1251         * @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or
1252         *                             an element that is non-repeatable but not already populated.
1253         */
1254        @SuppressWarnings("unchecked")
1255        @Nonnull
1256        public <T extends IBase> T addElement(@Nonnull IBase theTarget, @Nonnull String thePath) {
1257                return (T) doAddElement(theTarget, thePath, 1).get(0);
1258        }
1259
1260        @SuppressWarnings("unchecked")
1261        private <T extends IBase> List<T> doAddElement(IBase theTarget, String thePath, int theElementsToAdd) {
1262                if (theElementsToAdd == 0) {
1263                        return Collections.emptyList();
1264                }
1265
1266                IBase target = theTarget;
1267                BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(target.getClass());
1268                List<String> parts = parsePath(def, thePath);
1269
1270                for (int i = 0, partsSize = parts.size(); ; i++) {
1271                        String nextPart = parts.get(i);
1272                        boolean lastPart = i == partsSize - 1;
1273
1274                        BaseRuntimeChildDefinition nextChild = def.getChildByName(nextPart);
1275                        if (nextChild == null) {
1276                                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(", ")));
1277                        }
1278
1279                        List<IBase> childValues = nextChild.getAccessor().getValues(target);
1280                        IBase childValue;
1281                        if (childValues.size() > 0 && !lastPart) {
1282                                childValue = childValues.get(0);
1283                        } else {
1284
1285                                if (lastPart) {
1286                                        if (!childValues.isEmpty()) {
1287                                                if (theElementsToAdd == -1) {
1288                                                        return (List<T>) Collections.singletonList(childValues.get(0));
1289                                                } else if (nextChild.getMax() == 1 && !childValues.get(0).isEmpty()) {
1290                                                        throw new DataFormatException(Msg.code(1797) + "Element at path " + thePath + " is not repeatable and not empty");
1291                                                } else if (nextChild.getMax() == 1 && childValues.get(0).isEmpty()) {
1292                                                        return (List<T>) Collections.singletonList(childValues.get(0));
1293                                                }
1294                                        }
1295                                }
1296
1297                                BaseRuntimeElementDefinition<?> elementDef = nextChild.getChildByName(nextPart);
1298                                childValue = elementDef.newInstance(nextChild.getInstanceConstructorArguments());
1299                                nextChild.getMutator().addValue(target, childValue);
1300
1301                                if (lastPart) {
1302                                        if (theElementsToAdd == 1 || theElementsToAdd == -1) {
1303                                                return (List<T>) Collections.singletonList(childValue);
1304                                        } else {
1305                                                if (nextChild.getMax() == 1) {
1306                                                        throw new DataFormatException(Msg.code(1798) + "Can not add multiple values at path " + thePath + ": Element does not repeat");
1307                                                }
1308
1309                                                List<T> values = (List<T>) Lists.newArrayList(childValue);
1310                                                for (int j = 1; j < theElementsToAdd; j++) {
1311                                                        childValue = elementDef.newInstance(nextChild.getInstanceConstructorArguments());
1312                                                        nextChild.getMutator().addValue(target, childValue);
1313                                                        values.add((T) childValue);
1314                                                }
1315
1316                                                return values;
1317                                        }
1318                                }
1319
1320                        }
1321
1322                        target = childValue;
1323
1324                        if (!lastPart) {
1325                                BaseRuntimeElementDefinition<?> nextDef = myContext.getElementDefinition(target.getClass());
1326                                if (!(nextDef instanceof BaseRuntimeElementCompositeDefinition)) {
1327                                        throw new DataFormatException(Msg.code(1799) + "Invalid path " + thePath + ": Element of type " + def.getName() + " has no child named " + nextPart + " (this is a primitive type)");
1328                                }
1329                                def = (BaseRuntimeElementCompositeDefinition<?>) nextDef;
1330                        }
1331                }
1332
1333        }
1334
1335        /**
1336         * Adds and returns a new element at the given path within the given structure. The paths used here
1337         * are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions.
1338         * <p>
1339         * This method follows all of the same semantics as {@link #addElement(IBase, String)} but it
1340         * requires the path to point to an element with a primitive datatype and set the value of
1341         * the datatype to the given value.
1342         * </p>
1343         *
1344         * @param theTarget The element to add to. This will often be a {@link IBaseResource resource}
1345         *                  instance, but does not need to be.
1346         * @param thePath   The path.
1347         * @param theValue  The value to set, or <code>null</code>.
1348         * @return The newly added element
1349         * @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or
1350         *                             an element that is non-repeatable but not already populated.
1351         */
1352        @SuppressWarnings("unchecked")
1353        @Nonnull
1354        public <T extends IBase> T addElement(@Nonnull IBase theTarget, @Nonnull String thePath, @Nullable String theValue) {
1355                T value = (T) doAddElement(theTarget, thePath, 1).get(0);
1356                if (!(value instanceof IPrimitiveType)) {
1357                        throw new DataFormatException(Msg.code(1800) + "Element at path " + thePath + " is not a primitive datatype. Found: " + myContext.getElementDefinition(value.getClass()).getName());
1358                }
1359
1360                ((IPrimitiveType<?>) value).setValueAsString(theValue);
1361
1362                return value;
1363        }
1364
1365
1366        /**
1367         * Adds and returns a new element at the given path within the given structure. The paths used here
1368         * are <b>not FHIRPath expressions</b> but instead just simple dot-separated path expressions.
1369         * <p>
1370         * This method follows all of the same semantics as {@link #addElement(IBase, String)} but it
1371         * requires the path to point to an element with a primitive datatype and set the value of
1372         * the datatype to the given value.
1373         * </p>
1374         *
1375         * @param theTarget The element to add to. This will often be a {@link IBaseResource resource}
1376         *                  instance, but does not need to be.
1377         * @param thePath   The path.
1378         * @param theValue  The value to set, or <code>null</code>.
1379         * @return The newly added element
1380         * @throws DataFormatException If the path is invalid or does not end with either a repeatable element, or
1381         *                             an element that is non-repeatable but not already populated.
1382         */
1383        @SuppressWarnings("unchecked")
1384        @Nonnull
1385        public <T extends IBase> T setElement(@Nonnull IBase theTarget, @Nonnull String thePath, @Nullable String theValue) {
1386                T value = (T) doAddElement(theTarget, thePath, -1).get(0);
1387                if (!(value instanceof IPrimitiveType)) {
1388                        throw new DataFormatException(Msg.code(1801) + "Element at path " + thePath + " is not a primitive datatype. Found: " + myContext.getElementDefinition(value.getClass()).getName());
1389                }
1390
1391                ((IPrimitiveType<?>) value).setValueAsString(theValue);
1392
1393                return value;
1394        }
1395
1396
1397        /**
1398         * This method has the same semantics as {@link #addElement(IBase, String, String)} but adds
1399         * a collection of primitives instead of a single one.
1400         *
1401         * @param theTarget The element to add to. This will often be a {@link IBaseResource resource}
1402         *                  instance, but does not need to be.
1403         * @param thePath   The path.
1404         * @param theValues The values to set, or <code>null</code>.
1405         */
1406        public void addElements(IBase theTarget, String thePath, Collection<String> theValues) {
1407                List<IBase> targets = doAddElement(theTarget, thePath, theValues.size());
1408                Iterator<String> valuesIter = theValues.iterator();
1409                for (IBase target : targets) {
1410
1411                        if (!(target instanceof IPrimitiveType)) {
1412                                throw new DataFormatException(Msg.code(1802) + "Element at path " + thePath + " is not a primitive datatype. Found: " + myContext.getElementDefinition(target.getClass()).getName());
1413                        }
1414
1415                        ((IPrimitiveType<?>) target).setValueAsString(valuesIter.next());
1416                }
1417
1418        }
1419
1420        /**
1421         * Clones a resource object, copying all data elements from theSource into a new copy of the same type.
1422         * <p>
1423         * Note that:
1424         * <ul>
1425         *    <li>Only FHIR data elements are copied (i.e. user data maps are not copied)</li>
1426         *    <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>
1427         * </ul>
1428         *
1429         * @param theSource The source resource
1430         * @return A copy of the source resource
1431         * @since 5.6.0
1432         */
1433        @SuppressWarnings("unchecked")
1434        public <T extends IBaseResource> T clone(T theSource) {
1435                Validate.notNull(theSource, "theSource must not be null");
1436                T target = (T) myContext.getResourceDefinition(theSource).newInstance();
1437                cloneInto(theSource, target, false);
1438                return target;
1439        }
1440
1441
1442        public enum OptionsEnum {
1443
1444                /**
1445                 * Should we modify the resource in the case that contained resource IDs are assigned
1446                 * during a {@link #containResources(IBaseResource, OptionsEnum...)} pass.
1447                 */
1448                MODIFY_RESOURCE,
1449
1450                /**
1451                 * Store the results of the operation in the resource metadata and reuse them if
1452                 * subsequent calls are made.
1453                 */
1454                STORE_AND_REUSE_RESULTS
1455        }
1456
1457        public static class ContainedResources {
1458                private long myNextContainedId = 1;
1459
1460                private List<IBaseResource> myResourceList;
1461                private IdentityHashMap<IBaseResource, IIdType> myResourceToIdMap;
1462                private Map<String, IBaseResource> myExistingIdToContainedResourceMap;
1463
1464                public Map<String, IBaseResource> getExistingIdToContainedResource() {
1465                        if (myExistingIdToContainedResourceMap == null) {
1466                                myExistingIdToContainedResourceMap = new HashMap<>();
1467                        }
1468                        return myExistingIdToContainedResourceMap;
1469                }
1470
1471                public IIdType addContained(IBaseResource theResource) {
1472                        IIdType existing = getResourceToIdMap().get(theResource);
1473                        if (existing != null) {
1474                                return existing;
1475                        }
1476
1477                        IIdType newId = theResource.getIdElement();
1478                        if (isBlank(newId.getValue())) {
1479                                newId.setValue("#" + myNextContainedId++);
1480                        } else {
1481                                // Avoid auto-assigned contained IDs colliding with pre-existing ones
1482                                String idPart = newId.getValue();
1483                                if (substring(idPart, 0, 1).equals("#")) {
1484                                        idPart = idPart.substring(1);
1485                                        if (StringUtils.isNumeric(idPart)) {
1486                                                myNextContainedId = Long.parseLong(idPart) + 1;
1487                                        }
1488                                }
1489                        }
1490
1491                        getResourceToIdMap().put(theResource, newId);
1492                        getOrCreateResourceList().add(theResource);
1493                        return newId;
1494                }
1495
1496                public void addContained(IIdType theId, IBaseResource theResource) {
1497                        if (!getResourceToIdMap().containsKey(theResource)) {
1498                                getResourceToIdMap().put(theResource, theId);
1499                                getOrCreateResourceList().add(theResource);
1500                        }
1501                }
1502
1503                public List<IBaseResource> getContainedResources() {
1504                        if (getResourceToIdMap() == null) {
1505                                return Collections.emptyList();
1506                        }
1507                        return getOrCreateResourceList();
1508                }
1509
1510                public IIdType getResourceId(IBaseResource theNext) {
1511                        if (getResourceToIdMap() == null) {
1512                                return null;
1513                        }
1514                        return getResourceToIdMap().get(theNext);
1515                }
1516
1517                private List<IBaseResource> getOrCreateResourceList() {
1518                        if (myResourceList == null) {
1519                                myResourceList = new ArrayList<>();
1520                        }
1521                        return myResourceList;
1522                }
1523
1524                private IdentityHashMap<IBaseResource, IIdType> getResourceToIdMap() {
1525                        if (myResourceToIdMap == null) {
1526                                myResourceToIdMap = new IdentityHashMap<>();
1527                        }
1528                        return myResourceToIdMap;
1529                }
1530
1531                public boolean isEmpty() {
1532                        if (myResourceToIdMap == null) {
1533                                return true;
1534                        }
1535                        return myResourceToIdMap.isEmpty();
1536                }
1537
1538                public boolean hasExistingIdToContainedResource() {
1539                        return myExistingIdToContainedResourceMap != null;
1540                }
1541
1542                public void assignIdsToContainedResources() {
1543
1544                        if (!getContainedResources().isEmpty()) {
1545
1546                                /*
1547                                 * The idea with the code block below:
1548                                 *
1549                                 * We want to preserve any IDs that were user-assigned, so that if it's really
1550                                 * important to someone that their contained resource have the ID of #FOO
1551                                 * or #1 we will keep that.
1552                                 *
1553                                 * For any contained resources where no ID was assigned by the user, we
1554                                 * want to manually create an ID but make sure we don't reuse an existing ID.
1555                                 */
1556
1557                                Set<String> ids = new HashSet<>();
1558
1559                                // Gather any user assigned IDs
1560                                for (IBaseResource nextResource : getContainedResources()) {
1561                                        if (getResourceToIdMap().get(nextResource) != null) {
1562                                                ids.add(getResourceToIdMap().get(nextResource).getValue());
1563                                        }
1564                                }
1565
1566                                // Automatically assign IDs to the rest
1567                                for (IBaseResource nextResource : getContainedResources()) {
1568
1569                                        while (getResourceToIdMap().get(nextResource) == null) {
1570                                                String nextCandidate = "#" + myNextContainedId;
1571                                                myNextContainedId++;
1572                                                if (!ids.add(nextCandidate)) {
1573                                                        continue;
1574                                                }
1575
1576                                                getResourceToIdMap().put(nextResource, new IdDt(nextCandidate));
1577                                        }
1578
1579                                }
1580
1581                        }
1582
1583                }
1584        }
1585
1586}