001package ca.uhn.fhir.util;
002
003import static org.apache.commons.lang3.StringUtils.defaultString;
004import static org.apache.commons.lang3.StringUtils.isBlank;
005
006/*
007 * #%L
008 * HAPI FHIR - Core Library
009 * %%
010 * Copyright (C) 2014 - 2018 University Health Network
011 * %%
012 * Licensed under the Apache License, Version 2.0 (the "License");
013 * you may not use this file except in compliance with the License.
014 * You may obtain a copy of the License at
015 * 
016 *      http://www.apache.org/licenses/LICENSE-2.0
017 * 
018 * Unless required by applicable law or agreed to in writing, software
019 * distributed under the License is distributed on an "AS IS" BASIS,
020 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
021 * See the License for the specific language governing permissions and
022 * limitations under the License.
023 * #L%
024 */
025import java.util.*;
026import java.util.function.Predicate;
027
028import org.apache.commons.lang3.Validate;
029import org.hl7.fhir.instance.model.api.*;
030
031import ca.uhn.fhir.context.*;
032import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
033import ca.uhn.fhir.model.api.ExtensionDt;
034import ca.uhn.fhir.model.api.IResource;
035import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
036import ca.uhn.fhir.model.base.composite.BaseContainedDt;
037import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
038import ca.uhn.fhir.model.primitive.StringDt;
039import ca.uhn.fhir.parser.DataFormatException;
040
041public class FhirTerser {
042
043        private FhirContext myContext;
044
045        public FhirTerser(FhirContext theContext) {
046                super();
047                myContext = theContext;
048        }
049
050        private List<String> addNameToList(List<String> theCurrentList, BaseRuntimeChildDefinition theChildDefinition) {
051                if (theChildDefinition == null)
052                        return null;
053                if (theCurrentList == null || theCurrentList.isEmpty())
054                        return new ArrayList<String>(Arrays.asList(theChildDefinition.getElementName()));
055                List<String> newList = new ArrayList<String>(theCurrentList);
056                newList.add(theChildDefinition.getElementName());
057                return newList;
058        }
059        
060
061        /**
062         * Clones all values from a source object into the equivalent fields in a target object
063         * @param theSource The source object (must not be null)
064         * @param theTarget The target object to copy values into (must not be null)
065         * @param theIgnoreMissingFields The ignore fields in the target which do not exist (if false, an exception will be thrown if the target is unable to accept a value from the source)
066         */
067        public void cloneInto(IBase theSource, IBase theTarget, boolean theIgnoreMissingFields) {
068                Validate.notNull(theSource, "theSource must not be null");
069                Validate.notNull(theTarget, "theTarget must not be null");
070                
071                if (theSource instanceof IPrimitiveType<?>) {
072                        if (theTarget instanceof IPrimitiveType<?>) {
073                                ((IPrimitiveType<?>)theTarget).setValueAsString(((IPrimitiveType<?>)theSource).getValueAsString());
074                                return;
075                        }
076                        if (theIgnoreMissingFields) {
077                                return;
078                        }
079                        throw new DataFormatException("Can not copy value from primitive of type " + theSource.getClass().getName() + " into type " + theTarget.getClass().getName());
080                }
081                
082                BaseRuntimeElementCompositeDefinition<?> sourceDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theSource.getClass()); 
083                BaseRuntimeElementCompositeDefinition<?> targetDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theTarget.getClass());
084                
085                List<BaseRuntimeChildDefinition> children = sourceDef.getChildren();
086                if (sourceDef instanceof RuntimeExtensionDtDefinition) {
087                        children = ((RuntimeExtensionDtDefinition)sourceDef).getChildrenIncludingUrl();
088                }
089                
090                for (BaseRuntimeChildDefinition nextChild : children) {
091                        for (IBase nextValue : nextChild.getAccessor().getValues(theSource)) {
092                                String elementName = nextChild.getChildNameByDatatype(nextValue.getClass());
093                                BaseRuntimeChildDefinition targetChild = targetDef.getChildByName(elementName);
094                                if (targetChild == null) {
095                                        if (theIgnoreMissingFields) {
096                                                continue;
097                                        }
098                                        throw new DataFormatException("Type " + theTarget.getClass().getName() + " does not have a child with name " + elementName);
099                                }
100                                
101                                BaseRuntimeElementDefinition<?> childDef = targetChild.getChildByName(elementName);
102                                IBase target = childDef.newInstance();
103                                targetChild.getMutator().addValue(theTarget, target);
104                                cloneInto(nextValue, target, theIgnoreMissingFields);
105                        }
106                }
107                
108        }
109
110        /**
111         * Returns a list containing all child elements (including the resource itself) which are <b>non-empty</b> and are either of the exact type specified, or are a subclass of that type.
112         * <p>
113         * For example, specifying a type of {@link StringDt} would return all non-empty string instances within the message. Specifying a type of {@link IResource} would return the resource itself, as
114         * well as any contained resources.
115         * </p>
116         * <p>
117         * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g.
118         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
119         * </p>
120         * 
121         * @param theResource
122         *           The resource instance to search. Must not be null.
123         * @param theType
124         *           The type to search for. Must not be null.
125         * @return Returns a list of all matching elements
126         */
127        public <T extends IBase> List<T> getAllPopulatedChildElementsOfType(IBaseResource theResource, final Class<T> theType) {
128                final ArrayList<T> retVal = new ArrayList<T>();
129                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
130                visit(new IdentityHashMap<Object, Object>(), theResource, theResource, null, null, def, new IModelVisitor() {
131                        @SuppressWarnings("unchecked")
132                        @Override
133                        public void acceptElement(IBaseResource theOuterResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) {
134                                if (theElement == null || theElement.isEmpty()) {
135                                        return;
136                                }
137
138                                if (theType.isAssignableFrom(theElement.getClass())) {
139                                        retVal.add((T) theElement);
140                                }
141                        }
142                });
143                return retVal;
144        }
145
146        public List<ResourceReferenceInfo> getAllResourceReferences(final IBaseResource theResource) {
147                final ArrayList<ResourceReferenceInfo> retVal = new ArrayList<ResourceReferenceInfo>();
148                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
149                visit(new IdentityHashMap<Object, Object>(), theResource, theResource, null, null, def, new IModelVisitor() {
150                        @Override
151                        public void acceptElement(IBaseResource theOuterResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) {
152                                if (theElement == null || theElement.isEmpty()) {
153                                        return;
154                                }
155                                if (IBaseReference.class.isAssignableFrom(theElement.getClass())) {
156                                        retVal.add(new ResourceReferenceInfo(myContext, theOuterResource, thePathToElement, (IBaseReference) theElement));
157                                }
158                        }
159                });
160                return retVal;
161        }
162
163        private BaseRuntimeChildDefinition getDefinition(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, List<String> theSubList) {
164                BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(theSubList.get(0));
165
166                if (theSubList.size() == 1) {
167                        return nextDef;
168                }
169                BaseRuntimeElementCompositeDefinition<?> cmp = (BaseRuntimeElementCompositeDefinition<?>) nextDef.getChildByName(theSubList.get(0));
170                return getDefinition(cmp, theSubList.subList(1, theSubList.size()));
171        }
172
173        public BaseRuntimeChildDefinition getDefinition(Class<? extends IBaseResource> theResourceType, String thePath) {
174                RuntimeResourceDefinition def = myContext.getResourceDefinition(theResourceType);
175
176                BaseRuntimeElementCompositeDefinition<?> currentDef = def;
177
178                List<String> parts = Arrays.asList(thePath.split("\\."));
179                List<String> subList = parts.subList(1, parts.size());
180                if (subList.size() < 1) {
181                        throw new ConfigurationException("Invalid path: " + thePath);
182                }
183                return getDefinition(currentDef, subList);
184
185        }
186
187        public Object getSingleValueOrNull(IBase theTarget, String thePath) {
188                Class<Object> wantedType = Object.class;
189
190                return getSingleValueOrNull(theTarget, thePath, wantedType);
191        }
192
193        public <T> T getSingleValueOrNull(IBase theTarget, String thePath, Class<T> theWantedType) {
194                Validate.notNull(theTarget, "theTarget must not be null");
195                Validate.notBlank(thePath, "thePath must not be empty");
196
197                BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theTarget.getClass());
198                if (!(def instanceof BaseRuntimeElementCompositeDefinition)) {
199                        throw new IllegalArgumentException("Target is not a composite type: " + theTarget.getClass().getName());
200                }
201
202                BaseRuntimeElementCompositeDefinition<?> currentDef = (BaseRuntimeElementCompositeDefinition<?>) def;
203                Object currentObj = theTarget;
204
205                List<String> parts = parsePath(currentDef, thePath);
206
207                List<T> retVal = getValues(currentDef, currentObj, parts, theWantedType);
208                if (retVal.isEmpty()) {
209                        return null;
210                }
211                return retVal.get(0);
212        }
213
214        @SuppressWarnings("unchecked")
215        private <T> List<T> getValues(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, Object theCurrentObj, List<String> theSubList, Class<T> theWantedClass) {
216                String name = theSubList.get(0);
217                List<T> retVal = new ArrayList<>();
218
219                if (name.startsWith("extension('")) {
220                        String extensionUrl = name.substring("extension('".length());
221                        int endIndex = extensionUrl.indexOf('\'');
222                        if (endIndex != -1) {
223                                extensionUrl = extensionUrl.substring(0, endIndex);
224                        }
225
226                        List<ExtensionDt> extensions= Collections.emptyList();
227                        if (theCurrentObj instanceof ISupportsUndeclaredExtensions) {
228                                extensions = ((ISupportsUndeclaredExtensions) theCurrentObj).getUndeclaredExtensionsByUrl(extensionUrl);
229                        } else if (theCurrentObj instanceof IBaseExtension) {
230                                extensions = ((IBaseExtension)theCurrentObj).getExtension();
231                        }
232
233                        for (ExtensionDt next : extensions) {
234                                if (theWantedClass.isAssignableFrom(next.getClass())) {
235                                        retVal.add((T) next);
236                                }
237                        }
238
239                        if (theSubList.size() > 1) {
240                                List<T> values = retVal;
241                                retVal = new ArrayList<>();
242                                for (T nextElement : values) {
243                                        BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition((Class<? extends IBase>) nextElement.getClass());
244                                        List<T> foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()), theWantedClass);
245                                        retVal.addAll(foundValues);
246                                }
247                        }
248
249                        return retVal;
250                }
251
252                BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(name);
253                List<? extends IBase> values = nextDef.getAccessor().getValues(theCurrentObj);
254
255                if (theSubList.size() == 1) {
256                        if (nextDef instanceof RuntimeChildChoiceDefinition) {
257                                for (IBase next : values) {
258                                        if (next != null) {
259                                                if (name.endsWith("[x]")) {
260                                                        if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) {
261                                                                retVal.add((T) next);
262                                                        }
263                                                } else {
264                                                        String childName = nextDef.getChildNameByDatatype(next.getClass());
265                                                        if (theSubList.get(0).equals(childName)) {
266                                                                if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) {
267                                                                        retVal.add((T) next);
268                                                                }
269                                                        }
270                                                }
271                                        }
272                                }
273                        } else {
274                                for (IBase next : values) {
275                                        if (next != null) {
276                                                if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) {
277                                                        retVal.add((T) next);
278                                                }
279                                        }
280                                }
281                        }
282                } else {
283                        for (IBase nextElement : values) {
284                                BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(nextElement.getClass());
285                                List<T> foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()), theWantedClass);
286                                retVal.addAll(foundValues);
287                        }
288                }
289                return retVal;
290        }
291
292        public List<Object> getValues(IBaseResource theResource, String thePath) {
293                Class<Object> wantedClass = Object.class;
294
295                return getValues(theResource, thePath, wantedClass);
296
297        }
298
299        public <T> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass) {
300                RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
301                List<String> parts = parsePath(def, thePath);
302                return getValues(def, theResource, parts, theWantedClass);
303        }
304
305        private List<String> parsePath(BaseRuntimeElementCompositeDefinition<?> theElementDef, String thePath) {
306                List<String> parts = new ArrayList<>();
307
308                int currentStart = 0;
309                boolean inSingleQuote = false;
310                for (int i = 0; i < thePath.length(); i++) {
311                        switch (thePath.charAt(i)) {
312                                case '\'':
313                                        inSingleQuote = !inSingleQuote;
314                                        break;
315                                case '.':
316                                        if (!inSingleQuote) {
317                                                parts.add(thePath.substring(currentStart, i));
318                                                currentStart = i + 1;
319                                        }
320                                        break;
321                        }
322                }
323
324                parts.add(thePath.substring(currentStart));
325
326                if (theElementDef instanceof RuntimeResourceDefinition) {
327                        if (parts.size() > 0 && parts.get(0).equals(theElementDef.getName())) {
328                                parts = parts.subList(1, parts.size());
329                        }
330                }
331
332                if (parts.size() < 1) {
333                        throw new ConfigurationException("Invalid path: " + thePath);
334                }
335                return parts;
336        }
337
338        /**
339         * Returns <code>true</code> if <code>theSource</code> is in the compartment named <code>theCompartmentName</code>
340         * belonging to resource <code>theTarget</code>
341         * 
342         * @param theCompartmentName The name of the compartment
343         * @param theSource The potential member of the compartment
344         * @param theTarget The owner of the compartment. Note that both the resource type and ID must be filled in on this IIdType or the method will throw an {@link IllegalArgumentException}
345         * @return <code>true</code> if <code>theSource</code> is in the compartment
346         * @throws IllegalArgumentException If theTarget does not contain both a resource type and ID
347         */
348        public boolean isSourceInCompartmentForTarget(String theCompartmentName, IBaseResource theSource, IIdType theTarget) {
349                Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank");
350                Validate.notNull(theSource, "theSource must not be null");
351                Validate.notNull(theTarget, "theTarget must not be null");
352                Validate.notBlank(defaultString(theTarget.getResourceType()), "theTarget must have a populated resource type (theTarget.getResourceType() does not return a value)");
353                Validate.notBlank(defaultString(theTarget.getIdPart()), "theTarget must have a populated ID (theTarget.getIdPart() does not return a value)");
354                
355                String wantRef = theTarget.toUnqualifiedVersionless().getValue();
356                
357                RuntimeResourceDefinition sourceDef = myContext.getResourceDefinition(theSource);
358                if (theSource.getIdElement().hasIdPart()) {
359                        if (wantRef.equals(sourceDef.getName() + '/' + theSource.getIdElement().getIdPart())) {
360                                return true;
361                        }
362                }
363                
364                List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(theCompartmentName);
365                for (RuntimeSearchParam nextParam : params) {
366                        for (String nextPath : nextParam.getPathsSplit()) {
367                                for (IBaseReference nextValue : getValues(theSource, nextPath, IBaseReference.class)) {
368                                        String nextRef = nextValue.getReferenceElement().toUnqualifiedVersionless().getValue();
369
370                                        /*
371                                         * If the reference isn't an explicit resource ID, but instead is just
372                                         * a resource object, we'll calculate its ID and treat the target
373                                         * as that.
374                                         */
375                                        if (isBlank(nextRef) && nextValue.getResource() != null) {
376                                                IBaseResource nextTarget = nextValue.getResource();
377                                                IIdType nextTargetId = nextTarget.getIdElement().toUnqualifiedVersionless();
378                                                if (!nextTargetId.hasResourceType()) {
379                                                        String resourceType = myContext.getResourceDefinition(nextTarget).getName();
380                                                        nextTargetId.setParts(null, resourceType, nextTargetId.getIdPart(), null);
381                                                }
382                                                nextRef = nextTargetId.getValue();
383                                        }
384
385                                        if (wantRef.equals(nextRef)) {
386                                                return true;
387                                        }
388                                }
389                        }
390                }
391                
392                return false;
393        }
394
395        private void visit(IBase theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor2 theCallback, List<IBase> theContainingElementPath,
396                        List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
397                if (theChildDefinition != null) {
398                        theChildDefinitionPath.add(theChildDefinition);
399                }
400                theContainingElementPath.add(theElement);
401                theElementDefinitionPath.add(theDefinition);
402
403                theCallback.acceptElement(theElement, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath),
404                                Collections.unmodifiableList(theElementDefinitionPath));
405
406                /*
407                 * Visit undeclared extensions
408                 */
409                if (theElement instanceof ISupportsUndeclaredExtensions) {
410                        ISupportsUndeclaredExtensions containingElement = (ISupportsUndeclaredExtensions) theElement;
411                        for (ExtensionDt nextExt : containingElement.getUndeclaredExtensions()) {
412                                theContainingElementPath.add(nextExt);
413                                theCallback.acceptUndeclaredExtension(nextExt, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
414                                theContainingElementPath.remove(theContainingElementPath.size() - 1);
415                        }
416                }
417
418                /*
419                 * Now visit the children of the given element
420                 */
421                switch (theDefinition.getChildType()) {
422                case ID_DATATYPE:
423                case PRIMITIVE_XHTML_HL7ORG:
424                case PRIMITIVE_XHTML:
425                case PRIMITIVE_DATATYPE:
426                        // These are primitive types, so we don't need to visit their children
427                        break;
428                case RESOURCE:
429                case RESOURCE_BLOCK:
430                case COMPOSITE_DATATYPE: {
431                        BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) theDefinition;
432                        for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
433                                List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
434                                if (values != null) {
435                                        for (IBase nextValue : values) {
436                                                if (nextValue == null) {
437                                                        continue;
438                                                }
439                                                if (nextValue.isEmpty()) {
440                                                        continue;
441                                                }
442                                                BaseRuntimeElementDefinition<?> childElementDef;
443                                                childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass());
444
445                                                if (childElementDef == null) {
446                                                        StringBuilder b = new StringBuilder();
447                                                        b.append("Found value of type[");
448                                                        b.append(nextValue.getClass().getSimpleName());
449                                                        b.append("] which is not valid for field[");
450                                                        b.append(nextChild.getElementName());
451                                                        b.append("] in ");
452                                                        b.append(childDef.getName());
453                                                        b.append(" - Valid types: ");
454                                                        for (Iterator<String> iter = new TreeSet<String>(nextChild.getValidChildNames()).iterator(); iter.hasNext();) {
455                                                                BaseRuntimeElementDefinition<?> childByName = nextChild.getChildByName(iter.next());
456                                                                b.append(childByName.getImplementingClass().getSimpleName());
457                                                                if (iter.hasNext()) {
458                                                                        b.append(", ");
459                                                                }
460                                                        }
461                                                        throw new DataFormatException(b.toString());
462                                                }
463
464                                                if (nextChild instanceof RuntimeChildDirectResource) {
465                                                        // Don't descend into embedded resources
466                                                        theContainingElementPath.add(nextValue);
467                                                        theChildDefinitionPath.add(nextChild);
468                                                        theElementDefinitionPath.add(myContext.getElementDefinition(nextValue.getClass()));
469                                                        theCallback.acceptElement(nextValue, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath),
470                                                                        Collections.unmodifiableList(theElementDefinitionPath));
471                                                        theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1);
472                                                        theContainingElementPath.remove(theContainingElementPath.size() - 1);
473                                                        theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1);
474                                                } else {
475                                                        visit(nextValue, nextChild, childElementDef, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
476                                                }
477                                        }
478                                }
479                        }
480                        break;
481                }
482                case CONTAINED_RESOURCES: {
483                        BaseContainedDt value = (BaseContainedDt) theElement;
484                        for (IResource next : value.getContainedResources()) {
485                                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(next);
486                                visit(next, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
487                        }
488                        break;
489                }
490                case EXTENSION_DECLARED:
491                case UNDECL_EXT: {
492                        throw new IllegalStateException("state should not happen: " + theDefinition.getChildType());
493                }
494                case CONTAINED_RESOURCE_LIST: {
495                        if (theElement != null) {
496                                BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass());
497                                visit(theElement, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
498                        }
499                        break;
500                }
501                }
502
503                if (theChildDefinition != null) {
504                        theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1);
505                }
506                theContainingElementPath.remove(theContainingElementPath.size() - 1);
507                theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1);
508        }
509
510        /**
511         * Visit all elements in a given resource
512         * 
513         * <p>
514         * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g.
515         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
516         * </p>
517         * 
518         * @param theResource
519         *           The resource to visit
520         * @param theVisitor
521         *           The visitor
522         */
523        public void visit(IBaseResource theResource, IModelVisitor theVisitor) {
524                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
525                visit(new IdentityHashMap<Object, Object>(), theResource, theResource, null, null, def, theVisitor);
526        }
527
528        /**
529         * Visit all elements in a given resource
530         * 
531         * THIS ALTERNATE METHOD IS STILL EXPERIMENTAL
532         * 
533         * <p>
534         * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not descend into linked resources (e.g.
535         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
536         * </p>
537         * 
538         * @param theResource
539         *           The resource to visit
540         * @param theVisitor
541         *           The visitor
542         */
543        void visit(IBaseResource theResource, IModelVisitor2 theVisitor) {
544                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
545                visit(theResource, null, def, theVisitor, new ArrayList<IBase>(), new ArrayList<BaseRuntimeChildDefinition>(), new ArrayList<BaseRuntimeElementDefinition<?>>());
546        }
547
548        private void visit(IdentityHashMap<Object, Object> theStack, IBaseResource theResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition,
549                        BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor theCallback) {
550                List<String> pathToElement = addNameToList(thePathToElement, theChildDefinition);
551
552                if (theStack.put(theElement, theElement) != null) {
553                        return;
554                }
555                
556                theCallback.acceptElement(theResource, theElement, pathToElement, theChildDefinition, theDefinition);
557
558                BaseRuntimeElementDefinition<?> def = theDefinition;
559                if (def.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) {
560                        def = myContext.getElementDefinition(theElement.getClass());
561                }
562                
563                if (theElement instanceof IBaseReference) {
564                        IBaseResource target = ((IBaseReference)theElement).getResource();
565                        if (target != null) {
566                                if (target.getIdElement().hasIdPart() == false || target.getIdElement().isLocal()) {
567                                        RuntimeResourceDefinition targetDef = myContext.getResourceDefinition(target);
568                                        visit(theStack, target, target, pathToElement, null, targetDef, theCallback);
569                                }
570                        }
571                }
572                
573                switch (def.getChildType()) {
574                case ID_DATATYPE:
575                case PRIMITIVE_XHTML_HL7ORG:
576                case PRIMITIVE_XHTML:
577                case PRIMITIVE_DATATYPE:
578                        // These are primitive types
579                        break;
580                case RESOURCE:
581                case RESOURCE_BLOCK:
582                case COMPOSITE_DATATYPE: {
583                        BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) def;
584                        for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
585                                
586                                List<?> values = nextChild.getAccessor().getValues(theElement);
587                                if (values != null) {
588                                        for (Object nextValueObject : values) {
589                                                IBase nextValue;
590                                                try {
591                                                        nextValue = (IBase) nextValueObject;
592                                                } catch (ClassCastException e) {
593                                                        String s = "Found instance of " + nextValueObject.getClass() + " - Did you set a field value to the incorrect type? Expected " + IBase.class.getName();
594                                                        throw new ClassCastException(s);
595                                                }
596                                                if (nextValue == null) {
597                                                        continue;
598                                                }
599                                                if (nextValue.isEmpty()) {
600                                                        continue;
601                                                }
602                                                BaseRuntimeElementDefinition<?> childElementDef;
603                                                childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass());
604
605                                                if (childElementDef == null) {
606                                                        childElementDef = myContext.getElementDefinition(nextValue.getClass());
607                                                }
608
609                                                if (nextChild instanceof RuntimeChildDirectResource) {
610                                                        // Don't descend into embedded resources
611                                                        theCallback.acceptElement(theResource, nextValue, null, nextChild, childElementDef);
612                                                } else {
613                                                        visit(theStack, theResource, nextValue, pathToElement, nextChild, childElementDef, theCallback);
614                                                }
615                                        }
616                                }
617                        }
618                        break;
619                }
620                case CONTAINED_RESOURCES: {
621                        BaseContainedDt value = (BaseContainedDt) theElement;
622                        for (IResource next : value.getContainedResources()) {
623                                def = myContext.getResourceDefinition(next);
624                                visit(theStack, next, next, pathToElement, null, def, theCallback);
625                        }
626                        break;
627                }
628                case CONTAINED_RESOURCE_LIST:
629                case EXTENSION_DECLARED:
630                case UNDECL_EXT: {
631                        throw new IllegalStateException("state should not happen: " + def.getChildType());
632                }
633                }
634                
635                theStack.remove(theElement);
636                
637        }
638
639}