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

com.xdev.jadoth.lang.reflection.copy.ReflectiveCopier Maven / Gradle / Ivy

/*
 * XDEV Application Framework - XDEV Application Framework
 * Copyright © 2003 XDEV Software (https://xdev.software)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 */
package com.xdev.jadoth.lang.reflection.copy;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.xdev.jadoth.Jadoth;
import com.xdev.jadoth.lang.reflection.JaReflect;
import com.xdev.jadoth.lang.wrapperexceptions.IllegalAccessRuntimeException;




/**
 * The Class ReflectiveCopier.
 *
 * @author Thomas Muenz
 */
public class ReflectiveCopier {

	///////////////////////////////////////////////////////////////////////////
	// static fields //
	//////////////////

	/** The Constant cachedInstanceFields. */
	private static final HashMap, Field[]> cachedInstanceFields = new HashMap, Field[]>();



	///////////////////////////////////////////////////////////////////////////
	// static methods //
	///////////////////

	/**
	 * Gets the all copyable fields.
	 *
	 * @param c the c
	 * @return the all copyable fields
	 */
	public static final ArrayList getAllCopyableFields(final Class c) {
		return JaReflect.getAllFields(c, Modifier.FINAL | Modifier.STATIC);
	}

	/**
	 * Gets the cached instance fields.
	 *
	 * @param c the c
	 * @return the cached instance fields
	 */
	private static Field[] getCachedInstanceFields(final Class c){
		Field[] instanceFields = cachedInstanceFields.get(c);
		if(instanceFields == null) {
			instanceFields = Jadoth.toArray(getAllCopyableFields(c), Field.class);
			cachedInstanceFields.put(c, instanceFields);
		}
		return instanceFields;
	}

	/**
	 * Field untyped copy.
	 *
	 * @param source the source
	 * @param target the target
	 * @param f the f
	 * @param copyHandler the copy handler
	 * @return the object
	 */
	public static final Object fieldUntypedCopy(
			final Object source, final Object target, final Field f, final CopyHandler copyHandler
	){
		if(copyHandler != null){
			copyHandler.copy(f, source, target);
		}
		else {
			//default (shallow) copy if no handler is given
			JaReflect.setFieldValue(f, target, JaReflect.getFieldValue(f, source));
		}
		return target;
	}

	/**
	 * Field copy.
	 *
	 * @param  the generic type
	 * @param  the generic type
	 * @param  the generic type
	 * @param source the source
	 * @param target the target
	 * @param f the f
	 * @param copyHandler the copy handler
	 * @return the t
	 */
	@SuppressWarnings("unchecked")
	public static final  T fieldCopy(
			final S source, final T target, final Field f, final CopyHandler copyHandler
	){
		return (T)fieldUntypedCopy(source, target, f, copyHandler);
	}

	/**
	 * Untyped copy.
	 *
	 * @param source the source
	 * @param target the target
	 * @param commonClass the common class
	 * @return the object
	 * @throws IllegalAccessRuntimeException the illegal access runtime exception
	 */
	public static final Object untypedCopy(final Object source, final Object target, final Class commonClass)
		throws IllegalAccessRuntimeException
	{
		final Field[] instanceFields = getCachedInstanceFields(commonClass);
		for(final Field f : instanceFields) {
			JaReflect.setFieldValue(f, target, JaReflect.getFieldValue(f, source));
		}
		return target;
	}

	/**
	 * Untyped copy.
	 *
	 * @param source the source
	 * @param target the target
	 * @param commonClass the common class
	 * @param fieldsToExclude the fields to exclude
	 * @return the object
	 * @throws IllegalAccessRuntimeException the illegal access runtime exception
	 */
	public static final Object untypedCopy(final Object source, final Object target, final Class commonClass, final Set fieldsToExclude)
		throws IllegalAccessRuntimeException
	{
		if(fieldsToExclude == null) {
			return untypedCopy(source, target, commonClass);
		}

		final Field[] instanceFields = getCachedInstanceFields(commonClass);
		for(final Field f : instanceFields) {
			if(fieldsToExclude.contains(f)){
				continue;
			}
			JaReflect.setFieldValue(f, target, JaReflect.getFieldValue(f, source));
		}
		return target;
	}

