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

co.paralleluniverse.actors.InstanceUpgrader Maven / Gradle / Ivy

/*
 * Quasar: lightweight threads and actors for the JVM.
 * Copyright (c) 2013-2014, Parallel Universe Software Co. All rights reserved.
 * 
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *  
 *   or (per the licensee's choosing)
 *  
 * under the terms of the GNU Lesser General Public License version 3.0
 * as published by the Free Software Foundation.
 */
package co.paralleluniverse.actors;

import co.paralleluniverse.common.util.Exceptions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.MapMaker;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.reflect.ReflectionFactory;

/**
 * Copies fields from an instance of a previous version of a class to the current version
 *
 * @author pron
 */
class InstanceUpgrader {
    private static final Logger LOG = LoggerFactory.getLogger(InstanceUpgrader.class);
    private static final Object reflFactory;
    static final ClassValue> instanceUpgrader = new ClassValue>() {
        @Override
        protected InstanceUpgrader computeValue(Class type) {
            return new InstanceUpgrader(type);
        }
    };

    static {
        Object rf = null;
        try {
            rf = ReflectionFactory.getReflectionFactory();
        } catch (Throwable t) {
        }
        reflFactory = rf;
    }

    public static  InstanceUpgrader get(Class clazz) {
        return (InstanceUpgrader) instanceUpgrader.get(clazz);
    }
    private final Class toClass;
    private final Map fields;
    private final Map staticFields;
    private final ConcurrentMap copiers;
    private final Constructor ctor;
    private final List onUpgradeInstance;
    private final List onUpgradeStatic;

    public InstanceUpgrader(Class toClass) {
        this.toClass = toClass;
        this.copiers = new MapMaker().weakKeys().makeMap();
        Map fs = getInstanceFields(toClass, new HashMap());
        ImmutableMap.Builder builder = ImmutableMap.builder();
        for (Map.Entry entry : fs.entrySet()) {
            Field f = entry.getValue();
            f.setAccessible(true);

            Constructor innerClassCtor = null;
            if (Objects.equals(f.getType().getEnclosingClass(), toClass)) {
                try {
                    innerClassCtor = f.getType().getDeclaredConstructor(toClass);
                    innerClassCtor.setAccessible(true);
                } catch (NoSuchMethodException e) {
                }
            }
            
            builder.put(entry.getKey(), new FieldInfo(f, innerClassCtor));
        }
        this.fields = builder.build();

        this.staticFields = ImmutableMap.copyOf(getStaticFields(toClass, new HashMap()));
        for (Field sf : staticFields.values())
            sf.setAccessible(true);

        this.ctor = getNoArgConstructor(toClass);

        List upgradeMethods = getAnnotatedMethods(toClass, OnUpgrade.class, new ArrayList());
        ImmutableList.Builder ouib = ImmutableList.builder();
        ImmutableList.Builder ousb = ImmutableList.builder();
        for (Method m : upgradeMethods) {
            if (m.getParameterTypes().length > 0) {
                LOG.warn("@OnUpgrade method {} takes arguments and will therefore not be invoked.", m);
            } else {
                m.setAccessible(true);
                if (Modifier.isStatic(m.getModifiers()))
                    ousb.add(m);
                else
                    ouib.add(m);
            }
        }
        onUpgradeInstance = ouib.build();
        onUpgradeStatic = ousb.build();
    }

    private static  Constructor getNoArgConstructor(Class clazz) {
        if (reflFactory == null)
            return getNoArgConstructor1(clazz);
        else
            return getNoArgConstructor2(clazz);
    }

