org.libreplan.business.util.deepcopy.DeepCopy.java Source code

Java tutorial

Introduction

Here is the source code for org.libreplan.business.util.deepcopy.DeepCopy.java

Source

/*
 * This file is part of LibrePlan
 *
 * Copyright (C) 2009-2010 Fundacin para o Fomento da Calidade Industrial e
 *                         Desenvolvemento Tecnolxico de Galicia
 * Copyright (C) 2010-2011 Igalia, S.L.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.libreplan.business.util.deepcopy;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.commons.lang3.Validate;
import org.hibernate.proxy.HibernateProxy;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.libreplan.business.workingday.EffortDuration;

/**
 * @author scar Gonzlez Fernndez <ogonzalez@igalia.com>
 */
public class DeepCopy {

    private static Set<Class<?>> inmmutableTypes = new HashSet<>(Arrays.<Class<?>>asList(Boolean.class,
            String.class, BigDecimal.class, Double.class, Float.class, Integer.class, Short.class, Byte.class,
            Character.class, LocalDate.class, DateTime.class, EffortDuration.class));

    private static List<ICustomCopy> DEFAULT_CUSTOM_COPIERS = Arrays.asList(new DateCopy(), new SetCopy(),
            new MapCopy(), new ListCopy());

    private Map<ByIdentity, Object> alreadyCopiedObjects = new HashMap<>();

    public static boolean isImmutableType(Class<?> klass) {
        return klass.isPrimitive() || isEnum(klass) || inmmutableTypes.contains(klass);
    }

    private static boolean isEnum(Class<?> klass) {
        Class<?> currentClass = klass;
        do {
            if (currentClass.isEnum()) {
                return true;
            }
            currentClass = currentClass.getSuperclass();
        } while (currentClass != null);

        return false;
    }

    public interface ICustomCopy {

        boolean canHandle(Object object);

        Object instantiateCopy(Strategy strategy, Object originValue);

        void copyDataToResult(DeepCopy deepCopy, Object origin, Strategy strategy, Object result);
    }

    private static class DateCopy implements ICustomCopy {

        @Override
        public boolean canHandle(Object object) {
            return object instanceof Date;
        }

        @Override
        public void copyDataToResult(DeepCopy deepCopy, Object origin, Strategy strategy, Object result) {
            // Already completed
        }

        @Override
        public Object instantiateCopy(Strategy strategy, Object originValue) {
            Date date = (Date) originValue;
            return new Date(date.getTime());
        }

    }

    private abstract static class CollectionCopy implements ICustomCopy {

        @Override
        public Object instantiateCopy(Strategy strategy, Object originValue) {
            return getResultData(originValue);
        }

        protected abstract Collection<Object> getResultData(Object originValue);

        @Override
        public void copyDataToResult(DeepCopy deepCopy, Object origin, Strategy strategy, Object result) {
            copy(deepCopy, origin, strategy, (Collection<Object>) result);
        }

        private void copy(DeepCopy deepCopy, Object origin, Strategy strategy, Collection<Object> destination) {
            Strategy childrenStrategy = getChildrenStrategy(strategy);
            for (Object each : originDataAsIterable(origin)) {
                destination.add(deepCopy.copy(each, childrenStrategy));
            }
        }

        private Strategy getChildrenStrategy(Strategy strategy) {
            return strategy == Strategy.SHARE_COLLECTION_ELEMENTS ? Strategy.SHARE : strategy;
        }

        private Iterable<Object> originDataAsIterable(Object originValue) {
            return (Iterable<Object>) originValue;
        }
    }

    private static class SetCopy extends CollectionCopy {

        @Override
        public boolean canHandle(Object object) {
            return object instanceof Set;
        }

        @Override
        protected Collection<Object> getResultData(Object object) {
            return instantiate(object.getClass());
        }

        private Set<Object> instantiate(final Class<? extends Object> klass) {
            return new ImplementationInstantiation() {
                @Override
                protected Set<?> createDefault() {
                    return SortedSet.class.isAssignableFrom(klass) ? new TreeSet<>() : new HashSet<>();
                }
            }.instantiate(klass);
        }
    }

