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