DeepCopyHelper.java

package fr.inrae.agroclim.indicators.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Helper methods to make deep copies using copy constructors, in order to avoid {@link Cloneable}.
 *
 * @author Olivier Maury
 */
public interface DeepCopyHelper {

    /**
     * Make a copy of an object using a copy constructor.
     *
     * @param <T>   object class
     * @param obj   object to copy
     * @param clazz object class
     * @return copied object
     */
    static <T> T copyObject(final T obj, final Class<T> clazz) {
        if (obj == null) {
            return null;
        }

        final Constructor<T> copyConstructor = getCopyConstructor(clazz);

        if (copyConstructor == null) {
            return obj;
        }

        try {
            return copyConstructor.newInstance(obj);
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException ex) {
            throw new UnsupportedOperationException("Cannot create copy of " + obj, ex);
        }
    }

    /**
     * Get the copy constructor of a class if exists or return null.
     *
     * @param <T>   class
     * @param clazz class
     * @return copy constructor or null
     */
    static <T> Constructor<T> getCopyConstructor(final Class<T> clazz) {
        try {
            return clazz.getConstructor(clazz);
        } catch (NoSuchMethodException | SecurityException ex) {
            return null;
        }
    }

    /**
     * Deep copy a list.
     *
     * @param <T>   class of list items
     * @param list  list to copy
     * @param clazz class of list items
     * @return copied list
     */
    static <T> List<T> deepCopy(final List<T> list, final Class<T> clazz) {
        if (list == null) {
            return null;
        }

        final List<T> copy;
        if (list instanceof ArrayList) {
            copy = new ArrayList<>();
        } else {
            throw new UnsupportedOperationException("Only ArrayList is supported.");
        }
        for (T obj : list) {
            if (obj == null) {
                copy.add(null);
            } else if (obj instanceof Enum<?>) {
                copy.add(obj);
            } else {
                copy.add(copyObject(obj, clazz));
            }
        }
        return copy;
    }

    /**
     * Deep copy a map.
     *
     * @param <T>        class of key items
     * @param <U>        class of value items
     * @param map        map to copy
     * @param keyClass   class of key items
     * @param valueClass class of value items
     * @return copied map
     */
    static <T, U> Map<T, U> deepCopy(final Map<T, U> map, final Class<T> keyClass, final Class<U> valueClass) {
        if (map == null) {
            return null;
        }

        final Map<T, U> copy;
        if (map instanceof HashMap) {
            copy = new HashMap<>();
        } else {
            throw new UnsupportedOperationException("Only HashMap is supported.");
        }

        for (final Entry<T, U> entry : map.entrySet()) {
            T copiedKey = copyObject(entry.getKey(), keyClass);
            U copiedValue = copyObject(entry.getValue(), valueClass);
            copy.put(copiedKey, copiedValue);
        }
        return copy;
    }
}