    private static  Constructor getNoArgConstructor1(Class clazz) {
        try {
            Constructor cons = clazz.getDeclaredConstructor();
            cons.setAccessible(true);
            return cons;
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

    private static  Constructor getNoArgConstructor2(Class clazz) {
        Class initCl = Actor.class.isAssignableFrom(clazz) ? Actor.class : Object.class;
        try {
            Constructor cons = initCl.getDeclaredConstructor();
//                int mods = cons.getModifiers();
//                if ((mods & Modifier.PRIVATE) != 0
//                        || ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0
//                        && !packageEquals(cl, initCl))) {
//                    return null;
//                }
            cons = ((ReflectionFactory) reflFactory).newConstructorForSerialization(clazz, cons);
            cons.setAccessible(true);
            return cons;
        } catch (NoSuchMethodException ex) {
            return null;
        }
    }

//    private static boolean packageEquals(Class cl1, Class cl2) {
//        return cl1.getPackage().getName().equals(cl2.getPackage().getName()); // && cl1.getClassLoader() == cl2.getClassLoader();
//    }
    public T copy(T from, T to) {
        assert toClass.isInstance(to);
        return getCopier((Class) from.getClass()).copy(from, to);
    }

    public T copy(T from) {
        return getCopier((Class) from.getClass()).copy(from);
    }

    private Copier getCopier(Class fromClass) {
        Copier copier = copiers.get(fromClass);
        if (copier == null) {
            copier = new Copier(fromClass);
            Copier temp = copiers.putIfAbsent(fromClass, copier);
            if (temp != null)
                copier = temp;
        }
        return copier;
    }

    private class Copier {
        private final Class fromClass;
        private final Field[] fromFields;
        private final Field[] toFields;
        private final Constructor[] innerClassConstructor;
        private final Copier[] fieldCopier;

        Copier(Class fromClass) {
            if (!fromClass.getName().equals(toClass.getName()))
                throw new IllegalArgumentException("'fromClass' " + fromClass.getName() + " is not a version of 'toClass' " + toClass.getName());

            this.fromClass = fromClass;

            // static fields
            synchronized (InstanceUpgrader.this) {
                try {
                    Map sfs = getStaticFields(fromClass, new HashMap());
                    for (Map.Entry e : sfs.entrySet()) {
                        Field tf = staticFields.get(e.getKey());

                        Field ff = e.getValue();
                        ff.setAccessible(true);

                        if (tf != null && !Modifier.isFinal(tf.getModifiers())) {
                            final Object fromFieldValue = ff.get(null);
                            final Object toFieldValue;

                            if (tf.getType().isAssignableFrom(ff.getType()))
                                toFieldValue = fromFieldValue;
                            else if (tf.getType().getName().equals(ff.getType().getName()))
                                toFieldValue = ((Copier)instanceUpgrader.get(tf.getType()).getCopier(ff.getType())).copy(fromFieldValue);
                            else
                                continue;
                            LOG.debug("== static: {} <- {}: {} ({})", tf, ff, toFieldValue, fromFieldValue);
                            tf.set(null, toFieldValue);
                        }
                    }
                    try {
                        for (Method m : onUpgradeStatic)
                            m.invoke(null);
                    } catch (InvocationTargetException e) {
                        throw Exceptions.rethrow(e.getCause());
                    }
                } catch (IllegalAccessException e) {
                    throw new AssertionError(e);
                }
            }

            // instance fields
            Map fs = getInstanceFields(fromClass, new HashMap());

            ArrayList ffs = new ArrayList<>();
            ArrayList tfs = new ArrayList<>();
            ArrayList ics = new ArrayList<>();
            ArrayList fcs = new ArrayList<>();
            for (Map.Entry e : fs.entrySet()) {
                Field ff = e.getValue();
                FieldInfo tfi = fields.get(e.getKey());
                Field tf = tfi != null ? tfi.field : null;

                if (tf != null) {
                    boolean assignable = false;
                    Constructor innerClassCtor = null;
                    Copier fc = null;

                    if ("this$0".equals(tf.getName()))
                        continue;
                    if (Objects.equals(ff.getType().getEnclosingClass(), fromClass)
                            && Objects.equals(tf.getType().getEnclosingClass(), toClass)) {
                        innerClassCtor = tfi.innerClassCtor;
                        fc = instanceUpgrader.get(tf.getType()).getCopier(ff.getType());
                    } else if (tf.getType().isAssignableFrom(ff.getType())) {
                        assignable = true;
                    } else if (tf.getType().getName().equals(ff.getType().getName())) {
                        fc = instanceUpgrader.get(tf.getType()).getCopier(ff.getType());
                    }
                    if (assignable || innerClassCtor != null || fc != null) {
                        ffs.add(ff);
                        tfs.add(tf);
                        fcs.add(fc);
                        ics.add(innerClassCtor);
                    }
                }
            }
            this.fromFields = ffs.toArray(new Field[ffs.size()]);
            this.toFields = tfs.toArray(new Field[tfs.size()]);
            this.fieldCopier = fcs.toArray(new Copier[fcs.size()]);
            this.innerClassConstructor = ics.toArray(new Constructor[ics.size()]);

            for (Field f : fromFields)
                f.setAccessible(true);
        }

        T copy(T from, T to) {
            try {
                for (int i = 0; i < fromFields.length; i++) {
                    final Object fromFieldValue = fromFields[i].get(from);
                    final Object toFieldValue;
                    if (innerClassConstructor[i] != null)
                        toFieldValue = fieldCopier[i].copy(fromFieldValue, innerClassConstructor[i].newInstance(to));
                    else if (fieldCopier[i] != null)
                        toFieldValue = fieldCopier[i].copy(fromFieldValue);
                    else if (fromFieldValue != null && isInnerClassOf(fromFieldValue.getClass(), fromClass)) {
                        final Class fromFieldValueClass = fromFieldValue.getClass();
                        if (fromFieldValueClass.isAnonymousClass())
                            toFieldValue = null;
                        else {
                            Object tfv = null;
                            try {
                                final Class toFieldValueClass = toClass.getClassLoader().loadClass(fromFieldValueClass.getName());
                                final Copier c = instanceUpgrader.get(toFieldValueClass).getCopier(fromFieldValueClass);
                                final Constructor cstr = toFieldValueClass.getDeclaredConstructor(toClass);
                                cstr.setAccessible(true);
                                tfv = c.copy(fromFieldValue, cstr.newInstance(to));
                            } catch (ClassNotFoundException | NoSuchMethodException e) {
                                LOG.debug("Exception while copying " + fromFields[i] + " to " + toFields[i] + "(" + fromFieldValue + ")", e);
                            }
                            toFieldValue = tfv;
                        }
                    } else
                        toFieldValue = fromFieldValue;

                    //LOG.debug("== {} <- {}: {} ({})", toFields[i], fromFields[i], toFieldValue, fromFieldValue);
                    toFields[i].set(to, toFieldValue);
                }
                try {
                    for (Method m : onUpgradeInstance)
                        m.invoke(to);
                } catch (InvocationTargetException e) {
                    throw Exceptions.rethrow(e.getCause());
                }
                return to;
            } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
                throw new AssertionError(e);
            }
        }

        T copy(T from) {
            if (from == null)
                return null;
            if (ctor == null)
                throw new RuntimeException("Class " + toClass.getName()
                        + " in module " + (toClass.getClassLoader() instanceof ActorModule ? toClass.getClassLoader() : null)
                        + " does not have a no-arg constructor.");
            try {
                T to = (T)ctor.newInstance();
                return copy(from, to);
            } catch (InstantiationException | InvocationTargetException ex) {
                throw Exceptions.rethrow(ex.getCause());
            } catch (IllegalAccessException ex) {
                throw new AssertionError(ex);
            }
        }
    }

    private static Map getInstanceFields(Class clazz, Map fields) {
        if (clazz == null)
            return fields;
        for (Field f : clazz.getDeclaredFields()) {
            if (!Modifier.isStatic(f.getModifiers()))
                fields.put(new FieldDesc(f), f);
        }

        return getInstanceFields(clazz.getSuperclass(), fields);
    }

    private static Map getStaticFields(Class clazz, Map fields) {
        if (clazz == null)
            return fields;
        for (Field f : clazz.getDeclaredFields()) {
            if (Modifier.isStatic(f.getModifiers()))
                fields.put(new FieldDesc(f), f);
        }

        return getStaticFields(clazz.getSuperclass(), fields);
    }

    private static > T getAnnotatedMethods(Class clazz, Class ann, T methods) {
        if (clazz == null)
            return methods;
        for (Method m : clazz.getDeclaredMethods()) {
            if (m.getAnnotation(ann) != null)
                methods.add(m);
        }
        return methods;
    }

    private static boolean isInnerClassOf(Class maybeInner, Class maybeOuter) {
        return Objects.equals(maybeInner.getEnclosingClass(), maybeOuter) && hasField(maybeInner, "this$0");
    }

    private static boolean hasField(Class clazz, String field) {
        try {
            clazz.getDeclaredField(field);
            return true;
        } catch (NoSuchFieldException e) {
            return false;
        }
    }

    static void setFinalStatic(Field field, Object newValue) throws IllegalAccessException {
        field.setAccessible(true);

        try {
            // remove final modifier from field
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

            field.set(null, newValue);
        } catch (NoSuchFieldException e) {
            throw new AssertionError(e);
        }
    }

    private static class FieldDesc {
        final String declaringClass;
        final String name;

        FieldDesc(Field field) {
            this(field.getDeclaringClass().getName(), field.getName());
        }

        FieldDesc(String declaringClass, String name) {
            this.declaringClass = declaringClass;
            this.name = name;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof FieldDesc))
                return false;
            final FieldDesc other = (FieldDesc) obj;
            return this.declaringClass.equals(other.declaringClass) && this.name.equals(other.name);
        }

        @Override
        public int hashCode() {
            return declaringClass.hashCode() ^ name.hashCode();
        }
    }

    private static class FieldInfo {
        final Field field;
        final Constructor innerClassCtor;

        public FieldInfo(Field field, Constructor innerClassCtor) {
            this.field = field;
            this.innerClassCtor = innerClassCtor;
        }
    }
}