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