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 extends Annotation> 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 extends Annotation> 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);
}
}