	/**
	 * Untyped copy.
	 *
	 * @param source the source
	 * @param target the target
	 * @param commonClass the common class
	 * @param fieldsToExclude the fields to exclude
	 * @param targetFieldCopyHandlers the target field copy handlers
	 * @param targetAnnotationHandlers the target annotation handlers
	 * @param targetClassCopyHandlers the target class copy handlers
	 * @param genericCopyHandler the generic copy handler
	 * @return the object
	 * @throws IllegalAccessRuntimeException the illegal access runtime exception
	 */
	public static final Object untypedCopy(
			final Object source, final Object target, final Class commonClass,
			Set fieldsToExclude,
			Map targetFieldCopyHandlers,
			Map, CopyHandler> targetAnnotationHandlers,
			Map, CopyHandler> targetClassCopyHandlers,
			final CopyHandler genericCopyHandler
	)
		throws IllegalAccessRuntimeException
	{

		if(fieldsToExclude == null) {
			fieldsToExclude = new HashSet(0);
		}

		final Field[] instanceFields = getCachedInstanceFields(commonClass);

		// simple case without copyhandlers
		if(targetClassCopyHandlers == null && targetFieldCopyHandlers == null && targetAnnotationHandlers == null){
			for(final Field f : instanceFields) {
				if(fieldsToExclude.contains(f)) {
					continue;
				}
				if(genericCopyHandler != null){
					genericCopyHandler.copy(f, source, target);
				}
				else {
					JaReflect.setFieldValue(f, target, JaReflect.getFieldValue(f, source));
				}
			}
			return target;
		}



		// special treatment with copyhandlers

		if(targetClassCopyHandlers == null) {
			targetClassCopyHandlers = new HashMap, CopyHandler>(0);
		}
		if(targetAnnotationHandlers == null) {
			targetAnnotationHandlers = new HashMap, CopyHandler>(0);
		}
		if(targetFieldCopyHandlers == null) {
			targetFieldCopyHandlers = new HashMap(0);
		}
		for(final Field f : instanceFields) {
			if(fieldsToExclude.contains(f)) {
				continue;
			}

			//1.) fieldCopyHandler has highest priority
			CopyHandler handler = targetFieldCopyHandlers.get(f);

			//2.) if no fieldCopyHandler then look for annotationCopyHandler
			if(handler == null){
				final Annotation[] annotations = f.getAnnotations();
				for (final Annotation a : annotations) {
					handler = targetAnnotationHandlers.get(a.annotationType());
					if(handler != null){
						break;
					}
				}
			}
			//3.) if neither fieldCopyHandler nor annotationCopyHandler then look for classCopyHandler
			if(handler == null){
				handler = targetClassCopyHandlers.get(f.getType());
			}
			//4.) finally, try using genericCopyHandler
			if(handler == null){
				handler = genericCopyHandler;
			}

			fieldCopy(source, target, f, handler);
		}

		return target;
	}

