001package org.unix4j.util;
002
003import java.util.Arrays;
004import java.util.List;
005import java.util.Map;
006import java.util.Set;
007
008/**
009 * Utility class with static methods for cloning, for instance to deep-clone
010 * nested lists, sets and maps.
011 */
012public class CloneUtil {
013        
014        /**
015         * Creates a deep clone of the specified value and returns it. Deep clone
016         * means that lists, sets, maps, arrays and other cloneable objects are 
017         * recursively cloned.
018         * 
019         * @param <T>   the type of the value to clone 
020         * @param value the value to clone
021         * @return a deep clone of the specified value
022         */
023        @SuppressWarnings("unchecked")
024        public static <T> T cloneDeep(T value) {
025                if (value instanceof List) {
026                        return (T) cloneList((List<?>) value);
027                }
028                if (value instanceof Set) {
029                        return (T) cloneSet((Set<?>) value);
030                }
031                if (value instanceof Map) {
032                        return (T) cloneMap((Map<?, ?>) value);
033                }
034                if (value instanceof Cloneable || value.getClass().isArray()) {
035                        return (T) cloneReflective(value);
036                }
037                return value;
038        }
039
040        /**
041         * Clones the given value and returns the non-deep clone. Cloning is
042         * performed by calling the clone() method through reflection. If no such
043         * method exists or if calling the clone method fails, an exception is
044         * thrown.
045         * 
046         * @param <T>
047         *            the type of the value to clone
048         * @param value
049         *            the value to clone
050         * @return the non-deep clone of the value
051         * @throws IllegalArgumentException
052         *             if no clone method exists or if invoking the clone method
053         *             caused an exception
054         */
055        @SuppressWarnings("unchecked")
056        public static <T> T cloneReflective(T value) {
057                try {
058                        return (T) value.getClass().getMethod("clone").invoke(value);
059                } catch (Exception e) {
060                        throw new IllegalArgumentException("clone failed for value type " + value.getClass().getName() + ", e=" + e, e);
061                }
062        }
063
064        @SuppressWarnings({ "rawtypes", "unchecked" })
065        private static Map cloneMap(Map value) {
066                final Map<Object, Object> clone = cloneReflective(value);
067                for (final Map.Entry e : clone.entrySet()) {
068                        e.setValue(cloneDeep(e.getValue()));
069                }
070                return clone;
071        }
072
073        @SuppressWarnings({ "rawtypes", "unchecked" })
074        private static List cloneList(List value) {
075                final List<Object> clone;
076                if (value.getClass().getName().equals("java.util.Arrays$ArrayList")) {
077                        clone = Arrays.asList(value.toArray());
078                } else {
079                        clone = cloneReflective(value);
080                }
081                for (int i = 0; i < clone.size(); i++) {
082                        clone.set(i, cloneDeep(clone.get(i)));
083                }
084                return clone;
085        }
086
087        @SuppressWarnings({ "unchecked", "rawtypes" })
088        private static Set cloneSet(Set value) {
089                final Set<Object> clone = cloneReflective(value);
090                clone.clear();
091                for (final Object element : value) {
092                        clone.add(cloneDeep(element));
093                }
094                return clone;
095        }
096
097        // no instances
098        private CloneUtil() {
099                super();
100        }
101}