001package ca.uhn.fhir.util;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2016 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 java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collections;
026import java.util.IdentityHashMap;
027import java.util.Iterator;
028import java.util.List;
029import java.util.TreeSet;
030
031import org.apache.commons.lang3.Validate;
032import org.hl7.fhir.instance.model.api.IBase;
033import org.hl7.fhir.instance.model.api.IBaseExtension;
034import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
035import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
036import org.hl7.fhir.instance.model.api.IBaseReference;
037import org.hl7.fhir.instance.model.api.IBaseResource;
038import org.hl7.fhir.instance.model.api.IPrimitiveType;
039
040import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
041import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
042import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
043import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
044import ca.uhn.fhir.context.ConfigurationException;
045import ca.uhn.fhir.context.FhirContext;
046import ca.uhn.fhir.context.RuntimeChildChoiceDefinition;
047import ca.uhn.fhir.context.RuntimeChildDirectResource;
048import ca.uhn.fhir.context.RuntimeResourceDefinition;
049import ca.uhn.fhir.model.api.ExtensionDt;
050import ca.uhn.fhir.model.api.IResource;
051import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
052import ca.uhn.fhir.model.base.composite.BaseContainedDt;
053import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
054import ca.uhn.fhir.model.primitive.StringDt;
055import ca.uhn.fhir.parser.DataFormatException;
056
057public class FhirTerser {
058
059        private FhirContext myContext;
060
061        public FhirTerser(FhirContext theContext) {
062                super();
063                myContext = theContext;
064        }
065
066        private void addUndeclaredExtensions(IBase theElement, BaseRuntimeElementDefinition<?> theDefinition, BaseRuntimeChildDefinition theChildDefinition, IModelVisitor theCallback) {
067                if (theElement instanceof ISupportsUndeclaredExtensions) {
068                        ISupportsUndeclaredExtensions containingElement = (ISupportsUndeclaredExtensions) theElement;
069                        for (ExtensionDt nextExt : containingElement.getUndeclaredExtensions()) {
070                                theCallback.acceptUndeclaredExtension(containingElement, null, theChildDefinition, theDefinition, nextExt);
071                                addUndeclaredExtensions(nextExt, theDefinition, theChildDefinition, theCallback);
072                        }
073                }
074                
075                if (theElement instanceof IBaseHasExtensions) {
076                        for (IBaseExtension<?, ?> nextExt : ((IBaseHasExtensions)theElement).getExtension()) {
077                                theCallback.acceptElement(nextExt.getValue(), null, theChildDefinition, theDefinition);
078                                addUndeclaredExtensions(nextExt, theDefinition, theChildDefinition, theCallback);
079                        }
080                }
081                
082                if (theElement instanceof IBaseHasModifierExtensions) {
083                        for (IBaseExtension<?, ?> nextExt : ((IBaseHasModifierExtensions)theElement).getModifierExtension()) {
084                                theCallback.acceptElement(nextExt.getValue(), null, theChildDefinition, theDefinition);
085                                addUndeclaredExtensions(nextExt, theDefinition, theChildDefinition, theCallback);
086                        }
087                }
088
089        }
090
091        /**
092         * 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.
093         * <p>
094         * 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
095         * well as any contained resources.
096         * </p>
097         * <p>
098         * 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.
099         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
100         * </p>
101         * 
102         * @param theResource
103         *           The resource instance to search. Must not be null.
104         * @param theType
105         *           The type to search for. Must not be null.
106         * @return Returns a list of all matching elements
107         */
108        public <T extends IBase> List<T> getAllPopulatedChildElementsOfType(IBaseResource theResource, final Class<T> theType) {
109                final ArrayList<T> retVal = new ArrayList<T>();
110                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
111                visit(new IdentityHashMap<Object, Object>(), theResource, null, null, def, new IModelVisitor() {
112                        @SuppressWarnings("unchecked")
113                        @Override
114                        public void acceptElement(IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) {
115                                if (theElement == null || theElement.isEmpty()) {
116                                        return;
117                                }
118
119                                if (theType.isAssignableFrom(theElement.getClass())) {
120                                        retVal.add((T) theElement);
121                                }
122                        }
123
124                        @SuppressWarnings("unchecked")
125                        @Override
126                        public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition,
127                                        BaseRuntimeElementDefinition<?> theDefinition, ExtensionDt theNextExt) {
128                                if (theType.isAssignableFrom(theNextExt.getClass())) {
129                                        retVal.add((T) theNextExt);
130                                }
131                                if (theNextExt.getValue() != null && theType.isAssignableFrom(theNextExt.getValue().getClass())) {
132                                        retVal.add((T) theNextExt.getValue());
133                                }
134                        }
135                });
136                return retVal;
137        }
138
139        public List<ResourceReferenceInfo> getAllResourceReferences(final IBaseResource theResource) {
140                final ArrayList<ResourceReferenceInfo> retVal = new ArrayList<ResourceReferenceInfo>();
141                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
142                visit(new IdentityHashMap<Object, Object>(),theResource, null, null, def, new IModelVisitor() {
143                        @Override
144                        public void acceptElement(IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) {
145                                if (theElement == null || theElement.isEmpty()) {
146                                        return;
147                                }
148                                if (IBaseReference.class.isAssignableFrom(theElement.getClass())) {
149                                        retVal.add(new ResourceReferenceInfo(myContext, theResource, thePathToElement, (IBaseReference) theElement));
150                                }
151                        }
152
153                        @Override
154                        public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition,
155                                        BaseRuntimeElementDefinition<?> theDefinition, ExtensionDt theNextExt) {
156                                if (theNextExt.getValue() != null && BaseResourceReferenceDt.class.isAssignableFrom(theNextExt.getValue().getClass())) {
157                                        retVal.add(new ResourceReferenceInfo(myContext, theResource, thePathToElement, (BaseResourceReferenceDt) theNextExt.getValue()));
158                                }
159                        }
160                });
161                return retVal;
162        }
163
164        private BaseRuntimeChildDefinition getDefinition(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, List<String> theSubList) {
165                BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(theSubList.get(0));
166
167                if (theSubList.size() == 1) {
168                        return nextDef;
169                } else {
170                        BaseRuntimeElementCompositeDefinition<?> cmp = (BaseRuntimeElementCompositeDefinition<?>) nextDef.getChildByName(theSubList.get(0));
171                        return getDefinition(cmp, theSubList.subList(1, theSubList.size()));
172                }
173        }
174
175        public BaseRuntimeChildDefinition getDefinition(Class<? extends IBaseResource> theResourceType, String thePath) {
176                RuntimeResourceDefinition def = myContext.getResourceDefinition(theResourceType);
177
178                BaseRuntimeElementCompositeDefinition<?> currentDef = def;
179
180                List<String> parts = Arrays.asList(thePath.split("\\."));
181                List<String> subList = parts.subList(1, parts.size());
182                if (subList.size() < 1) {
183                        throw new ConfigurationException("Invalid path: " + thePath);
184                }
185                return getDefinition(currentDef, subList);
186
187        }
188
189        @SuppressWarnings("unchecked")
190        private <T> List<T> getValues(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, Object theCurrentObj, List<String> theSubList, Class<T> theWantedClass) {
191                String name = theSubList.get(0);
192                                
193                BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(name);
194                List<? extends IBase> values = nextDef.getAccessor().getValues(theCurrentObj);
195                List<T> retVal = new ArrayList<T>();
196
197                if (theSubList.size() == 1) {
198                        if (nextDef instanceof RuntimeChildChoiceDefinition) {
199                                for (IBase next : values) {
200                                        if (next != null) {
201                                                if (name.endsWith("[x]")) {
202                                                        if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) {
203                                                                retVal.add((T) next);
204                                                        }
205                                                } else {
206                                                        String childName = nextDef.getChildNameByDatatype(next.getClass());
207                                                        if (theSubList.get(0).equals(childName)) {
208                                                                if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) {
209                                                                        retVal.add((T) next);
210                                                                }
211                                                        }
212                                                }
213                                        }
214                                }
215                        } else {
216                                for (IBase next : values) {
217                                        if (next != null) {
218                                                if (theWantedClass == null || theWantedClass.isAssignableFrom(next.getClass())) {
219                                                        retVal.add((T) next);
220                                                }
221                                        }
222                                }
223                        }
224                } else {
225                        for (IBase nextElement : values) {
226                                BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(nextElement.getClass());
227                                List<T> foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()), theWantedClass);
228                                retVal.addAll(foundValues);
229                        }
230                }
231                return retVal;
232        }
233
234        public List<Object> getValues(IBaseResource theResource, String thePath) {
235                Class<Object> wantedClass = Object.class;
236
237                return getValues(theResource, thePath, wantedClass);
238
239        }
240
241        public <T> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass) {
242                RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
243
244                BaseRuntimeElementCompositeDefinition<?> currentDef = def;
245                Object currentObj = theResource;
246
247                List<String> parts = Arrays.asList(thePath.split("\\."));
248                List<String> subList = parts.subList(1, parts.size());
249                if (subList.size() < 1) {
250                        throw new ConfigurationException("Invalid path: " + thePath);
251                }
252                return getValues(currentDef, currentObj, subList, theWantedClass);
253        }
254
255        private List<String> addNameToList(List<String> theCurrentList, BaseRuntimeChildDefinition theChildDefinition) {
256                if (theChildDefinition == null)
257                        return null;
258                if (theCurrentList == null || theCurrentList.isEmpty())
259                        return new ArrayList<String>(Arrays.asList(theChildDefinition.getElementName()));
260                List<String> newList = new ArrayList<String>(theCurrentList);
261                newList.add(theChildDefinition.getElementName());
262                return newList;
263        }
264
265        private void visit(IdentityHashMap<Object, Object> theStack, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition,
266                        BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor theCallback) {
267                List<String> pathToElement = addNameToList(thePathToElement, theChildDefinition);
268
269                if (theStack.put(theElement, theElement) != null) {
270                        return;
271                }
272                
273                theCallback.acceptElement(theElement, pathToElement, theChildDefinition, theDefinition);
274                addUndeclaredExtensions(theElement, theDefinition, theChildDefinition, theCallback);
275
276                BaseRuntimeElementDefinition<?> def = theDefinition;
277                if (def.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) {
278                        def = myContext.getElementDefinition(theElement.getClass());
279                }
280                
281                switch (def.getChildType()) {
282                case ID_DATATYPE:
283                case PRIMITIVE_XHTML_HL7ORG:
284                case PRIMITIVE_XHTML:
285                case PRIMITIVE_DATATYPE:
286                        // These are primitive types
287                        break;
288                case RESOURCE_REF:
289                        IBaseReference resRefDt = (IBaseReference) theElement;
290                        if (resRefDt.getReferenceElement().getValue() == null && resRefDt.getResource() != null) {
291                                IBaseResource theResource = resRefDt.getResource();
292                                if (theResource.getIdElement() == null || theResource.getIdElement().isEmpty() || theResource.getIdElement().isLocal()) {
293                                        def = myContext.getResourceDefinition(theResource);
294                                        visit(theStack, theResource, pathToElement, null, def, theCallback);
295                                }
296                        }
297                        break;
298                case RESOURCE:
299                case RESOURCE_BLOCK:
300                case COMPOSITE_DATATYPE: {
301                        BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) def;
302                        for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
303                                List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
304                                if (values != null) {
305                                        for (IBase nextValue : values) {
306                                                if (nextValue == null) {
307                                                        continue;
308                                                }
309                                                if (nextValue.isEmpty()) {
310                                                        continue;
311                                                }
312                                                BaseRuntimeElementDefinition<?> childElementDef;
313                                                childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass());
314
315                                                if (childElementDef == null) {
316                                                        childElementDef = myContext.getElementDefinition(nextValue.getClass());
317                                                }
318
319                                                if (nextChild instanceof RuntimeChildDirectResource) {
320                                                        // Don't descend into embedded resources
321                                                        theCallback.acceptElement(nextValue, null, nextChild, childElementDef);
322                                                } else {
323                                                        visit(theStack, nextValue, pathToElement, nextChild, childElementDef, theCallback);
324                                                }
325                                        }
326                                }
327                        }
328                        break;
329                }
330                case CONTAINED_RESOURCES: {
331                        BaseContainedDt value = (BaseContainedDt) theElement;
332                        for (IResource next : value.getContainedResources()) {
333                                def = myContext.getResourceDefinition(next);
334                                visit(theStack, next, pathToElement, null, def, theCallback);
335                        }
336                        break;
337                }
338                case CONTAINED_RESOURCE_LIST:
339                case EXTENSION_DECLARED:
340                case UNDECL_EXT: {
341                        throw new IllegalStateException("state should not happen: " + def.getChildType());
342                }
343                }
344                
345                theStack.remove(theElement);
346                
347        }
348
349        private void visit(IBase theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor2 theCallback, List<IBase> theContainingElementPath,
350                        List<BaseRuntimeChildDefinition> theChildDefinitionPath, List<BaseRuntimeElementDefinition<?>> theElementDefinitionPath) {
351                if (theChildDefinition != null) {
352                        theChildDefinitionPath.add(theChildDefinition);
353                }
354                theContainingElementPath.add(theElement);
355                theElementDefinitionPath.add(theDefinition);
356
357                theCallback.acceptElement(theElement, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath),
358                                Collections.unmodifiableList(theElementDefinitionPath));
359
360                /*
361                 * Visit undeclared extensions
362                 */
363                if (theElement instanceof ISupportsUndeclaredExtensions) {
364                        ISupportsUndeclaredExtensions containingElement = (ISupportsUndeclaredExtensions) theElement;
365                        for (ExtensionDt nextExt : containingElement.getUndeclaredExtensions()) {
366                                theContainingElementPath.add(nextExt);
367                                theCallback.acceptUndeclaredExtension(nextExt, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
368                                theContainingElementPath.remove(theContainingElementPath.size() - 1);
369                        }
370                }
371
372                /*
373                 * Now visit the children of the given element
374                 */
375                switch (theDefinition.getChildType()) {
376                case ID_DATATYPE:
377                case PRIMITIVE_XHTML_HL7ORG:
378                case PRIMITIVE_XHTML:
379                case PRIMITIVE_DATATYPE:
380                        // These are primitive types, so we don't need to visit their children
381                        break;
382                case RESOURCE_REF:
383                        IBaseReference resRefDt = (IBaseReference) theElement;
384                        if (resRefDt.getReferenceElement().getValue() == null && resRefDt.getResource() != null) {
385                                IBaseResource theResource = resRefDt.getResource();
386                                if (theResource.getIdElement() == null || theResource.getIdElement().isEmpty() || theResource.getIdElement().isLocal()) {
387                                        BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
388                                        visit(theResource, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
389                                }
390                        }
391                        break;
392                case RESOURCE:
393                case RESOURCE_BLOCK:
394                case COMPOSITE_DATATYPE: {
395                        BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) theDefinition;
396                        for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
397                                List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
398                                if (values != null) {
399                                        for (IBase nextValue : values) {
400                                                if (nextValue == null) {
401                                                        continue;
402                                                }
403                                                if (nextValue.isEmpty()) {
404                                                        continue;
405                                                }
406                                                BaseRuntimeElementDefinition<?> childElementDef;
407                                                childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass());
408
409                                                if (childElementDef == null) {
410                                                        StringBuilder b = new StringBuilder();
411                                                        b.append("Found value of type[");
412                                                        b.append(nextValue.getClass().getSimpleName());
413                                                        b.append("] which is not valid for field[");
414                                                        b.append(nextChild.getElementName());
415                                                        b.append("] in ");
416                                                        b.append(childDef.getName());
417                                                        b.append(" - Valid types: ");
418                                                        for (Iterator<String> iter = new TreeSet<String>(nextChild.getValidChildNames()).iterator(); iter.hasNext();) {
419                                                                BaseRuntimeElementDefinition<?> childByName = nextChild.getChildByName(iter.next());
420                                                                b.append(childByName.getImplementingClass().getSimpleName());
421                                                                if (iter.hasNext()) {
422                                                                        b.append(", ");
423                                                                }
424                                                        }
425                                                        throw new DataFormatException(b.toString());
426                                                }
427
428                                                if (nextChild instanceof RuntimeChildDirectResource) {
429                                                        // Don't descend into embedded resources
430                                                        theContainingElementPath.add(nextValue);
431                                                        theChildDefinitionPath.add(nextChild);
432                                                        theElementDefinitionPath.add(myContext.getElementDefinition(nextValue.getClass()));
433                                                        theCallback.acceptElement(nextValue, Collections.unmodifiableList(theContainingElementPath), Collections.unmodifiableList(theChildDefinitionPath),
434                                                                        Collections.unmodifiableList(theElementDefinitionPath));
435                                                        theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1);
436                                                        theContainingElementPath.remove(theContainingElementPath.size() - 1);
437                                                        theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1);
438                                                } else {
439                                                        visit(nextValue, nextChild, childElementDef, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
440                                                }
441                                        }
442                                }
443                        }
444                        break;
445                }
446                case CONTAINED_RESOURCES: {
447                        BaseContainedDt value = (BaseContainedDt) theElement;
448                        for (IResource next : value.getContainedResources()) {
449                                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(next);
450                                visit(next, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
451                        }
452                        break;
453                }
454                case EXTENSION_DECLARED:
455                case UNDECL_EXT: {
456                        throw new IllegalStateException("state should not happen: " + theDefinition.getChildType());
457                }
458                case CONTAINED_RESOURCE_LIST: {
459                        if (theElement != null) {
460                                BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass());
461                                visit(theElement, null, def, theCallback, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath);
462                        }
463                        break;
464                }
465                }
466
467                if (theChildDefinition != null) {
468                        theChildDefinitionPath.remove(theChildDefinitionPath.size() - 1);
469                }
470                theContainingElementPath.remove(theContainingElementPath.size() - 1);
471                theElementDefinitionPath.remove(theElementDefinitionPath.size() - 1);
472        }
473
474        /**
475         * Visit all elements in a given resource
476         * 
477         * <p>
478         * 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.
479         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
480         * </p>
481         * 
482         * @param theResource
483         *           The resource to visit
484         * @param theVisitor
485         *           The visitor
486         */
487        public void visit(IBaseResource theResource, IModelVisitor theVisitor) {
488                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
489                visit(new IdentityHashMap<Object, Object>(), theResource, null, null, def, theVisitor);
490        }
491
492        /**
493         * Visit all elements in a given resource
494         * 
495         * THIS ALTERNATE METHOD IS STILL EXPERIMENTAL
496         * 
497         * <p>
498         * 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.
499         * {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
500         * </p>
501         * 
502         * @param theResource
503         *           The resource to visit
504         * @param theVisitor
505         *           The visitor
506         */
507        void visit(IBaseResource theResource, IModelVisitor2 theVisitor) {
508                BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
509                visit(theResource, null, def, theVisitor, new ArrayList<IBase>(), new ArrayList<BaseRuntimeChildDefinition>(), new ArrayList<BaseRuntimeElementDefinition<?>>());
510        }
511
512        public Object getSingleValueOrNull(IBase theTarget, String thePath) {
513                Class<Object> wantedType = Object.class;
514
515                return getSingleValueOrNull(theTarget, thePath, wantedType);
516        }
517
518        public <T> T getSingleValueOrNull(IBase theTarget, String thePath, Class<T> theWantedType) {
519                Validate.notNull(theTarget, "theTarget must not be null");
520                Validate.notBlank(thePath, "thePath must not be empty");
521
522                BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theTarget.getClass());
523                if (!(def instanceof BaseRuntimeElementCompositeDefinition)) {
524                        throw new IllegalArgumentException("Target is not a composite type: " + theTarget.getClass().getName());
525                }
526
527                BaseRuntimeElementCompositeDefinition<?> currentDef = (BaseRuntimeElementCompositeDefinition<?>) def;
528                Object currentObj = theTarget;
529
530                List<String> parts = Arrays.asList(thePath.split("\\."));
531                List<T> retVal = getValues(currentDef, currentObj, parts, theWantedType);
532                if (retVal.isEmpty()) {
533                        return null;
534                } else {
535                        return retVal.get(0);
536                }
537        }
538
539        /**
540         * Clones all values from a source object into the equivalent fields in a target object
541         * @param theSource The source object (must not be null)
542         * @param theTarget The target object to copy values into (must not be null)
543         * @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)
544         */
545        public void cloneInto(IBase theSource, IBase theTarget, boolean theIgnoreMissingFields) {
546                Validate.notNull(theSource, "theSource must not be null");
547                Validate.notNull(theTarget, "theTarget must not be null");
548                
549                if (theSource instanceof IPrimitiveType<?>) {
550                        if (theTarget instanceof IPrimitiveType<?>) {
551                                ((IPrimitiveType<?>)theTarget).setValueAsString(((IPrimitiveType<?>)theSource).getValueAsString());
552                                return;
553                        } else {
554                                if (theIgnoreMissingFields) {
555                                        return;
556                                } else {
557                                        throw new DataFormatException("Can not copy value from primitive of type " + theSource.getClass().getName() + " into type " + theTarget.getClass().getName());
558                                }
559                        }
560                }
561                
562                BaseRuntimeElementCompositeDefinition<?> sourceDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theSource.getClass()); 
563                BaseRuntimeElementCompositeDefinition<?> targetDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theTarget.getClass());
564                
565                for (BaseRuntimeChildDefinition nextChild : sourceDef.getChildren()) {
566                        for (IBase nextValue : nextChild.getAccessor().getValues(theSource)) {
567                                BaseRuntimeChildDefinition targetChild = targetDef.getChildByName(nextChild.getElementName());
568                                if (targetChild == null) {
569                                        if (theIgnoreMissingFields) {
570                                                continue;
571                                        } else {
572                                                throw new DataFormatException("Type " + theTarget.getClass().getName() + " does not have a child with name " + nextChild.getElementName());
573                                        }
574                                }
575                                
576                                IBase target = targetChild.getChildByName(nextChild.getElementName()).newInstance();
577                                targetChild.getMutator().addValue(theTarget, target);
578                                cloneInto(nextValue, target, theIgnoreMissingFields);
579                        }
580                }
581                
582        }
583
584}