	/**
	 * This method is a convenience version for copy(source, target, commonClass, null) which means
	 * that no fields are excluded from copiing.
	 * 

* See {@link #execute(Object, Object, Class, Set)} for further details. * * @param the common type of source and target. Can be the same class or a common super class. * @param the type of the source object, which must be of Type O * @param the type of the source object, which must be of Type O * @param source the source object from which the values are read. * @param target the target object to which the values from source are written * @param commonClass the Class that determines the level, at field values are copied from source and target. * Can be the same class or a common super class. * @return * @throws IllegalAccessRuntimeException the illegal access runtime exception * @see {@link #execute(Object, Object, Class, Set)} */ @SuppressWarnings("unchecked") public static final T copy(final S source, final T target, final Class commonClass) throws IllegalAccessRuntimeException { return (T)untypedCopy(source, target, commonClass); } /** * Copies the values of all copiable instance fields (non static, non final) from source object source * to target object target. *

* The parameter commonClass ensures the type compatibility of source and target. * It also determines the class level, on which the copiing shall be done. *

* Examples:
* 1.)
* Both sourceObj and targetObj are objects of Class MyClassA extends MyCommonClass. *

* The call copy(sourceObj, targetObj, MyClassA.class) enables the compiler to check if both objects are really * of the same Type and instructs the copy method to copy all (non static, non final) fields from Classes MyClassA and * MyCommonClass as well. *

* The call copy(sourceObj, targetObj, MyCommonClass.class) causes the method only to copy fields of Class * MyCommonClass. *

* 2.)
* sourceObj is of Class MyClassA extends MyCommonClass.
* targetObj is of Class MyClassB extends MyCommonClass.
*

* The call copy(sourceObj, targetObj, MyCommonClass.class) would be valid.
* The call copy(sourceObj, targetObj, MyClassA.class) would cause a compiler error
* *

* Notes:

    *
  • copy does not create any new instances *
  • Copiable fields are cached after the first call to improve performance. *
  • The source object will not be modified in any kind. *
  • All field values of target that are not overwritten by values from source keep their values. *
  • The Java IllegalAccessException that can occur by reflection access should be prevented by internal mechanisms. * In case it might still occur, it is nested into a RuntimeException of type IllegalAccessRuntimeException *
* * @param the common type of source and target. Can be the same class or a common super class. * @param the type of the source object, which must be of Type O * @param the type of the source object, which must be of Type O * @param source the source object from which the values are read. * @param target the target object to which the values from source are written * @param commonClass the Class that determines the level, at field values are copied from source and target. * Can be the same class or a common super class. * @param fieldsToExclude fields that shall explicitly excluded from copiing. * @return * @throws IllegalAccessRuntimeException the illegal access runtime exception * @see {@link #execute(Object, Object, Class)} */ @SuppressWarnings("unchecked") public static final T copy(final S source, final T target, final Class commonClass, final Set fieldsToExclude) throws IllegalAccessRuntimeException { if(fieldsToExclude == null) { return (T)untypedCopy(source, target, commonClass); } return (T)untypedCopy(source, target, commonClass, fieldsToExclude); } /** * Copy. * * @param the generic type * @param the generic type * @param the generic type * @param source the source * @param target the target * @param commonClass the common class * @param fieldsToExclude the fields to exclude * @param targetFieldCopyHandlers the target field copy handlers * @param targetAnnotationHandlers the target annotation handlers * @param targetClassCopyHandlers the target class copy handlers * @param genericCopyHandler the generic copy handler * @return the t * @throws IllegalAccessRuntimeException the illegal access runtime exception */ @SuppressWarnings("unchecked") public static final T copy(final S source, final T target, final Class commonClass, final Set fieldsToExclude, final Map targetFieldCopyHandlers, final Map, CopyHandler> targetAnnotationHandlers, final Map, CopyHandler> targetClassCopyHandlers, final CopyHandler genericCopyHandler ) throws IllegalAccessRuntimeException { return (T)untypedCopy(source, target, commonClass, fieldsToExclude, targetFieldCopyHandlers, targetAnnotationHandlers, targetClassCopyHandlers, genericCopyHandler); } /////////////////////////////////////////////////////////////////////////// // instance fields // //////////////////// /** The location class annotation class handlers. */ private HashMap, HashMap, CopyHandler>> locationClassAnnotationClassHandlers = new HashMap, HashMap, CopyHandler>>(); /** The location class copy target class handlers. */ private HashMap, HashMap, CopyHandler>> locationClassCopyTargetClassHandlers = new HashMap, HashMap, CopyHandler>>(); /** The location class copy target field handlers. */ private HashMap, HashMap> locationClassCopyTargetFieldHandlers = new HashMap, HashMap>(); /* * only one GenericCopyHandler per locationClass makes sense */ /** The location class generic handlers. */ private HashMap, CopyHandler> locationClassGenericHandlers = new HashMap, CopyHandler>(); /** The general annotation handlers. */ private HashMap, CopyHandler> generalAnnotationHandlers = new HashMap, CopyHandler>(); /** The general copy target class handlers. */ private HashMap, CopyHandler> generalCopyTargetClassHandlers = new HashMap, CopyHandler>(); /** The general copy target field handlers. */ private HashMap generalCopyTargetFieldHandlers = new HashMap(); /** The generic handler. */ private CopyHandler genericHandler = null; /////////////////////////////////////////////////////////////////////////// // constructors // ///////////////// /** * Trivial default constructor. */ public ReflectiveCopier() { super(); } /////////////////////////////////////////////////////////////////////////// // declared methods // ///////////////////// /** * Sets the generic copy handler. * * @param handler the handler * @return the reflective copier */ public ReflectiveCopier setGenericCopyHandler(final CopyHandler handler){ this.genericHandler = handler; return this; } /** * Adds the copy handler by location class. * * @param handler the handler * @param locationClass the location class * @return the reflective copier */ public ReflectiveCopier addCopyHandlerByLocationClass(final CopyHandler handler, final Class locationClass){ return this.addCopyHandler(handler, locationClass, null, null, null); } /** * Adds the copy handler by copy class. * * @param handler the handler * @param copyTargetClass the copy target class * @return the reflective copier */ public ReflectiveCopier addCopyHandlerByCopyClass(final CopyHandler handler, final Class copyTargetClass){ return this.addCopyHandler(handler, null, copyTargetClass, null, null); } /** * Adds the copy handler by annotation. * * @param handler the handler * @param copyTargetAnnotation the copy target annotation * @return the reflective copier */ public ReflectiveCopier addCopyHandlerByAnnotation(final CopyHandler handler, final Class copyTargetAnnotation){ return this.addCopyHandler(handler, null, null, copyTargetAnnotation, null); } /** * Adds the copy handler by copy field. * * @param handler the handler * @param copyTargetField the copy target field * @return the reflective copier */ public ReflectiveCopier addCopyHandlerByCopyField(final CopyHandler handler, final Field copyTargetField){ return this.addCopyHandler(handler, null, null, null, copyTargetField); } /** * Registers handler to be used by this DeepCopier. * * @param handler the handler * @param locationClass the location class * @param copyTargetClass the copy target class * @param copyTargetAnnotation the copy target annotation * @param copyTargetField the copy target field * @return the reflective copier * @return */ public ReflectiveCopier addCopyHandler( final CopyHandler handler, final Class locationClass, final Class copyTargetClass, final Class copyTargetAnnotation, final Field copyTargetField ){ if(locationClass != null){ // register locationClass-specific handlers if(copyTargetField != null) { HashMap targetFieldHandlers = this.locationClassCopyTargetFieldHandlers.get(locationClass); if(targetFieldHandlers == null){ targetFieldHandlers = new HashMap(); this.locationClassCopyTargetFieldHandlers.put(locationClass, targetFieldHandlers); } targetFieldHandlers.put(copyTargetField, handler); } if(copyTargetAnnotation != null) { HashMap, CopyHandler> targetAnnotationHandlers = this.locationClassAnnotationClassHandlers.get(locationClass); if(targetAnnotationHandlers == null){ targetAnnotationHandlers = new HashMap, CopyHandler>(); this.locationClassAnnotationClassHandlers.put(locationClass, targetAnnotationHandlers); } targetAnnotationHandlers.put(copyTargetAnnotation, handler); } else if(copyTargetClass != null){ HashMap, CopyHandler> targetClassHandlers = this.locationClassCopyTargetClassHandlers.get(locationClass); if(targetClassHandlers == null){ targetClassHandlers = new HashMap, CopyHandler>(); this.locationClassCopyTargetClassHandlers.put(locationClass, targetClassHandlers); } targetClassHandlers.put(copyTargetClass, handler); } else { this.locationClassGenericHandlers.put(locationClass, handler); } } else if(copyTargetField != null){ this.generalCopyTargetFieldHandlers.put(copyTargetField, handler); } else if(copyTargetAnnotation != null){ this.generalAnnotationHandlers.put(copyTargetAnnotation, handler); } else if(copyTargetClass != null){ this.generalCopyTargetClassHandlers.put(copyTargetClass, handler); } else { this.genericHandler = handler; } return this; } /** * Gets the all copy target class handlers for. * * @param locationClass the location class * @return the all copy target class handlers for */ private HashMap, CopyHandler> getAllCopyTargetClassHandlersFor(final Class locationClass) { final HashMap , CopyHandler> locationClassMap; locationClassMap = this.locationClassCopyTargetClassHandlers.get(locationClass); if(locationClassMap == null){ return this.generalCopyTargetClassHandlers; } final HashMap, CopyHandler> returnMap = new HashMap, CopyHandler>(); returnMap.putAll(this.generalCopyTargetClassHandlers); returnMap.putAll(locationClassMap); return returnMap; } /** * Gets the all copy target field handlers for. * * @param locationClass the location class * @return the all copy target field handlers for */ private HashMap getAllCopyTargetFieldHandlersFor(final Class locationClass) { final HashMap locationClassMap; locationClassMap = this.locationClassCopyTargetFieldHandlers.get(locationClass); if(locationClassMap == null){ return this.generalCopyTargetFieldHandlers; } final HashMap returnMap = new HashMap(); returnMap.putAll(this.generalCopyTargetFieldHandlers); returnMap.putAll(locationClassMap); return returnMap; } /** * Gets the all copy target annotation handlers for. * * @param locationClass the location class * @return the all copy target annotation handlers for */ private HashMap, CopyHandler> getAllCopyTargetAnnotationHandlersFor(final Class locationClass) { final HashMap, CopyHandler> locationClassMap; locationClassMap = this.locationClassAnnotationClassHandlers.get(locationClass); if(locationClassMap == null){ return this.generalAnnotationHandlers; } final HashMap, CopyHandler> returnMap = new HashMap, CopyHandler>(); returnMap.putAll(this.generalAnnotationHandlers); returnMap.putAll(locationClassMap); return returnMap; } /** * Execute. * * @param the generic type * @param the generic type * @param the generic type * @param source the source * @param target the target * @param commonClass the common class * @return the t * @throws IllegalAccessRuntimeException the illegal access runtime exception */ public T execute(final S source, final T target, final Class commonClass) throws IllegalAccessRuntimeException { return this.execute(source, target, commonClass, null); } /** * Execute. * * @param the generic type * @param the generic type * @param the generic type * @param source the source * @param target the target * @param commonClass the common class * @param fieldsToExclude the fields to exclude * @return the t * @throws IllegalAccessRuntimeException the illegal access runtime exception */ public T execute(final S source, final T target, final Class commonClass, final Set fieldsToExclude) throws IllegalAccessRuntimeException { final Class locationClass = source.getClass(); HashMap, CopyHandler> copyTargetClassHandlers = this.getAllCopyTargetClassHandlersFor(locationClass); HashMap, CopyHandler> annotationHandlers = this.getAllCopyTargetAnnotationHandlersFor(locationClass); HashMap copyTargetFieldHandlers = this.getAllCopyTargetFieldHandlersFor(locationClass); //all 3 null will cause the copy method to take a more performant code branch if(copyTargetClassHandlers.size() + copyTargetFieldHandlers.size() + annotationHandlers.size() == 0 ){ copyTargetClassHandlers = null; copyTargetFieldHandlers = null; annotationHandlers = null; } CopyHandler genericHandler = this.locationClassGenericHandlers.get(locationClass); if(genericHandler == null){ genericHandler = this.genericHandler; } return copy(source, target, commonClass, fieldsToExclude, copyTargetFieldHandlers, annotationHandlers, copyTargetClassHandlers, genericHandler); } }