View Javadoc
1   package ca.uhn.fhir.util;
2   
3   import ca.uhn.fhir.context.*;
4   import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
5   import ca.uhn.fhir.model.api.ExtensionDt;
6   import ca.uhn.fhir.model.api.IResource;
7   import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
8   import ca.uhn.fhir.model.base.composite.BaseContainedDt;
9   import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
10  import ca.uhn.fhir.model.primitive.StringDt;
11  import ca.uhn.fhir.parser.DataFormatException;
12  import org.apache.commons.lang3.Validate;
13  import org.hl7.fhir.instance.model.api.*;
14  
15  import java.util.*;
16  import java.util.regex.Matcher;
17  import java.util.regex.Pattern;
18  import java.util.stream.Collectors;
19  
20  import static org.apache.commons.lang3.StringUtils.*;
21  
22  /*
23   * #%L
24   * HAPI FHIR - Core Library
25   * %%
26   * Copyright (C) 2014 - 2019 University Health Network
27   * %%
28   * Licensed under the Apache License, Version 2.0 (the "License");
29   * you may not use this file except in compliance with the License.
30   * You may obtain a copy of the License at
31   * 
32   *      http://www.apache.org/licenses/LICENSE-2.0
33   * 
34   * Unless required by applicable law or agreed to in writing, software
35   * distributed under the License is distributed on an "AS IS" BASIS,
36   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
37   * See the License for the specific language governing permissions and
38   * limitations under the License.
39   * #L%
40   */
41  
42  public class FhirTerser {
43  
44  	public static final Pattern COMPARTMENT_MATCHER_PATH = Pattern.compile("([a-zA-Z.]+)\\.where\\(resolve\\(\\) is ([a-zA-Z]+)\\)");
45  	private FhirContext myContext;
46  
47  	public FhirTerser(FhirContext theContext) {
48  		super();
49  		myContext = theContext;
50  	}
51  
52  	private List<String> addNameToList(List<String> theCurrentList, BaseRuntimeChildDefinition theChildDefinition) {
53  		if (theChildDefinition == null)
54  			return null;
55  		if (theCurrentList == null || theCurrentList.isEmpty())
56  			return new ArrayList<>(Arrays.asList(theChildDefinition.getElementName()));
57  		List<String> newList = new ArrayList<>(theCurrentList);
58  		newList.add(theChildDefinition.getElementName());
59  		return newList;
60  	}
61  
62  	private ExtensionDt createEmptyExtensionDt(IBaseExtension theBaseExtension, String theUrl) {
63  		return createEmptyExtensionDt(theBaseExtension, false, theUrl);
64  	}
65  
66  	@SuppressWarnings("unchecked")
67  	private ExtensionDt createEmptyExtensionDt(IBaseExtension theBaseExtension, boolean theIsModifier, String theUrl) {
68  		ExtensionDt retVal = new ExtensionDt(theIsModifier, theUrl);
69  		theBaseExtension.getExtension().add(retVal);
70  		return retVal;
71  	}
72  
73  	private ExtensionDt createEmptyExtensionDt(ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, String theUrl) {
74  		return createEmptyExtensionDt(theSupportsUndeclaredExtensions, false, theUrl);
75  	}
76  
77  	private ExtensionDt createEmptyExtensionDt(ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, boolean theIsModifier, String theUrl) {
78  		return theSupportsUndeclaredExtensions.addUndeclaredExtension(theIsModifier, theUrl);
79  	}
80  
81  	private IBaseExtension createEmptyExtension(IBaseHasExtensions theBaseHasExtensions, String theUrl) {
82  		return (IBaseExtension) theBaseHasExtensions.addExtension().setUrl(theUrl);
83  	}
84  
85  	private IBaseExtension createEmptyModifierExtension(IBaseHasModifierExtensions theBaseHasModifierExtensions, String theUrl) {
86  		return (IBaseExtension) theBaseHasModifierExtensions.addModifierExtension().setUrl(theUrl);
87  	}
88  
89  	private ExtensionDt createEmptyModifierExtensionDt(IBaseExtension theBaseExtension, String theUrl) {
90  		return createEmptyExtensionDt(theBaseExtension, true, theUrl);
91  	}
92  
93  	private ExtensionDt createEmptyModifierExtensionDt(ISupportsUndeclaredExtensions theSupportsUndeclaredExtensions, String theUrl) {
94  		return createEmptyExtensionDt(theSupportsUndeclaredExtensions, true, theUrl);
95  	}
96  
97  	/**
98  	 * Clones all values from a source object into the equivalent fields in a target object
99  	 *
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../../../../org/hl7/fhir/instance/model/api/IBase.html#IBase">IBase"../../../../org/hl7/fhir/instance/model/api/IBase.html#IBase">IBase cloneInto(IBase../../../../org/hl7/fhir/instance/model/api/IBase.html#IBase">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 }