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