001package ca.uhn.fhir.util;
002
003import ca.uhn.fhir.context.*;
004import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
005import ca.uhn.fhir.model.api.ExtensionDt;
006import ca.uhn.fhir.model.api.IResource;
007import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
008import ca.uhn.fhir.model.base.composite.BaseContainedDt;
009import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
010import ca.uhn.fhir.model.primitive.StringDt;
011import ca.uhn.fhir.parser.DataFormatException;
012import org.apache.commons.lang3.Validate;
013import org.hl7.fhir.instance.model.api.*;
014
015import java.util.*;
016import java.util.regex.Matcher;
017import java.util.regex.Pattern;
018import java.util.stream.Collectors;
019
020import static org.apache.commons.lang3.StringUtils.*;
021
022/*
023 * #%L
024 * HAPI FHIR - Core Library
025 * %%
026 * Copyright (C) 2014 - 2018 University Health Network
027 * %%
028 * Licensed under the Apache License, Version 2.0 (the "License");
029 * you may not use this file except in compliance with the License.
030 * You may obtain a copy of the License at
031 * 
032 *      http://www.apache.org/licenses/LICENSE-2.0
033 * 
034 * Unless required by applicable law or agreed to in writing, software
035 * distributed under the License is distributed on an "AS IS" BASIS,
036 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
037 * See the License for the specific language governing permissions and
038 * limitations under the License.
039 * #L%
040 */
041
042public class FhirTerser {
043
044        public static final Pattern COMPARTMENT_MATCHER_PATH = Pattern.compile("([a-zA-Z.]+)\\.where\\(resolve\\(\\) is ([a-zA-Z]+)\\)");
045        private FhirContext myContext;
046
047        public FhirTerser(FhirContext theContext) {
048                super();
049                myContext = theContext;
050        }
051
052        private List<String> addNameToList(List<String> theCurrentList, BaseRuntimeChildDefinition theChildDefinition) {
053                if (theChildDefinition == null)
054                        return null;
055                if (theCurrentList == null || theCurrentList.isEmpty())
056                        return new ArrayList<String>(Arrays.asList(theChildDefinition.getElementName()));
057                List<String> newList = new ArrayList<String>(theCurrentList);
058                newList.add(theChildDefinition.getElementName());
059                return newList;
060        }
061
062        private ExtensionDt createEmptyExtensionDt(IBaseExtension theBaseExtension, String theUrl) {
063                return createEmptyExtensionDt(theBaseExtension, false, theUrl);
064        }
065
066        @SuppressWarnings("unchecked")
067        private ExtensionDt createEmptyExtensionDt(IBaseExtension theBaseExtension, boolean theIsModifier, String theUrl) {
068                ExtensionDt retVal = new ExtensionDt(theIsModifier, theUrl);
069                theBaseExtension.getExtension().add(retVal);
070                return retVal;
071        }
072
073        private ExtensionDt createEmptyExtensionDt(ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, String theUrl) {
074                return createEmptyExtensionDt(theSupportsUndeclaredExtensions, false, theUrl);
075        }
076
077        private ExtensionDt createEmptyExtensionDt(ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, boolean theIsModifier, String theUrl) {
078                return theSupportsUndeclaredExtensions.addUndeclaredExtension(theIsModifier, theUrl);
079        }
080
081        private IBaseExtension createEmptyExtension(IBaseHasExtensions theBaseHasExtensions, String theUrl) {
082                return (IBaseExtension) theBaseHasExtensions.addExtension().setUrl(theUrl);
083        }
084
085        private IBaseExtension createEmptyModifierExtension(IBaseHasModifierExtensions theBaseHasModifierExtensions, String theUrl) {
086                return (IBaseExtension) theBaseHasModifierExtensions.addModifierExtension().setUrl(theUrl);
087        }
088
089        private ExtensionDt createEmptyModifierExtensionDt(IBaseExtension theBaseExtension, String theUrl) {
090                return createEmptyExtensionDt(theBaseExtension, true, theUrl);
091        }
092
093        private ExtensionDt createEmptyModifierExtensionDt(ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, String theUrl) {
094                return createEmptyExtensionDt(theSupportsUndeclaredExtensions, true, theUrl);
095        }
096
097        /**
098         * Clones all values from a source object into the equivalent fields in a target object
099         *
100         * @param theSource              The source object (must not be null)
101         * @param theTarget              The target object to copy values into (must not be null)
102         * @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)
103         * @return Returns the target (which will be the same object that was passed into theTarget) for easy chaining
104         */
105        public IBase cloneInto(IBase theSource, IBase theTarget, boolean theIgnoreMissingFields) {
106                Validate.notNull(theSource, "theSource must not be null");
107                Validate.notNull(theTarget, "theTarget must not be null");
108
109                if (theSource instanceof IPrimitiveType<?>) {
110                        if (theTarget instanceof IPrimitiveType<?>) {
111                                ((IPrimitiveType<?>) theTarget).setValueAsString(((IPrimitiveType<?>) theSource).getValueAsString());
112                                return theSource;
113                        }
114                        if (theIgnoreMissingFields) {
115                                return theSource;
116                        }
117                        throw new DataFormatException("Can not copy value from primitive of type " + theSource.getClass().getName() + " into type " + theTarget.getClass().getName());
118                }
119
120                BaseRuntimeElementCompositeDefinition<?> sourceDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theSource.getClass());
121                BaseRuntimeElementCompositeDefinition<?> targetDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theTarget.getClass());
122
123                List<BaseRuntimeChildDefinition> children = sourceDef.getChildren();
124                if (sourceDef instanceof RuntimeExtensionDtDefinition) {
125                        children = ((RuntimeExtensionDtDefinition) sourceDef).getChildrenIncludingUrl();
126                }
127
128                for (BaseRuntimeChildDefinition nextChild : children)
129                        for (IBase nextValue : nextChild.getAccessor().getValues(theSource)) {
130                                String elementName = nextChild.getChildNameByDatatype(nextValue.getClass());
131                                BaseRuntimeChildDefinition targetChild = targetDef.getChildByName(elementName);
132                                if (targetChild == null) {
133                                        if (theIgnoreMissingFields) {
134                                                continue;
135                                        }
136                                        throw new DataFormatException("Type " + theTarget.getClass().getName() + " does not have a child with name " + elementName);
137                                }
138
139                                BaseRuntimeElementDefinition<?> element = myContext.getElementDefinition(nextValue.getClass());
140                                IBase target = element.newInstance();
141
142                                targetChild.getMutator().addValue(theTarget, target);
143                                cloneInto(nextValue, target, theIgnoreMissingFields);
144                        }
145
146                return theTarget;
147        }
148
149        /**
150         * 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.
151         * <p>
152         * 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
153         * well as any contained resources.
154         * </p>
155         * <p>
156         * 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.
157         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
158         * </p>
159         *
160         * @param theResource The resource instance to search. Must not be null.
161         * @param theType     The type to search for. Must not be null.
162         * @return Returns a list of all matching elements
163         */
164        public <T extends IBase> List<T> getAllPopulatedChildElementsOfType(IBaseResource theResource, final Class<T> theType) {
165                final ArrayList<T> retVal = new ArrayList<T>();
166                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
167                visit(new IdentityHashMap<Object, Object>(), theResource, theResource, null, null, def, new IModelVisitor() {
168                        @SuppressWarnings("unchecked")
169                        @Override
170                        public void acceptElement(IBaseResource theOuterResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) {
171                                if (theElement == null || theElement.isEmpty()) {
172                                        return;
173                                }
174
175                                if (theType.isAssignableFrom(theElement.getClass())) {
176                                        retVal.add((T) theElement);
177                                }
178                        }
179                });
180                return retVal;
181        }
182
183        public List<ResourceReferenceInfo> getAllResourceReferences(final IBaseResource theResource) {
184                final ArrayList<ResourceReferenceInfo> retVal = new ArrayList<ResourceReferenceInfo>();
185                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
186                visit(new IdentityHashMap<Object, Object>(), theResource, theResource, null, null, def, new IModelVisitor() {
187                        @Override
188                        public void acceptElement(IBaseResource theOuterResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) {
189                                if (theElement == null || theElement.isEmpty()) {
190                                        return;
191                                }
192                                if (IBaseReference.class.isAssignableFrom(theElement.getClass())) {
193                                        retVal.add(new ResourceReferenceInfo(myContext, theOuterResource, thePathToElement, (IBaseReference) theElement));
194                                }
195                        }
196                });
197                return retVal;
198        }
199
200        private BaseRuntimeChildDefinition getDefinition(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, List<String> theSubList) {
201                BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(theSubList.get(0));
202
203                if (theSubList.size() == 1) {
204                        return nextDef;
205                }
206                BaseRuntimeElementCompositeDefinition<?> cmp = (BaseRuntimeElementCompositeDefinition<?>) nextDef.getChildByName(theSubList.get(0));
207                return getDefinition(cmp, theSubList.subList(1, theSubList.size()));
208        }
209
210        public BaseRuntimeChildDefinition getDefinition(Class<? extends IBaseResource> theResourceType, String thePath) {
211                RuntimeResourceDefinition def = myContext.getResourceDefinition(theResourceType);
212
213                BaseRuntimeElementCompositeDefinition<?> currentDef = def;
214
215                List<String> parts = Arrays.asList(thePath.split("\\."));
216                List<String> subList = parts.subList(1, parts.size());
217                if (subList.size() < 1) {
218                        throw new ConfigurationException("Invalid path: " + thePath);
219                }
220                return getDefinition(currentDef, subList);
221
222        }
223
224        public Object getSingleValueOrNull(IBase theTarget, String thePath) {
225                Class<Object> wantedType = Object.class;
226
227                return getSingleValueOrNull(theTarget, thePath, wantedType);
228        }
229
230        public <T> T getSingleValueOrNull(IBase theTarget, String thePath, Class<T> theWantedType) {
231                Validate.notNull(theTarget, "theTarget must not be null");
232                Validate.notBlank(thePath, "thePath must not be empty");
233
234                BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theTarget.getClass());
235                if (!(def instanceof BaseRuntimeElementCompositeDefinition)) {
236                        throw new IllegalArgumentException("Target is not a composite type: " + theTarget.getClass().getName());
237                }
238
239                BaseRuntimeElementCompositeDefinition<?> currentDef = (BaseRuntimeElementCompositeDefinition<?>) def;
240                Object currentObj = theTarget;
241
242                List<String> parts = parsePath(currentDef, thePath);
243
244                List<T> retVal = getValues(currentDef, currentObj, parts, theWantedType);
245                if (retVal.isEmpty()) {
246                        return null;
247                }
248                return retVal.get(0);
249        }
250
251        private <T> List<T> getValues(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, Object theCurrentObj, List<String> theSubList, Class<T> theWantedClass) {
252                return getValues(theCurrentDef, theCurrentObj, theSubList, theWantedClass, false, false);
253        }
254
255        @SuppressWarnings("unchecked")
256        private <T> List<T> getValues(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, Object theCurrentObj, List<String> theSubList, Class<T> theWantedClass, boolean theCreate, boolean theAddExtension) {
257                String name = theSubList.get(0);
258                List<T> retVal = new ArrayList<>();
259
260                if (name.startsWith("extension('")) {
261                        String extensionUrl = name.substring("extension('".length());
262                        int endIndex = extensionUrl.indexOf('\'');
263                        if (endIndex != -1) {
264                                extensionUrl = extensionUrl.substring(0, endIndex);
265                        }
266
267                        if (myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
268                                // DTSU2
269                                final String extensionDtUrlForLambda = extensionUrl;
270                                List<ExtensionDt> extensionDts = Collections.emptyList();
271                                if (theCurrentObj instanceof ISupportsUndeclaredExtensions) {
272                                        extensionDts = ((ISupportsUndeclaredExtensions) theCurrentObj).getUndeclaredExtensions()
273                                                .stream()
274                                                .filter(t -> t.getUrl().equals(extensionDtUrlForLambda))
275                                                .collect(Collectors.toList());
276
277                                        if (theAddExtension
278                                                && (!(theCurrentObj instanceof IBaseExtension) || (extensionDts.isEmpty() && theSubList.size() == 1))) {
279                                                extensionDts.add(createEmptyExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl));
280                                        }
281
282                                        if (extensionDts.isEmpty() && theCreate) {
283                                                extensionDts.add(createEmptyExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl));
284                                        }
285
286                                } else if (theCurrentObj instanceof IBaseExtension) {
287                                        extensionDts = ((IBaseExtension) theCurrentObj).getExtension();
288
289                                        if (theAddExtension
290                                                && (extensionDts.isEmpty() && theSubList.size() == 1)) {
291                                                extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl));
292                                        }
293
294                                        if (extensionDts.isEmpty() && theCreate) {
295                                                extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl));
296                                        }
297                                }
298
299                                for (ExtensionDt next : extensionDts) {
300                                        if (theWantedClass.isAssignableFrom(next.getClass())) {
301                                                retVal.add((T) next);
302                                        }
303                                }
304                        } else {
305                                // DSTU3+
306                                final String extensionUrlForLambda = extensionUrl;
307                                List<IBaseExtension> extensions = Collections.emptyList();
308                                if (theCurrentObj instanceof IBaseHasExtensions) {
309                                        extensions = ((IBaseHasExtensions) theCurrentObj).getExtension()
310                                                .stream()
311                                                .filter(t -> t.getUrl().equals(extensionUrlForLambda))
312                                                .collect(Collectors.toList());
313
314                                        if (theAddExtension
315                                                && (!(theCurrentObj instanceof IBaseExtension) || (extensions.isEmpty() && theSubList.size() == 1))) {
316                                                extensions.add(createEmptyExtension((IBaseHasExtensions) theCurrentObj, extensionUrl));
317                                        }
318
319                                        if (extensions.isEmpty() && theCreate) {
320                                                extensions.add(createEmptyExtension((IBaseHasExtensions) theCurrentObj, extensionUrl));
321                                        }
322                                }
323
324                                for (IBaseExtension next : extensions) {
325                                        if (theWantedClass.isAssignableFrom(next.getClass())) {
326                                                retVal.add((T) next);
327                                        }
328                                }
329                        }
330
331                        if (theSubList.size() > 1) {
332                                List<T> values = retVal;
333                                retVal = new ArrayList<>();
334                                for (T nextElement : values) {
335                                        BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition((Class<? extends IBase>) nextElement.getClass());
336                                        List<T> foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()), theWantedClass, theCreate, theAddExtension);
337                                        retVal.addAll(foundValues);
338                                }
339                        }
340
341                        return retVal;
342                }
343
344                if (name.startsWith("modifierExtension('")) {
345                        String extensionUrl = name.substring("modifierExtension('".length());
346                        int endIndex = extensionUrl.indexOf('\'');
347                        if (endIndex != -1) {
348                                extensionUrl = extensionUrl.substring(0, endIndex);
349                        }
350
351                        if (myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
352                                // DSTU2
353                                final String extensionDtUrlForLambda = extensionUrl;
354                                List<ExtensionDt> extensionDts = Collections.emptyList();
355                                if (theCurrentObj instanceof ISupportsUndeclaredExtensions) {
356                                        extensionDts = ((ISupportsUndeclaredExtensions) theCurrentObj).getUndeclaredModifierExtensions()
357                                                .stream()
358                                                .filter(t -> t.getUrl().equals(extensionDtUrlForLambda))
359                                                .collect(Collectors.toList());
360
361                                        if (theAddExtension
362                                                && (!(theCurrentObj instanceof IBaseExtension) || (extensionDts.isEmpty() && theSubList.size() == 1))) {
363                                                extensionDts.add(createEmptyModifierExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl));
364                                        }
365
366                                        if (extensionDts.isEmpty() && theCreate) {
367                                                extensionDts.add(createEmptyModifierExtensionDt((ISupportsUndeclaredExtensions) theCurrentObj, extensionUrl));
368                                        }
369
370                                } else if (theCurrentObj instanceof IBaseExtension) {
371                                        extensionDts = ((IBaseExtension) theCurrentObj).getExtension();
372
373                                        if (theAddExtension
374                                                && (extensionDts.isEmpty() && theSubList.size() == 1)) {
375                                                extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl));
376                                        }
377
378                                        if (extensionDts.isEmpty() && theCreate) {
379                                                extensionDts.add(createEmptyExtensionDt((IBaseExtension) theCurrentObj, extensionUrl));
380                                        }
381                                }
382
383                                for (ExtensionDt next : extensionDts) {
384                                        if (theWantedClass.isAssignableFrom(next.getClass())) {
385                                                retVal.add((T) next);
386                                        }
387                                }
388                        } else {
389                                // DSTU3+
390                                final String extensionUrlForLambda = extensionUrl;
391                                List<IBaseExtension> extensions = Collections.emptyList();
392
393                                if (theCurrentObj instanceof IBaseHasModifierExtensions) {
394                                        extensions = ((IBaseHasModifierExtensions) theCurrentObj).getModifierExtension()
395                                                .stream()
396                                                .filter(t -> t.getUrl().equals(extensionUrlForLambda))
397                                                .collect(Collectors.toList());
398
399                                        if (theAddExtension
400                                                && (!(theCurrentObj instanceof IBaseExtension) || (extensions.isEmpty() && theSubList.size() == 1))) {
401                                                extensions.add(createEmptyModifierExtension((IBaseHasModifierExtensions) theCurrentObj, extensionUrl));
402                                        }
403
404                                        if (extensions.isEmpty() && theCreate) {
405                                                extensions.add(createEmptyModifierExtension((IBaseHasModifierExtensions) theCurrentObj, extensionUrl));
406                                        }
407                                }
408
409                                for (IBaseExtension next : extensions) {
410                                        if (theWantedClass.isAssignableFrom(next.getClass())) {
411                                                retVal.add((T) next);
412                                        }
413                                }
414                        }
415
416                        if (theSubList.size() > 1) {
417                                List<T> values = retVal;
418                                retVal = new ArrayList<>();
419                                for (T nextElement : values) {
420                                        BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition((Class<? extends IBase>) nextElement.getClass());
421                                        List<T> foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()), theWantedClass, theCreate, theAddExtension);
422                                        retVal.addAll(foundValues);
423                                }
424                        }
425
426                        return retVal;
427                }
428
429                BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(name);
430                List<? extends IBase> values = nextDef.getAccessor().getValues(theCurrentObj);
431
432                if (values.isEmpty() && theCreate) {
433                        IBase value = nextDef.getChildByName(name).newInstance();
434                        nextDef.getMutator().addValue(theCurrentObj, value);
435                        List<IBase> list = new ArrayList<>();
436                        list.add(value);
437                        values = list;
438                }
439
440                if (theSubList.size() == 1) {
441                        if (nextDef instanceof RuntimeChildChoiceDefinition) {
442                                for (IBase next : values) {
443                                        if (next != null) {
444                                                if (name.endsWith("[x]")) {
445                                                        if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) {
446                                                                retVal.add((T) next);
447                                                        }
448                                                } else {
449                                                        String childName = nextDef.getChildNameByDatatype(next.getClass());
450                                                        if (theSubList.get(0).equals(childName)) {
451                                                                if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) {
452                                                                        retVal.add((T) next);
453                                                                }
454                                                        }
455                                                }
456                                        }
457                                }
458                        } else {
459                                for (IBase next : values) {
460                                        if (next != null) {
461                                                if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) {
462                                                        retVal.add((T) next);
463                                                }
464                                        }
465                                }
466                        }
467                } else {
468                        for (IBase nextElement : values) {
469                                BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(nextElement.getClass());
470                                List<T> foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()), theWantedClass, theCreate, theAddExtension);
471                                retVal.addAll(foundValues);
472                        }
473                }
474                return retVal;
475        }
476
477        /**
478         * Returns values stored in an element identified by its path. The list of values is of
479         * type {@link Object}.
480         *
481         * @param theResource The resource instance to be accessed. Must not be null.
482         * @param thePath     The path for the element to be accessed.
483         * @return A list of values of type {@link Object}.
484         */
485        public List<Object> getValues(IBaseResource theResource, String thePath) {
486                Class<Object> wantedClass = Object.class;
487
488                return getValues(theResource, thePath, wantedClass);
489        }
490
491        /**
492         * Returns values stored in an element identified by its path. The list of values is of
493         * type {@link Object}.
494         *
495         * @param theResource The resource instance to be accessed. Must not be null.
496         * @param thePath     The path for the element to be accessed.
497         * @param theCreate   When set to <code>true</code>, the terser will create a null-valued element where none exists.
498         * @return A list of values of type {@link Object}.
499         */
500        public List<Object> getValues(IBaseResource theResource, String thePath, boolean theCreate) {
501                Class<Object> wantedClass = Object.class;
502
503                return getValues(theResource, thePath, wantedClass, theCreate);
504        }
505
506        /**
507         * Returns values stored in an element identified by its path. The list of values is of
508         * type {@link Object}.
509         *
510         * @param theResource     The resource instance to be accessed. Must not be null.
511         * @param thePath         The path for the element to be accessed.
512         * @param theCreate       When set to <code>true</code>, the terser will create a null-valued element where none exists.
513         * @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist.
514         * @return A list of values of type {@link Object}.
515         */
516        public List<Object> getValues(IBaseResource theResource, String thePath, boolean theCreate, boolean theAddExtension) {
517                Class<Object> wantedClass = Object.class;
518
519                return getValues(theResource, thePath, wantedClass, theCreate, theAddExtension);
520        }
521
522        /**
523         * Returns values stored in an element identified by its path. The list of values is of
524         * type <code>theWantedClass</code>.
525         *
526         * @param theResource    The resource instance to be accessed. Must not be null.
527         * @param thePath        The path for the element to be accessed.
528         * @param theWantedClass The desired class to be returned in a list.
529         * @param <T>            Type declared by <code>theWantedClass</code>
530         * @return A list of values of type <code>theWantedClass</code>.
531         */
532        public <T> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass) {
533                RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
534                List<String> parts = parsePath(def, thePath);
535                return getValues(def, theResource, parts, theWantedClass);
536        }
537
538        /**
539         * Returns values stored in an element identified by its path. The list of values is of
540         * type <code>theWantedClass</code>.
541         *
542         * @param theResource    The resource instance to be accessed. Must not be null.
543         * @param thePath        The path for the element to be accessed.
544         * @param theWantedClass The desired class to be returned in a list.
545         * @param theCreate      When set to <code>true</code>, the terser will create a null-valued element where none exists.
546         * @param <T>            Type declared by <code>theWantedClass</code>
547         * @return A list of values of type <code>theWantedClass</code>.
548         */
549        public <T> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass, boolean theCreate) {
550                RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
551                List<String> parts = parsePath(def, thePath);
552                return getValues(def, theResource, parts, theWantedClass, theCreate, false);
553        }
554
555        /**
556         * Returns values stored in an element identified by its path. The list of values is of
557         * type <code>theWantedClass</code>.
558         *
559         * @param theResource     The resource instance to be accessed. Must not be null.
560         * @param thePath         The path for the element to be accessed.
561         * @param theWantedClass  The desired class to be returned in a list.
562         * @param theCreate       When set to <code>true</code>, the terser will create a null-valued element where none exists.
563         * @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist.
564         * @param <T>             Type declared by <code>theWantedClass</code>
565         * @return A list of values of type <code>theWantedClass</code>.
566         */
567        public <T> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass, boolean theCreate, boolean theAddExtension) {
568                RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
569                List<String> parts = parsePath(def, thePath);
570                return getValues(def, theResource, parts, theWantedClass, theCreate, theAddExtension);
571        }
572
573        private List<String> parsePath(BaseRuntimeElementCompositeDefinition<?> theElementDef, String thePath) {
574                List<String> parts = new ArrayList<>();
575
576                int currentStart = 0;
577                boolean inSingleQuote = false;
578                for (int i = 0; i < thePath.length(); i++) {
579                        switch (thePath.charAt(i)) {
580                                case '\'':
581                                        inSingleQuote = !inSingleQuote;
582                                        break;
583                                case '.':
584                                        if (!inSingleQuote) {
585                                                parts.add(thePath.substring(currentStart, i));
586                                                currentStart = i + 1;
587                                        }
588                                        break;
589                        }
590                }
591
592                parts.add(thePath.substring(currentStart));
593
594                if (theElementDef instanceof RuntimeResourceDefinition) {
595                        if (parts.size() > 0 && parts.get(0).equals(theElementDef.getName())) {
596                                parts = parts.subList(1, parts.size());
597                        }
598                }
599
600                if (parts.size() < 1) {
601                        throw new ConfigurationException("Invalid path: " + thePath);
602                }
603                return parts;
604        }
605
606        /**
607         * Returns <code>true</code> if <code>theSource</code> is in the compartment named <code>theCompartmentName</code>
608         * belonging to resource <code>theTarget</code>
609         *
610         * @param theCompartmentName The name of the compartment
611         * @param theSource          The potential member of the compartment
612         * @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}
613         * @return <code>true</code> if <code>theSource</code> is in the compartment
614         * @throws IllegalArgumentException If theTarget does not contain both a resource type and ID
615         */
616        public boolean isSourceInCompartmentForTarget(String theCompartmentName, IBaseResource theSource, IIdType theTarget) {
617                Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank");
618                Validate.notNull(theSource, "theSource must not be null");
619                Validate.notNull(theTarget, "theTarget must not be null");
620                Validate.notBlank(defaultString(theTarget.getResourceType()), "theTarget must have a populated resource type (theTarget.getResourceType() does not return a value)");
621                Validate.notBlank(defaultString(theTarget.getIdPart()), "theTarget must have a populated ID (theTarget.getIdPart() does not return a value)");
622
623                String wantRef = theTarget.toUnqualifiedVersionless().getValue();
624
625                RuntimeResourceDefinition sourceDef = myContext.getResourceDefinition(theSource);
626                if (theSource.getIdElement().hasIdPart()) {
627                        if (wantRef.equals(sourceDef.getName() + '/' + theSource.getIdElement().getIdPart())) {
628                                return true;
629                        }
630                }
631
632                List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(theCompartmentName);
633                for (RuntimeSearchParam nextParam : params) {
634                        for (String nextPath : nextParam.getPathsSplit()) {
635
636                                /*
637                                 * DSTU3 and before just defined compartments as being (e.g.) named
638                                 * Patient with a path like CarePlan.subject
639                                 *
640                                 * R4 uses a fancier format like CarePlan.subject.where(resolve() is Patient)
641                                 *
642                                 * The following Regex is a hack to make that efficient at runtime.
643                                 */
644                                String wantType = null;
645                                Pattern pattern = COMPARTMENT_MATCHER_PATH;
646                                Matcher matcher = pattern.matcher(nextPath);
647                                if (matcher.matches()) {
648                                        nextPath = matcher.group(1);
649                                        wantType = matcher.group(2);
650                                }
651
652                                for (IBaseReference nextValue : getValues(theSource, nextPath, IBaseReference.class)) {
653                                        IIdType nextTargetId = nextValue.getReferenceElement();
654                                        String nextRef = nextTargetId.toUnqualifiedVersionless().getValue();
655
656                                        /*
657                                         * If the reference isn't an explicit resource ID, but instead is just
658                                         * a resource object, we'll calculate its ID and treat the target
659                                         * as that.
660                                         */
661                                        if (isBlank(nextRef) && nextValue.getResource() != null) {
662                                                IBaseResource nextTarget = nextValue.getResource();
663                                                nextTargetId = nextTarget.getIdElement().toUnqualifiedVersionless();
664                                                if (!nextTargetId.hasResourceType()) {
665                                                        String resourceType = myContext.getResourceDefinition(nextTarget).getName();
666                                                        nextTargetId.setParts(null, resourceType, nextTargetId.getIdPart(), null);
667                                                }
668                                                nextRef = nextTargetId.getValue();
669                                        }
670
671                                        if (isNotBlank(wantType)) {
672                                                if (!nextTargetId.getResourceType().equals(wantType)) {
673                                                        continue;
674                                                }
675                                        }
676
677                                        if (wantRef.equals(nextRef)) {
678                                                return true;
679                                        }
680                                }
681                        }
682                }
683
684                return false;
685        }
686
687        private void visit(IBase theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor2 theCallback, List<IBase> theContainingElementPath,
688                                                         List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
689                if (theChildDefinition != null) {
690                        theChildDefinitionPath.add(theChildDefinition);
691                }
692                theContainingElementPath.add(theElement);
693                theElementDefinitionPath.add(theDefinition);
694
695                theCallback.acceptElement(theElement, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath),
696                        Collections.unmodifiableList(theElementDefinitionPath));
697
698                /*
699                 * Visit undeclared extensions
700                 */
701                if (theElement instanceof ISupportsUndeclaredExtensions) {
702                        ISupportsUndeclaredExtensions containingElement = (ISupportsUndeclaredExtensions) theElement;
703                        for (ExtensionDt nextExt : containingElement.getUndeclaredExtensions()) {
704                                theContainingElementPath.add(nextExt);
705                                theCallback.acceptUndeclaredExtension(nextExt, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
706                                theContainingElementPath.remove(theContainingElementPath.size() - 1);
707                        }
708                }
709
710                /*
711                 * Now visit the children of the given element
712                 */
713                switch (theDefinition.getChildType()) {
714                        case ID_DATATYPE:
715                        case PRIMITIVE_XHTML_HL7ORG:
716                        case PRIMITIVE_XHTML:
717                        case PRIMITIVE_DATATYPE:
718                                // These are primitive types, so we don't need to visit their children
719                                break;
720                        case RESOURCE:
721                        case RESOURCE_BLOCK:
722                        case COMPOSITE_DATATYPE: {
723                                BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) theDefinition;
724                                for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
725                                        List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
726                                        if (values != null) {
727                                                for (IBase nextValue : values) {
728                                                        if (nextValue == null) {
729                                                                continue;
730                                                        }
731                                                        if (nextValue.isEmpty()) {
732                                                                continue;
733                                                        }
734                                                        BaseRuntimeElementDefinition<?> childElementDef;
735                                                        childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass());
736
737                                                        if (childElementDef == null) {
738                                                                StringBuilder b = new StringBuilder();
739                                                                b.append("Found value of type[");
740                                                                b.append(nextValue.getClass().getSimpleName());
741                                                                b.append("] which is not valid for field[");
742                                                                b.append(nextChild.getElementName());
743                                                                b.append("] in ");
744                                                                b.append(childDef.getName());
745                                                                b.append(" - Valid types: ");
746                                                                for (Iterator<String> iter = new TreeSet<String>(nextChild.getValidChildNames()).iterator(); iter.hasNext(); ) {
747                                                                        BaseRuntimeElementDefinition<?> childByName = nextChild.getChildByName(iter.next());
748                                                                        b.append(childByName.getImplementingClass().getSimpleName());
749                                                                        if (iter.hasNext()) {
750                                                                                b.append(", ");
751                                                                        }
752                                                                }
753                                                                throw new DataFormatException(b.toString());
754                                                        }
755
756                                                        if (nextChild instanceof RuntimeChildDirectResource) {
757                                                                // Don't descend into embedded resources
758                                                                theContainingElementPath.add(nextValue);
759                                                                theChildDefinitionPath.add(nextChild);
760                                                                theElementDefinitionPath.add(myContext.getElementDefinition(nextValue.getClass()));
761                                                                theCallback.acceptElement(nextValue, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath),
762                                                                        Collections.unmodifiableList(theElementDefinitionPath));
763                                                                theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1);
764                                                                theContainingElementPath.remove(theContainingElementPath.size() - 1);
765                                                                theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1);
766                                                        } else {
767                                                                visit(nextValue, nextChild, childElementDef, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
768                                                        }
769                                                }
770                                        }
771                                }
772                                break;
773                        }
774                        case CONTAINED_RESOURCES: {
775                                BaseContainedDt value = (BaseContainedDt) theElement;
776                                for (IResource next : value.getContainedResources()) {
777                                        BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(next);
778                                        visit(next, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
779                                }
780                                break;
781                        }
782                        case EXTENSION_DECLARED:
783                        case UNDECL_EXT: {
784                                throw new IllegalStateException("state should not happen: " + theDefinition.getChildType());
785                        }
786                        case CONTAINED_RESOURCE_LIST: {
787                                if (theElement != null) {
788                                        BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass());
789                                        visit(theElement, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
790                                }
791                                break;
792                        }
793                }
794
795                if (theChildDefinition != null) {
796                        theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1);
797                }
798                theContainingElementPath.remove(theContainingElementPath.size() - 1);
799                theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1);
800        }
801
802        /**
803         * Visit all elements in a given resource
804         *
805         * <p>
806         * 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.
807         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
808         * </p>
809         *
810         * @param theResource The resource to visit
811         * @param theVisitor  The visitor
812         */
813        public void visit(IBaseResource theResource, IModelVisitor theVisitor) {
814                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
815                visit(new IdentityHashMap<Object, Object>(), theResource, theResource, null, null, def, theVisitor);
816        }
817
818        /**
819         * Visit all elements in a given resource
820         * <p>
821         * THIS ALTERNATE METHOD IS STILL EXPERIMENTAL
822         *
823         * <p>
824         * 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.
825         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
826         * </p>
827         *
828         * @param theResource The resource to visit
829         * @param theVisitor  The visitor
830         */
831        void visit(IBaseResource theResource, IModelVisitor2 theVisitor) {
832                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
833                visit(theResource, null, def, theVisitor, new ArrayList<IBase>(), new ArrayList<BaseRuntimeChildDefinition>(), new ArrayList<BaseRuntimeElementDefinition<?>>());
834        }
835
836        private void visit(IdentityHashMap<Object, Object> theStack, IBaseResource theResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition,
837                                                         BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor theCallback) {
838                List<String> pathToElement = addNameToList(thePathToElement, theChildDefinition);
839
840                if (theStack.put(theElement, theElement) != null) {
841                        return;
842                }
843
844                theCallback.acceptElement(theResource, theElement, pathToElement, theChildDefinition, theDefinition);
845
846                BaseRuntimeElementDefinition<?> def = theDefinition;
847                if (def.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) {
848                        def = myContext.getElementDefinition(theElement.getClass());
849                }
850
851                if (theElement instanceof IBaseReference) {
852                        IBaseResource target = ((IBaseReference) theElement).getResource();
853                        if (target != null) {
854                                if (target.getIdElement().hasIdPart() == false || target.getIdElement().isLocal()) {
855                                        RuntimeResourceDefinition targetDef = myContext.getResourceDefinition(target);
856                                        visit(theStack, target, target, pathToElement, null, targetDef, theCallback);
857                                }
858                        }
859                }
860
861                switch (def.getChildType()) {
862                        case ID_DATATYPE:
863                        case PRIMITIVE_XHTML_HL7ORG:
864                        case PRIMITIVE_XHTML:
865                        case PRIMITIVE_DATATYPE:
866                                // These are primitive types
867                                break;
868                        case RESOURCE:
869                        case RESOURCE_BLOCK:
870                        case COMPOSITE_DATATYPE: {
871                                BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) def;
872                                for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
873
874                                        List<?> values = nextChild.getAccessor().getValues(theElement);
875                                        if (values != null) {
876                                                for (Object nextValueObject : values) {
877                                                        IBase nextValue;
878                                                        try {
879                                                                nextValue = (IBase) nextValueObject;
880                                                        } catch (ClassCastException e) {
881                                                                String s = "Found instance of " + nextValueObject.getClass() + " - Did you set a field value to the incorrect type? Expected " + IBase.class.getName();
882                                                                throw new ClassCastException(s);
883                                                        }
884                                                        if (nextValue == null) {
885                                                                continue;
886                                                        }
887                                                        if (nextValue.isEmpty()) {
888                                                                continue;
889                                                        }
890                                                        BaseRuntimeElementDefinition<?> childElementDef;
891                                                        childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass());
892
893                                                        if (childElementDef == null) {
894                                                                childElementDef = myContext.getElementDefinition(nextValue.getClass());
895                                                        }
896
897                                                        if (nextChild instanceof RuntimeChildDirectResource) {
898                                                                // Don't descend into embedded resources
899                                                                theCallback.acceptElement(theResource, nextValue, null, nextChild, childElementDef);
900                                                        } else {
901                                                                visit(theStack, theResource, nextValue, pathToElement, nextChild, childElementDef, theCallback);
902                                                        }
903                                                }
904                                        }
905                                }
906                                break;
907                        }
908                        case CONTAINED_RESOURCES: {
909                                BaseContainedDt value = (BaseContainedDt) theElement;
910                                for (IResource next : value.getContainedResources()) {
911                                        def = myContext.getResourceDefinition(next);
912                                        visit(theStack, next, next, pathToElement, null, def, theCallback);
913                                }
914                                break;
915                        }
916                        case CONTAINED_RESOURCE_LIST:
917                        case EXTENSION_DECLARED:
918                        case UNDECL_EXT: {
919                                throw new IllegalStateException("state should not happen: " + def.getChildType());
920                        }
921                }
922
923                theStack.remove(theElement);
924
925        }
926
927}