All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.uhn.fhir.util.TerserUtil Maven / Gradle / Ivy

There is a newer version: 7.4.5
Show newest version
/*-
 * #%L
 * HAPI FHIR - Core Library
 * %%
 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package ca.uhn.fhir.util;

import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeChildChoiceDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.i18n.Msg;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.tuple.Triple;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.slf4j.LoggerFactory.getLogger;

public final class TerserUtil {

	public static final String FIELD_NAME_IDENTIFIER = "identifier";
	/**
	 * Exclude for id, identifier and meta fields of a resource.
	 */
	public static final Collection IDS_AND_META_EXCLUDES =
			Collections.unmodifiableSet(Stream.of("id", "identifier", "meta").collect(Collectors.toSet()));
	/**
	 * Exclusion predicate for id, identifier, meta fields.
	 */
	public static final Predicate EXCLUDE_IDS_AND_META = new Predicate() {
		@Override
		public boolean test(String s) {
			return !IDS_AND_META_EXCLUDES.contains(s);
		}
	};
	/**
	 * Exclusion predicate for id/identifier, meta and fields with empty values. This ensures that source / target resources,
	 * empty source fields will not results in erasure of target fields.
	 */
	public static final Predicate> EXCLUDE_IDS_META_AND_EMPTY =
			new Predicate>() {
				@Override
				public boolean test(Triple theTriple) {
					if (!EXCLUDE_IDS_AND_META.test(theTriple.getLeft().getElementName())) {
						return false;
					}
					BaseRuntimeChildDefinition childDefinition = theTriple.getLeft();
					boolean isSourceFieldEmpty = childDefinition
							.getAccessor()
							.getValues(theTriple.getMiddle())
							.isEmpty();
					return !isSourceFieldEmpty;
				}
			};
	/**
	 * Exclusion predicate for keeping all fields.
	 */
	public static final Predicate INCLUDE_ALL = new Predicate() {
		@Override
		public boolean test(String s) {
			return true;
		}
	};

	private static final Logger ourLog = getLogger(TerserUtil.class);
	private static final String EQUALS_DEEP = "equalsDeep";

	private TerserUtil() {}

	/**
	 * 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.
	 */
	public static void cloneIdentifierIntoResource(
			FhirContext theFhirContext,
			BaseRuntimeChildDefinition theIdentifierDefinition,
			IBase theNewIdentifier,
			IBaseResource theResourceToCloneInto) {
		// FHIR choice types - fields within fhir where we have a choice of ids
		BaseRuntimeElementCompositeDefinition childIdentifierElementDefinition =
				(BaseRuntimeElementCompositeDefinition)
						theIdentifierDefinition.getChildByName(FIELD_NAME_IDENTIFIER);

		List existingIdentifiers = getValues(theFhirContext, theResourceToCloneInto, FIELD_NAME_IDENTIFIER);
		if (existingIdentifiers != null) {
			for (IBase existingIdentifier : existingIdentifiers) {
				if (equals(existingIdentifier, theNewIdentifier)) {
					ourLog.trace(
							"Identifier {} already exists in resource {}", theNewIdentifier, theResourceToCloneInto);
					return;
				}
			}
		}

		IBase newIdentifierBase = childIdentifierElementDefinition.newInstance();

		FhirTerser terser = theFhirContext.newTerser();
		terser.cloneInto(theNewIdentifier, newIdentifierBase, true);
		theIdentifierDefinition.getMutator().addValue(theResourceToCloneInto, newIdentifierBase);
	}

	/**
	 * Checks if the specified fields has any values
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theResource    Resource to check if the specified field is set
	 * @param theFieldName   name of the field to check
	 * @return Returns true if field exists and has any values set, and false otherwise
	 */
	public static boolean hasValues(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) {
		RuntimeResourceDefinition resourceDefinition = theFhirContext.getResourceDefinition(theResource);
		BaseRuntimeChildDefinition resourceIdentifier = resourceDefinition.getChildByName(theFieldName);
		if (resourceIdentifier == null) {
			return false;
		}
		return !(resourceIdentifier.getAccessor().getValues(theResource).isEmpty());
	}

	/**
	 * Gets all values of the specified field.
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theResource    Resource to check if the specified field is set
	 * @param theFieldName   name of the field to check
	 * @return Returns all values for the specified field or null if field with the provided name doesn't exist
	 */
	public static List getValues(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) {
		RuntimeResourceDefinition resourceDefinition = theFhirContext.getResourceDefinition(theResource);
		BaseRuntimeChildDefinition resourceIdentifier = resourceDefinition.getChildByName(theFieldName);
		if (resourceIdentifier == null) {
			ourLog.info("There is no field named {} in Resource {}", theFieldName, resourceDefinition.getName());
			return null;
		}
		return resourceIdentifier.getAccessor().getValues(theResource);
	}

	/**
	 * Gets the first available value for the specified field.
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theResource    Resource to check if the specified field is set
	 * @param theFieldName   name of the field to check
	 * @return Returns the first value for the specified field or null if field with the provided name doesn't exist or
	 * has no values
	 */
	public static IBase getValueFirstRep(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) {
		List values = getValues(theFhirContext, theResource, theFieldName);
		if (values == null || values.isEmpty()) {
			return null;
		}
		return values.get(0);
	}

	/**
	 * Clones specified composite field (collection). Composite field values must conform to the collections
	 * contract.
	 *
	 * @param theFrom  Resource to clone the specified field from
	 * @param theTo    Resource to clone the specified field to
	 * @param theField Field name to be copied
	 */
	public static void cloneCompositeField(
			FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo, String theField) {
		FhirTerser terser = theFhirContext.newTerser();

		RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom);
		BaseRuntimeChildDefinition childDefinition = definition.getChildByName(theField);
		Validate.notNull(childDefinition);

		List theFromFieldValues = childDefinition.getAccessor().getValues(theFrom);
		List theToFieldValues = childDefinition.getAccessor().getValues(theTo);

		for (IBase theFromFieldValue : theFromFieldValues) {
			if (containsPrimitiveValue(theFromFieldValue, theToFieldValues)) {
				continue;
			}

			IBase newFieldValue = newElement(terser, childDefinition, theFromFieldValue, null);
			terser.cloneInto(theFromFieldValue, newFieldValue, true);

			try {
				theToFieldValues.add(newFieldValue);
			} catch (Exception e) {
				childDefinition.getMutator().setValue(theTo, newFieldValue);
			}
		}
	}

	private static boolean containsPrimitiveValue(IBase theItem, List theItems) {
		PrimitiveTypeEqualsPredicate predicate = new PrimitiveTypeEqualsPredicate();
		return theItems.stream().anyMatch(i -> {
			return predicate.test(i, theItem);
		});
	}

	private static Method getMethod(IBase theBase, String theMethodName) {
		Method method = null;
		for (Method m : theBase.getClass().getDeclaredMethods()) {
			if (m.getName().equals(theMethodName)) {
				method = m;
				break;
			}
		}
		return method;
	}

	/**
	 * Checks if two items are equal via {@link #EQUALS_DEEP} method
	 *
	 * @param theItem1 First item to compare
	 * @param theItem2 Second item to compare
	 * @return Returns true if they are equal and false otherwise
	 */
	public static boolean equals(IBase theItem1, IBase theItem2) {
		if (theItem1 == null) {
			return theItem2 == null;
		}

		final Method method = getMethod(theItem1, EQUALS_DEEP);
		Validate.notNull(method);
		return equals(theItem1, theItem2, method);
	}

	private static boolean equals(IBase theItem1, IBase theItem2, Method theMethod) {
		if (theMethod != null) {
			try {
				return (Boolean) theMethod.invoke(theItem1, theItem2);
			} catch (Exception e) {
				throw new RuntimeException(
						Msg.code(1746) + String.format("Unable to compare equality via %s", EQUALS_DEEP), e);
			}
		}
		return theItem1.equals(theItem2);
	}

	private static boolean contains(IBase theItem, List theItems) {
		final Method method = getMethod(theItem, EQUALS_DEEP);
		return theItems.stream().anyMatch(i -> equals(i, theItem, method));
	}

	/**
	 * Merges all fields on the provided instance. theTo will contain a union of all values from theFrom
	 * instance and theTo instance.
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theFrom        The resource to merge the fields from
	 * @param theTo          The resource to merge the fields into
	 */
	public static void mergeAllFields(FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo) {
		mergeFields(theFhirContext, theFrom, theTo, INCLUDE_ALL);
	}

	/**
	 * Replaces all fields that have matching field names by the given inclusion strategy. theTo will contain a copy of the
	 * values from theFrom instance.
	 *
	 * @param theFhirContext        Context holding resource definition
	 * @param theFrom               The resource to merge the fields from
	 * @param theTo                 The resource to merge the fields into
	 * @param theFieldNameInclusion Inclusion strategy that checks if a given field should be replaced
	 */
	public static void replaceFields(
			FhirContext theFhirContext,
			IBaseResource theFrom,
			IBaseResource theTo,
			Predicate theFieldNameInclusion) {
		Predicate> predicate =
				(t) -> theFieldNameInclusion.test(t.getLeft().getElementName());
		replaceFieldsByPredicate(theFhirContext, theFrom, theTo, predicate);
	}

	/**
	 * Replaces fields on theTo resource that test positive by the given predicate. theTo will contain a copy of the
	 * values from theFrom for which predicate tests positive. Please note that composite fields will be replaced fully.
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theFrom        The resource to merge the fields from
	 * @param theTo          The resource to merge the fields into
	 * @param thePredicate   Predicate that checks if a given field should be replaced
	 */
	public static void replaceFieldsByPredicate(
			FhirContext theFhirContext,
			IBaseResource theFrom,
			IBaseResource theTo,
			Predicate> thePredicate) {
		RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom);
		FhirTerser terser = theFhirContext.newTerser();
		for (BaseRuntimeChildDefinition childDefinition : definition.getChildrenAndExtension()) {
			if (thePredicate.test(Triple.of(childDefinition, theFrom, theTo))) {
				replaceField(terser, theFrom, theTo, childDefinition);
			}
		}
	}

	/**
	 * Checks if the field exists on the resource
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theFieldName   Name of the field to check
	 * @param theInstance    Resource instance to check
	 * @return Returns true if resource definition has a child with the specified name and false otherwise
	 */
	public static boolean fieldExists(FhirContext theFhirContext, String theFieldName, IBaseResource theInstance) {
		RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theInstance);
		return definition.getChildByName(theFieldName) != null;
	}

	/**
	 * Replaces the specified fields on theTo resource with the value from theFrom resource.
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theFrom        The resource to replace the field from
	 * @param theTo          The resource to replace the field on
	 */
	public static void replaceField(
			FhirContext theFhirContext, String theFieldName, IBaseResource theFrom, IBaseResource theTo) {
		RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom);
		Validate.notNull(definition);
		replaceField(
				theFhirContext.newTerser(),
				theFrom,
				theTo,
				theFhirContext.getResourceDefinition(theFrom).getChildByName(theFieldName));
	}

	/**
	 * Clears the specified field on the resource provided
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theResource
	 * @param theFieldName
	 */
	public static void clearField(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) {
		BaseRuntimeChildDefinition childDefinition =
				getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theResource);
		childDefinition.getMutator().setValue(theResource, null);
	}

	/**
	 * Clears the specified field on the resource provided by the FHIRPath.  If more than one value matches
	 * the FHIRPath, all values will be cleared.
	 *
	 * @param theFhirContext
	 * @param theResource
	 * @param theFhirPath
	 */
	public static void clearFieldByFhirPath(FhirContext theFhirContext, IBaseResource theResource, String theFhirPath) {

		if (theFhirPath.contains(".")) {
			String parentPath = theFhirPath.substring(0, theFhirPath.lastIndexOf("."));
			String fieldName = theFhirPath.substring(theFhirPath.lastIndexOf(".") + 1);
			FhirTerser terser = theFhirContext.newTerser();
			List parents = terser.getValues(theResource, parentPath);
			for (IBase parent : parents) {
				clearField(theFhirContext, fieldName, parent);
			}
		} else {
			clearField(theFhirContext, theResource, theFhirPath);
		}
	}

	/**
	 * Clears the specified field on the element provided
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theFieldName   Name of the field to clear values for
	 * @param theBase        The element definition to clear values on
	 */
	public static void clearField(FhirContext theFhirContext, String theFieldName, IBase theBase) {
		BaseRuntimeElementDefinition definition = theFhirContext.getElementDefinition(theBase.getClass());
		BaseRuntimeChildDefinition childDefinition = definition.getChildByName(theFieldName);
		Validate.notNull(childDefinition);
		BaseRuntimeChildDefinition.IAccessor accessor = childDefinition.getAccessor();
		clear(accessor.getValues(theBase));
		List newValue = accessor.getValues(theBase);

		if (newValue != null && !newValue.isEmpty()) {
			// Our clear failed, probably because it was an immutable SingletonList returned by a FieldPlainAccessor
			// that cannot be cleared.
			// Let's just null it out instead.
			childDefinition.getMutator().setValue(theBase, null);
		}
	}

	/**
	 * Sets the provided field with the given values. This method will add to the collection of existing field values
	 * in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)}
	 * to remove values before setting
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theFieldName   Child field name of the resource to set
	 * @param theResource    The resource to set the values on
	 * @param theValues      The values to set on the resource child field name
	 */
	public static void setField(
			FhirContext theFhirContext, String theFieldName, IBaseResource theResource, IBase... theValues) {
		setField(theFhirContext, theFhirContext.newTerser(), theFieldName, theResource, theValues);
	}

	/**
	 * Sets the provided field with the given values. This method will add to the collection of existing field values
	 * in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)}
	 * to remove values before setting
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theTerser      Terser to be used when cloning field values
	 * @param theFieldName   Child field name of the resource to set
	 * @param theResource    The resource to set the values on
	 * @param theValues      The values to set on the resource child field name
	 */
	public static void setField(
			FhirContext theFhirContext,
			FhirTerser theTerser,
			String theFieldName,
			IBaseResource theResource,
			IBase... theValues) {
		BaseRuntimeChildDefinition childDefinition =
				getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theResource);
		List theFromFieldValues = childDefinition.getAccessor().getValues(theResource);
		if (theFromFieldValues.isEmpty()) {
			for (IBase value : theValues) {
				try {
					childDefinition.getMutator().addValue(theResource, value);
				} catch (UnsupportedOperationException e) {
					ourLog.warn(
							"Resource {} does not support multiple values, but an attempt to set {} was made. Setting the first item only",
							theResource,
							theValues);
					childDefinition.getMutator().setValue(theResource, value);
					break;
				}
			}
			return;
		}
		List theToFieldValues = Arrays.asList(theValues);
		mergeFields(theTerser, theResource, childDefinition, theFromFieldValues, theToFieldValues);
	}

	/**
	 * Sets the provided field with the given values. This method will add to the collection of existing field values
	 * in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)}
	 * to remove values before setting
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theFieldName   Child field name of the resource to set
	 * @param theResource    The resource to set the values on
	 * @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
	 */
	public static void setStringField(
			FhirContext theFhirContext, String theFieldName, IBaseResource theResource, String theValue) {
		setField(theFhirContext, theFieldName, theResource, theFhirContext.newPrimitiveString(theValue));
	}

	/**
	 * Sets the specified value at the FHIR path provided.
	 *
	 * @param theTerser   The terser that should be used for cloning the field value.
	 * @param theFhirPath The FHIR path to set the field at
	 * @param theResource The resource on which the value should be set
	 * @param theValue    The value to set
	 */
	public static void setFieldByFhirPath(
			FhirTerser theTerser, String theFhirPath, IBaseResource theResource, IBase theValue) {
		List theFromFieldValues = theTerser.getValues(theResource, theFhirPath, true, false);
		for (IBase theFromFieldValue : theFromFieldValues) {
			theTerser.cloneInto(theValue, theFromFieldValue, true);
		}
	}

	/**
	 * Sets the specified value at the FHIR path provided.
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theFhirPath    The FHIR path to set the field at
	 * @param theResource    The resource on which the value should be set
	 * @param theValue       The value to set
	 */
	public static void setFieldByFhirPath(
			FhirContext theFhirContext, String theFhirPath, IBaseResource theResource, IBase theValue) {
		setFieldByFhirPath(theFhirContext.newTerser(), theFhirPath, theResource, theValue);
	}

	/**
	 * Sets the specified String value at the FHIR path provided.
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theFhirPath    The FHIR path to set the field at
	 * @param theResource    The resource on which the value should be set
	 * @param theValue       The String value to set. The string is converted to the appropriate primitive type before setting the field
	 */
	public static void setStringFieldByFhirPath(
			FhirContext theFhirContext, String theFhirPath, IBaseResource theResource, String theValue) {
		setFieldByFhirPath(
				theFhirContext.newTerser(), theFhirPath, theResource, theFhirContext.newPrimitiveString(theValue));
	}

	/**
	 * Returns field values ant the specified FHIR path from the resource.
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theFhirPath    The FHIR path to get the field from
	 * @param theResource    The resource from which the value should be retrieved
	 * @return Returns the list of field values at the given FHIR path
	 */
	public static List getFieldByFhirPath(FhirContext theFhirContext, String theFhirPath, IBase theResource) {
		return theFhirContext.newTerser().getValues(theResource, theFhirPath, false, false);
	}

	/**
	 * Returns the first available field value at the specified FHIR path from the resource.
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theFhirPath    The FHIR path to get the field from
	 * @param theResource    The resource from which the value should be retrieved
	 * @return Returns the first available value or null if no values can be retrieved
	 */
	public static IBase getFirstFieldByFhirPath(FhirContext theFhirContext, String theFhirPath, IBase theResource) {
		List values = getFieldByFhirPath(theFhirContext, theFhirPath, theResource);
		if (values == null || values.isEmpty()) {
			return null;
		}
		return values.get(0);
	}

	private static void replaceField(
			FhirTerser theTerser,
			IBaseResource theFrom,
			IBaseResource theTo,
			BaseRuntimeChildDefinition childDefinition) {
		List fromValues = childDefinition.getAccessor().getValues(theFrom);
		List toValues = childDefinition.getAccessor().getValues(theTo);

		if (fromValues.isEmpty() && !toValues.isEmpty()) {
			childDefinition.getMutator().setValue(theTo, null);
		} else if (fromValues != toValues) {
			clear(toValues);

			mergeFields(theTerser, theTo, childDefinition, fromValues, toValues);
		}
	}

	/**
	 * Merges values of all fields except for "identifier" and "meta" from theFrom resource to
	 * theTo resource. Fields values are compared via the equalsDeep method, or via object identity if this
	 * method is not available.
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theFrom        Resource to merge the specified field from
	 * @param theTo          Resource to merge the specified field into
	 */
	public static void mergeFieldsExceptIdAndMeta(
			FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo) {
		mergeFields(theFhirContext, theFrom, theTo, EXCLUDE_IDS_AND_META);
	}

	/**
	 * Merges values of all field from theFrom resource to theTo resource. Fields
	 * values are compared via the equalsDeep method, or via object identity if this method is not available.
	 *
	 * @param theFhirContext    Context holding resource definition
	 * @param theFrom           Resource to merge the specified field from
	 * @param theTo             Resource to merge the specified field into
	 * @param inclusionStrategy Predicate to test which fields should be merged
	 */
	public static void mergeFields(
			FhirContext theFhirContext,
			IBaseResource theFrom,
			IBaseResource theTo,
			Predicate inclusionStrategy) {
		FhirTerser terser = theFhirContext.newTerser();

		RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom);
		for (BaseRuntimeChildDefinition childDefinition : definition.getChildrenAndExtension()) {
			if (!inclusionStrategy.test(childDefinition.getElementName())) {
				continue;
			}

			List theFromFieldValues = childDefinition.getAccessor().getValues(theFrom);
			List theToFieldValues = childDefinition.getAccessor().getValues(theTo);

			mergeFields(terser, theTo, childDefinition, theFromFieldValues, theToFieldValues);
		}
	}

	/**
	 * Merges value of the specified field from theFrom resource to theTo resource. Fields
	 * values are compared via the equalsDeep method, or via object identity if this method is not available.
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theFieldName   Name of the child filed to merge
	 * @param theFrom        Resource to merge the specified field from
	 * @param theTo          Resource to merge the specified field into
	 */
	public static void mergeField(
			FhirContext theFhirContext, String theFieldName, IBaseResource theFrom, IBaseResource theTo) {
		mergeField(theFhirContext, theFhirContext.newTerser(), theFieldName, theFrom, theTo);
	}

	/**
	 * Merges value of the specified field from theFrom resource to theTo resource. Fields
	 * values are compared via the equalsDeep method, or via object identity if this method is not available.
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theTerser      Terser to be used when cloning the field values
	 * @param theFieldName   Name of the child filed to merge
	 * @param theFrom        Resource to merge the specified field from
	 * @param theTo          Resource to merge the specified field into
	 */
	public static void mergeField(
			FhirContext theFhirContext,
			FhirTerser theTerser,
			String theFieldName,
			IBaseResource theFrom,
			IBaseResource theTo) {
		BaseRuntimeChildDefinition childDefinition =
				getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theFrom);

		List theFromFieldValues = childDefinition.getAccessor().getValues(theFrom);
		List theToFieldValues = childDefinition.getAccessor().getValues(theTo);

		mergeFields(theTerser, theTo, childDefinition, theFromFieldValues, theToFieldValues);
	}

	private static BaseRuntimeChildDefinition getBaseRuntimeChildDefinition(
			FhirContext theFhirContext, String theFieldName, IBaseResource theFrom) {
		RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom);
		BaseRuntimeChildDefinition childDefinition = definition.getChildByName(theFieldName);
		Validate.notNull(childDefinition);
		return childDefinition;
	}

	/**
	 * Creates a new element taking into consideration elements with choice that are not directly retrievable by element
	 * name
	 *
	 * @param theFhirTerser
	 * @param theChildDefinition  Child to create a new instance for
	 * @param theFromFieldValue   The base parent field
	 * @param theConstructorParam Optional constructor param
	 * @return Returns the new element with the given value if configured
	 */
	private static IBase newElement(
			FhirTerser theFhirTerser,
			BaseRuntimeChildDefinition theChildDefinition,
			IBase theFromFieldValue,
			Object theConstructorParam) {
		BaseRuntimeElementDefinition runtimeElementDefinition;
		if (theChildDefinition instanceof RuntimeChildChoiceDefinition) {
			runtimeElementDefinition =
					theChildDefinition.getChildElementDefinitionByDatatype(theFromFieldValue.getClass());
		} else {
			runtimeElementDefinition = theChildDefinition.getChildByName(theChildDefinition.getElementName());
		}
		if ("contained".equals(runtimeElementDefinition.getName())) {
			IBaseResource sourceResource = (IBaseResource) theFromFieldValue;
			return theFhirTerser.clone(sourceResource);
		} else if (theConstructorParam == null) {
			return runtimeElementDefinition.newInstance();
		} else {
			return runtimeElementDefinition.newInstance(theConstructorParam);
		}
	}

	private static void mergeFields(
			FhirTerser theTerser,
			IBaseResource theTo,
			BaseRuntimeChildDefinition childDefinition,
			List theFromFieldValues,
			List theToFieldValues) {
		for (IBase theFromFieldValue : theFromFieldValues) {
			if (contains(theFromFieldValue, theToFieldValues)) {
				continue;
			}

			IBase newFieldValue = newElement(theTerser, childDefinition, theFromFieldValue, null);
			if (theFromFieldValue instanceof IPrimitiveType) {
				try {
					Method copyMethod = getMethod(theFromFieldValue, "copy");
					if (copyMethod != null) {
						newFieldValue = (IBase) copyMethod.invoke(theFromFieldValue, new Object[] {});
					}
				} catch (Throwable t) {
					((IPrimitiveType) newFieldValue)
							.setValueAsString(((IPrimitiveType) theFromFieldValue).getValueAsString());
				}
			} else {
				theTerser.cloneInto(theFromFieldValue, newFieldValue, true);
			}

			try {
				theToFieldValues.add(newFieldValue);
			} catch (UnsupportedOperationException e) {
				childDefinition.getMutator().setValue(theTo, newFieldValue);
				theToFieldValues = childDefinition.getAccessor().getValues(theTo);
			}
		}
	}

	/**
	 * Clones the specified resource.
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theInstance    The instance to be cloned
	 * @param             Base resource type
	 * @return Returns a cloned instance
	 */
	public static  T clone(FhirContext theFhirContext, T theInstance) {
		RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theInstance.getClass());
		T retVal = (T) definition.newInstance();

		FhirTerser terser = theFhirContext.newTerser();
		terser.cloneInto(theInstance, retVal, true);
		return retVal;
	}

	/**
	 * Creates a new element instance
	 *
	 * @param theFhirContext Context holding resource definition
	 * @param theElementType Element type name
	 * @param             Base element type
	 * @return Returns a new instance of the element
	 */
	public static  T newElement(FhirContext theFhirContext, String theElementType) {
		BaseRuntimeElementDefinition def = theFhirContext.getElementDefinition(theElementType);
		return (T) def.newInstance();
	}

	/**
	 * Creates a new element instance
	 *
	 * @param theFhirContext      Context holding resource definition
	 * @param theElementType      Element type name
	 * @param theConstructorParam Initialization parameter for the element
	 * @param                  Base element type
	 * @return Returns a new instance of the element with the specified initial value
	 */
	public static  T newElement(
			FhirContext theFhirContext, String theElementType, Object theConstructorParam) {
		BaseRuntimeElementDefinition def = theFhirContext.getElementDefinition(theElementType);
		Validate.notNull(def);
		return (T) def.newInstance(theConstructorParam);
	}

	/**
	 * Creates a new resource definition.
	 *
	 * @param theFhirContext  Context holding resource definition
	 * @param theResourceName Name of the resource in the context
	 * @param              Type of the resource
	 * @return Returns a new instance of the resource
	 */
	public static  T newResource(FhirContext theFhirContext, String theResourceName) {
		RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theResourceName);
		return (T) def.newInstance();
	}

	/**
	 * Creates a new resource definition.
	 *
	 * @param theFhirContext      Context holding resource definition
	 * @param theResourceName     Name of the resource in the context
	 * @param theConstructorParam Initialization parameter for the new instance
	 * @param                  Type of the resource
	 * @return Returns a new instance of the resource
	 */
	public static  T newResource(
			FhirContext theFhirContext, String theResourceName, Object theConstructorParam) {
		RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theResourceName);
		return (T) def.newInstance(theConstructorParam);
	}

	/**
	 * Creates a new BackboneElement.
	 *
	 * @param theFhirContext        Context holding resource definition
	 * @param theTargetResourceName Name of the resource in the context
	 * @param theTargetFieldName    Name of the backbone element in the resource
	 * @return Returns a new instance of the element
	 */
	public static IBaseBackboneElement instantiateBackboneElement(
			FhirContext theFhirContext, String theTargetResourceName, String theTargetFieldName) {
		BaseRuntimeElementDefinition targetParentElementDefinition =
				theFhirContext.getResourceDefinition(theTargetResourceName);
		BaseRuntimeChildDefinition childDefinition = targetParentElementDefinition.getChildByName(theTargetFieldName);
		return (IBaseBackboneElement)
				childDefinition.getChildByName(theTargetFieldName).newInstance();
	}

	private static void clear(List values) {
		if (values == null) {
			return;
		}

		try {
			values.clear();
		} catch (Throwable t) {
			ourLog.debug("Unable to clear values " + String.valueOf(values), t);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy