org.unitils.mock.core.proxy.CloneUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.unitils.mock.core.proxy.CloneUtil.java

Source

/*
 *
 *  * Copyright 2010,  Unitils.org
 *  *
 *  * 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.
 *
 */
package org.unitils.mock.core.proxy;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.objenesis.Objenesis;
import org.objenesis.ObjenesisStd;
import org.unitils.core.UnitilsException;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Map;

import static java.lang.reflect.Modifier.isStatic;
import static java.lang.reflect.Modifier.isTransient;
import static org.unitils.mock.core.proxy.ProxyUtils.isProxy;

/**
 * Utility class for deep cloning objects.
 * In a deep clone, not only the object itself is cloned, but also all the inner objects.
 *
 * @author Tim Ducheyne
 * @author Filip Neven
 * @author Kenny Claes
 */
public class CloneUtil {

    /* The logger instance for this class */
    private static Log logger = LogFactory.getLog(CloneUtil.class);

    /* Objenesis instance for creating new instances of types */
    private static Objenesis objenesis = new ObjenesisStd(true);

    /**
     * Creates a deep clone of the given object. If for some reason, the clone cannot be made, a warning is logged
     * and the object itself will be returned. This is also true for all inner objects. If an inner object
     * cannot be cloned, the object itself is used instead.
     *
     * @param object The object to clone
     * @return The cloned instance
     */
    @SuppressWarnings({ "unchecked" })
    public static <T> T createDeepClone(T object) {
        try {
            return (T) cloneObject(object, new IdentityHashMap<Object, Object>());

        } catch (Throwable e) {
            throw new UnitilsException("Unexpected exception during cloning of " + object, e);
        }
    }

    /**
     * Actual implementation of the cloning.
     *
     * It will try several ways to clone the object. First it will look for the simple cases: null, primitives,
     * immutables... If not it will check whether it's an array and clone it using the {@link #cloneArray} method.
     * Finally it will see whether the object is cloneable and the clone method can be used. If not, Objenisis is
     * used to create the instance. The last step is to recursively do the same operation for the inner fields.
     *
     * An object is cloned once. All created clones are put in a cache and if an object is to be cloned a second time,
     * the cached instance is used. This way the object graph is preserved.
     *
     * @param instanceToClone The instance, not null
     * @param cloneCache      The cached clones, not null
     * @return The clone, the instance to clone if the clone could not be made
     */
    protected static Object cloneObject(Object instanceToClone, Map<Object, Object> cloneCache) throws Throwable {
        if (instanceToClone == null) {
            return null;
        }
        // check whether the instance was already cloned, this will preserve the object graph
        if (cloneCache.containsKey(instanceToClone)) {
            return cloneCache.get(instanceToClone);
        }
        // if the value is immutable, return the instance itself
        if (isImmutable(instanceToClone)) {
            return instanceToClone;
        }
        // check for arrays
        if (instanceToClone.getClass().isArray()) {
            return cloneArray(instanceToClone, cloneCache);
        }
        // if the instance is cloneable, try to clone it
        if (instanceToClone instanceof Cloneable) {
            return createInstanceUsingClone(instanceToClone);
        }
        // don't clone java classes (unless they are cloneable)
        if (isJdkClass(instanceToClone)) {
            return instanceToClone;
        }
        // don't clone proxies
        if (isProxy(instanceToClone)) {
            return instanceToClone;
        }
        // try to clone it ourselves
        Object clonedInstance = createInstanceUsingObjenesis(instanceToClone);

        // Unable to create an instance
        if (clonedInstance == null) {
            logger.warn("Could not create an instance of class " + instanceToClone.getClass() + " using objenesis");
            return instanceToClone;
        }
        // cache the clone
        cloneCache.put(instanceToClone, clonedInstance);

        // recursively do the same for all inner fields
        cloneFields(instanceToClone.getClass(), instanceToClone, clonedInstance, cloneCache);
        return clonedInstance;
    }