    private static class MapCopy implements ICustomCopy {
        @Override
        public boolean canHandle(Object object) {
            return object instanceof Map;
        }

        @Override
        public Object instantiateCopy(Strategy strategy, Object originValue) {
            return instantiate(originValue.getClass());
        }

        private Map<Object, Object> instantiate(Class<? extends Object> klass) {
            return new ImplementationInstantiation() {
                @Override
                protected Object createDefault() {
                    return new HashMap<>();
                }
            }.instantiate(klass);
        }

        @Override
        public void copyDataToResult(DeepCopy deepCopy, Object origin, Strategy strategy, Object result) {
            doCopy(deepCopy, (Map<?, ?>) origin, strategy, ((Map<Object, Object>) result));
        }

        private void doCopy(DeepCopy deepCopy, Map<?, ?> origin, Strategy strategy, Map<Object, Object> resultMap) {
            Strategy keyStrategy = getKeysStrategy(strategy);
            Strategy valueStrategy = getValuesStrategy(strategy);

            for (Entry<?, ?> entry : origin.entrySet()) {
                Object key = deepCopy.copy(entry.getKey(), keyStrategy);
                Object value = deepCopy.copy(entry.getValue(), valueStrategy);
                resultMap.put(key, value);
            }
        }

        private Strategy getKeysStrategy(Strategy strategy) {
            return Strategy.ONLY_SHARE_KEYS == strategy || Strategy.SHARE_COLLECTION_ELEMENTS == strategy
                    ? Strategy.SHARE
                    : strategy;
        }

        private Strategy getValuesStrategy(Strategy strategy) {
            return Strategy.ONLY_SHARE_VALUES == strategy || Strategy.SHARE_COLLECTION_ELEMENTS == strategy
                    ? Strategy.SHARE
                    : strategy;
        }
    }

    private static class ListCopy extends CollectionCopy {

        @Override
        public boolean canHandle(Object object) {
            return object instanceof List;
        }

        @Override
        protected Collection<Object> getResultData(Object originValue) {
            return instantiate(originValue.getClass());
        }

        private List<Object> instantiate(Class<? extends Object> klass) {
            return new ImplementationInstantiation() {
                @Override
                protected Object createDefault() {
                    return new ArrayList<>();
                }
            }.instantiate(klass);
        }

    }

    private static abstract class ImplementationInstantiation {

        private static final String[] VETOED_IMPLEMENTATIONS = { "PersistentSet", "PersistentList", "PersistentMap",
                "PersistentSortedSet" };

        ImplementationInstantiation() {
        }

        <T> T instantiate(Class<?> type) {
            if (!isVetoed(type)) {
                try {
                    Constructor<? extends Object> constructor = type.getConstructor();
                    return (T) type.cast(constructor.newInstance());
                } catch (Exception ignored) {
                }
            }
            return (T) createDefault();
        }

        private static boolean isVetoed(Class<?> type) {
            final String simpleName = type.getSimpleName();
            for (String each : VETOED_IMPLEMENTATIONS) {
                if (each.equalsIgnoreCase(simpleName)) {
                    return true;
                }
            }
            return false;
        }

        protected abstract Object createDefault();
    }

    private static class ByIdentity {

        private final Object wrapped;

        ByIdentity(Object wrapped) {
            Validate.notNull(wrapped);
            this.wrapped = wrapped;
        }

