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.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         */
290        public static void mergeAllFields(FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo) {
291                mergeFields(theFhirContext, theFrom, theTo, INCLUDE_ALL);
292        }
293
294        /**
295         * Replaces all fields that have matching field names by the given inclusion strategy. <code>theTo</code> will contain a copy of the
296         * values from <code>theFrom</code> instance.
297         *
298         * @param theFhirContext        Context holding resource definition
299         * @param theFrom               The resource to merge the fields from
300         * @param theTo                 The resource to merge the fields into
301         * @param theFieldNameInclusion Inclusion strategy that checks if a given field should be replaced
302         */
303        public static void replaceFields(
304                        FhirContext theFhirContext,
305                        IBaseResource theFrom,
306                        IBaseResource theTo,
307                        Predicate<String> theFieldNameInclusion) {
308                Predicate<Triple<BaseRuntimeChildDefinition, IBase, IBase>> predicate =
309                                (t) -> theFieldNameInclusion.test(t.getLeft().getElementName());
310                replaceFieldsByPredicate(theFhirContext, theFrom, theTo, predicate);
311        }
312
313        /**
314         * Replaces fields on theTo resource that test positive by the given predicate. <code>theTo</code> will contain a copy of the
315         * values from <code>theFrom</code> for which predicate tests positive. Please note that composite fields will be replaced fully.
316         *
317         * @param theFhirContext Context holding resource definition
318         * @param theFrom        The resource to merge the fields from
319         * @param theTo          The resource to merge the fields into
320         * @param thePredicate   Predicate that checks if a given field should be replaced
321         */
322        public static void replaceFieldsByPredicate(
323                        FhirContext theFhirContext,
324                        IBaseResource theFrom,
325                        IBaseResource theTo,
326                        Predicate<Triple<BaseRuntimeChildDefinition, IBase, IBase>> thePredicate) {
327                RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom);
328                FhirTerser terser = theFhirContext.newTerser();
329                for (BaseRuntimeChildDefinition childDefinition : definition.getChildrenAndExtension()) {
330                        if (thePredicate.test(Triple.of(childDefinition, theFrom, theTo))) {
331                                replaceField(terser, theFrom, theTo, childDefinition);
332                        }
333                }
334        }
335
336        /**
337         * Checks if the field exists on the resource
338         *
339         * @param theFhirContext Context holding resource definition
340         * @param theFieldName   Name of the field to check
341         * @param theInstance    Resource instance to check
342         * @return Returns true if resource definition has a child with the specified name and false otherwise
343         */
344        public static boolean fieldExists(FhirContext theFhirContext, String theFieldName, IBaseResource theInstance) {
345                RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theInstance);
346                return definition.getChildByName(theFieldName) != null;
347        }
348
349        /**
350         * Replaces the specified fields on <code>theTo</code> resource with the value from <code>theFrom</code> resource.
351         *
352         * @param theFhirContext Context holding resource definition
353         * @param theFrom        The resource to replace the field from
354         * @param theTo          The resource to replace the field on
355         */
356        public static void replaceField(
357                        FhirContext theFhirContext, String theFieldName, IBaseResource theFrom, IBaseResource theTo) {
358                RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom);
359                Validate.notNull(definition);
360                replaceField(
361                                theFhirContext.newTerser(),
362                                theFrom,
363                                theTo,
364                                theFhirContext.getResourceDefinition(theFrom).getChildByName(theFieldName));
365        }
366
367        /**
368         * Clears the specified field on the resource provided
369         *
370         * @param theFhirContext Context holding resource definition
371         * @param theResource
372         * @param theFieldName
373         */
374        public static void clearField(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) {
375                BaseRuntimeChildDefinition childDefinition =
376                                getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theResource);
377                childDefinition.getMutator().setValue(theResource, null);
378        }
379
380        /**
381         * Clears the specified field on the resource provided by the FHIRPath.  If more than one value matches
382         * the FHIRPath, all values will be cleared.
383         *
384         * @param theFhirContext
385         * @param theResource
386         * @param theFhirPath
387         */
388        public static void clearFieldByFhirPath(FhirContext theFhirContext, IBaseResource theResource, String theFhirPath) {
389
390                if (theFhirPath.contains(".")) {
391                        String parentPath = theFhirPath.substring(0, theFhirPath.lastIndexOf("."));
392                        String fieldName = theFhirPath.substring(theFhirPath.lastIndexOf(".") + 1);
393                        FhirTerser terser = theFhirContext.newTerser();
394                        List<IBase> parents = terser.getValues(theResource, parentPath);
395                        for (IBase parent : parents) {
396                                clearField(theFhirContext, fieldName, parent);
397                        }
398                } else {
399                        clearField(theFhirContext, theResource, theFhirPath);
400                }
401        }
402
403        /**
404         * Clears the specified field on the element provided
405         *
406         * @param theFhirContext Context holding resource definition
407         * @param theFieldName   Name of the field to clear values for
408         * @param theBase        The element definition to clear values on
409         */
410        public static void clearField(FhirContext theFhirContext, String theFieldName, IBase theBase) {
411                BaseRuntimeElementDefinition definition = theFhirContext.getElementDefinition(theBase.getClass());
412                BaseRuntimeChildDefinition childDefinition = definition.getChildByName(theFieldName);
413                Validate.notNull(childDefinition);
414                BaseRuntimeChildDefinition.IAccessor accessor = childDefinition.getAccessor();
415                clear(accessor.getValues(theBase));
416                List<IBase> newValue = accessor.getValues(theBase);
417
418                if (newValue != null && !newValue.isEmpty()) {
419                        // Our clear failed, probably because it was an immutable SingletonList returned by a FieldPlainAccessor
420                        // that cannot be cleared.
421                        // Let's just null it out instead.
422                        childDefinition.getMutator().setValue(theBase, null);
423                }
424        }
425
426        /**
427         * Sets the provided field with the given values. This method will add to the collection of existing field values
428         * in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)}
429         * to remove values before setting
430         *
431         * @param theFhirContext Context holding resource definition
432         * @param theFieldName   Child field name of the resource to set
433         * @param theResource    The resource to set the values on
434         * @param theValues      The values to set on the resource child field name
435         */
436        public static void setField(
437                        FhirContext theFhirContext, String theFieldName, IBaseResource theResource, IBase... theValues) {
438                setField(theFhirContext, theFhirContext.newTerser(), theFieldName, theResource, theValues);
439        }
440
441        /**
442         * Sets the provided field with the given values. This method will add to the collection of existing field values
443         * in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)}
444         * to remove values before setting
445         *
446         * @param theFhirContext Context holding resource definition
447         * @param theTerser      Terser to be used when cloning field values
448         * @param theFieldName   Child field name of the resource to set
449         * @param theResource    The resource to set the values on
450         * @param theValues      The values to set on the resource child field name
451         */
452        public static void setField(
453                        FhirContext theFhirContext,
454                        FhirTerser theTerser,
455                        String theFieldName,
456                        IBaseResource theResource,
457                        IBase... theValues) {
458                BaseRuntimeChildDefinition childDefinition =
459                                getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theResource);
460                List<IBase> theFromFieldValues = childDefinition.getAccessor().getValues(theResource);
461                if (theFromFieldValues.isEmpty()) {
462                        for (IBase value : theValues) {
463                                try {
464                                        childDefinition.getMutator().addValue(theResource, value);
465                                } catch (UnsupportedOperationException e) {
466                                        ourLog.warn(
467                                                        "Resource {} does not support multiple values, but an attempt to set {} was made. Setting the first item only",
468                                                        theResource,
469                                                        theValues);
470                                        childDefinition.getMutator().setValue(theResource, value);
471                                        break;
472                                }
473                        }
474                        return;
475                }
476                List<IBase> theToFieldValues = Arrays.asList(theValues);
477                mergeFields(theTerser, theResource, childDefinition, theFromFieldValues, theToFieldValues);
478        }
479
480        /**
481         * Sets the provided field with the given values. This method will add to the collection of existing field values
482         * in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)}
483         * to remove values before setting
484         *
485         * @param theFhirContext Context holding resource definition
486         * @param theFieldName   Child field name of the resource to set
487         * @param theResource    The resource to set the values on
488         * @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
489         */
490        public static void setStringField(
491                        FhirContext theFhirContext, String theFieldName, IBaseResource theResource, String theValue) {
492                setField(theFhirContext, theFieldName, theResource, theFhirContext.newPrimitiveString(theValue));
493        }
494
495        /**
496         * Sets the specified value at the FHIR path provided.
497         *
498         * @param theTerser   The terser that should be used for cloning the field value.
499         * @param theFhirPath The FHIR path to set the field at
500         * @param theResource The resource on which the value should be set
501         * @param theValue    The value to set
502         */
503        public static void setFieldByFhirPath(
504                        FhirTerser theTerser, String theFhirPath, IBaseResource theResource, IBase theValue) {
505                List<IBase> theFromFieldValues = theTerser.getValues(theResource, theFhirPath, true, false);
506                for (IBase theFromFieldValue : theFromFieldValues) {
507                        theTerser.cloneInto(theValue, theFromFieldValue, true);
508                }
509        }
510
511        /**
512         * Sets the specified value at the FHIR path provided.
513         *
514         * @param theFhirContext Context holding resource definition
515         * @param theFhirPath    The FHIR path to set the field at
516         * @param theResource    The resource on which the value should be set
517         * @param theValue       The value to set
518         */
519        public static void setFieldByFhirPath(
520                        FhirContext theFhirContext, String theFhirPath, IBaseResource theResource, IBase theValue) {
521                setFieldByFhirPath(theFhirContext.newTerser(), theFhirPath, theResource, theValue);
522        }
523
524        /**
525         * Sets the specified String value at the FHIR path provided.
526         *
527         * @param theFhirContext Context holding resource definition
528         * @param theFhirPath    The FHIR path to set the field at
529         * @param theResource    The resource on which the value should be set
530         * @param theValue       The String value to set. The string is converted to the appropriate primitive type before setting the field
531         */
532        public static void setStringFieldByFhirPath(
533                        FhirContext theFhirContext, String theFhirPath, IBaseResource theResource, String theValue) {
534                setFieldByFhirPath(
535                                theFhirContext.newTerser(), theFhirPath, theResource, theFhirContext.newPrimitiveString(theValue));
536        }
537
538        /**
539         * Returns field values ant the specified FHIR path from the resource.
540         *
541         * @param theFhirContext Context holding resource definition
542         * @param theFhirPath    The FHIR path to get the field from
543         * @param theResource    The resource from which the value should be retrieved
544         * @return Returns the list of field values at the given FHIR path
545         */
546        public static List<IBase> getFieldByFhirPath(FhirContext theFhirContext, String theFhirPath, IBase theResource) {
547                return theFhirContext.newTerser().getValues(theResource, theFhirPath, false, false);
548        }
549
550        /**
551         * Returns the first available field value at the specified FHIR path from the resource.
552         *
553         * @param theFhirContext Context holding resource definition
554         * @param theFhirPath    The FHIR path to get the field from
555         * @param theResource    The resource from which the value should be retrieved
556         * @return Returns the first available value or null if no values can be retrieved
557         */
558        public static IBase getFirstFieldByFhirPath(FhirContext theFhirContext, String theFhirPath, IBase theResource) {
559                List<IBase> values = getFieldByFhirPath(theFhirContext, theFhirPath, theResource);
560                if (values == null || values.isEmpty()) {
561                        return null;
562                }
563                return values.get(0);
564        }
565
566        private static void replaceField(
567                        FhirTerser theTerser,
568                        IBaseResource theFrom,
569                        IBaseResource theTo,
570                        BaseRuntimeChildDefinition childDefinition) {
571                List<IBase> fromValues = childDefinition.getAccessor().getValues(theFrom);
572                List<IBase> toValues = childDefinition.getAccessor().getValues(theTo);
573
574                if (fromValues.isEmpty() && !toValues.isEmpty()) {
575                        childDefinition.getMutator().setValue(theTo, null);
576                } else if (fromValues != toValues) {
577                        clear(toValues);
578
579                        mergeFields(theTerser, theTo, childDefinition, fromValues, toValues);
580                }
581        }
582
583        /**
584         * Merges values of all fields except for "identifier" and "meta" from <code>theFrom</code> resource to
585         * <code>theTo</code> resource. Fields values are compared via the equalsDeep method, or via object identity if this
586         * method is not available.
587         *
588         * @param theFhirContext Context holding resource definition
589         * @param theFrom        Resource to merge the specified field from
590         * @param theTo          Resource to merge the specified field into
591         */
592        public static void mergeFieldsExceptIdAndMeta(
593                        FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo) {
594                mergeFields(theFhirContext, theFrom, theTo, EXCLUDE_IDS_AND_META);
595        }
596
597        /**
598         * Merges values of all field from <code>theFrom</code> resource to <code>theTo</code> resource. Fields
599         * values are compared via the equalsDeep method, or via object identity if this method is not available.
600         *
601         * @param theFhirContext    Context holding resource definition
602         * @param theFrom           Resource to merge the specified field from
603         * @param theTo             Resource to merge the specified field into
604         * @param inclusionStrategy Predicate to test which fields should be merged
605         */
606        public static void mergeFields(
607                        FhirContext theFhirContext,
608                        IBaseResource theFrom,
609                        IBaseResource theTo,
610                        Predicate<String> inclusionStrategy) {
611                FhirTerser terser = theFhirContext.newTerser();
612
613                RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom);
614                for (BaseRuntimeChildDefinition childDefinition : definition.getChildrenAndExtension()) {
615                        if (!inclusionStrategy.test(childDefinition.getElementName())) {
616                                continue;
617                        }
618
619                        List<IBase> theFromFieldValues = childDefinition.getAccessor().getValues(theFrom);
620                        List<IBase> theToFieldValues = childDefinition.getAccessor().getValues(theTo);
621
622                        mergeFields(terser, theTo, childDefinition, theFromFieldValues, theToFieldValues);
623                }
624        }
625
626        /**
627         * Merges value of the specified field from <code>theFrom</code> resource to <code>theTo</code> resource. Fields
628         * values are compared via the equalsDeep method, or via object identity if this method is not available.
629         *
630         * @param theFhirContext Context holding resource definition
631         * @param theFieldName   Name of the child filed to merge
632         * @param theFrom        Resource to merge the specified field from
633         * @param theTo          Resource to merge the specified field into
634         */
635        public static void mergeField(
636                        FhirContext theFhirContext, String theFieldName, IBaseResource theFrom, IBaseResource theTo) {
637                mergeField(theFhirContext, theFhirContext.newTerser(), theFieldName, theFrom, theTo);
638        }
639
640        /**
641         * Merges value of the specified field from <code>theFrom</code> resource to <code>theTo</code> resource. Fields
642         * values are compared via the equalsDeep method, or via object identity if this method is not available.
643         *
644         * @param theFhirContext Context holding resource definition
645         * @param theTerser      Terser to be used when cloning the field values
646         * @param theFieldName   Name of the child filed to merge
647         * @param theFrom        Resource to merge the specified field from
648         * @param theTo          Resource to merge the specified field into
649         */
650        public static void mergeField(
651                        FhirContext theFhirContext,
652                        FhirTerser theTerser,
653                        String theFieldName,
654                        IBaseResource theFrom,
655                        IBaseResource theTo) {
656                BaseRuntimeChildDefinition childDefinition =
657                                getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theFrom);
658
659                List<IBase> theFromFieldValues = childDefinition.getAccessor().getValues(theFrom);
660                List<IBase> theToFieldValues = childDefinition.getAccessor().getValues(theTo);
661
662                mergeFields(theTerser, theTo, childDefinition, theFromFieldValues, theToFieldValues);
663        }
664
665        private static BaseRuntimeChildDefinition getBaseRuntimeChildDefinition(
666                        FhirContext theFhirContext, String theFieldName, IBaseResource theFrom) {
667                RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom);
668                BaseRuntimeChildDefinition childDefinition = definition.getChildByName(theFieldName);
669                Validate.notNull(childDefinition);
670                return childDefinition;
671        }
672
673        /**
674         * Creates a new element taking into consideration elements with choice that are not directly retrievable by element
675         * name
676         *
677         * @param theFhirTerser
678         * @param theChildDefinition  Child to create a new instance for
679         * @param theFromFieldValue   The base parent field
680         * @param theConstructorParam Optional constructor param
681         * @return Returns the new element with the given value if configured
682         */
683        private static IBase newElement(
684                        FhirTerser theFhirTerser,
685                        BaseRuntimeChildDefinition theChildDefinition,
686                        IBase theFromFieldValue,
687                        Object theConstructorParam) {
688                BaseRuntimeElementDefinition runtimeElementDefinition;
689                if (theChildDefinition instanceof RuntimeChildChoiceDefinition) {
690                        runtimeElementDefinition =
691                                        theChildDefinition.getChildElementDefinitionByDatatype(theFromFieldValue.getClass());
692                } else {
693                        runtimeElementDefinition = theChildDefinition.getChildByName(theChildDefinition.getElementName());
694                }
695                if ("contained".equals(runtimeElementDefinition.getName())) {
696                        IBaseResource sourceResource = (IBaseResource) theFromFieldValue;
697                        return theFhirTerser.clone(sourceResource);
698                } else if (theConstructorParam == null) {
699                        return runtimeElementDefinition.newInstance();
700                } else {
701                        return runtimeElementDefinition.newInstance(theConstructorParam);
702                }
703        }
704
705        private static void mergeFields(
706                        FhirTerser theTerser,
707                        IBaseResource theTo,
708                        BaseRuntimeChildDefinition childDefinition,
709                        List<IBase> theFromFieldValues,
710                        List<IBase> theToFieldValues) {
711                if (!theFromFieldValues.isEmpty() && theToFieldValues.stream().anyMatch(TerserUtil::hasDataAbsentReason)) {
712                        // If the to resource has a data absent reason, and there is potentially real data incoming
713                        // in the from resource, we should clear the data absent reason because it won't be absent anymore.
714                        theToFieldValues = removeDataAbsentReason(theTo, childDefinition, theToFieldValues);
715                }
716
717                for (IBase fromFieldValue : theFromFieldValues) {
718                        if (contains(fromFieldValue, theToFieldValues)) {
719                                continue;
720                        }
721
722                        if (hasDataAbsentReason(fromFieldValue) && !theToFieldValues.isEmpty()) {
723                                // if the from field value asserts a reason the field isn't populated, but the to field is populated,
724                                // we don't want to overwrite real data with the extension
725                                continue;
726                        }
727
728                        IBase newFieldValue = newElement(theTerser, childDefinition, fromFieldValue, null);
729                        if (fromFieldValue instanceof IPrimitiveType) {
730                                try {
731                                        Method copyMethod = getMethod(fromFieldValue, "copy");
732                                        if (copyMethod != null) {
733                                                newFieldValue = (IBase) copyMethod.invoke(fromFieldValue, new Object[] {});
734                                        }
735                                } catch (Throwable t) {
736                                        ((IPrimitiveType<?>) newFieldValue)
737                                                        .setValueAsString(((IPrimitiveType<?>) fromFieldValue).getValueAsString());
738                                }
739                        } else {
740                                theTerser.cloneInto(fromFieldValue, newFieldValue, true);
741                        }
742
743                        try {
744                                theToFieldValues.add(newFieldValue);
745                        } catch (UnsupportedOperationException e) {
746                                childDefinition.getMutator().setValue(theTo, newFieldValue);
747                                theToFieldValues = childDefinition.getAccessor().getValues(theTo);
748                        }
749                }
750        }
751
752        private static List<IBase> removeDataAbsentReason(
753                        IBaseResource theResource, BaseRuntimeChildDefinition theFieldDefinition, List<IBase> theFieldValues) {
754                for (int i = 0; i < theFieldValues.size(); i++) {
755                        if (hasDataAbsentReason(theFieldValues.get(i))) {
756                                try {
757                                        theFieldDefinition.getMutator().remove(theResource, i);
758                                } catch (UnsupportedOperationException e) {
759                                        // the field must be single-valued, just clear it
760                                        theFieldDefinition.getMutator().setValue(theResource, null);
761                                }
762                        }
763                }
764                return theFieldDefinition.getAccessor().getValues(theResource);
765        }
766
767        /**
768         * Clones the specified resource.
769         *
770         * @param theFhirContext Context holding resource definition
771         * @param theInstance    The instance to be cloned
772         * @param <T>            Base resource type
773         * @return Returns a cloned instance
774         */
775        public static <T extends IBaseResource> T clone(FhirContext theFhirContext, T theInstance) {
776                RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theInstance.getClass());
777                T retVal = (T) definition.newInstance();
778
779                FhirTerser terser = theFhirContext.newTerser();
780                terser.cloneInto(theInstance, retVal, true);
781                return retVal;
782        }
783
784        /**
785         * Creates a new element instance
786         *
787         * @param theFhirContext Context holding resource definition
788         * @param theElementType Element type name
789         * @param <T>            Base element type
790         * @return Returns a new instance of the element
791         */
792        public static <T extends IBase> T newElement(FhirContext theFhirContext, String theElementType) {
793                BaseRuntimeElementDefinition def = theFhirContext.getElementDefinition(theElementType);
794                return (T) def.newInstance();
795        }
796
797        /**
798         * Creates a new element instance
799         *
800         * @param theFhirContext      Context holding resource definition
801         * @param theElementType      Element type name
802         * @param theConstructorParam Initialization parameter for the element
803         * @param <T>                 Base element type
804         * @return Returns a new instance of the element with the specified initial value
805         */
806        public static <T extends IBase> T newElement(
807                        FhirContext theFhirContext, String theElementType, Object theConstructorParam) {
808                BaseRuntimeElementDefinition def = theFhirContext.getElementDefinition(theElementType);
809                Validate.notNull(def);
810                return (T) def.newInstance(theConstructorParam);
811        }
812
813        /**
814         * Creates a new resource definition.
815         *
816         * @param theFhirContext  Context holding resource definition
817         * @param theResourceName Name of the resource in the context
818         * @param <T>             Type of the resource
819         * @return Returns a new instance of the resource
820         */
821        public static <T extends IBase> T newResource(FhirContext theFhirContext, String theResourceName) {
822                RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theResourceName);
823                return (T) def.newInstance();
824        }
825
826        /**
827         * Creates a new resource definition.
828         *
829         * @param theFhirContext      Context holding resource definition
830         * @param theResourceName     Name of the resource in the context
831         * @param theConstructorParam Initialization parameter for the new instance
832         * @param <T>                 Type of the resource
833         * @return Returns a new instance of the resource
834         */
835        public static <T extends IBase> T newResource(
836                        FhirContext theFhirContext, String theResourceName, Object theConstructorParam) {
837                RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theResourceName);
838                return (T) def.newInstance(theConstructorParam);
839        }
840
841        /**
842         * Creates a new BackboneElement.
843         *
844         * @param theFhirContext        Context holding resource definition
845         * @param theTargetResourceName Name of the resource in the context
846         * @param theTargetFieldName    Name of the backbone element in the resource
847         * @return Returns a new instance of the element
848         */
849        public static IBaseBackboneElement instantiateBackboneElement(
850                        FhirContext theFhirContext, String theTargetResourceName, String theTargetFieldName) {
851                BaseRuntimeElementDefinition<?> targetParentElementDefinition =
852                                theFhirContext.getResourceDefinition(theTargetResourceName);
853                BaseRuntimeChildDefinition childDefinition = targetParentElementDefinition.getChildByName(theTargetFieldName);
854                return (IBaseBackboneElement)
855                                childDefinition.getChildByName(theTargetFieldName).newInstance();
856        }
857
858        private static void clear(List<IBase> values) {
859                if (values == null) {
860                        return;
861                }
862
863                try {
864                        values.clear();
865                } catch (Throwable t) {
866                        ourLog.debug("Unable to clear values " + String.valueOf(values), t);
867                }
868        }
869}