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