        @Override
        public int hashCode() {
            return wrapped.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj instanceof ByIdentity) {
                ByIdentity other = (ByIdentity) obj;
                return this.wrapped == other.wrapped;
            }
            return false;
        }
    }

    private ByIdentity byIdentity(Object value) {
        return new ByIdentity(value);
    }

    public <T> T copy(T entity) {
        return copy(entity, null);
    }

    private <T> T copy(T couldBeProxyValue, Strategy strategy) {
        if (couldBeProxyValue == null) {
            return null;
        }

        T value = desproxify(couldBeProxyValue);
        if (alreadyCopiedObjects.containsKey(byIdentity(value))) {
            return (T) alreadyCopiedObjects.get(byIdentity(value));
        }

        if (Strategy.SHARE == strategy || isImmutable(value)) {
            return value;
        }

        ICustomCopy copier = findCopier(value);
        if (copier != null) {
            Object resultData = copier.instantiateCopy(strategy, value);
            alreadyCopiedObjects.put(byIdentity(value), resultData);
            copier.copyDataToResult(this, value, strategy, resultData);
            return (T) resultData;
        }

        T result = instantiateUsingDefaultConstructor(getTypedClassFrom(value));
        alreadyCopiedObjects.put(byIdentity(value), result);
        copyProperties(value, result);
        callAfterCopyHooks(result);
        return result;
    }

    private <T> T desproxify(T value) {
        if (value instanceof HibernateProxy) {
            HibernateProxy proxy = (HibernateProxy) value;
            return (T) proxy.getHibernateLazyInitializer().getImplementation();
        }
        return value;
    }

    private boolean isImmutable(Object value) {
        return isImmutableType(value.getClass());
    }

    @SuppressWarnings("unchecked")
    private <T> Class<T> getTypedClassFrom(T entity) {
        return (Class<T>) entity.getClass();
    }

    private <T> T instantiateUsingDefaultConstructor(Class<T> klass) {
        Constructor<T> constructor;
        try {
            constructor = klass.getConstructor();
        } catch (SecurityException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("could not invoke default no-args constructor for " + klass, e);
        }
        try {
            return constructor.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void copyProperties(Object source, Object target) {
        List<Field> fields = getAllFieldsFor(source);
        for (Field each : fields) {
            each.setAccessible(true);
            if (!isIgnored(each)) {
                Object sourceValue = readFieldValue(source, each);
                if (sourceValue != null) {
                    Strategy strategy = getStrategy(each);
                    try {
                        writeFieldValue(target, each, copy(sourceValue, strategy));
                    } catch (Exception e) {
                        e.printStackTrace();
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

    private List<Field> getAllFieldsFor(Object source) {
        List<Field> result = new ArrayList<>();
        Class<? extends Object> currentClass = source.getClass();
        while (currentClass != null) {
            result.addAll(Arrays.asList(currentClass.getDeclaredFields()));
            currentClass = currentClass.getSuperclass();
        }
        return result;
    }

    private boolean isIgnored(Field field) {
        return isStatic(field) || isMarkedWithIgnore(field);
    }

    private boolean isStatic(Field field) {
        return Modifier.isStatic(field.getModifiers());
    }

    private boolean isMarkedWithIgnore(Field each) {
        OnCopy onCopy = each.getAnnotation(OnCopy.class);
        return onCopy != null && onCopy.value() == Strategy.IGNORE;
    }

    private void writeFieldValue(Object target, Field field, Object value) {
        try {
            field.set(target, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private Object readFieldValue(Object source, Field field) {
        try {
            return field.get(source);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private Strategy getStrategy(Field field) {
        OnCopy onCopy = field.getAnnotation(OnCopy.class);
        return onCopy != null ? onCopy.value() : null;
    }

    private ICustomCopy findCopier(Object sourceValue) {
        for (ICustomCopy each : DEFAULT_CUSTOM_COPIERS) {
            if (each.canHandle(sourceValue)) {
                return each;
            }
        }
        return null;
    }

    private void callAfterCopyHooks(Object value) {
        assert value != null;
        for (Method each : getAfterCopyHooks(value.getClass())) {
            each.setAccessible(true);
            try {
                each.invoke(value);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private List<Method> getAfterCopyHooks(Class<?> klass) {
        Class<?> current = klass;
        List<Method> result = new ArrayList<>();
        while (current != null) {
            result.addAll(getAfterCopyDeclaredAt(current));
            current = current.getSuperclass();
        }
        return result;
    }

    private List<Method> getAfterCopyDeclaredAt(Class<?> klass) {
        List<Method> result = new ArrayList<>();
        for (Method each : klass.getDeclaredMethods()) {
            if (isAfterCopyHook(each)) {
                result.add(each);
            }
        }
        return result;
    }

    private boolean isAfterCopyHook(Method each) {
        AfterCopy annotation = each.getAnnotation(AfterCopy.class);
        return annotation != null;
    }

    public <T> DeepCopy replace(T toBeReplaced, T substitution) {
        alreadyCopiedObjects.put(byIdentity(toBeReplaced), substitution);
        return this;
    }
}