View Javadoc
1   package ca.uhn.fhir.parser;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 - 2018 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   * http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import ca.uhn.fhir.context.*;
24  import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
25  import ca.uhn.fhir.model.api.*;
26  import ca.uhn.fhir.model.primitive.IdDt;
27  import ca.uhn.fhir.rest.api.Constants;
28  import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
29  import ca.uhn.fhir.util.UrlUtil;
30  import org.apache.commons.lang3.StringUtils;
31  import org.apache.commons.lang3.Validate;
32  import org.hl7.fhir.instance.model.api.*;
33  
34  import java.io.*;
35  import java.lang.reflect.Modifier;
36  import java.util.*;
37  
38  import static org.apache.commons.lang3.StringUtils.isBlank;
39  import static org.apache.commons.lang3.StringUtils.isNotBlank;
40  
41  @SuppressWarnings("WeakerAccess")
42  public abstract class BaseParser implements IParser {
43  
44  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class);
45  
46  	private ContainedResources myContainedResources;
47  	private boolean myEncodeElementsAppliesToChildResourcesOnly;
48  	private FhirContext myContext;
49  	private Set<String> myDontEncodeElements;
50  	private boolean myDontEncodeElementsIncludesStars;
51  	private Set<String> myEncodeElements;
52  	private Set<String> myEncodeElementsAppliesToResourceTypes;
53  	private boolean myEncodeElementsIncludesStars;
54  	private IIdType myEncodeForceResourceId;
55  	private IParserErrorHandler myErrorHandler;
56  	private boolean myOmitResourceId;
57  	private List<Class<? extends IBaseResource>> myPreferTypes;
58  	private String myServerBaseUrl;
59  	private Boolean myStripVersionsFromReferences;
60  	private Boolean myOverrideResourceIdWithBundleEntryFullUrl;
61  	private boolean mySummaryMode;
62  	private boolean mySuppressNarratives;
63  	private Set<String> myDontStripVersionsFromReferencesAtPaths;
64  
65  	/**
66  	 * Constructor
67  	 *
68  	 * @param theParserErrorHandler
69  	 */
70  	public BaseParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
71  		myContext = theContext;
72  		myErrorHandler = theParserErrorHandler;
73  	}
74  
75  	protected Iterable<CompositeChildElement> compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final boolean theSubResource, final CompositeChildElement theParent) {
76  
77  		BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theCompositeElement.getClass());
78  		final List<BaseRuntimeChildDefinition> children = elementDef.getChildrenAndExtension();
79  
80  		return new Iterable<BaseParser.CompositeChildElement>() {
81  			@Override
82  			public Iterator<CompositeChildElement> iterator() {
83  
84  				return new Iterator<CompositeChildElement>() {
85  					private Iterator<? extends BaseRuntimeChildDefinition> myChildrenIter;
86  					private Boolean myHasNext = null;
87  					private CompositeChildElement myNext;
88  
89  					/**
90  					 * Constructor
91  					 */ {
92  						myChildrenIter = children.iterator();
93  					}
94  
95  					@Override
96  					public boolean hasNext() {
97  						if (myHasNext != null) {
98  							return myHasNext;
99  						}
100 
101 						myNext = null;
102 						do {
103 							if (myChildrenIter.hasNext() == false) {
104 								myHasNext = Boolean.FALSE;
105 								return false;
106 							}
107 
108 							myNext = new CompositeChildElement(theParent, myChildrenIter.next(), theSubResource);
109 
110 							/*
111 							 * There are lots of reasons we might skip encoding a particular child
112 							 */
113 							if (myNext.getDef().getElementName().equals("id")) {
114 								myNext = null;
115 							} else if (!myNext.shouldBeEncoded()) {
116 								myNext = null;
117 							} else if (isSummaryMode() && !myNext.getDef().isSummary()) {
118 								myNext = null;
119 							} else if (myNext.getDef() instanceof RuntimeChildNarrativeDefinition) {
120 								if (isSuppressNarratives() || isSummaryMode()) {
121 									myNext = null;
122 								} else if (theContainedResource) {
123 									myNext = null;
124 								}
125 							} else if (myNext.getDef() instanceof RuntimeChildContainedResources) {
126 								if (theContainedResource) {
127 									myNext = null;
128 								}
129 							}
130 
131 						} while (myNext == null);
132 
133 						myHasNext = true;
134 						return true;
135 					}
136 
137 					@Override
138 					public CompositeChildElement next() {
139 						if (myHasNext == null) {
140 							if (!hasNext()) {
141 								throw new IllegalStateException();
142 							}
143 						}
144 						CompositeChildElement retVal = myNext;
145 						myNext = null;
146 						myHasNext = null;
147 						return retVal;
148 					}
149 
150 					@Override
151 					public void remove() {
152 						throw new UnsupportedOperationException();
153 					}
154 				};
155 			}
156 		};
157 	}
158 
159 	private void containResourcesForEncoding(ContainedResources theContained, IBaseResource theResource, IBaseResource theTarget) {
160 		Map<String, IBaseResource> existingIdToContainedResource = null;
161 
162 		if (theTarget instanceof IResource) {
163 			List<? extends IResource> containedResources = ((IResource) theTarget).getContained().getContainedResources();
164 			for (IResource next : containedResources) {
165 				String nextId = next.getId().getValue();
166 				if (StringUtils.isNotBlank(nextId)) {
167 					if (!nextId.startsWith("#")) {
168 						nextId = '#' + nextId;
169 					}
170 					if (existingIdToContainedResource == null) {
171 						existingIdToContainedResource = new HashMap<>();
172 					}
173 					existingIdToContainedResource.put(nextId, next);
174 				}
175 			}
176 		} else if (theTarget instanceof IDomainResource) {
177 			List<? extends IAnyResource> containedResources = ((IDomainResource) theTarget).getContained();
178 			for (IAnyResource next : containedResources) {
179 				String nextId = next.getIdElement().getValue();
180 				if (StringUtils.isNotBlank(nextId)) {
181 					if (!nextId.startsWith("#")) {
182 						nextId = '#' + nextId;
183 					}
184 					if (existingIdToContainedResource == null) {
185 						existingIdToContainedResource = new HashMap<>();
186 					}
187 					existingIdToContainedResource.put(nextId, next);
188 				}
189 			}
190 		} else {
191 			// no resources to contain
192 		}
193 
194 		// Make sure we don't reuse local IDs
195 		if (existingIdToContainedResource != null) {
196 			theContained.addPreExistingLocalIds(existingIdToContainedResource.keySet());
197 		}
198 
199 		{
200 			List<IBaseReference> allElements = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
201 			for (IBaseReference next : allElements) {
202 				IBaseResource resource = next.getResource();
203 				if (resource == null && next.getReferenceElement().isLocal()) {
204 					if (existingIdToContainedResource != null) {
205 						IBaseResource potentialTarget = existingIdToContainedResource.remove(next.getReferenceElement().getValue());
206 						if (potentialTarget != null) {
207 							theContained.addContained(next.getReferenceElement(), potentialTarget);
208 							containResourcesForEncoding(theContained, potentialTarget, theTarget);
209 						}
210 					}
211 				}
212 			}
213 
214 			for (IBaseReference next : allElements) {
215 				IBaseResource resource = next.getResource();
216 				if (resource != null) {
217 					if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) {
218 						if (theContained.getResourceId(resource) != null) {
219 							// Prevent infinite recursion if there are circular loops in the contained resources
220 							continue;
221 						}
222 						theContained.addContained(resource);
223 						if (resource.getIdElement().isLocal() && existingIdToContainedResource != null) {
224 							existingIdToContainedResource.remove(resource.getIdElement().getValue());
225 						}
226 					} else {
227 						continue;
228 					}
229 
230 					containResourcesForEncoding(theContained, resource, theTarget);
231 				}
232 
233 			}
234 		}
235 
236 	}
237 
238 	protected void containResourcesForEncoding(IBaseResource theResource) {
239 		ContainedResources contained = new ContainedResources();
240 		containResourcesForEncoding(contained, theResource, theResource);
241 		myContainedResources = contained;
242 	}
243 
244 	private String determineReferenceText(IBaseReference theRef, CompositeChildElement theCompositeChildElement) {
245 		IIdType ref = theRef.getReferenceElement();
246 		if (isBlank(ref.getIdPart())) {
247 			String reference = ref.getValue();
248 			if (theRef.getResource() != null) {
249 				IIdType containedId = getContainedResources().getResourceId(theRef.getResource());
250 				if (containedId != null && !containedId.isEmpty()) {
251 					if (containedId.isLocal()) {
252 						reference = containedId.getValue();
253 					} else {
254 						reference = "#" + containedId.getValue();
255 					}
256 				} else {
257 					IIdType refId = theRef.getResource().getIdElement();
258 					if (refId != null) {
259 						if (refId.hasIdPart()) {
260 							if (refId.getValue().startsWith("urn:")) {
261 								reference = refId.getValue();
262 							} else {
263 								if (!refId.hasResourceType()) {
264 									refId = refId.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName());
265 								}
266 								if (isStripVersionsFromReferences(theCompositeChildElement)) {
267 									reference = refId.toVersionless().getValue();
268 								} else {
269 									reference = refId.getValue();
270 								}
271 							}
272 						}
273 					}
274 				}
275 			}
276 			return reference;
277 		}
278 		if (!ref.hasResourceType() && !ref.isLocal() && theRef.getResource() != null) {
279 			ref = ref.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName());
280 		}
281 		if (isNotBlank(myServerBaseUrl) && StringUtils.equals(myServerBaseUrl, ref.getBaseUrl())) {
282 			if (isStripVersionsFromReferences(theCompositeChildElement)) {
283 				return ref.toUnqualifiedVersionless().getValue();
284 			}
285 			return ref.toUnqualified().getValue();
286 		}
287 		if (isStripVersionsFromReferences(theCompositeChildElement)) {
288 			return ref.toVersionless().getValue();
289 		}
290 		return ref.getValue();
291 	}
292 
293 	protected abstract void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException;
294 
295 	protected abstract <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException;
296 
297 	@Override
298 	public String encodeResourceToString(IBaseResource theResource) throws DataFormatException {
299 		Writer stringWriter = new StringWriter();
300 		try {
301 			encodeResourceToWriter(theResource, stringWriter);
302 		} catch (IOException e) {
303 			throw new Error("Encountered IOException during write to string - This should not happen!");
304 		}
305 		return stringWriter.toString();
306 	}
307 
308 	@Override
309 	public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException {
310 		Validate.notNull(theResource, "theResource can not be null");
311 		Validate.notNull(theWriter, "theWriter can not be null");
312 
313 		if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) {
314 			throw new IllegalArgumentException(
315 				"This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
316 		}
317 
318 		doEncodeResourceToWriter(theResource, theWriter);
319 	}
320 
321 	private void filterCodingsWithNoCodeOrSystem(List<? extends IBaseCoding> tagList) {
322 		for (int i = 0; i < tagList.size(); i++) {
323 			if (isBlank(tagList.get(i).getCode()) && isBlank(tagList.get(i).getSystem())) {
324 				tagList.remove(i);
325 				i--;
326 			}
327 		}
328 	}
329 
330 	protected IIdType fixContainedResourceId(String theValue) {
331 		IIdType retVal = (IIdType) myContext.getElementDefinition("id").newInstance();
332 		if (StringUtils.isNotBlank(theValue) && theValue.charAt(0) == '#') {
333 			retVal.setValue(theValue.substring(1));
334 		} else {
335 			retVal.setValue(theValue);
336 		}
337 		return retVal;
338 	}
339 
340 	@SuppressWarnings("unchecked")
341 	ChildNameAndDef getChildNameAndDef(BaseRuntimeChildDefinition theChild, IBase theValue) {
342 		Class<? extends IBase> type = theValue.getClass();
343 		String childName = theChild.getChildNameByDatatype(type);
344 		BaseRuntimeElementDefinition<?> childDef = theChild.getChildElementDefinitionByDatatype(type);
345 		if (childDef == null) {
346 			// if (theValue instanceof IBaseExtension) {
347 			// return null;
348 			// }
349 
350 			/*
351 			 * For RI structures Enumeration class, this replaces the child def
352 			 * with the "code" one. This is messy, and presumably there is a better
353 			 * way..
354 			 */
355 			BaseRuntimeElementDefinition<?> elementDef = myContext.getElementDefinition(type);
356 			if (elementDef.getName().equals("code")) {
357 				Class<? extends IBase> type2 = myContext.getElementDefinition("code").getImplementingClass();
358 				childDef = theChild.getChildElementDefinitionByDatatype(type2);
359 				childName = theChild.getChildNameByDatatype(type2);
360 			}
361 
362 			// See possibly the user has extended a built-in type without
363 			// declaring it anywhere, as in XmlParserDstu3Test#testEncodeUndeclaredBlock
364 			if (childDef == null) {
365 				Class<?> nextSuperType = theValue.getClass();
366 				while (IBase.class.isAssignableFrom(nextSuperType) && childDef == null) {
367 					if (Modifier.isAbstract(nextSuperType.getModifiers()) == false) {
368 						BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition((Class<? extends IBase>) nextSuperType);
369 						Class<?> nextChildType = def.getImplementingClass();
370 						childDef = theChild.getChildElementDefinitionByDatatype((Class<? extends IBase>) nextChildType);
371 						childName = theChild.getChildNameByDatatype((Class<? extends IBase>) nextChildType);
372 					}
373 					nextSuperType = nextSuperType.getSuperclass();
374 				}
375 			}
376 
377 			if (childDef == null) {
378 				throwExceptionForUnknownChildType(theChild, type);
379 			}
380 		}
381 
382 		return new ChildNameAndDef(childName, childDef);
383 	}
384 
385 	protected String getCompositeElementId(IBase theElement) {
386 		String elementId = null;
387 		if (!(theElement instanceof IBaseResource)) {
388 			if (theElement instanceof IBaseElement) {
389 				elementId = ((IBaseElement) theElement).getId();
390 			} else if (theElement instanceof IIdentifiableElement) {
391 				elementId = ((IIdentifiableElement) theElement).getElementSpecificId();
392 			}
393 		}
394 		return elementId;
395 	}
396 
397 	ContainedResources getContainedResources() {
398 		return myContainedResources;
399 	}
400 
401 	@Override
402 	public Set<String> getDontStripVersionsFromReferencesAtPaths() {
403 		return myDontStripVersionsFromReferencesAtPaths;
404 	}
405 
406 	/**
407 	 * See {@link #setEncodeElements(Set)}
408 	 */
409 	@Override
410 	public Set<String> getEncodeElements() {
411 		return myEncodeElements;
412 	}
413 
414 	@Override
415 	public void setEncodeElements(Set<String> theEncodeElements) {
416 		myEncodeElementsIncludesStars = false;
417 		if (theEncodeElements == null || theEncodeElements.isEmpty()) {
418 			myEncodeElements = null;
419 		} else {
420 			myEncodeElements = theEncodeElements;
421 			for (String next : theEncodeElements) {
422 				if (next.startsWith("*.")) {
423 					myEncodeElementsIncludesStars = true;
424 				}
425 			}
426 		}
427 	}
428 
429 	/**
430 	 * See {@link #setEncodeElementsAppliesToResourceTypes(Set)}
431 	 */
432 	@Override
433 	public Set<String> getEncodeElementsAppliesToResourceTypes() {
434 		return myEncodeElementsAppliesToResourceTypes;
435 	}
436 
437 	@Override
438 	public void setEncodeElementsAppliesToResourceTypes(Set<String> theEncodeElementsAppliesToResourceTypes) {
439 		if (theEncodeElementsAppliesToResourceTypes == null || theEncodeElementsAppliesToResourceTypes.isEmpty()) {
440 			myEncodeElementsAppliesToResourceTypes = null;
441 		} else {
442 			myEncodeElementsAppliesToResourceTypes = theEncodeElementsAppliesToResourceTypes;
443 		}
444 	}
445 
446 	@Override
447 	public IIdType getEncodeForceResourceId() {
448 		return myEncodeForceResourceId;
449 	}
450 
451 	@Override
452 	public BaseParser setEncodeForceResourceId(IIdType theEncodeForceResourceId) {
453 		myEncodeForceResourceId = theEncodeForceResourceId;
454 		return this;
455 	}
456 
457 	protected IParserErrorHandler getErrorHandler() {
458 		return myErrorHandler;
459 	}
460 
461 	protected List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> getExtensionMetadataKeys(IResource resource) {
462 		List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys = new ArrayList<Map.Entry<ResourceMetadataKeyEnum<?>, Object>>();
463 		for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : resource.getResourceMetadata().entrySet()) {
464 			if (entry.getKey() instanceof ResourceMetadataKeyEnum.ExtensionResourceMetadataKey) {
465 				extensionMetadataKeys.add(entry);
466 			}
467 		}
468 
469 		return extensionMetadataKeys;
470 	}
471 
472 	protected String getExtensionUrl(final String extensionUrl) {
473 		String url = extensionUrl;
474 		if (StringUtils.isNotBlank(extensionUrl) && StringUtils.isNotBlank(myServerBaseUrl)) {
475 			url = !UrlUtil.isValid(extensionUrl) && extensionUrl.startsWith("/") ? myServerBaseUrl + extensionUrl : extensionUrl;
476 		}
477 		return url;
478 	}
479 
480 	protected TagList getMetaTagsForEncoding(IResource theIResource) {
481 		TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(theIResource);
482 		if (shouldAddSubsettedTag()) {
483 			tags = new TagList(tags);
484 			tags.add(new Tag(Constants.TAG_SUBSETTED_SYSTEM, Constants.TAG_SUBSETTED_CODE, subsetDescription()));
485 		}
486 
487 		return tags;
488 	}
489 
490 	@Override
491 	public Boolean getOverrideResourceIdWithBundleEntryFullUrl() {
492 		return myOverrideResourceIdWithBundleEntryFullUrl;
493 	}
494 
495 	@Override
496 	public List<Class<? extends IBaseResource>> getPreferTypes() {
497 		return myPreferTypes;
498 	}
499 
500 	@Override
501 	public void setPreferTypes(List<Class<? extends IBaseResource>> thePreferTypes) {
502 		if (thePreferTypes != null) {
503 			ArrayList<Class<? extends IBaseResource>> types = new ArrayList<>();
504 			for (Class<? extends IBaseResource> next : thePreferTypes) {
505 				if (Modifier.isAbstract(next.getModifiers()) == false) {
506 					types.add(next);
507 				}
508 			}
509 			myPreferTypes = Collections.unmodifiableList(types);
510 		} else {
511 			myPreferTypes = thePreferTypes;
512 		}
513 	}
514 
515 	@SuppressWarnings("deprecation")
516 	protected <T extends IPrimitiveType<String>> List<T> getProfileTagsForEncoding(IBaseResource theResource, List<T> theProfiles) {
517 		switch (myContext.getAddProfileTagWhenEncoding()) {
518 			case NEVER:
519 				return theProfiles;
520 			case ONLY_FOR_CUSTOM:
521 				RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
522 				if (resDef.isStandardType()) {
523 					return theProfiles;
524 				}
525 				break;
526 			case ALWAYS:
527 				break;
528 		}
529 
530 		RuntimeResourceDefinition nextDef = myContext.getResourceDefinition(theResource);
531 		String profile = nextDef.getResourceProfile(myServerBaseUrl);
532 		if (isNotBlank(profile)) {
533 			for (T next : theProfiles) {
534 				if (profile.equals(next.getValue())) {
535 					return theProfiles;
536 				}
537 			}
538 
539 			List<T> newList = new ArrayList<>(theProfiles);
540 
541 			BaseRuntimeElementDefinition<?> idElement = myContext.getElementDefinition("id");
542 			@SuppressWarnings("unchecked")
543 			T newId = (T) idElement.newInstance();
544 			newId.setValue(profile);
545 
546 			newList.add(newId);
547 			return newList;
548 		}
549 
550 		return theProfiles;
551 	}
552 
553 	protected String getServerBaseUrl() {
554 		return myServerBaseUrl;
555 	}
556 
557 	@Override
558 	public Boolean getStripVersionsFromReferences() {
559 		return myStripVersionsFromReferences;
560 	}
561 
562 	/**
563 	 * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded
564 	 * values.
565 	 *
566 	 * @deprecated Use {@link #isSuppressNarratives()}
567 	 */
568 	@Deprecated
569 	public boolean getSuppressNarratives() {
570 		return mySuppressNarratives;
571 	}
572 
573 	protected boolean isChildContained(BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource) {
574 		return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && getContainedResources().isEmpty() == false
575 			&& theIncludedResource == false;
576 	}
577 
578 	@Override
579 	public boolean isEncodeElementsAppliesToChildResourcesOnly() {
580 		return myEncodeElementsAppliesToChildResourcesOnly;
581 	}
582 
583 	@Override
584 	public void setEncodeElementsAppliesToChildResourcesOnly(boolean theEncodeElementsAppliesToChildResourcesOnly) {
585 		myEncodeElementsAppliesToChildResourcesOnly = theEncodeElementsAppliesToChildResourcesOnly;
586 	}
587 
588 	@Override
589 	public boolean isOmitResourceId() {
590 		return myOmitResourceId;
591 	}
592 
593 	private boolean isOverrideResourceIdWithBundleEntryFullUrl() {
594 		Boolean overrideResourceIdWithBundleEntryFullUrl = myOverrideResourceIdWithBundleEntryFullUrl;
595 		if (overrideResourceIdWithBundleEntryFullUrl != null) {
596 			return overrideResourceIdWithBundleEntryFullUrl;
597 		}
598 
599 		return myContext.getParserOptions().isOverrideResourceIdWithBundleEntryFullUrl();
600 	}
601 
602 	private boolean isStripVersionsFromReferences(CompositeChildElement theCompositeChildElement) {
603 		Boolean stripVersionsFromReferences = myStripVersionsFromReferences;
604 		if (stripVersionsFromReferences != null) {
605 			return stripVersionsFromReferences;
606 		}
607 
608 		if (myContext.getParserOptions().isStripVersionsFromReferences() == false) {
609 			return false;
610 		}
611 
612 		Set<String> dontStripVersionsFromReferencesAtPaths = myDontStripVersionsFromReferencesAtPaths;
613 		if (dontStripVersionsFromReferencesAtPaths != null) {
614 			if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
615 				return false;
616 			}
617 		}
618 
619 		dontStripVersionsFromReferencesAtPaths = myContext.getParserOptions().getDontStripVersionsFromReferencesAtPaths();
620 		if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
621 			return false;
622 		}
623 
624 		return true;
625 	}
626 
627 	@Override
628 	public boolean isSummaryMode() {
629 		return mySummaryMode;
630 	}
631 
632 	/**
633 	 * If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded
634 	 * values.
635 	 *
636 	 * @since 1.2
637 	 */
638 	public boolean isSuppressNarratives() {
639 		return mySuppressNarratives;
640 	}
641 
642 	@Override
643 	public <T extends IBaseResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException {
644 
645 		/*
646 		 * We do this so that the context can verify that the structure is for
647 		 * the correct FHIR version
648 		 */
649 		if (theResourceType != null) {
650 			myContext.getResourceDefinition(theResourceType);
651 		}
652 
653 		// Actually do the parse
654 		T retVal = doParseResource(theResourceType, theReader);
655 
656 		RuntimeResourceDefinition def = myContext.getResourceDefinition(retVal);
657 		if ("Bundle".equals(def.getName())) {
658 
659 			BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
660 			BaseRuntimeElementCompositeDefinition<?> entryDef = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
661 			List<IBase> entries = entryChild.getAccessor().getValues(retVal);
662 			if (entries != null) {
663 				for (IBase nextEntry : entries) {
664 
665 					/**
666 					 * If Bundle.entry.fullUrl is populated, set the resource ID to that
667 					 */
668 					// TODO: should emit a warning and maybe notify the error handler if the resource ID doesn't match the
669 					// fullUrl idPart
670 					BaseRuntimeChildDefinition fullUrlChild = entryDef.getChildByName("fullUrl");
671 					if (fullUrlChild == null) {
672 						continue; // TODO: remove this once the data model in tinder plugin catches up to 1.2
673 					}
674 					if (isOverrideResourceIdWithBundleEntryFullUrl()) {
675 						List<IBase> fullUrl = fullUrlChild.getAccessor().getValues(nextEntry);
676 						if (fullUrl != null && !fullUrl.isEmpty()) {
677 							IPrimitiveType<?> value = (IPrimitiveType<?>) fullUrl.get(0);
678 							if (value.isEmpty() == false) {
679 								List<IBase> entryResources = entryDef.getChildByName("resource").getAccessor().getValues(nextEntry);
680 								if (entryResources != null && entryResources.size() > 0) {
681 									IBaseResource res = (IBaseResource) entryResources.get(0);
682 									String versionId = res.getIdElement().getVersionIdPart();
683 									res.setId(value.getValueAsString());
684 									if (isNotBlank(versionId) && res.getIdElement().hasVersionIdPart() == false) {
685 										res.setId(res.getIdElement().withVersion(versionId));
686 									}
687 								}
688 							}
689 						}
690 					}
691 				}
692 			}
693 
694 		}
695 
696 		return retVal;
697 	}
698 
699 	@SuppressWarnings("cast")
700 	@Override
701 	public <T extends IBaseResource> T parseResource(Class<T> theResourceType, String theMessageString) {
702 		StringReader reader = new StringReader(theMessageString);
703 		return (T) parseResource(theResourceType, reader);
704 	}
705 
706 	@Override
707 	public IBaseResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException {
708 		return parseResource(null, theReader);
709 	}
710 
711 	@Override
712 	public IBaseResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException {
713 		return parseResource(null, theMessageString);
714 	}
715 
716 	protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition theMetaChildUncast, IBaseResource theResource, List<? extends IBase> theValues,
717 																	 CompositeChildElement theCompositeChildElement) {
718 		if (myContext.getVersion().getVersion().isRi()) {
719 
720 			/*
721 			 * If we're encoding the meta tag, we do some massaging of the meta values before
722 			 * encoding. But if there is no meta element at all, we create one since we're possibly going to be
723 			 * adding things to it
724 			 */
725 			if (theValues.isEmpty() && theMetaChildUncast.getElementName().equals("meta")) {
726 				BaseRuntimeElementDefinition<?> metaChild = theMetaChildUncast.getChildByName("meta");
727 				if (IBaseMetaType.class.isAssignableFrom(metaChild.getImplementingClass())) {
728 					IBaseMetaType newType = (IBaseMetaType) metaChild.newInstance();
729 					theValues = Collections.singletonList(newType);
730 				}
731 			}
732 
733 			if (theValues.size() == 1 && theValues.get(0) instanceof IBaseMetaType) {
734 
735 				IBaseMetaType metaValue = (IBaseMetaType) theValues.get(0);
736 				try {
737 					metaValue = (IBaseMetaType) metaValue.getClass().getMethod("copy").invoke(metaValue);
738 				} catch (Exception e) {
739 					throw new InternalErrorException("Failed to duplicate meta", e);
740 				}
741 
742 				if (isBlank(metaValue.getVersionId())) {
743 					if (theResource.getIdElement().hasVersionIdPart()) {
744 						metaValue.setVersionId(theResource.getIdElement().getVersionIdPart());
745 					}
746 				}
747 
748 				filterCodingsWithNoCodeOrSystem(metaValue.getTag());
749 				filterCodingsWithNoCodeOrSystem(metaValue.getSecurity());
750 
751 				List<? extends IPrimitiveType<String>> newProfileList = getProfileTagsForEncoding(theResource, metaValue.getProfile());
752 				List<? extends IPrimitiveType<String>> oldProfileList = metaValue.getProfile();
753 				if (oldProfileList != newProfileList) {
754 					oldProfileList.clear();
755 					for (IPrimitiveType<String> next : newProfileList) {
756 						if (isNotBlank(next.getValue())) {
757 							metaValue.addProfile(next.getValue());
758 						}
759 					}
760 				}
761 
762 				if (shouldAddSubsettedTag()) {
763 					IBaseCoding coding = metaValue.addTag();
764 					coding.setCode(Constants.TAG_SUBSETTED_CODE);
765 					coding.setSystem(Constants.TAG_SUBSETTED_SYSTEM);
766 					coding.setDisplay(subsetDescription());
767 				}
768 
769 				return Collections.singletonList(metaValue);
770 			}
771 		}
772 
773 		@SuppressWarnings("unchecked")
774 		List<IBase> retVal = (List<IBase>) theValues;
775 
776 		for (int i = 0; i < retVal.size(); i++) {
777 			IBase next = retVal.get(i);
778 
779 			/*
780 			 * If we have automatically contained any resources via
781 			 * their references, this ensures that we output the new
782 			 * local reference
783 			 */
784 			if (next instanceof IBaseReference) {
785 				IBaseReference nextRef = (IBaseReference) next;
786 				String refText = determineReferenceText(nextRef, theCompositeChildElement);
787 				if (!StringUtils.equals(refText, nextRef.getReferenceElement().getValue())) {
788 
789 					if (retVal == theValues) {
790 						retVal = new ArrayList<IBase>(theValues);
791 					}
792 					IBaseReference newRef = (IBaseReference) myContext.getElementDefinition(nextRef.getClass()).newInstance();
793 					myContext.newTerser().cloneInto(nextRef, newRef, true);
794 					newRef.setReference(refText);
795 					retVal.set(i, newRef);
796 
797 				}
798 			}
799 		}
800 
801 		return retVal;
802 	}
803 
804 	@Override
805 	public void setDontEncodeElements(Set<String> theDontEncodeElements) {
806 		myDontEncodeElementsIncludesStars = false;
807 		if (theDontEncodeElements == null || theDontEncodeElements.isEmpty()) {
808 			myDontEncodeElements = null;
809 		} else {
810 			myDontEncodeElements = theDontEncodeElements;
811 			for (String next : theDontEncodeElements) {
812 				if (next.startsWith("*.")) {
813 					myDontEncodeElementsIncludesStars = true;
814 				}
815 			}
816 		}
817 	}
818 
819 	@Override
820 	public IParser setDontStripVersionsFromReferencesAtPaths(String... thePaths) {
821 		if (thePaths == null) {
822 			setDontStripVersionsFromReferencesAtPaths((List<String>) null);
823 		} else {
824 			setDontStripVersionsFromReferencesAtPaths(Arrays.asList(thePaths));
825 		}
826 		return this;
827 	}
828 
829 	@SuppressWarnings("unchecked")
830 	@Override
831 	public IParser setDontStripVersionsFromReferencesAtPaths(Collection<String> thePaths) {
832 		if (thePaths == null) {
833 			myDontStripVersionsFromReferencesAtPaths = Collections.emptySet();
834 		} else if (thePaths instanceof HashSet) {
835 			myDontStripVersionsFromReferencesAtPaths = (Set<String>) ((HashSet<String>) thePaths).clone();
836 		} else {
837 			myDontStripVersionsFromReferencesAtPaths = new HashSet<String>(thePaths);
838 		}
839 		return this;
840 	}
841 
842 	@Override
843 	public IParser setOmitResourceId(boolean theOmitResourceId) {
844 		myOmitResourceId = theOmitResourceId;
845 		return this;
846 	}
847 
848 	@Override
849 	public IParser setOverrideResourceIdWithBundleEntryFullUrl(Boolean theOverrideResourceIdWithBundleEntryFullUrl) {
850 		myOverrideResourceIdWithBundleEntryFullUrl = theOverrideResourceIdWithBundleEntryFullUrl;
851 		return this;
852 	}
853 
854 	@Override
855 	public IParser setParserErrorHandler(IParserErrorHandler theErrorHandler) {
856 		Validate.notNull(theErrorHandler, "theErrorHandler must not be null");
857 		myErrorHandler = theErrorHandler;
858 		return this;
859 	}
860 
861 	@Override
862 	public IParser setServerBaseUrl(String theUrl) {
863 		myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null;
864 		return this;
865 	}
866 
867 	@Override
868 	public IParser setStripVersionsFromReferences(Boolean theStripVersionsFromReferences) {
869 		myStripVersionsFromReferences = theStripVersionsFromReferences;
870 		return this;
871 	}
872 
873 	@Override
874 	public IParser setSummaryMode(boolean theSummaryMode) {
875 		mySummaryMode = theSummaryMode;
876 		return this;
877 	}
878 
879 	@Override
880 	public IParser setSuppressNarratives(boolean theSuppressNarratives) {
881 		mySuppressNarratives = theSuppressNarratives;
882 		return this;
883 	}
884 
885 	protected boolean shouldAddSubsettedTag() {
886 		return isSummaryMode() || isSuppressNarratives() || getEncodeElements() != null;
887 	}
888 
889 	protected boolean shouldEncodeResourceId(IBaseResource theResource, boolean theSubResource) {
890 		boolean retVal = true;
891 		if (isOmitResourceId()) {
892 			retVal = false;
893 		} else {
894 			if (myDontEncodeElements != null) {
895 				String resourceName = myContext.getResourceDefinition(theResource).getName();
896 				if (myDontEncodeElements.contains(resourceName + ".id")) {
897 					retVal = false;
898 				} else if (myDontEncodeElements.contains("*.id")) {
899 					retVal = false;
900 				} else if (theSubResource == false && myDontEncodeElements.contains("id")) {
901 					retVal = false;
902 				}
903 			}
904 		}
905 		return retVal;
906 	}
907 
908 	/**
909 	 * Used for DSTU2 only
910 	 */
911 	protected boolean shouldEncodeResourceMeta(IResource theResource) {
912 		if (myDontEncodeElements != null) {
913 			String resourceName = myContext.getResourceDefinition(theResource).getName();
914 			if (myDontEncodeElements.contains(resourceName + ".meta")) {
915 				return false;
916 			} else if (myDontEncodeElements.contains("*.meta")) {
917 				return false;
918 			}
919 		}
920 		return true;
921 	}
922 
923 	private String subsetDescription() {
924 		return "Resource encoded in summary mode";
925 	}
926 
927 	protected void throwExceptionForUnknownChildType(BaseRuntimeChildDefinition nextChild, Class<? extends IBase> theType) {
928 		if (nextChild instanceof BaseRuntimeDeclaredChildDefinition) {
929 			StringBuilder b = new StringBuilder();
930 			b.append(((BaseRuntimeDeclaredChildDefinition) nextChild).getElementName());
931 			b.append(" has type ");
932 			b.append(theType.getName());
933 			b.append(" but this is not a valid type for this element");
934 			if (nextChild instanceof RuntimeChildChoiceDefinition) {
935 				RuntimeChildChoiceDefinition choice = (RuntimeChildChoiceDefinition) nextChild;
936 				b.append(" - Expected one of: " + choice.getValidChildTypes());
937 			}
938 			throw new DataFormatException(b.toString());
939 		}
940 		throw new DataFormatException(nextChild + " has no child of type " + theType);
941 	}
942 
943 	protected static <T> List<T> extractMetadataListNotNull(IResource resource, ResourceMetadataKeyEnum<List<T>> key) {
944 		List<? extends T> securityLabels = key.get(resource);
945 		if (securityLabels == null) {
946 			securityLabels = Collections.emptyList();
947 		}
948 		return new ArrayList<T>(securityLabels);
949 	}
950 
951 	static boolean hasExtensions(IBase theElement) {
952 		if (theElement instanceof ISupportsUndeclaredExtensions) {
953 			ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
954 			if (res.getUndeclaredExtensions().size() > 0 || res.getUndeclaredModifierExtensions().size() > 0) {
955 				return true;
956 			}
957 		}
958 		if (theElement instanceof IBaseHasExtensions) {
959 			IBaseHasExtensions res = (IBaseHasExtensions) theElement;
960 			if (res.hasExtension()) {
961 				return true;
962 			}
963 		}
964 		if (theElement instanceof IBaseHasModifierExtensions) {
965 			IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement;
966 			if (res.hasModifierExtension()) {
967 				return true;
968 			}
969 		}
970 		return false;
971 	}
972 
973 	class ChildNameAndDef {
974 
975 		private final BaseRuntimeElementDefinition<?> myChildDef;
976 		private final String myChildName;
977 
978 		public ChildNameAndDef(String theChildName, BaseRuntimeElementDefinition<?> theChildDef) {
979 			myChildName = theChildName;
980 			myChildDef = theChildDef;
981 		}
982 
983 		public BaseRuntimeElementDefinition<?> getChildDef() {
984 			return myChildDef;
985 		}
986 
987 		public String getChildName() {
988 			return myChildName;
989 		}
990 
991 	}
992 
993 	protected class CompositeChildElement {
994 		private final BaseRuntimeChildDefinition myDef;
995 		private final CompositeChildElement myParent;
996 		private final RuntimeResourceDefinition myResDef;
997 		private final boolean mySubResource;
998 
999 		public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef, boolean theSubResource) {
1000 			myDef = theDef;
1001 			myParent = theParent;
1002 			myResDef = null;
1003 			mySubResource = theSubResource;
1004 
1005 			if (ourLog.isTraceEnabled()) {
1006 				if (theParent != null) {
1007 					StringBuilder path = theParent.buildPath();
1008 					if (path != null) {
1009 						path.append('.');
1010 						path.append(myDef.getElementName());
1011 						ourLog.trace(" * Next path: {}", path.toString());
1012 					}
1013 				}
1014 			}
1015 
1016 		}
1017 
1018 		public CompositeChildElement(RuntimeResourceDefinition theResDef, boolean theSubResource) {
1019 			myResDef = theResDef;
1020 			myDef = null;
1021 			myParent = null;
1022 			mySubResource = theSubResource;
1023 		}
1024 
1025 		private void addParent(CompositeChildElement theParent, StringBuilder theB) {
1026 			if (theParent != null) {
1027 				if (theParent.myResDef != null) {
1028 					theB.append(theParent.myResDef.getName());
1029 					return;
1030 				}
1031 
1032 				if (theParent.myParent != null) {
1033 					addParent(theParent.myParent, theB);
1034 				}
1035 
1036 				if (theParent.myDef != null) {
1037 					if (theB.length() > 0) {
1038 						theB.append('.');
1039 					}
1040 					theB.append(theParent.myDef.getElementName());
1041 				}
1042 			}
1043 		}
1044 
1045 		public boolean anyPathMatches(Set<String> thePaths) {
1046 			StringBuilder b = new StringBuilder();
1047 			addParent(this, b);
1048 
1049 			String path = b.toString();
1050 			return thePaths.contains(path);
1051 		}
1052 
1053 		private StringBuilder buildPath() {
1054 			if (myResDef != null) {
1055 				StringBuilder b = new StringBuilder();
1056 				b.append(myResDef.getName());
1057 				return b;
1058 			} else if (myParent != null) {
1059 				StringBuilder b = myParent.buildPath();
1060 				if (b != null && myDef != null) {
1061 					b.append('.');
1062 					b.append(myDef.getElementName());
1063 				}
1064 				return b;
1065 			} else {
1066 				return null;
1067 			}
1068 		}
1069 
1070 		private boolean checkIfParentShouldBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
1071 			Set<String> encodeElements = myEncodeElements;
1072 			if (encodeElements != null && encodeElements.isEmpty() == false) {
1073 				if (isEncodeElementsAppliesToChildResourcesOnly() && !mySubResource) {
1074 					encodeElements = null;
1075 				}
1076 			}
1077 			return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, myEncodeElementsAppliesToResourceTypes, encodeElements, true);
1078 		}
1079 
1080 		private boolean checkIfParentShouldNotBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
1081 			return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, null, myDontEncodeElements, false);
1082 		}
1083 
1084 		private boolean checkIfPathMatchesForEncoding(StringBuilder thePathBuilder, boolean theStarPass, Set<String> theResourceTypes, Set<String> theElements, boolean theCheckingForWhitelist) {
1085 			if (myResDef != null) {
1086 				if (theResourceTypes != null) {
1087 					if (!theResourceTypes.contains(myResDef.getName())) {
1088 						return true;
1089 					}
1090 				}
1091 				if (theStarPass) {
1092 					thePathBuilder.append('*');
1093 				} else {
1094 					thePathBuilder.append(myResDef.getName());
1095 				}
1096 				if (theElements == null) {
1097 					return true;
1098 				}
1099 				if (theElements.contains(thePathBuilder.toString())) {
1100 					return true;
1101 				}
1102 				return false;
1103 			} else if (myParent != null) {
1104 				boolean parentCheck;
1105 				if (theCheckingForWhitelist) {
1106 					parentCheck = myParent.checkIfParentShouldBeEncodedAndBuildPath(thePathBuilder, theStarPass);
1107 				} else {
1108 					parentCheck = myParent.checkIfParentShouldNotBeEncodedAndBuildPath(thePathBuilder, theStarPass);
1109 				}
1110 				if (parentCheck) {
1111 					return true;
1112 				}
1113 
1114 				if (myDef != null) {
1115 					if (myDef.getMin() > 0) {
1116 						if (theElements.contains("*.(mandatory)")) {
1117 							return true;
1118 						}
1119 					}
1120 
1121 					thePathBuilder.append('.');
1122 					thePathBuilder.append(myDef.getElementName());
1123 					String currentPath = thePathBuilder.toString();
1124 					boolean retVal = theElements.contains(currentPath);
1125 					int dotIdx = currentPath.indexOf('.');
1126 					if (!retVal) {
1127 						if (dotIdx != -1 && theElements.contains(currentPath.substring(dotIdx + 1))) {
1128 							if (!myParent.isSubResource()) {
1129 								return true;
1130 							}
1131 						}
1132 					}
1133 					return retVal;
1134 				}
1135 			}
1136 
1137 			return false;
1138 		}
1139 
1140 		public BaseRuntimeChildDefinition getDef() {
1141 			return myDef;
1142 		}
1143 
1144 		public CompositeChildElement getParent() {
1145 			return myParent;
1146 		}
1147 
1148 		public RuntimeResourceDefinition getResDef() {
1149 			return myResDef;
1150 		}
1151 
1152 		private boolean isSubResource() {
1153 			return mySubResource;
1154 		}
1155 
1156 		public boolean shouldBeEncoded() {
1157 			boolean retVal = true;
1158 			if (myEncodeElements != null) {
1159 				retVal = checkIfParentShouldBeEncodedAndBuildPath(new StringBuilder(), false);
1160 				if (retVal == false && myEncodeElementsIncludesStars) {
1161 					retVal = checkIfParentShouldBeEncodedAndBuildPath(new StringBuilder(), true);
1162 				}
1163 			}
1164 			if (retVal && myDontEncodeElements != null) {
1165 				retVal = !checkIfParentShouldNotBeEncodedAndBuildPath(new StringBuilder(), false);
1166 				if (retVal && myDontEncodeElementsIncludesStars) {
1167 					retVal = !checkIfParentShouldNotBeEncodedAndBuildPath(new StringBuilder(), true);
1168 				}
1169 			}
1170 			// if (retVal == false && myEncodeElements.contains("*.(mandatory)")) {
1171 			// if (myDef.getMin() > 0) {
1172 			// retVal = true;
1173 			// }
1174 			// }
1175 
1176 			return retVal;
1177 		}
1178 	}
1179 
1180 	static class ContainedResources {
1181 		private long myNextContainedId = 1;
1182 
1183 		private List<IBaseResource> myResources;
1184 		private IdentityHashMap<IBaseResource, IIdType> myResourceToId;
1185 		private Set<String> myPreExistingLocalIds;
1186 
1187 		public void addContained(IBaseResource theResource) {
1188 			initializeMapsIfNeeded();
1189 			if (myResourceToId.containsKey(theResource)) {
1190 				return;
1191 			}
1192 
1193 			IIdType newId;
1194 			if (theResource.getIdElement().isLocal()) {
1195 				newId = theResource.getIdElement();
1196 			} else {
1197 				// TODO: make this configurable between the two below (and something else?)
1198 				// newId = new IdDt(UUID.randomUUID().toString());
1199 
1200 				// Generally we won't even go into this loop once since
1201 				// our next ID shouldn't already exist, but there is
1202 				// always a chance that it does if someone is mixing
1203 				// manually assigned local IDs with HAPI assigned ones
1204 				// See JsonParser#testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocalFirst
1205 				// and JsonParser#testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocalLast
1206 				// for examples of where this is needed...
1207 				while (true) {
1208 					String nextCandidate = "#" + myNextContainedId;
1209 					if (myPreExistingLocalIds.add(nextCandidate)) {
1210 							break;
1211 					}
1212 					myNextContainedId++;
1213 				}
1214 				newId = new IdDt(myNextContainedId);
1215 
1216 				myNextContainedId++;
1217 			}
1218 
1219 			myResourceToId.put(theResource, newId);
1220 			myResources.add(theResource);
1221 		}
1222 
1223 		public void addContained(IIdType theId, IBaseResource theResource) {
1224 			initializeMapsIfNeeded();
1225 			if (!myResourceToId.containsKey(theResource)) {
1226 				myResourceToId.put(theResource, theId);
1227 				myResources.add(theResource);
1228 			}
1229 		}
1230 
1231 		public void addPreExistingLocalIds(Set<String> theIds) {
1232 			initializeMapsIfNeeded();
1233 			myPreExistingLocalIds.addAll(theIds);
1234 		}
1235 
1236 		public List<IBaseResource> getContainedResources() {
1237 			if (myResources == null) {
1238 				return Collections.emptyList();
1239 			}
1240 			return myResources;
1241 		}
1242 
1243 		public IIdType getResourceId(IBaseResource theNext) {
1244 			if (myResourceToId == null) {
1245 				return null;
1246 			}
1247 			return myResourceToId.get(theNext);
1248 		}
1249 
1250 		private void initializeMapsIfNeeded() {
1251 			if (myResources == null) {
1252 				myResources = new ArrayList<>();
1253 				myResourceToId = new IdentityHashMap<>();
1254 				myPreExistingLocalIds = new HashSet<>();
1255 			}
1256 		}
1257 
1258 		public boolean isEmpty() {
1259 			if (myResourceToId == null) {
1260 				return true;
1261 			}
1262 			return myResourceToId.isEmpty();
1263 		}
1264 
1265 	}
1266 
1267 }