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

org.iworkz.common.helper.CloneHelper Maven / Gradle / Ivy

The newest version!
package org.iworkz.common.helper;

import java.io.Closeable;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Date;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Singleton;

import org.iworkz.common.reflection.PropertyInfo;


@Singleton
public class CloneHelper {
	
	private static final Object PROPERTIES_CACHE_LOCK = new Object();
	private static final Map,PropertyInfo[]> PROPERTIES_CACHE = new IdentityHashMap<>();
	

	
	@Inject
	protected ReflectionHelper reflectionHelper;
	
	/**
	 * Clone the source bean and the properties of the source bean recursively (deep copy).
	 * Cyclic dependencies are detected and handled correctly.
	 * 
	 * @param source
	 * @return
	 */
	public  T cloneBean(T source) {
		Map instanceMap = new IdentityHashMap<>();
		return cloneBean(source,instanceMap);
	}
	
	@SuppressWarnings("unchecked")
	public  T cloneBean(final T source, final Map instanceMap) {
		if (source != null) {
			if (instanceMap.containsKey(source)) {
				return (T)instanceMap.get(source);
			} else {
				Class sourceClass = source.getClass();
				T destination;
				if (sourceClass.isArray()) {
					final Class componentType = sourceClass.getComponentType();
					boolean finalImmutuable = reflectionHelper.isImmutable(componentType);
					destination = (T)cloneArray(source,componentType,finalImmutuable,instanceMap);
				} else if (Collection.class.isAssignableFrom(sourceClass)) {
					destination = (T)cloneCollection((Collection)source,instanceMap);
				} else if (Map.class.isAssignableFrom(sourceClass)) {
					destination = (T)cloneMap((Map)source,instanceMap);
				} else if (isCloneRequired(sourceClass)) {
					destination = clone(source,instanceMap);
				} else {
					destination = source;
				}
				return destination;
			}
		}
		return null;
	}
	
	protected  T clone(final T source, final Map instanceMap) {
		T destination = createCustomClone(source,instanceMap);
		if (destination != null) {
			instanceMap.put(source, destination);
		} else {
			/* create generic clone */
			destination = createClone(source);
			instanceMap.put(source, destination);
			cloneProperties(source, destination, instanceMap);
		}
		return destination;
	}
	
	@SuppressWarnings("unchecked")
	protected  T createClone(final T source) {
		return (T)reflectionHelper.createObject(source.getClass());
	}
	
	@SuppressWarnings("unchecked")
	protected  T createCustomClone(final T source, final Map instanceMap) {
		T destination = null;
		Class sourceClass = source.getClass();
		if (Date.class.isAssignableFrom(sourceClass)) {
			if (java.sql.Date.class == sourceClass) {
				destination = (T)new java.sql.Date(((java.sql.Date)source).getTime());
			} else if (java.util.Date.class == sourceClass) {
				destination = (T)new java.util.Date(((java.util.Date)source).getTime());
			} else if (java.sql.Time.class == sourceClass) {
				destination = (T)new java.sql.Time(((java.sql.Time)source).getTime());
			} else if (java.sql.Timestamp.class == sourceClass) {
				destination = (T)new java.sql.Timestamp(((java.sql.Timestamp)source).getTime());
			} else {
				destination = null;
			}
		}
		return destination;
	}
	
	protected  void cloneProperties(final T source, final T destination, final Map instanceMap) {
		if (source != null) {
			if (destination == null) {
				throw new IllegalArgumentException("The destination bean is null");
			}
			try {
				PropertyInfo[] propertyInfos = getPropertyInfos(source.getClass());
				for (PropertyInfo propertyInfo : propertyInfos) {
					Object value = propertyInfo.getReadMethod().invoke(source);
					if (value != null) {
						if (propertyInfo.isImmutable()) {
							propertyInfo.getWriteMethod().invoke(destination,value);
						} else if (propertyInfo.isMap()) {
							propertyInfo.getWriteMethod().invoke(destination,cloneMap((Map)value,instanceMap));
						} else if (propertyInfo.isCollection()) {
							propertyInfo.getWriteMethod().invoke(destination,cloneCollection((Collection)value,instanceMap));
						} else if (propertyInfo.isArray()) {
							final Class componentType = value.getClass().getComponentType();
							propertyInfo.getWriteMethod().invoke(destination,cloneArray(value,componentType,propertyInfo.isComponentImmutable(),instanceMap));
						} else {
							propertyInfo.getWriteMethod().invoke(destination,cloneBean(value,instanceMap));
						}
					}
				}
			} catch (Exception e) {
				throw new RuntimeException("Can not clone bean '"+source.getClass().getCanonicalName()+"'",e);
			}
		} 
	}
	
	@SuppressWarnings("unchecked")
	protected  Collection createCollection(final Collection sourceCollection) {
		return (Collection)reflectionHelper.createObject(sourceCollection.getClass());
	}
	
	@SuppressWarnings("unchecked")
	protected  Map createMap(final Map sourceMap) {
		return (Map)reflectionHelper.createObject(sourceMap.getClass());
	}

	protected boolean isCloneRequired(Class sourceClass) {
		if (reflectionHelper.isImmutable(sourceClass)) {
			return false;
		}
		if (Closeable.class.isAssignableFrom(sourceClass)) {
			return false;
		}
		return true;
	}
	
	protected Object cloneArray(Object source, Class sourceClass, final boolean finalImmutable, final Map instanceMap) {
		if (instanceMap.containsKey(source)) {
			return instanceMap.get(source);
		} else {
			int length = Array.getLength(source);
			Object destination = Array.newInstance(sourceClass,length);
			instanceMap.put(source, destination);
			if (finalImmutable) {
				System.arraycopy(source,0,destination,0,length);
			} else {
				for (int i=0;i Collection cloneCollection(final Collection collection, final Map instanceMap) {
		if (instanceMap.containsKey(collection)) {
			return (Collection)instanceMap.get(collection);
		} else {
			Collection destination = createCollection(collection);
			instanceMap.put(collection, destination);
			for (T sourceItem : collection) {
				destination.add(cloneBean(sourceItem,instanceMap));
			}
			return destination;
		}
	}
	
	protected  Map cloneMap(final Map map, final Map instanceMap) {
		if (instanceMap.containsKey(map)) {
			return (Map)instanceMap.get(map);
		} else {
			Map destination = createMap(map);
			instanceMap.put(map, destination);
			for (T sourceKey : map.keySet()) {
				R sourceItem = map.get(sourceKey);
				destination.put(sourceKey,cloneBean(sourceItem,instanceMap));
			}
			return destination;
		}
	}
	
	protected PropertyInfo[] getPropertyInfos(final Class sourceClass) {
		PropertyInfo[] propertyInfos = PROPERTIES_CACHE.get(sourceClass);
		if (propertyInfos == null) {
			synchronized (PROPERTIES_CACHE_LOCK) {
				try {
					/* get propertyInfos again but this time synchronized */
					propertyInfos = PROPERTIES_CACHE.get(sourceClass);
					if (propertyInfos == null) {
						List propertyInfoList = reflectionHelper.createPropertyInfos(sourceClass);
						propertyInfos = propertyInfoList.toArray(new PropertyInfo[propertyInfoList.size()]);
						PROPERTIES_CACHE.put(sourceClass, propertyInfos);
					}
				} catch (Exception ex) {
					throw new RuntimeException("Can not create property infos",ex);
				}
			}
		}
		return propertyInfos;
	}
	

	
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy