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