001/*-
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.util;
021
022import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
023import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
024import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
025import ca.uhn.fhir.context.FhirContext;
026import ca.uhn.fhir.context.RuntimeChildChoiceDefinition;
027import ca.uhn.fhir.context.RuntimeResourceDefinition;
028import ca.uhn.fhir.i18n.Msg;
029import org.apache.commons.lang3.Validate;
030import org.apache.commons.lang3.tuple.Triple;
031import org.hl7.fhir.instance.model.api.IBase;
032import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
033import org.hl7.fhir.instance.model.api.IBaseResource;
034import org.hl7.fhir.instance.model.api.IPrimitiveType;
035import org.slf4j.Logger;
036
037import java.lang.reflect.Method;
038import java.util.Arrays;
039import java.util.Collection;
040import java.util.Collections;
041import java.util.List;
042import java.util.function.Predicate;
043import java.util.stream.Collectors;
044import java.util.stream.Stream;
045
046import static org.slf4j.LoggerFactory.getLogger;
047
048public final class TerserUtil {
049
050        public static final String FIELD_NAME_IDENTIFIER = "identifier";
051        /**
052         * Exclude for id, identifier and meta fields of a resource.
053         */
054        public static final Collection<String> IDS_AND_META_EXCLUDES =
055                        Collections.unmodifiableSet(Stream.of("id", "identifier", "meta").collect(Collectors.toSet()));
056        /**
057         * Exclusion predicate for id, identifier, meta fields.
058         */
059        public static final Predicate<String> EXCLUDE_IDS_AND_META = new Predicate<String>() {
060                @Override
061                public boolean test(String s) {
062                        return !IDS_AND_META_EXCLUDES.contains(s);
063                }
064        };
065        /**
066         * Exclusion predicate for id/identifier, meta and fields with empty values. This ensures that source / target resources,
067         * empty source fields will not results in erasure of target fields.
068         */
069        public static final Predicate<Triple<BaseRuntimeChildDefinition, IBase, IBase>> EXCLUDE_IDS_META_AND_EMPTY =
070                        new Predicate<Triple<BaseRuntimeChildDefinition, IBase, IBase>>() {
071                                @Override
072                                public boolean test(Triple<BaseRuntimeChildDefinition, IBase, IBase> theTriple) {
073                                        if (!EXCLUDE_IDS_AND_META.test(theTriple.getLeft().getElementName())) {
074                                                return false;
075                                        }
076                                        BaseRuntimeChildDefinition childDefinition = theTriple.getLeft();
077                                        boolean isSourceFieldEmpty = childDefinition
078                                                        .getAccessor()
079                                                        .getValues(theTriple.getMiddle())
080                                                        .isEmpty();
081                                        return !isSourceFieldEmpty;
082                                }
083                        };
084        /**
085         * Exclusion predicate for keeping all fields.
086         */
087        public static final Predicate<String> INCLUDE_ALL = new Predicate<String>() {
088                @Override
089                public boolean test(String s) {
090                        return true;
091                }
092        };
093
094        private static final Logger ourLog = getLogger(TerserUtil.class);
095        private static final String EQUALS_DEEP = "equalsDeep";
096
097        private TerserUtil() {}
098
099        /**
100         * Given an Child Definition of `identifier`, a R4/DSTU3 Identifier, and a new resource, clone the identifier into that resources' identifier list if it is not already present.
101         */
102        public static void cloneIdentifierIntoResource(
103                        FhirContext theFhirContext,
104                        BaseRuntimeChildDefinition theIdentifierDefinition,
105                        IBase theNewIdentifier,
106                        IBaseResource theResourceToCloneInto) {
107                // FHIR choice types - fields within fhir where we have a choice of ids
108                BaseRuntimeElementCompositeDefinition<?> childIdentifierElementDefinition =
109                                (BaseRuntimeElementCompositeDefinition<?>)
110                                                theIdentifierDefinition.getChildByName(FIELD_NAME_IDENTIFIER);
111
112                List<IBase> existingIdentifiers = getValues(theFhirContext, theResourceToCloneInto, FIELD_NAME_IDENTIFIER);
113                if (existingIdentifiers != null) {
114                        for (IBase existingIdentifier : existingIdentifiers) {
115                                if (equals(existingIdentifier, theNewIdentifier)) {
116                                        ourLog.trace(
117                                                        "Identifier {} already exists in resource {}", theNewIdentifier, theResourceToCloneInto);
118                                        return;
119                                }
120                        }
121                }
122
123                IBase newIdentifierBase = childIdentifierElementDefinition.newInstance();
124
125                FhirTerser terser = theFhirContext.newTerser();
126                terser.cloneInto(theNewIdentifier, newIdentifierBase, true);
127                theIdentifierDefinition.getMutator().addValue(theResourceToCloneInto, newIdentifierBase);
128        }
129
130        /**
131         * Checks if the specified fields has any values
132         *
133         * @param theFhirContext Context holding resource definition
134         * @param theResource    Resource to check if the specified field is set
135         * @param theFieldName   name of the field to check
136         * @return Returns true if field exists and has any values set, and false otherwise
137         */
138        public static boolean hasValues(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) {
139                RuntimeResourceDefinition resourceDefinition = theFhirContext.getResourceDefinition(theResource);
140                BaseRuntimeChildDefinition resourceIdentifier = resourceDefinition.getChildByName(theFieldName);
141                if (resourceIdentifier == null) {
142                        return false;
143                }
144                return !(resourceIdentifier.getAccessor().getValues(theResource).isEmpty());
145        }
146
147        /**
148         * Gets all values of the specified field.
149         *
150         * @param theFhirContext Context holding resource definition
151         * @param theResource    Resource to check if the specified field is set
152         * @param theFieldName   name of the field to check
153         * @return Returns all values for the specified field or null if field with the provided name doesn't exist
154         */
155        public static List<IBase> getValues(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) {
156                RuntimeResourceDefinition resourceDefinition = theFhirContext.getResourceDefinition(theResource);
157                BaseRuntimeChildDefinition resourceIdentifier = resourceDefinition.getChildByName(theFieldName);
158                if (resourceIdentifier == null) {
159                        ourLog.info("There is no field named {} in Resource {}", theFieldName, resourceDefinition.getName());
160                        return null;
161                }
162                return resourceIdentifier.getAccessor().getValues(theResource);
163        }
164
165        /**
166         * Gets the first available value for the specified field.
167         *
168         * @param theFhirContext Context holding resource definition
169         * @param theResource    Resource to check if the specified field is set
170         * @param theFieldName   name of the field to check
171         * @return Returns the first value for the specified field or null if field with the provided name doesn't exist or
172         * has no values
173         */
174        public static IBase getValueFirstRep(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) {
175                List<IBase> values = getValues(theFhirContext, theResource, theFieldName);
176                if (values == null || values.isEmpty()) {
177                        return null;
178                }
179                return values.get(0);
180        }
181
182        /**
183         * Clones specified composite field (collection). Composite field values must conform to the collections
184         * contract.
185         *
186         * @param theFrom  Resource to clone the specified field from
187         * @param theTo    Resource to clone the specified field to
188         * @param theField Field name to be copied
189         */
190        public static void cloneCompositeField(
191                        FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo, String theField) {
192                FhirTerser terser = theFhirContext.newTerser();
193
194                RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom);
195                BaseRuntimeChildDefinition childDefinition = definition.getChildByName(theField);
196                Validate.notNull(childDefinition);
197
198                List<IBase> theFromFieldValues = childDefinition.getAccessor().getValues(theFrom);
199                List<IBase> theToFieldValues = childDefinition.getAccessor().getValues(theTo);
200
201                for (IBase theFromFieldValue : theFromFieldValues) {
202                        if (containsPrimitiveValue(theFromFieldValue, theToFieldValues)) {
203                                continue;
204                        }
205
206                        IBase newFieldValue = newElement(terser, childDefinition, theFromFieldValue, null);
207                        terser.cloneInto(theFromFieldValue, newFieldValue, true);
208
209                        try {
210                                theToFieldValues.add(newFieldValue);
211                        } catch (Exception e) {
212                                childDefinition.getMutator().setValue(theTo, newFieldValue);
213                        }
214                }
215        }
216
217        private static boolean containsPrimitiveValue(IBase theItem, List<IBase> theItems) {
218                PrimitiveTypeEqualsPredicate predicate = new PrimitiveTypeEqualsPredicate();
219                return theItems.stream().anyMatch(i -> {
220                        return predicate.test(i, theItem);
221                });
222        }
223
224        private static Method getMethod(IBase theBase, String theMethodName) {
225                Method method = null;
226                for (Method m : theBase.getClass().getDeclaredMethods()) {
227                        if (m.getName().equals(theMethodName)) {
228                                method = m;
229                                break;
230                        }
231                }
232                return method;
233        }
234
235        /**
236         * Checks if two items are equal via {@link #EQUALS_DEEP} method
237         *
238         * @param theItem1 First item to compare
239         * @param theItem2 Second item to compare
240         * @return Returns true if they are equal and false otherwise
241         */
242        public static boolean equals(IBase theItem1, IBase theItem2) {
243                if (theItem1 == null) {
244                        return theItem2 == null;
245                }
246
247                final Method method = getMethod(theItem1, EQUALS_DEEP);
248                Validate.notNull(method);
249                return equals(theItem1, theItem2, method);
250        }
251
252        private static boolean equals(IBase theItem1, IBase theItem2, Method theMethod) {
253                if (theMethod != null) {
254                        try {
255                                return (Boolean) theMethod.invoke(theItem1, theItem2);
256                        } catch (Exception e) {
257                                throw new RuntimeException(
258                                                Msg.code(1746) + String.format("Unable to compare equality via %s", EQUALS_DEEP), e);
259                        }
260                }
261                return theItem1.equals(theItem2);
262        }
263
264        private static boolean contains(IBase theItem, List<IBase> theItems) {
265                final Method method = getMethod(theItem, EQUALS_DEEP);
266                return theItems.stream().anyMatch(i -> equals(i, theItem, method));
267        }
268
269        /**
270         * Merges all fields on the provided instance. <code>theTo</code> will contain a union of all values from <code>theFrom</code>
271         * instance and <code>theTo</code> instance.
272         *
273         * @param theFhirContext Context holding resource definition
274         * @param theFrom        The resource to merge the fields from
275         * @param theTo          The resource to merge the fields into
276         */
277        public static void mergeAllFields(FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo) {
278                mergeFields(theFhirContext, theFrom, theTo, INCLUDE_ALL);
279        }
280
281        /**
282         * Replaces all fields that have matching field names by the given inclusion strategy. <code>theTo</code> will contain a copy of the
283         * values from <code>theFrom</code> instance.
284         *
285         * @param theFhirContext        Context holding resource definition
286         * @param theFrom               The resource to merge the fields from
287         * @param theTo                 The resource to merge the fields into
288         * @param theFieldNameInclusion Inclusion strategy that checks if a given field should be replaced
289         */
290        public static void replaceFields(
291                        FhirContext theFhirContext,
292                        IBaseResource theFrom,
293                        IBaseResource theTo,
294                        Predicate<String> theFieldNameInclusion) {
295                Predicate<Triple<BaseRuntimeChildDefinition, IBase, IBase>> predicate =
296                                (t) -> theFieldNameInclusion.test(t.getLeft().getElementName());
297                replaceFieldsByPredicate(theFhirContext, theFrom, theTo, predicate);
298        }
299
300        /**
301         * Replaces fields on theTo resource that test positive by the given predicate. <code>theTo</code> will contain a copy of the
302         * values from <code>theFrom</code> for which predicate tests positive. Please note that composite fields will be replaced fully.
303         *
304         * @param theFhirContext Context holding resource definition
305         * @param theFrom        The resource to merge the fields from
306         * @param theTo          The resource to merge the fields into
307         * @param thePredicate   Predicate that checks if a given field should be replaced
308         */
309        public static void replaceFieldsByPredicate(
310                        FhirContext theFhirContext,
311                        IBaseResource theFrom,
312                        IBaseResource theTo,
313                        Predicate<Triple<BaseRuntimeChildDefinition, IBase, IBase>> thePredicate) {
314                RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom);
315                FhirTerser terser = theFhirContext.newTerser();
316                for (BaseRuntimeChildDefinition childDefinition : definition.getChildrenAndExtension()) {
317                        if (thePredicate.test(Triple.of(childDefinition, theFrom, theTo))) {
318                                replaceField(terser, theFrom, theTo, childDefinition);
319                        }
320                }
321        }
322
323        /**
324         * Checks if the field exists on the resource
325         *
326         * @param theFhirContext Context holding resource definition
327         * @param theFieldName   Name of the field to check
328         * @param theInstance    Resource instance to check
329         * @return Returns true if resource definition has a child with the specified name and false otherwise
330         */
331        public static boolean fieldExists(FhirContext theFhirContext, String theFieldName, IBaseResource theInstance) {
332                RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theInstance);
333                return definition.getChildByName(theFieldName) != null;
334        }
335
336        /**
337         * Replaces the specified fields on <code>theTo</code> resource with the value from <code>theFrom</code> resource.
338         *
339         * @param theFhirContext Context holding resource definition
340         * @param theFrom        The resource to replace the field from
341         * @param theTo          The resource to replace the field on
342         */
343        public static void replaceField(
344                        FhirContext theFhirContext, String theFieldName, IBaseResource theFrom, IBaseResource theTo) {
345                RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom);
346                Validate.notNull(definition);
347                replaceField(
348                                theFhirContext.newTerser(),
349                                theFrom,
350                                theTo,
351                                theFhirContext.getResourceDefinition(theFrom).getChildByName(theFieldName));
352        }
353
354        /**
355         * Clears the specified field on the resource provided
356         *
357         * @param theFhirContext Context holding resource definition
358         * @param theResource
359         * @param theFieldName
360         */
361        public static void clearField(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) {
362                BaseRuntimeChildDefinition childDefinition =
363                                getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theResource);
364                childDefinition.getMutator().setValue(theResource, null);
365        }
366
367        /**
368         * Clears the specified field on the resource provided by the FHIRPath.  If more than one value matches
369         * the FHIRPath, all values will be cleared.
370         *
371         * @param theFhirContext
372         * @param theResource
373         * @param theFhirPath
374         */
375        public static void clearFieldByFhirPath(FhirContext theFhirContext, IBaseResource theResource, String theFhirPath) {
376
377                if (theFhirPath.contains(".")) {
378                        String parentPath = theFhirPath.substring(0, theFhirPath.lastIndexOf("."));
379                        String fieldName = theFhirPath.substring(theFhirPath.lastIndexOf(".") + 1);
380                        FhirTerser terser = theFhirContext.newTerser();
381                        List<IBase> parents = terser.getValues(theResource, parentPath);
382                        for (IBase parent : parents) {
383                                clearField(theFhirContext, fieldName, parent);
384                        }
385                } else {
386                        clearField(theFhirContext, theResource, theFhirPath);
387                }
388        }
389
390        /**
391         * Clears the specified field on the element provided
392         *
393         * @param theFhirContext Context holding resource definition
394         * @param theFieldName   Name of the field to clear values for
395         * @param theBase        The element definition to clear values on
396         */
397        public static void clearField(FhirContext theFhirContext, String theFieldName, IBase theBase) {
398                BaseRuntimeElementDefinition definition = theFhirContext.getElementDefinition(theBase.getClass());
399                BaseRuntimeChildDefinition childDefinition = definition.getChildByName(theFieldName);
400                Validate.notNull(childDefinition);
401                BaseRuntimeChildDefinition.IAccessor accessor = childDefinition.getAccessor();
402                clear(accessor.getValues(theBase));
403                List<IBase> newValue = accessor.getValues(theBase);
404
405                if (newValue != null && !newValue.isEmpty()) {
406                        // Our clear failed, probably because it was an immutable SingletonList returned by a FieldPlainAccessor
407                        // that cannot be cleared.
408                        // Let's just null it out instead.
409                        childDefinition.getMutator().setValue(theBase, null);
410                }
411        }
412
413        /**
414         * Sets the provided field with the given values. This method will add to the collection of existing field values
415         * in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)}
416         * to remove values before setting
417         *
418         * @param theFhirContext Context holding resource definition
419         * @param theFieldName   Child field name of the resource to set
420         * @param theResource    The resource to set the values on
421         * @param theValues      The values to set on the resource child field name
422         */
423        public static void setField(
424                        FhirContext theFhirContext, String theFieldName, IBaseResource theResource, IBase... theValues) {
425                setField(theFhirContext, theFhirContext.newTerser(), theFieldName, theResource, theValues);
426        }
427
428        /**
429         * Sets the provided field with the given values. This method will add to the collection of existing field values
430         * in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)}
431         * to remove values before setting
432         *
433         * @param theFhirContext Context holding resource definition
434         * @param theTerser      Terser to be used when cloning field values
435         * @param theFieldName   Child field name of the resource to set
436         * @param theResource    The resource to set the values on
437         * @param theValues      The values to set on the resource child field name
438         */
439        public static void setField(
440                        FhirContext theFhirContext,
441                        FhirTerser theTerser,
442                        String theFieldName,
443                        IBaseResource theResource,
444                        IBase... theValues) {
445                BaseRuntimeChildDefinition childDefinition =
446                                getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theResource);
447                List<IBase> theFromFieldValues = childDefinition.getAccessor().getValues(theResource);
448                if (theFromFieldValues.isEmpty()) {
449                        for (IBase value : theValues) {
450                                try {
451                                        childDefinition.getMutator().addValue(theResource, value);
452                                } catch (UnsupportedOperationException e) {
453                                        ourLog.warn(
454                                                        "Resource {} does not support multiple values, but an attempt to set {} was made. Setting the first item only",
455                                                        theResource,
456                                                        theValues);
457                                        childDefinition.getMutator().setValue(theResource, value);
458                                        break;
459                                }
460                        }
461                        return;
462                }
463                List<IBase> theToFieldValues = Arrays.asList(theValues);
464                mergeFields(theTerser, theResource, childDefinition, theFromFieldValues, theToFieldValues);
465        }
466
467        /**
468         * Sets the specified value at the FHIR path provided.
469         *
470         * @param theTerser   The terser that should be used for cloning the field value.
471         * @param theFhirPath The FHIR path to set the field at
472         * @param theResource The resource on which the value should be set
473         * @param theValue    The value to set
474         */
475        public static void setFieldByFhirPath(
476                        FhirTerser theTerser, String theFhirPath, IBaseResource theResource, IBase theValue) {
477                List<IBase> theFromFieldValues = theTerser.getValues(theResource, theFhirPath, true, false);
478                for (IBase theFromFieldValue : theFromFieldValues) {
479                        theTerser.cloneInto(theValue, theFromFieldValue, true);
480                }
481        }
482
483        /**
484         * Sets the specified value at the FHIR path provided.
485         *
486         * @param theFhirContext Context holding resource definition
487         * @param theFhirPath    The FHIR path to set the field at
488         * @param theResource    The resource on which the value should be set
489         * @param theValue       The value to set
490         */
491        public static void setFieldByFhirPath(
492                        FhirContext theFhirContext, String theFhirPath, IBaseResource theResource, IBase theValue) {
493                setFieldByFhirPath(theFhirContext.newTerser(), theFhirPath, theResource, theValue);
494        }
495
496        /**
497         * Returns field values ant the specified FHIR path from the resource.
498         *
499         * @param theFhirContext Context holding resource definition
500         * @param theFhirPath    The FHIR path to get the field from
501         * @param theResource    The resource from which the value should be retrieved
502         * @return Returns the list of field values at the given FHIR path
503         */
504        public static List<IBase> getFieldByFhirPath(FhirContext theFhirContext, String theFhirPath, IBase theResource) {
505                return theFhirContext.newTerser().getValues(theResource, theFhirPath, false, false);
506        }
507
508        /**
509         * Returns the first available field value at the specified FHIR path from the resource.
510         *
511         * @param theFhirContext Context holding resource definition
512         * @param theFhirPath    The FHIR path to get the field from
513         * @param theResource    The resource from which the value should be retrieved
514         * @return Returns the first available value or null if no values can be retrieved
515         */
516        public static IBase getFirstFieldByFhirPath(FhirContext theFhirContext, String theFhirPath, IBase theResource) {
517                List<IBase> values = getFieldByFhirPath(theFhirContext, theFhirPath, theResource);
518                if (values == null || values.isEmpty()) {
519                        return null;
520                }
521                return values.get(0);
522        }
523
524        private static void replaceField(
525                        FhirTerser theTerser,
526                        IBaseResource theFrom,
527                        IBaseResource theTo,
528                        BaseRuntimeChildDefinition childDefinition) {
529                List<IBase> fromValues = childDefinition.getAccessor().getValues(theFrom);
530                List<IBase> toValues = childDefinition.getAccessor().getValues(theTo);
531
532                if (fromValues.isEmpty() && !toValues.isEmpty()) {
533                        childDefinition.getMutator().setValue(theTo, null);
534                } else if (fromValues != toValues) {
535                        clear(toValues);
536
537                        mergeFields(theTerser, theTo, childDefinition, fromValues, toValues);
538                }
539        }
540
541        /**
542         * Merges values of all fields except for "identifier" and "meta" from <code>theFrom</code> resource to
543         * <code>theTo</code> resource. Fields values are compared via the equalsDeep method, or via object identity if this
544         * method is not available.
545         *
546         * @param theFhirContext Context holding resource definition
547         * @param theFrom        Resource to merge the specified field from
548         * @param theTo          Resource to merge the specified field into
549         */
550        public static void mergeFieldsExceptIdAndMeta(
551                        FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo) {
552                mergeFields(theFhirContext, theFrom, theTo, EXCLUDE_IDS_AND_META);
553        }
554
555        /**
556         * Merges values of all field from <code>theFrom</code> resource to <code>theTo</code> resource. Fields
557         * values are compared via the equalsDeep method, or via object identity if this method is not available.
558         *
559         * @param theFhirContext    Context holding resource definition
560         * @param theFrom           Resource to merge the specified field from
561         * @param theTo             Resource to merge the specified field into
562         * @param inclusionStrategy Predicate to test which fields should be merged
563         */
564        public static void mergeFields(
565                        FhirContext theFhirContext,
566                        IBaseResource theFrom,
567                        IBaseResource theTo,
568                        Predicate<String> inclusionStrategy) {
569                FhirTerser terser = theFhirContext.newTerser();
570
571                RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom);
572                for (BaseRuntimeChildDefinition childDefinition : definition.getChildrenAndExtension()) {
573                        if (!inclusionStrategy.test(childDefinition.getElementName())) {
574                                continue;
575                        }
576
577                        List<IBase> theFromFieldValues = childDefinition.getAccessor().getValues(theFrom);
578                        List<IBase> theToFieldValues = childDefinition.getAccessor().getValues(theTo);
579
580                        mergeFields(terser, theTo, childDefinition, theFromFieldValues, theToFieldValues);
581                }
582        }
583
584        /**
585         * Merges value of the specified field from <code>theFrom</code> resource to <code>theTo</code> resource. Fields
586         * values are compared via the equalsDeep method, or via object identity if this method is not available.
587         *
588         * @param theFhirContext Context holding resource definition
589         * @param theFieldName   Name of the child filed to merge
590         * @param theFrom        Resource to merge the specified field from
591         * @param theTo          Resource to merge the specified field into
592         */
593        public static void mergeField(
594                        FhirContext theFhirContext, String theFieldName, IBaseResource theFrom, IBaseResource theTo) {
595                mergeField(theFhirContext, theFhirContext.newTerser(), theFieldName, theFrom, theTo);
596        }
597
598        /**
599         * Merges value of the specified field from <code>theFrom</code> resource to <code>theTo</code> resource. Fields
600         * values are compared via the equalsDeep method, or via object identity if this method is not available.
601         *
602         * @param theFhirContext Context holding resource definition
603         * @param theTerser      Terser to be used when cloning the field values
604         * @param theFieldName   Name of the child filed to merge
605         * @param theFrom        Resource to merge the specified field from
606         * @param theTo          Resource to merge the specified field into
607         */
608        public static void mergeField(
609                        FhirContext theFhirContext,
610                        FhirTerser theTerser,
611                        String theFieldName,
612                        IBaseResource theFrom,
613                        IBaseResource theTo) {
614                BaseRuntimeChildDefinition childDefinition =
615                                getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theFrom);
616
617                List<IBase> theFromFieldValues = childDefinition.getAccessor().getValues(theFrom);
618                List<IBase> theToFieldValues = childDefinition.getAccessor().getValues(theTo);
619
620                mergeFields(theTerser, theTo, childDefinition, theFromFieldValues, theToFieldValues);
621        }
622
623        private static BaseRuntimeChildDefinition getBaseRuntimeChildDefinition(
624                        FhirContext theFhirContext, String theFieldName, IBaseResource theFrom) {
625                RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom);
626                BaseRuntimeChildDefinition childDefinition = definition.getChildByName(theFieldName);
627                Validate.notNull(childDefinition);
628                return childDefinition;
629        }
630
631        /**
632         * Creates a new element taking into consideration elements with choice that are not directly retrievable by element
633         * name
634         *
635         * @param theFhirTerser
636         * @param theChildDefinition  Child to create a new instance for
637         * @param theFromFieldValue   The base parent field
638         * @param theConstructorParam Optional constructor param
639         * @return Returns the new element with the given value if configured
640         */
641        private static IBase newElement(
642                        FhirTerser theFhirTerser,
643                        BaseRuntimeChildDefinition theChildDefinition,
644                        IBase theFromFieldValue,
645                        Object theConstructorParam) {
646                BaseRuntimeElementDefinition runtimeElementDefinition;
647                if (theChildDefinition instanceof RuntimeChildChoiceDefinition) {
648                        runtimeElementDefinition =
649                                        theChildDefinition.getChildElementDefinitionByDatatype(theFromFieldValue.getClass());
650                } else {
651                        runtimeElementDefinition = theChildDefinition.getChildByName(theChildDefinition.getElementName());
652                }
653                if ("contained".equals(runtimeElementDefinition.getName())) {
654                        IBaseResource sourceResource = (IBaseResource) theFromFieldValue;
655                        return theFhirTerser.clone(sourceResource);
656                } else if (theConstructorParam == null) {
657                        return runtimeElementDefinition.newInstance();
658                } else {
659                        return runtimeElementDefinition.newInstance(theConstructorParam);
660                }
661        }
662
663        private static void mergeFields(
664                        FhirTerser theTerser,
665                        IBaseResource theTo,
666                        BaseRuntimeChildDefinition childDefinition,
667                        List<IBase> theFromFieldValues,
668                        List<IBase> theToFieldValues) {
669                for (IBase theFromFieldValue : theFromFieldValues) {
670                        if (contains(theFromFieldValue, theToFieldValues)) {
671                                continue;
672                        }
673
674                        IBase newFieldValue = newElement(theTerser, childDefinition, theFromFieldValue, null);
675                        if (theFromFieldValue instanceof IPrimitiveType) {
676                                try {
677                                        Method copyMethod = getMethod(theFromFieldValue, "copy");
678                                        if (copyMethod != null) {
679                                                newFieldValue = (IBase) copyMethod.invoke(theFromFieldValue, new Object[] {});
680                                        }
681                                } catch (Throwable t) {
682                                        ((IPrimitiveType) newFieldValue)
683                                                        .setValueAsString(((IPrimitiveType) theFromFieldValue).getValueAsString());
684                                }
685                        } else {
686                                theTerser.cloneInto(theFromFieldValue, newFieldValue, true);
687                        }
688
689                        try {
690                                theToFieldValues.add(newFieldValue);
691                        } catch (UnsupportedOperationException e) {
692                                childDefinition.getMutator().setValue(theTo, newFieldValue);
693                                theToFieldValues = childDefinition.getAccessor().getValues(theTo);
694                        }
695                }
696        }
697
698        /**
699         * Clones the specified resource.
700         *
701         * @param theFhirContext Context holding resource definition
702         * @param theInstance    The instance to be cloned
703         * @param <T>            Base resource type
704         * @return Returns a cloned instance
705         */
706        public static <T extends IBaseResource> T clone(FhirContext theFhirContext, T theInstance) {
707                RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theInstance.getClass());
708                T retVal = (T) definition.newInstance();
709
710                FhirTerser terser = theFhirContext.newTerser();
711                terser.cloneInto(theInstance, retVal, true);
712                return retVal;
713        }
714
715        /**
716         * Creates a new element instance
717         *
718         * @param theFhirContext Context holding resource definition
719         * @param theElementType Element type name
720         * @param <T>            Base element type
721         * @return Returns a new instance of the element
722         */
723        public static <T extends IBase> T newElement(FhirContext theFhirContext, String theElementType) {
724                BaseRuntimeElementDefinition def = theFhirContext.getElementDefinition(theElementType);
725                return (T) def.newInstance();
726        }
727
728        /**
729         * Creates a new element instance
730         *
731         * @param theFhirContext      Context holding resource definition
732         * @param theElementType      Element type name
733         * @param theConstructorParam Initialization parameter for the element
734         * @param <T>                 Base element type
735         * @return Returns a new instance of the element with the specified initial value
736         */
737        public static <T extends IBase> T newElement(
738                        FhirContext theFhirContext, String theElementType, Object theConstructorParam) {
739                BaseRuntimeElementDefinition def = theFhirContext.getElementDefinition(theElementType);
740                Validate.notNull(def);
741                return (T) def.newInstance(theConstructorParam);
742        }
743
744        /**
745         * Creates a new resource definition.
746         *
747         * @param theFhirContext  Context holding resource definition
748         * @param theResourceName Name of the resource in the context
749         * @param <T>             Type of the resource
750         * @return Returns a new instance of the resource
751         */
752        public static <T extends IBase> T newResource(FhirContext theFhirContext, String theResourceName) {
753                RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theResourceName);
754                return (T) def.newInstance();
755        }
756
757        /**
758         * Creates a new resource definition.
759         *
760         * @param theFhirContext      Context holding resource definition
761         * @param theResourceName     Name of the resource in the context
762         * @param theConstructorParam Initialization parameter for the new instance
763         * @param <T>                 Type of the resource
764         * @return Returns a new instance of the resource
765         */
766        public static <T extends IBase> T newResource(
767                        FhirContext theFhirContext, String theResourceName, Object theConstructorParam) {
768                RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theResourceName);
769                return (T) def.newInstance(theConstructorParam);
770        }
771
772        /**
773         * Creates a new BackboneElement.
774         *
775         * @param theFhirContext        Context holding resource definition
776         * @param theTargetResourceName Name of the resource in the context
777         * @param theTargetFieldName    Name of the backbone element in the resource
778         * @return Returns a new instance of the element
779         */
780        public static IBaseBackboneElement instantiateBackboneElement(
781                        FhirContext theFhirContext, String theTargetResourceName, String theTargetFieldName) {
782                BaseRuntimeElementDefinition<?> targetParentElementDefinition =
783                                theFhirContext.getResourceDefinition(theTargetResourceName);
784                BaseRuntimeChildDefinition childDefinition = targetParentElementDefinition.getChildByName(theTargetFieldName);
785                return (IBaseBackboneElement)
786                                childDefinition.getChildByName(theTargetFieldName).newInstance();
787        }
788
789        private static void clear(List<IBase> values) {
790                if (values == null) {
791                        return;
792                }
793
794                try {
795                        values.clear();
796                } catch (Throwable t) {
797                        ourLog.debug("Unable to clear values " + String.valueOf(values), t);
798                }
799        }
800}