/* * 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<T> { private static final Logger LOG = LoggerFactory.getLogger(InstanceUpgrader.class); private static final Object reflFactory; static final ClassValue<InstanceUpgrader<?>> instanceUpgrader = new ClassValue<InstanceUpgrader<?>>() { @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 <T> InstanceUpgrader<T> get(Class<T> clazz) { return (InstanceUpgrader<T>) instanceUpgrader.get(clazz); } private final Class<T> toClass; private final Map<FieldDesc, FieldInfo> fields; private final Map<FieldDesc, Field> staticFields; private final ConcurrentMap<Class, Copier> copiers; private final Constructor<T> ctor; private final List<Method> onUpgradeInstance; private final List<Method> onUpgradeStatic; public InstanceUpgrader(Class<T> toClass) { this.toClass = toClass; this.copiers = new MapMaker().weakKeys().makeMap(); Map<FieldDesc, Field> fs = getInstanceFields(toClass, new HashMap<FieldDesc, Field>()); ImmutableMap.Builder<FieldDesc, FieldInfo> builder = ImmutableMap.builder(); for (Map.Entry<FieldDesc, Field> 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<FieldDesc, Field>())); for (Field sf : staticFields.values()) sf.setAccessible(true); this.ctor = getNoArgConstructor(toClass); List<Method> upgradeMethods = getAnnotatedMethods(toClass, OnUpgrade.class, new ArrayList<Method>()); ImmutableList.Builder<Method> ouib = ImmutableList.builder(); ImmutableList.Builder<Method> 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 <T> Constructor<T> getNoArgConstructor(Class<T> clazz) { if (reflFactory == null) return getNoArgConstructor1(clazz); else return getNoArgConstructor2(clazz); } private static <T> Constructor<T> getNoArgConstructor1(Class<T> clazz) { try { Constructor cons = clazz.getDeclaredConstructor(); cons.setAccessible(true); return cons; } catch (NoSuchMethodException e) { return null; } } private static <T> Constructor<T> getNoArgConstructor2(Class<T> 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<T>) from.getClass()).copy(from, to); } public T copy(T from) { return getCopier((Class<T>) from.getClass()).copy(from); } private Copier<T> 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<T> { private final Class<T> fromClass; private final Field[] fromFields; private final Field[] toFields; private final Constructor[] innerClassConstructor; private final Copier[] fieldCopier; Copier(Class<T> 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<FieldDesc, Field> sfs = getStaticFields(fromClass, new HashMap<FieldDesc, Field>()); for (Map.Entry<FieldDesc, Field> 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<Object>)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<FieldDesc, Field> fs = getInstanceFields(fromClass, new HashMap<FieldDesc, Field>()); ArrayList<Field> ffs = new ArrayList<>(); ArrayList<Field> tfs = new ArrayList<>(); ArrayList<Constructor> ics = new ArrayList<>(); ArrayList<Copier> fcs = new ArrayList<>(); for (Map.Entry<FieldDesc, Field> 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<FieldDesc, Field> getInstanceFields(Class<?> clazz, Map<FieldDesc, Field> 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<FieldDesc, Field> getStaticFields(Class<?> clazz, Map<FieldDesc, Field> 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 extends Collection<Method>> T getAnnotatedMethods(Class<?> clazz, Class<? extends Annotation> 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; } } }