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