package org.raidenjpa.reflection; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import java.util.Stack; class BeanClass<T> { private static final String LOOKUP_ERROR = "Error retrieving %s for field %s (type %s) of class %s"; private final Class<T> clazz; private final MethodHandle constructor; private final List<BeanField> fields; private BeanClass(Class<T> clazz, MethodHandle constructor, List<BeanField> fields) { this.clazz = clazz; this.constructor = constructor; this.fields = fields; } public Class<T> getBeanClass() { return clazz; } public MethodHandle getConstructor() { return constructor; } public List<BeanField> getFields() { return fields; } public T shallowCopy(T original) throws Throwable { T clone = (T) this.constructor.invoke(); for (BeanField f : this.fields) { f.copyValue(original, clone); } return clone; } public static <E> BeanClass<E> buildFrom(Class<E> clazz) { Stack<Class<? super E>> stack = classesOf(clazz); List<BeanField> fields = new ArrayList<>(); Lookup lookup = null; while (!stack.isEmpty()) { Class<?> superclass = stack.pop(); lookup = privateLookup(superclass); fields.addAll(fieldsOf(superclass, lookup)); } MethodHandle constructor = constructorOf(clazz, lookup); return new BeanClass<E>(clazz, constructor, fields); } private static <E> Stack<Class<? super E>> classesOf(Class<E> superclass) { Class<? super E> clazz = superclass; Stack<Class<? super E>> stack = new Stack<>(); while (clazz != Object.class) { stack.push(clazz); clazz = clazz.getSuperclass(); } return stack; } private static <T> List<BeanField> fieldsOf(Class<T> clazz, Lookup lookup) { Field[] fields = clazz.getDeclaredFields(); List<BeanField> beanFields = new ArrayList<>(); for (Field field : fields) { if ((field.getModifiers() & Modifier.FINAL) != 0 || (field.getModifiers() & Modifier.STATIC) != 0) { continue; } field.setAccessible(true); String name = field.getName(); Class<?> type = field.getType(); MethodHandle getter = tryFindGetter(lookup, clazz, name, type); MethodHandle setter = tryFindSetter(lookup, clazz, name, type); beanFields.add(new BeanField(name, type, getter, setter)); } return beanFields; } private static <T> MethodHandle constructorOf(Class<T> clazz, Lookup lookup) { try { Constructor<T> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); return lookup.unreflectConstructor(constructor); } catch (ReflectiveOperationException e) { String msg = String.format("Could not get no-arg constructor for %s", clazz.getName()); throw new RuntimeException(msg, e); } } private static MethodHandle tryFindGetter(Lookup lookup, Class<?> objType, String name, Class<?> fieldType) { try { return lookup.findGetter(objType, name, fieldType); } catch (ReflectiveOperationException e) { String msg = String.format(LOOKUP_ERROR, "Getter", name, fieldType.getName(), objType.getName()); throw new RuntimeException(msg, e); } } private static MethodHandle tryFindSetter(Lookup lookup, Class<?> objType, String name, Class<?> fieldType) { try { return lookup.findSetter(objType, name, fieldType); } catch (ReflectiveOperationException e) { String msg = String.format(LOOKUP_ERROR, "Setter", name, fieldType.getName(), objType.getName()); throw new RuntimeException(msg, e); } } private static Lookup privateLookup(Class<?> clazz) { try { Constructor<Lookup> lookupConstructor = Lookup.class.getDeclaredConstructor(Class.class); lookupConstructor.setAccessible(true); return lookupConstructor.newInstance(clazz); } catch (ReflectiveOperationException e) { throw new RuntimeException("Could not construct MethodHandles.Lookup object", e); } } }