    /**
     * @param instanceToClone The instance, not null
     * @return True if the instance is immutable, e.g. a primitive
     */
    protected static boolean isImmutable(Object instanceToClone) {
        Class<?> clazz = instanceToClone.getClass();

        if (clazz.isPrimitive() || clazz.isEnum() || clazz.isAnnotation()) {
            return true;
        }
        if (instanceToClone instanceof Number || instanceToClone instanceof String
                || instanceToClone instanceof Character || instanceToClone instanceof Boolean) {
            return true;
        }
        if (clazz.getName().startsWith("com.google.common.collect") && clazz.getName().contains("Immutable")) {
            return true;
        }

        return false;
    }

    /**
     * @param instanceToClone The instance, not null
     * @return True if the instance is should not be cloned, e.g. a java lang class or a data source
     */
    protected static boolean isJdkClass(Object instanceToClone) {
        if (instanceToClone instanceof Collection || instanceToClone instanceof Map) {
            // make sure to clone collections
            return false;
        }
        String className = instanceToClone.getClass().getName();
        if (className.startsWith("java.")) {
            return true;
        }
        return false;
    }

    /**
     * If the given value is cloneable and the cloning succeeds, the clone is returned, else null is returned.
     *
     * @param instanceToClone The instance, not null
     * @return The clone if it could be cloned, else null
     */
    protected static Object createInstanceUsingClone(Object instanceToClone) {
        try {
            Method cloneMethod = Object.class.getDeclaredMethod("clone");
            cloneMethod.setAccessible(true);
            return cloneMethod.invoke(instanceToClone);

        } catch (Throwable t) {
            return null;
        }
    }

    /**
     * Tries to create an instance of the same type as the given value using Objenesis.
     *
     * @param instanceToClone The instance, not null
     * @return The new instance if it could be created, else null
     */
    protected static Object createInstanceUsingObjenesis(Object instanceToClone) {
        try {
            return objenesis.newInstance(instanceToClone.getClass());

        } catch (Throwable t) {
            return null;
        }
    }

    /**
     * Clones all values in all fields of the given class and superclasses.
     *
     * @param clazz           The current class
     * @param instanceToClone The instance, not null
     * @param clonedInstance  The clone, not null
     * @param cloneCache      The cached clones, not null
     */
    protected static void cloneFields(Class<?> clazz, Object instanceToClone, Object clonedInstance,
            Map<Object, Object> cloneCache) throws Throwable {
        if (clazz == null || Object.class.equals(clazz)) {
            return;
        }

        Field[] fields = clazz.getDeclaredFields();
        AccessibleObject.setAccessible(fields, true);

        for (Field field : fields) {
            // skip static fields
            if (isStatic(field.getModifiers())) {
                continue;
            }
            if (field.getDeclaringClass().getName().startsWith("org.hibernate")
                    && isTransient(field.getModifiers())) {
                continue;
            }

            Object fieldValue = field.get(instanceToClone);
            Object clonedFieldValue = cloneObject(fieldValue, cloneCache);
            field.set(clonedInstance, clonedFieldValue);
        }
        cloneFields(clazz.getSuperclass(), instanceToClone, clonedInstance, cloneCache);
    }

    /**
     * Clones the given array and all it's elements.
     *
     * @param arrayToClone The array, not null
     * @param cloneCache   The cached clones, not null
     * @return The cloned array, not null
     */
    protected static Object cloneArray(Object arrayToClone, Map<Object, Object> cloneCache) throws Throwable {
        int lenght = Array.getLength(arrayToClone);
        Object clonedArray = Array.newInstance(arrayToClone.getClass().getComponentType(), lenght);
        // Make sure we put the array in the cache before we start cloning the elements, since the array itself may also
        // be one of the elements, and in this case we want to reuse the same element, to avoid infinite recursion.
        cloneCache.put(arrayToClone, clonedArray);

        for (int i = 0; i < lenght; i++) {
            Object elementValue = Array.get(arrayToClone, i);
            Object clonedElementValue = cloneObject(elementValue, cloneCache);
            Array.set(clonedArray, i, clonedElementValue);
        }
        return clonedArray;
    }

}