org.springsource.loaded.ri.ReflectiveInterceptor Maven / Gradle / Ivy
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.ref.WeakReference;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.AnnotatedElement;
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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.springsource.loaded.C;
import org.springsource.loaded.Constants;
import org.springsource.loaded.CurrentLiveVersion;
import org.springsource.loaded.FieldMember;
import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.MethodMember;
import org.springsource.loaded.ReloadException;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.TypeDescriptor;
import org.springsource.loaded.TypeRegistry;
import org.springsource.loaded.Utils;
import org.springsource.loaded.infra.UsedByGeneratedCode;
import org.springsource.loaded.jvm.JVM;
/**
* The reflective interceptor is called to rewrite any reflective calls that are found in the bytecode. Intercepting the calls means
* we can delegate to the SpringLoaded infrastructure.
*
* @author Andy Clement
* @author Kris De Volder
* @since 0.5.0
*/
public class ReflectiveInterceptor {
public static Logger log = Logger.getLogger(ReflectiveInterceptor.class.getName());
private static Map, WeakReference> classToRType = null;
static {
boolean synchronize = false;
try {
String prop = System.getProperty("springloaded.synchronize", "false");
if (prop.equalsIgnoreCase("true")) {
synchronize = true;
}
} catch (Throwable t) {
// likely security manager
}
if (synchronize) {
classToRType = Collections.synchronizedMap(new WeakHashMap, WeakReference>());
} else {
classToRType = new WeakHashMap, WeakReference>();
}
}
@UsedByGeneratedCode
public static boolean jlosHasStaticInitializer(Class> clazz) {
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Exception tells the caller to use the 'old way' to determine if there is a static initializer
throw new IllegalStateException();
}
return rtype.hasStaticInitializer();
}
/*
* Implementation of java.lang.class.getDeclaredMethod(String name, Class... params).
*/
@UsedByGeneratedCode
public static Method jlClassGetDeclaredMethod(Class> clazz, String name, Class>... params) throws SecurityException,
NoSuchMethodException {
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Not reloadable...
return clazz.getDeclaredMethod(name, params);
} else {
// Reloadable
MethodProvider methods = MethodProvider.create(rtype);
Invoker method = methods.getDeclaredMethod(name, params);
if (method == null) {
throw Exceptions.noSuchMethodException(clazz, name, params);
} else {
return method.createJavaMethod();
}
}
}
/*
* Implementation of java.lang.class.getMethod(String name, Class... params).
*/
@UsedByGeneratedCode
public static Method jlClassGetMethod(Class> clazz, String name, Class>... params) throws SecurityException,
NoSuchMethodException {
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Not reloadable...
return clazz.getMethod(name, params);
} else {
MethodProvider methods = MethodProvider.create(rtype);
Invoker method = methods.getMethod(name, params);
if (method == null) {
throw Exceptions.noSuchMethodException(clazz, name, params);
} else {
return method.createJavaMethod();
}
}
}
public static Method[] jlClassGetDeclaredMethods(Class> clazz) {
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Not reloadable...
return clazz.getDeclaredMethods();
} else {
MethodProvider methods = MethodProvider.create(rtype);
List invokers = methods.getDeclaredMethods();
Method[] javaMethods = new Method[invokers.size()];
for (int i = 0; i < javaMethods.length; i++) {
javaMethods[i] = invokers.get(i).createJavaMethod();
}
return javaMethods;
}
}
public static Method[] jlClassGetMethods(Class> clazz) {
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Not reloadable...
return clazz.getMethods();
} else {
MethodProvider methods = MethodProvider.create(rtype);
Collection invokers = methods.getMethods();
Method[] javaMethods = new Method[invokers.size()];
int i = 0;
for (Invoker invoker : invokers) {
javaMethods[i++] = invoker.createJavaMethod();
}
return javaMethods;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static String toParamString(Class>[] params) {
if (params == null || params.length == 0) {
return "()";
}
StringBuilder s = new StringBuilder();
s.append('(');
for (int i = 0, max = params.length; i < max; i++) {
if (i > 0) {
s.append(", ");
}
if (params[i] == null) {
s.append("null");
}
else {
s.append(params[i].getName());
}
}
s.append(')');
return s.toString();
}
private static int depth = 4;
/*
* Get the Class that declares the method calling interceptor method that called this method.
*/
@SuppressWarnings("deprecation")
public static Class> getCallerClass() {
//0 = sun.reflect.Reflection.getCallerClass
//1 = this method's frame
//2 = caller of 'getCallerClass' = asAccesibleMethod
//3 = caller of 'asAccesibleMethod' = jlrInvoke
//4 = caller we are interested in...
// In jdk17u25 there is an extra frame inserted:
// "This also fixes a regression introduced in 7u25 in which
// getCallerClass(int) is now a Java method that adds an additional frame
// that wasn't taken into account." in http://permalink.gmane.org/gmane.comp.java.openjdk.jdk7u.devel/6573
Class> caller = sun.reflect.Reflection.getCallerClass(depth);
if (caller==ReflectiveInterceptor.class) {
// If this is true we have that extra frame on the stack
depth=5;
caller = sun.reflect.Reflection.getCallerClass(depth);
}
String callerClassName = caller.getName();
Matcher matcher = Constants.executorClassNamePattern.matcher(callerClassName);
if (matcher.find()) {
// Complication... the caller may in fact be an executor method...
// in this case the caller will be an executor class.
ClassLoader loader = caller.getClassLoader();
try {
return Class.forName(callerClassName.substring(0, matcher.start()), false, loader);
} catch (ClassNotFoundException e) {
//Supposedly it wasn't an executor class after all...
log.log(Level.INFO, "Potential trouble determining caller of reflective method", e);
}
}
return caller;
}
/**
* Called to satisfy an invocation of java.lang.Class.getDeclaredAnnotations().
*
* @param clazz the class upon which the original call was being invoked
* @return array of annotations on the class
*/
public static Annotation[] jlClassGetDeclaredAnnotations(Class> clazz) {
if (TypeRegistry.nothingReloaded) {
return clazz.getDeclaredAnnotations();
}
ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(clazz);
if (rtype == null) {
return clazz.getDeclaredAnnotations();
}
CurrentLiveVersion clv = rtype.getLiveVersion();
return clv.getExecutorClass().getDeclaredAnnotations();
}
/*
* Called to satisfy an invocation of java.lang.Class.getDeclaredAnnotations().
*
* @param clazz the class upon which the original call was being invoked
*/
public static Annotation[] jlClassGetAnnotations(Class> clazz) {
if (TypeRegistry.nothingReloaded) {
return clazz.getAnnotations();
}
ReloadableType rtype = getRType(clazz);
//Note: even if class has not been reloaded, it's superclass may have been and this may affect
// the inherited annotations, so we must *not* use 'getReloadableTypeIfHasBeenReloaded' above!
if (rtype == null) {
return clazz.getAnnotations();
}
Class> superClass = clazz.getSuperclass();
if (superClass == null) {
return jlClassGetDeclaredAnnotations(clazz); //Nothing to inherit so it's ok to call this
}
Map, Annotation> combinedAnnotations = new HashMap, Annotation>();
Annotation[] annotationsToAdd = jlClassGetAnnotations(superClass);
for (Annotation annotation : annotationsToAdd) {
if (isInheritable(annotation)) {
combinedAnnotations.put(annotation.annotationType(), annotation);
}
}
annotationsToAdd = jlClassGetDeclaredAnnotations(clazz);
for (Annotation annotation : annotationsToAdd) {
combinedAnnotations.put(annotation.annotationType(), annotation);
}
return combinedAnnotations.values().toArray(new Annotation[combinedAnnotations.size()]);
}
public static Annotation jlClassGetAnnotation(Class> clazz, Class extends Annotation> annoType) {
ReloadableType rtype = getRType(clazz);
//Note: even if class has not been reloaded, it's superclass may have been and this may affect
// the inherited annotations, so we must *not* use 'getReloadableTypeIfHasBeenReloaded' above!
if (rtype == null) {
return clazz.getAnnotation(annoType);
}
if (annoType == null) {
throw new NullPointerException();
}
for (Annotation localAnnot : jlClassGetDeclaredAnnotations(clazz)) {
if (localAnnot.annotationType() == annoType) {
return localAnnot;
}
}
if (annoType.isAnnotationPresent(Inherited.class)) {
Class> superClass = clazz.getSuperclass();
if (superClass != null) {
return jlClassGetAnnotation(superClass, annoType);
}
}
return null;
}
public static boolean jlClassIsAnnotationPresent(Class> clazz, Class extends Annotation> annoType) {
ReloadableType rtype = getRType(clazz);
//Note: even if class has not been reloaded, it's superclass may have been and this may affect
// the inherited annotations, so we must *not* use 'getReloadableTypeIfHasBeenReloaded' above!
if (rtype == null) {
return clazz.isAnnotationPresent(annoType);
}
return jlClassGetAnnotation(clazz, annoType) != null;
}
public static Constructor>[] jlClassGetDeclaredConstructors(Class> clazz) {
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Non reloadable type
Constructor>[] cs = clazz.getDeclaredConstructors();
return cs;
} else if (!rtype.hasBeenReloaded()) {
// Reloadable but not yet reloaded
Constructor>[] cs = clazz.getDeclaredConstructors();
int i = 0;
for (Constructor> c : cs) {
if (isMetaConstructor(clazz, c)) {
// We must remove the 'special' constructor added by SpringLoaded
continue;
}
// SpringLoaded changes modifiers, so must fix them
fixModifier(rtype, c);
cs[i++] = c;
}
return Utils.arrayCopyOf(cs, i);
} else {
CurrentLiveVersion liveVersion = rtype.getLiveVersion();
// Reloaded type
Constructor>[] clazzCs = null;
TypeDescriptor desc = rtype.getLatestTypeDescriptor();
MethodMember[] members = desc.getConstructors();
Constructor>[] cs = new Constructor>[members.length];
for (int i = 0; i < cs.length; i++) {
MethodMember m = members[i];
if (!liveVersion.hasConstructorChanged(m)) {
if (clazzCs == null) {
clazzCs = clazz.getDeclaredConstructors();
}
cs[i] = findConstructor(clazzCs, m);
// SpringLoaded changes modifiers, so must fix them
fixModifier(rtype, cs[i]);
} else {
cs[i] = newConstructor(rtype, m);
}
}
return cs;
}
}
private static Constructor> findConstructor(Constructor>[] constructors, MethodMember searchFor) {
String paramDescriptor = searchFor.getDescriptor();
for (int i = 0, max = constructors.length; i < max; i++) {
String candidateDescriptor = Utils.toConstructorDescriptor(constructors[i].getParameterTypes());
if (candidateDescriptor.equals(paramDescriptor)) {
return constructors[i];
}
}
return null;
}
private static boolean isMetaConstructor(Class> clazz, Constructor> c) {
Class>[] params = c.getParameterTypes();
if (clazz.isEnum()) {
return params.length > 2 && params[2].getName().equals(Constants.magicDescriptorForGeneratedCtors);
} else if (clazz.getSuperclass() != null && clazz.getSuperclass().getName().equals("groovy.lang.Closure")) {
return params.length > 2 && params[2].getName().equals(Constants.magicDescriptorForGeneratedCtors);
} else {
return params.length > 0 && params[0].getName().equals(Constants.magicDescriptorForGeneratedCtors);
}
}
private static Constructor> newConstructor(ReloadableType rtype, MethodMember m) {
ClassLoader classLoader = rtype.getTypeRegistry().getClassLoader();
try {
return JVM.newConstructor(Utils.toClass(rtype), //declaring
Utils.toParamClasses(m.getDescriptor(), classLoader), // params
Utils.slashedNamesToClasses(m.getExceptions(), classLoader), //exceptions
m.getModifiers(), //modifiers
m.getGenericSignature() //signature
);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Couldn't create j.l.Constructor for " + m, e);
}
}
private static void fixModifiers(ReloadableType rtype, Field[] fields) {
TypeDescriptor typeDesc = rtype.getLatestTypeDescriptor();
for (Field field : fields) {
fixModifier(typeDesc, field);
}
}
static void fixModifier(TypeDescriptor typeDesc, Field field) {
int mods = typeDesc.getField(field.getName()).getModifiers();
if (mods != field.getModifiers()) {
JVM.setFieldModifiers(field, mods);
}
}
protected static void fixModifier(ReloadableType rtype, Constructor> constructor) {
String desc = Type.getConstructorDescriptor(constructor);
MethodMember rCons = rtype.getCurrentConstructor(desc);
if (constructor.getModifiers() != rCons.getModifiers()) {
JVM.setConstructorModifiers(constructor, rCons.getModifiers());
}
}
public static Constructor>[] jlClassGetConstructors(Class> clazz) {
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
return clazz.getConstructors();
} else {
Constructor>[] candidates = jlClassGetDeclaredConstructors(clazz);
//We need to throw away any non-public constructors.
List> keep = new ArrayList>(candidates.length);
for (Constructor> candidate : candidates) {
if (Modifier.isPublic(candidate.getModifiers())) {
keep.add(candidate);
}
}
return keep.toArray(new Constructor>[keep.size()]);
}
}
public static Constructor> jlClassGetDeclaredConstructor(Class> clazz, Class>... params) throws SecurityException,
NoSuchMethodException {
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Non reloadable type
Constructor> c = clazz.getDeclaredConstructor(params);
return c;
} else if (!rtype.hasBeenReloaded()) {
// Reloadable but not yet reloaded
Constructor> c = clazz.getDeclaredConstructor(params);
if (isMetaConstructor(clazz, c)) {
// not a real constructor !
throw Exceptions.noSuchConstructorException(clazz, params);
}
// SpringLoaded changes modifiers, so must fix them
fixModifier(rtype, c);
return c;
} else {
// This would be the right thing to do but makes getDeclaredConstructors() very messy
CurrentLiveVersion clv = rtype.getLiveVersion();
boolean b = clv.hasConstructorChanged(Utils.toConstructorDescriptor(params));
if (!b) {
Constructor> c = clazz.getDeclaredConstructor(params);
if (isMetaConstructor(clazz, c)) {
// not a real constructor !
throw Exceptions.noSuchConstructorException(clazz, params);
}
// SpringLoaded changes modifiers, so must fix them
fixModifier(rtype, c);
return c;
} else {
// Reloaded type
TypeDescriptor desc = rtype.getLatestTypeDescriptor();
MethodMember[] members = desc.getConstructors();
String searchFor = Utils.toConstructorDescriptor(params);
for (MethodMember m : members) {
if (m.getDescriptor().equals(searchFor)) {
return newConstructor(rtype, m);
}
}
throw Exceptions.noSuchConstructorException(clazz, params);
}
}
}
public static Constructor> jlClassGetConstructor(Class> clazz, Class>... params) throws SecurityException,
NoSuchMethodException {
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
return clazz.getConstructor(params);
} else {
Constructor> c = jlClassGetDeclaredConstructor(clazz, params);
if (Modifier.isPublic(c.getModifiers())) {
return c;
} else {
throw Exceptions.noSuchMethodException(clazz, "", params);
}
}
}
private static boolean isInheritable(Annotation annotation) {
return annotation.annotationType().isAnnotationPresent(Inherited.class);
}
/**
* Performs access checks and returns a (potential) copy of the method with accessibility flag set if this necessary for the
* invoke to succeed.
*
* Also checks for deleted methods.
*
* If any checks fail, an appropriate exception is raised.
*/
private static Method asAccessibleMethod(ReloadableType methodDeclaringTypeReloadableType, Method method, Object target,
boolean makeAccessibleCopy) throws IllegalAccessException {
if (methodDeclaringTypeReloadableType != null && isDeleted(methodDeclaringTypeReloadableType, method)) {
throw Exceptions.noSuchMethodError(method);
}
if (method.isAccessible()) {
//More expensive check not required / copy not required
} else {
Class> clazz = method.getDeclaringClass();
int mods = method.getModifiers();
int classmods;
// ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(clazz);
if (methodDeclaringTypeReloadableType == null || !methodDeclaringTypeReloadableType.hasBeenReloaded()) {
classmods = clazz.getModifiers();
} else {
//Note: the "super bit" may be set in class modifiers but we should block it out, it
//shouldn't be shown to users of the reflection API.
classmods = methodDeclaringTypeReloadableType.getLatestTypeDescriptor().getModifiers() & ~Opcodes.ACC_SUPER;
}
if (Modifier.isPublic(mods & classmods/*jlClassGetModifiers(clazz)*/)) {
//More expensive check not required / copy not required
} else {
//More expensive check required
Class> callerClass = getCallerClass();
JVM.ensureMemberAccess(callerClass, clazz, target, mods);
if (makeAccessibleCopy) {
method = JVM.copyMethod(method); // copy: we must not change accessible flag on original method!
method.setAccessible(true);
}
}
}
return makeAccessibleCopy ? method : null;
}
private static Constructor> asAccessibleConstructor(Constructor> c, boolean makeAccessibleCopy)
throws NoSuchMethodException, IllegalAccessException {
if (isDeleted(c)) {
throw Exceptions.noSuchConstructorError(c);
}
Class> clazz = c.getDeclaringClass();
int mods = c.getModifiers();
if (c.isAccessible() || Modifier.isPublic(mods & jlClassGetModifiers(clazz))) {
//More expensive check not required / copy not required
} else {
//More expensive check required
Class> callerClass = getCallerClass();
JVM.ensureMemberAccess(callerClass, clazz, null, mods);
if (makeAccessibleCopy) {
c = JVM.copyConstructor(c); // copy: we must not change accessible flag on original method!
c.setAccessible(true);
}
}
return makeAccessibleCopy ? c : null;
}
/**
* Performs access checks and returns a (potential) copy of the field with accessibility flag set if this necessary for the
* acces operation to succeed.
*
* If any checks fail, an appropriate exception is raised.
*
* Warning this method is sensitive to stack depth! Should expects to be called DIRECTLY from a jlr redicriction method only!
*/
private static Field asAccessibleField(Field field, Object target, boolean makeAccessibleCopy) throws IllegalAccessException {
if (isDeleted(field)) {
throw Exceptions.noSuchFieldError(field);
}
Class> clazz = field.getDeclaringClass();
int mods = field.getModifiers();
if (field.isAccessible() || Modifier.isPublic(mods & jlClassGetModifiers(clazz))) {
//More expensive check not required / copy not required
} else {
//More expensive check required
Class> callerClass = getCallerClass();
JVM.ensureMemberAccess(callerClass, clazz, target, mods);
if (makeAccessibleCopy) {
//TODO: This code is not covered by a test. It needs a non-reloadable type with non-public
// field, being accessed reflectively from a context that is "priviliged" to access it without setting the access flag.
field = JVM.copyField(field); // copy: we must not change accessible flag on original method!
field.setAccessible(true);
}
}
return makeAccessibleCopy ? field : null;
}
/**
* Performs all necessary checks that need to be done before a field set should be allowed.
*
* @throws IllegalAccessException
*/
private static Field asSetableField(Field field, Object target, Class> valueType, Object value, boolean makeAccessibleCopy)
throws IllegalAccessException {
// Must do the checks exactly in the same order as JVM if we want identical error messages.
// JVM doesn't do this, since it cannot happen without reloading, we do it first of all.
if (isDeleted(field)) {
throw Exceptions.noSuchFieldError(field);
}
Class> clazz = field.getDeclaringClass();
int mods = field.getModifiers();
if (field.isAccessible() || Modifier.isPublic(mods & jlClassGetModifiers(clazz))) {
//More expensive check not required / copy not required
} else {
//More expensive check required
Class> callerClass = getCallerClass();
JVM.ensureMemberAccess(callerClass, clazz, target, mods);
if (makeAccessibleCopy) {
//TODO: This code is not covered by a test. It needs a non-reloadable type with non-public
// field, being accessed reflectively from a context that is "priviliged" to access it without setting the access flag.
field = JVM.copyField(field); // copy: we must not change accessible flag on original field!
field.setAccessible(true);
}
}
if (isPrimitive(valueType)) {
//It seems for primitive types, the order of the checks (in Sun JVM) is different!
typeCheckFieldSet(field, valueType, value);
if (!field.isAccessible() && Modifier.isFinal(mods)) {
throw Exceptions.illegalSetFinalFieldException(field, field.getType(), coerce(value, field.getType()));
}
} else {
if (!field.isAccessible() && Modifier.isFinal(mods)) {
throw Exceptions.illegalSetFinalFieldException(field, valueType, value);
}
typeCheckFieldSet(field, valueType, value);
}
return makeAccessibleCopy ? field : null;
}
private static Object coerce(Object value, Class> toType) {
//Warning: this method's implementation is not for general use, it's only intended use is to
// ensure correctness of error messages, so it doesn't need to cover all 'coercable' cases,
// only those cases where the coerced value print out differently, and which are reachable
// from 'asSetableField'.
Class extends Object> fromType = value.getClass();
if (Integer.class.equals(fromType)) {
if (float.class.equals(toType)) {
return (float) (Integer) value;
} else if (double.class.equals(toType)) {
return (double) (Integer) value;
}
} else if (Byte.class.equals(fromType)) {
if (float.class.equals(toType)) {
return (float) (Byte) value;
} else if (double.class.equals(toType)) {
return (double) (Byte) value;
}
} else if (Character.class.equals(fromType)) {
if (int.class.equals(toType)) {
return (int) (Character) value;
} else if (long.class.equals(toType)) {
return (long) (Character) value;
} else if (float.class.equals(toType)) {
return (float) (Character) value;
} else if (double.class.equals(toType)) {
return (double) (Character) value;
}
} else if (Short.class.equals(fromType)) {
if (float.class.equals(toType)) {
return (float) (Short) value;
} else if (double.class.equals(toType)) {
return (double) (Short) value;
}
} else if (Long.class.equals(fromType)) {
if (float.class.equals(toType)) {
return (float) (Long) value;
} else if (double.class.equals(toType)) {
return (double) (Long) value;
}
} else if (Float.class.equals(fromType)) {
if (double.class.equals(toType)) {
return (double) (Float) value;
}
}
return value;
}
/**
* Perform a dynamic type check needed when setting a field value onto a field. Raises the appropriate exception when the check
* fails and returns normally otherwise. This method should only be called for object types. For primitive types call the three
* parameter variant instead.
*
* @throws IllegalAccessException
*/
private static void typeCheckFieldSet(Field field, Object value) throws IllegalAccessException {
Class> fieldType = field.getType();
if (value == null) {
if (fieldType.isPrimitive()) {
throw Exceptions.illegalSetFieldTypeException(field, null, value);
}
} else {
if (fieldType.isPrimitive()) {
fieldType = boxTypeFor(fieldType);
}
Class> valueType = value.getClass();
if (!Utils.isConvertableFrom(fieldType, valueType)) {
throw Exceptions.illegalSetFieldTypeException(field, valueType, value);
}
}
}
/**
* Perform a dynamic type check needed when setting a field value onto a field. Raises the appropriate exception when the check
* fails and returns normally otherwise.
*
* @throws IllegalAccessException
*/
private static void typeCheckFieldSet(Field field, Class> valueType, Object value) throws IllegalAccessException {
if (!isPrimitive(valueType)) {
//Call the version of this method that considers autoboxing
typeCheckFieldSet(field, value);
} else {
//Value type is primitive.
// Note: In this case value was a primitive value that became boxed, so it can't be null.
Class> fieldType = field.getType();
if (!Utils.isConvertableFrom(fieldType, valueType)) {
throw Exceptions.illegalSetFieldTypeException(field, valueType, value);
}
}
}
/**
* Checks whether given 'valueType' is a primitive type, considering that we use 'null' as the type for 'null' (to distinguish
* it from the type 'Object' which is not the same!)
*/
private static boolean isPrimitive(Class> valueType) {
return valueType != null && valueType.isPrimitive();
}
/**
* Determine a "valueType" from a given value object. Note that this should really only be used for values that are
* non-primitive, otherwise it will be impossible to distinguish between a primitive value and its boxed representation.
*
* In a context where you have a primitive value that gets boxed up, its valueType should be passed in explicitly as a class
* like, for example, int.class.
*/
private static Class> valueType(Object value) {
if (value == null) {
return null;
} else {
return value.getClass();
}
}
/**
* Retrieve modifiers for a Java class, which might or might not be reloadable or reloaded.
*
* @param clazz the class for which to discover modifiers
* @return the modifiers
*/
public static int jlClassGetModifiers(Class> clazz) {
// ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(clazz);
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
return clazz.getModifiers();
} else {
//Note: the "super bit" may be set in class modifiers but we should block it out, it
//shouldn't be shown to users of the reflection API.
return rtype.getLatestTypeDescriptor().getModifiers() & ~Opcodes.ACC_SUPER;
}
}
private static boolean isDeleted(ReloadableType rtype, Method method) {
// ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(method.getDeclaringClass());
if (rtype == null || !rtype.hasBeenReloaded()) {
return false;
} else {
MethodMember currentMethod = rtype.getCurrentMethod(method.getName(), Type.getMethodDescriptor(method));
if (currentMethod == null) {
return true; // Method not there, consider it deleted
} else {
return MethodMember.isDeleted(currentMethod); // Deleted bit is set consider deleted
}
}
}
private static boolean isDeleted(Constructor> c) {
ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(c.getDeclaringClass());
if (rtype == null) {
return false;
} else {
TypeDescriptor desc = rtype.getLatestTypeDescriptor();
MethodMember currentConstructor = desc.getConstructor(Type.getConstructorDescriptor(c));
if (currentConstructor == null) {
//TODO: test case with a deleted constructor
return true; // Method not there, consider it deleted
} else {
return false;
}
}
}
private static boolean isDeleted(Field field) {
ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(field.getDeclaringClass());
if (rtype == null) {
return false;
} else {
TypeDescriptor desc = rtype.getLatestTypeDescriptor();
FieldMember currentField = desc.getField(field.getName());
if (currentField == null) {
return true; // Method not there, consider it deleted
} else {
return false;
}
// Fields don't have deleted bits now, but maybe they get them in the future?
// } else {
// return FieldMember.isDeleted(currentField); // Deleted bit is set consider deleted
// }
}
}
/**
* If clazz is reloadable and has been reloaded at least once then return the ReloadableType instance for it, otherwise
* return null.
*
* @param clazz the type which may or may not be reloadable
* @return the reloadable type or null
*/
private static ReloadableType getReloadableTypeIfHasBeenReloaded(Class> clazz) {
if (TypeRegistry.nothingReloaded) {
return null;
}
ReloadableType rtype = getRType(clazz);
if (rtype != null && rtype.hasBeenReloaded()) {
return rtype;
} else {
return null;
}
}
private final static boolean theOldWay = false;
/**
* Access and return the ReloadableType field on a specified class.
*
* @param clazz the class for which to discover the reloadable type
* @return the reloadable type for the class, or null if not reloadable
*/
public static ReloadableType getRType(Class> clazz) {
// ReloadableType rtype = null;
WeakReference ref = classToRType.get(clazz);
ReloadableType rtype = null;
if (ref != null) {
rtype = ref.get();
}
if (rtype == null) {
if (!theOldWay) {
// 'theOldWay' attempts to grab the field from the type via reflection. This usually works except
// in cases where the class is not resolved yet since it can cause the class to resolve and its
// static initializer to run. This was happening on a grails compile where the compiler is
// loading dependencies (but not initializing them). Instead we can use this route of
// discovering the type registry and locating the reloadable type. This does some map lookups
// which may be a problem, but once discovered, it is cached in the weak ref so that shouldn't
// be an ongoing perf problem.
// TODO testcases for something that is reloaded without having been resolved
ClassLoader cl = clazz.getClassLoader();
TypeRegistry tr = TypeRegistry.getTypeRegistryFor(cl);
if (tr == null) {
classToRType.put(clazz, ReloadableType.NOT_RELOADABLE_TYPE_REF);
} else {
rtype = tr.getReloadableType(clazz.getName().replace('.', '/'));
if (rtype == null) {
classToRType.put(clazz, ReloadableType.NOT_RELOADABLE_TYPE_REF);
} else {
classToRType.put(clazz, new WeakReference(rtype));
}
}
} else {
// need to work it out
Field rtypeField;
try {
// System.out.println("discovering field for " + clazz.getName());
// TODO cache somewhere - will need a clazz>Field cache
rtypeField = clazz.getDeclaredField(Constants.fReloadableTypeFieldName);
} catch (NoSuchFieldException nsfe) {
classToRType.put(clazz, ReloadableType.NOT_RELOADABLE_TYPE_REF);
// expensive if constantly discovering this
return null;
}
try {
rtypeField.setAccessible(true);
rtype = (ReloadableType) rtypeField.get(null);
if (rtype == null) {
classToRType.put(clazz, ReloadableType.NOT_RELOADABLE_TYPE_REF);
throw new ReloadException("ReloadableType field '" + Constants.fReloadableTypeFieldName
+ "' is 'null' on type " + clazz.getName());
} else {
classToRType.put(clazz, new WeakReference(rtype));
}
} catch (Exception e) {
throw new ReloadException("Unable to access ReloadableType field '" + Constants.fReloadableTypeFieldName
+ "' on type " + clazz.getName(), e);
}
}
} else if (rtype == ReloadableType.NOT_RELOADABLE_TYPE) {
return null;
}
return rtype;
}
public static Annotation[] jlrMethodGetDeclaredAnnotations(Method method) {
ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(method.getDeclaringClass());
if (rtype == null) {
//Nothing special to be done
return method.getDeclaredAnnotations();
} else {
// Method could have changed...
CurrentLiveVersion clv = rtype.getLiveVersion();
MethodMember methodMember = rtype.getCurrentMethod(method.getName(), Type.getMethodDescriptor(method));
if (MethodMember.isCatcher(methodMember)) {
if (clv.getExecutorMethod(methodMember) != null) {
throw new IllegalStateException();
}
return method.getDeclaredAnnotations();
}
Method executor = clv.getExecutorMethod(methodMember);
return executor.getAnnotations();
}
}
public static Annotation[][] jlrMethodGetParameterAnnotations(Method method) {
ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(method.getDeclaringClass());
if (rtype == null) {
//Nothing special to be done
return method.getParameterAnnotations();
} else {
// Method could have changed...
CurrentLiveVersion clv = rtype.getLiveVersion();
MethodMember currentMethod = rtype.getCurrentMethod(method.getName(), Type.getMethodDescriptor(method));
Method executor = clv.getExecutorMethod(currentMethod);
Annotation[][] result = executor.getParameterAnnotations();
if (!currentMethod.isStatic()) {
//Non=static methods have an extra param.
//Though extra param is added to front...
//Annotations aren't being moved so we have to actually drop the *last* array element
result = Utils.arrayCopyOf(result, result.length - 1);
}
return result;
}
}
public static Object jlClassNewInstance(Class> clazz) throws SecurityException, NoSuchMethodException,
IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
// Note: no special case for non-reloadable types here, because access checks:
// access checks depend on stack depth and springloaded rewriting changes that even for non-reloadable types!
// TODO: This implementation doesn't check access modifiers on the class. So may allow
// instantiations that wouldn't be allowed by the JVM (e.g if constructor is public, but class is private)
// TODO: what about trying to instantiate an abstract class? should produce an error, does it?
Constructor> c;
try {
c = jlClassGetDeclaredConstructor(clazz);
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw Exceptions.instantiation(clazz);
}
c = asAccessibleConstructor(c, true);
return jlrConstructorNewInstance(c);
}
public static Object jlrConstructorNewInstance(Constructor> c, Object... params) throws InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException, NoSuchMethodException {
//Note: unlike for methods we don't need to handle the reloadable but not reloaded case specially, that is because there
// is no inheritance on constructors, so reloaded superclasses can affect method lookup in the same way.
Class> clazz = c.getDeclaringClass();
ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(clazz);
if (rtype == null) {
c = asAccessibleConstructor(c, true);
//Nothing special to be done
return c.newInstance(params);
} else {
// Constructor may have changed...
// this is the right thing to do but makes a mess of getDeclaredConstructors (and affects getDeclaredConstructor)
// // TODO should check about constructor changing
// rtype.getTypeDescriptor().getConstructor("").
boolean ctorChanged = rtype.getLiveVersion()
.hasConstructorChanged(Utils.toConstructorDescriptor(c.getParameterTypes()));
if (!ctorChanged) {
// if we let the getDeclaredConstructor(s) code run as is, it may create invalid ctors, if we want to run the real one we should discover it here and use it.
// would it be cheaper to fix up getDeclaredConstructor to always return valid ones if we are going to use them, or should we intercept here? probably the former...
c = asAccessibleConstructor(c, true);
return c.newInstance(params);
}
asAccessibleConstructor(c, false);
CurrentLiveVersion clv = rtype.getLiveVersion();
Method executor = clv.getExecutorMethod(rtype.getCurrentConstructor(Type.getConstructorDescriptor(c)));
Constructor> magicConstructor = clazz.getConstructor(C.class);
Object instance = magicConstructor.newInstance((Object) null);
Object[] instanceAndParams;
if (params == null || params.length == 0) {
instanceAndParams = new Object[] { instance };
} else {
//Must add instance as first param: executor is a static method.
instanceAndParams = new Object[params.length + 1];
instanceAndParams[0] = instance;
System.arraycopy(params, 0, instanceAndParams, 1, params.length);
}
executor.invoke(null, instanceAndParams);
return instance;
}
}
// private static String toString(Object... params) {
// if (params == null) {
// return "null";
// }
// StringBuilder s = new StringBuilder();
// for (Object param : params) {
// s.append(param).append(" ");
// }
// return "[" + s.toString().trim() + "]";
// }
@SuppressWarnings({ "rawtypes", "unchecked" })
public static Object jlrMethodInvoke(Method method, Object target, Object... params) throws IllegalArgumentException,
IllegalAccessException, InvocationTargetException {
// System.out.println("> jlrMethodInvoke:method=" + method + " target=" + target + " params=" + toString(params));
Class declaringClass = method.getDeclaringClass();
if (declaringClass == Class.class) {
String mname = method.getName();
try {
if (mname.equals("getFields")) {
return jlClassGetFields((Class) target);
} else if (mname.equals("getDeclaredFields")) {
return jlClassGetDeclaredFields((Class) target);
} else if (mname.equals("getDeclaredField")) {
return jlClassGetDeclaredField((Class) target, (String) params[0]);
} else if (mname.equals("getField")) {
return jlClassGetField((Class) target, (String) params[0]);
} else if (mname.equals("getConstructors")) {
return jlClassGetConstructors((Class) target);
} else if (mname.equals("getDeclaredConstructors")) {
return jlClassGetDeclaredConstructors((Class) target);
} else if (mname.equals("getDeclaredMethod")) {
return jlClassGetDeclaredMethod((Class) target, (String) params[0], (Class[]) params[1]);
} else if (mname.equals("getDeclaredMethods")) {
return jlClassGetDeclaredMethods((Class) target);
} else if (mname.equals("getMethod")) {
return jlClassGetMethod((Class) target, (String) params[0], (Class[]) params[1]);
} else if (mname.equals("getMethods")) {
return jlClassGetMethods((Class) target);
} else if (mname.equals("getConstructor")) {
return jlClassGetConstructor((Class) target, (Class[]) params[0]);
} else if (mname.equals("getDeclaredConstructor")) {
return jlClassGetDeclaredConstructor((Class) target, (Class[]) params[0]);
} else if (mname.equals("getModifiers")) {
return jlClassGetModifiers((Class) target);
} else if (mname.equals("isAnnotationPresent")) {
return jlClassIsAnnotationPresent((Class) target, (Class extends Annotation>) params[0]);
} else if (mname.equals("newInstance")) {
return jlClassNewInstance((Class) target);
} else if (mname.equals("getDeclaredAnnotations")) {
return jlClassGetDeclaredAnnotations((Class) target);
} else if (mname.equals("getAnnotation")) {
return jlClassGetAnnotation((Class) target, (Class) params[0]);
} else if (mname.equals("getAnnotations")) {
return jlClassGetAnnotations((Class) target);
}
} catch (NoSuchMethodException nsme) {
throw new InvocationTargetException(nsme);
} catch (NoSuchFieldException nsfe) {
throw new InvocationTargetException(nsfe);
} catch (InstantiationException ie) {
throw new InvocationTargetException(ie);
}
} else if (declaringClass == Method.class) {
String mname = method.getName();
if (mname.equals("invoke")) {
return jlrMethodInvoke((Method) target, params[0], (Object[]) params[1]);
} else if (mname.equals("getAnnotation")) {
return jlrMethodGetAnnotation((Method) target, (Class) params[0]);
} else if (mname.equals("getAnnotations")) {
return jlrMethodGetAnnotations((Method) target);
} else if (mname.equals("getDeclaredAnnotations")) {
return jlrMethodGetDeclaredAnnotations((Method) target);
} else if (mname.equals("getParameterAnnotations")) {
return jlrMethodGetParameterAnnotations((Method) target);
} else if (mname.equals("isAnnotationPresent")) {
return jlrMethodIsAnnotationPresent((Method) target, (Class) params[0]);
}
} else if (declaringClass == Constructor.class) {
String mname = method.getName();
try {
if (mname.equals("getAnnotation")) {
return jlrConstructorGetAnnotation((Constructor) target, (Class) params[0]);
} else if (mname.equals("newInstance")) {
return jlrConstructorNewInstance((Constructor) target, (Object[]) params[0]);
} else if (mname.equals("getAnnotations")) {
return jlrConstructorGetAnnotations((Constructor) target);
} else if (mname.equals("getDeclaredAnnotations")) {
return jlrConstructorGetDeclaredAnnotations((Constructor) target);
} else if (mname.equals("isAnnotationPresent")) {
return jlrConstructorIsAnnotationPresent((Constructor) target, (Class) params[0]);
} else if (mname.equals("getParameterAnnotations")) {
return jlrConstructorGetParameterAnnotations((Constructor) target);
}
} catch (InstantiationException ie) {
throw new InvocationTargetException(ie);
} catch (NoSuchMethodException nsme) {
throw new InvocationTargetException(nsme);
}
} else if (declaringClass == Field.class) {
String mname = method.getName();
if (mname.equals("set")) {
jlrFieldSet((Field) target, params[0], params[1]);
return null;
} else if (mname.equals("setBoolean")) {
jlrFieldSetBoolean((Field) target, params[0], (Boolean) params[1]);
return null;
} else if (mname.equals("setByte")) {
jlrFieldSetByte((Field) target, params[0], (Byte) params[1]);
return null;
} else if (mname.equals("setChar")) {
jlrFieldSetChar((Field) target, params[0], (Character) params[1]);
return null;
} else if (mname.equals("setFloat")) {
jlrFieldSetFloat((Field) target, params[0], (Float) params[1]);
return null;
} else if (mname.equals("setShort")) {
jlrFieldSetShort((Field) target, params[0], (Short) params[1]);
return null;
} else if (mname.equals("setLong")) {
jlrFieldSetLong((Field) target, params[0], (Long) params[1]);
return null;
} else if (mname.equals("setDouble")) {
jlrFieldSetDouble((Field) target, params[0], (Double) params[1]);
return null;
} else if (mname.equals("setInt")) {
jlrFieldSetInt((Field) target, params[0], (Integer) params[1]);
return null;
} else if (mname.equals("get")) {
return jlrFieldGet((Field) target, params[0]);
} else if (mname.equals("getByte")) {
return jlrFieldGetByte((Field) target, params[0]);
} else if (mname.equals("getChar")) {
return jlrFieldGetChar((Field) target, params[0]);
} else if (mname.equals("getDouble")) {
return jlrFieldGetDouble((Field) target, params[0]);
} else if (mname.equals("getBoolean")) {
return jlrFieldGetBoolean((Field) target, params[0]);
} else if (mname.equals("getLong")) {
return jlrFieldGetLong((Field) target, params[0]);
} else if (mname.equals("getFloat")) {
return jlrFieldGetFloat((Field) target, params[0]);
} else if (mname.equals("getInt")) {
return jlrFieldGetInt((Field) target, params[0]);
} else if (mname.equals("getShort")) {
return jlrFieldGetShort((Field) target, params[0]);
} else if (mname.equals("getAnnotations")) {
return jlrFieldGetAnnotations((Field) target);
} else if (mname.equals("getDeclaredAnnotations")) {
return jlrFieldGetDeclaredAnnotations((Field) target);
} else if (mname.equals("isAnnotationPresent")) {
return jlrFieldIsAnnotationPresent((Field) target, (Class) params[0]);
} else if (mname.equals("getAnnotation")) {
return jlrFieldGetAnnotation((Field) target, (Class) params[0]);
}
} else if (declaringClass == AccessibleObject.class) {
String mname = method.getName();
if (mname.equals("isAnnotationPresent")) {
if (target instanceof Constructor) {
// TODO what about null target - how should things go bang?
return jlrConstructorIsAnnotationPresent((Constructor) target, (Class) params[0]);
} else if (target instanceof Method) {
return jlrMethodIsAnnotationPresent((Method) target, (Class) params[0]);
} else if (target instanceof Field) {
return jlrFieldIsAnnotationPresent((Field) target, (Class) params[0]);
}
} else if (mname.equals("getAnnotations")) {
if (target instanceof Constructor) {
return jlrConstructorGetAnnotations((Constructor) target);
} else if (target instanceof Method) {
return jlrMethodGetAnnotations((Method) target);
} else if (target instanceof Field) {
return jlrFieldGetAnnotations((Field) target);
}
} else if (mname.equals("getDeclaredAnnotations")) {
if (target instanceof Constructor) {
return jlrConstructorGetDeclaredAnnotations((Constructor) target);
} else if (target instanceof Method) {
return jlrMethodGetDeclaredAnnotations((Method) target);
} else if (target instanceof Field) {
return jlrFieldGetDeclaredAnnotations((Field) target);
}
} else if (mname.equals("getAnnotation")) {
if (target instanceof Constructor) {
return jlrConstructorGetAnnotation((Constructor) target, (Class) params[0]);
} else if (target instanceof Method) {
return jlrMethodGetAnnotation((Method) target, (Class) params[0]);
} else if (target instanceof Field) {
return jlrFieldGetAnnotation((Field) target, (Class) params[0]);
}
}
} else if (declaringClass == AnnotatedElement.class) {
String mname = method.getName();
if (mname.equals("isAnnotationPresent")) {
if (target instanceof Constructor) {
// TODO what about null target - how should things go bang?
return jlrConstructorIsAnnotationPresent((Constructor) target, (Class) params[0]);
} else if (target instanceof Method) {
return jlrMethodIsAnnotationPresent((Method) target, (Class) params[0]);
} else if (target instanceof Field) {
return jlrFieldIsAnnotationPresent((Field) target, (Class) params[0]);
}
} else if (mname.equals("getAnnotations")) {
if (target instanceof Constructor) {
return jlrConstructorGetAnnotations((Constructor) target);
} else if (target instanceof Method) {
return jlrMethodGetAnnotations((Method) target);
} else if (target instanceof Field) {
return jlrFieldGetAnnotations((Field) target);
}
} else if (mname.equals("getDeclaredAnnotations")) {
if (target instanceof Constructor) {
return jlrConstructorGetDeclaredAnnotations((Constructor) target);
} else if (target instanceof Method) {
return jlrMethodGetDeclaredAnnotations((Method) target);
} else if (target instanceof Field) {
return jlrFieldGetDeclaredAnnotations((Field) target);
}
} else if (mname.equals("getAnnotation")) {
if (target instanceof Constructor) {
return jlrConstructorGetAnnotation((Constructor) target, (Class) params[0]);
} else if (target instanceof Method) {
return jlrMethodGetAnnotation((Method) target, (Class) params[0]);
} else if (target instanceof Field) {
return jlrFieldGetAnnotation((Field) target, (Class) params[0]);
}
}
}
// Even though we tinker with the visibility of methods, we don't damage private ones (which would really cause chaos if we tried
// to allow the JVM to do the dispatch). That means this should be OK:
if (TypeRegistry.nothingReloaded) {
method = asAccessibleMethod(null, method, target, true);
return method.invoke(target, params);
}
ReloadableType declaringType = getRType(declaringClass);
if (declaringType == null) {
//Not reloadable...
method = asAccessibleMethod(declaringType, method, target, true);
return method.invoke(target, params);
} else {
//Reloadable...
asAccessibleMethod(declaringType, method, target, false);
int mods = method.getModifiers();
Invoker invoker;
if ((mods & (Modifier.STATIC | Modifier.PRIVATE)) != 0) {
//These methods are dispatched statically
MethodProvider methods = MethodProvider.create(declaringType);
invoker = methods.staticLookup(mods, method.getName(), Type.getMethodDescriptor(method));
} else {
//These methods are dispatched dynamically
ReloadableType targetType = getRType(target.getClass()); //NPE possible but is what should happen here!
if (targetType == null) {
System.out.println("GRAILS-7799: Subtype '" + target.getClass().getName() + "' of reloadable type "
+ method.getDeclaringClass().getName()
+ " is not reloadable: may not see changes reloaded in this hierarchy (please comment on that jira)");
method = asAccessibleMethod(declaringType, method, target, true);
return method.invoke(target, params);
}
MethodProvider methods = MethodProvider.create(targetType); //use target not declaring type for Dynamic lookkup
invoker = methods.dynamicLookup(mods, method.getName(), Type.getMethodDescriptor(method));
}
return invoker.invoke(target, params);
}
}
public static boolean jlrMethodIsAnnotationPresent(Method method, Class extends Annotation> annotClass) {
return jlrMethodGetAnnotation(method, annotClass) != null;
}
public static Annotation jlrMethodGetAnnotation(Method method, Class extends Annotation> annotClass) {
ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(method.getDeclaringClass());
if (rtype == null) {
//Nothing special to be done
return method.getAnnotation(annotClass);
} else {
if (annotClass == null) {
throw new NullPointerException();
}
// Method could have changed...
Annotation[] annots = jlrMethodGetDeclaredAnnotations(method);
for (Annotation annotation : annots) {
if (annotClass.equals(annotation.annotationType())) {
return annotation;
}
}
return null;
}
}
public static Annotation[] jlrAnnotatedElementGetAnnotations(AnnotatedElement elem) {
if (elem instanceof Class>) {
return jlClassGetAnnotations((Class>) elem);
} else if (elem instanceof AccessibleObject) {
return jlrAccessibleObjectGetAnnotations((AccessibleObject) elem);
} else {
//Don't know what it is... not something we handle anyway
return elem.getAnnotations();
}
}
public static Annotation[] jlrAnnotatedElementGetDeclaredAnnotations(AnnotatedElement elem) {
if (elem instanceof Class>) {
return jlClassGetDeclaredAnnotations((Class>) elem);
} else if (elem instanceof AccessibleObject) {
return jlrAccessibleObjectGetDeclaredAnnotations((AccessibleObject) elem);
} else {
//Don't know what it is... not something we handle anyway
return elem.getDeclaredAnnotations();
}
}
public static Annotation[] jlrAccessibleObjectGetDeclaredAnnotations(AccessibleObject obj) {
if (obj instanceof Method) {
return jlrMethodGetDeclaredAnnotations((Method) obj);
} else if (obj instanceof Field) {
return jlrFieldGetDeclaredAnnotations((Field) obj);
} else if (obj instanceof Constructor>) {
return jlrConstructorGetDeclaredAnnotations((Constructor>) obj);
} else {
//Some other type of member which we don't support reloading...
return obj.getDeclaredAnnotations();
}
}
public static Annotation[] jlrFieldGetDeclaredAnnotations(Field field) {
ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(field.getDeclaringClass());
if (rtype == null) {
//Nothing special to be done
return field.getDeclaredAnnotations();
} else {
// Field could have changed...
CurrentLiveVersion clv = rtype.getLiveVersion();
Field executor;
try {
executor = clv.getExecutorField(field.getName());
return executor.getAnnotations();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
public static boolean jlrFieldIsAnnotationPresent(Field field, Class extends Annotation> annotType) {
if (annotType == null) {
throw new NullPointerException();
}
ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(field.getDeclaringClass());
if (rtype == null) {
//Nothing special to be done
return field.isAnnotationPresent(annotType);
} else {
// Field could have changed...
CurrentLiveVersion clv = rtype.getLiveVersion();
try {
Field executor = clv.getExecutorField(field.getName());
return executor.isAnnotationPresent(annotType);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
public static Annotation[] jlrFieldGetAnnotations(Field field) {
//Fields do not inherit annotations so we can just call...
return jlrFieldGetDeclaredAnnotations(field);
}
public static Annotation[] jlrAccessibleObjectGetAnnotations(AccessibleObject obj) {
if (obj instanceof Method) {
return jlrMethodGetAnnotations((Method) obj);
} else if (obj instanceof Field) {
return jlrFieldGetAnnotations((Field) obj);
} else if (obj instanceof Constructor>) {
return jlrConstructorGetAnnotations((Constructor>) obj);
} else {
//Some other type of member which we don't support reloading...
// (actually there are really no other cases any more!)
return obj.getAnnotations();
}
}
public static Annotation[] jlrConstructorGetAnnotations(Constructor> c) {
return jlrConstructorGetDeclaredAnnotations(c);
}
public static Annotation[] jlrConstructorGetDeclaredAnnotations(Constructor> c) {
ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(c.getDeclaringClass());
if (rtype == null) {
//Nothing special to be done
return c.getDeclaredAnnotations();
} else {
// Constructor could have changed...
CurrentLiveVersion clv = rtype.getLiveVersion();
Method executor = clv.getExecutorMethod(rtype.getCurrentConstructor(Type.getConstructorDescriptor(c)));
return executor.getAnnotations();
}
}
public static Annotation jlrConstructorGetAnnotation(Constructor> c, Class extends Annotation> annotType) {
ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(c.getDeclaringClass());
if (rtype == null) {
//Nothing special to be done
return c.getAnnotation(annotType);
} else {
// Constructor could have changed...
CurrentLiveVersion clv = rtype.getLiveVersion();
Method executor = clv.getExecutorMethod(rtype.getCurrentConstructor(Type.getConstructorDescriptor(c)));
return executor.getAnnotation(annotType);
}
}
public static Annotation[][] jlrConstructorGetParameterAnnotations(Constructor> c) {
ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(c.getDeclaringClass());
if (rtype == null) {
//Nothing special to be done
return c.getParameterAnnotations();
} else {
// Method could have changed...
CurrentLiveVersion clv = rtype.getLiveVersion();
MethodMember currentConstructor = rtype.getCurrentConstructor(Type.getConstructorDescriptor(c));
Method executor = clv.getExecutorMethod(currentConstructor);
Annotation[][] result = executor.getParameterAnnotations();
//Constructor executor methods have an extra param.
//Though extra param is added to front... annotations aren't being moved so we have to actually drop
//the *last* array element
result = Utils.arrayCopyOf(result, result.length - 1);
return result;
}
}
public static boolean jlrConstructorIsAnnotationPresent(Constructor> c, Class extends Annotation> annotType) {
ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(c.getDeclaringClass());
if (rtype == null) {
//Nothing special to be done
return c.isAnnotationPresent(annotType);
} else {
// Constructor could have changed...
CurrentLiveVersion clv = rtype.getLiveVersion();
Method executor = clv.getExecutorMethod(rtype.getCurrentConstructor(Type.getConstructorDescriptor(c)));
return executor.isAnnotationPresent(annotType);
}
}
public static Annotation jlrFieldGetAnnotation(Field field, Class extends Annotation> annotType) {
if (annotType == null) {
throw new NullPointerException();
}
ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(field.getDeclaringClass());
if (rtype == null) {
//Nothing special to be done
return field.getAnnotation(annotType);
} else {
// Field could have changed...
CurrentLiveVersion clv = rtype.getLiveVersion();
try {
Field executor = clv.getExecutorField(field.getName());
return executor.getAnnotation(annotType);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
public static Annotation[] jlrMethodGetAnnotations(Method method) {
return jlrMethodGetDeclaredAnnotations(method);
}
public static boolean jlrAnnotatedElementIsAnnotationPresent(AnnotatedElement elem, Class extends Annotation> annotType) {
if (elem instanceof Class>) {
return jlClassIsAnnotationPresent((Class>) elem, annotType);
} else if (elem instanceof AccessibleObject) {
return jlrAccessibleObjectIsAnnotationPresent((AccessibleObject) elem, annotType);
} else {
//Don't know what it is... not something we handle anyway
return elem.isAnnotationPresent(annotType);
}
}
public static boolean jlrAccessibleObjectIsAnnotationPresent(AccessibleObject obj, Class extends Annotation> annotType) {
if (obj instanceof Method) {
return jlrMethodIsAnnotationPresent((Method) obj, annotType);
} else if (obj instanceof Field) {
return jlrFieldIsAnnotationPresent((Field) obj, annotType);
} else if (obj instanceof Constructor) {
return jlrConstructorIsAnnotationPresent((Constructor>) obj, annotType);
} else {
//Some other type of member which we don't support reloading...
return obj.isAnnotationPresent(annotType);
}
}
public static Annotation jlrAnnotatedElementGetAnnotation(AnnotatedElement elem, Class extends Annotation> annotType) {
if (elem instanceof Class>) {
return jlClassGetAnnotation((Class>) elem, annotType);
} else if (elem instanceof AccessibleObject) {
return jlrAccessibleObjectGetAnnotation((AccessibleObject) elem, annotType);
} else {
//Don't know what it is... not something we handle anyway
// Note: only thing it can be is probably java.lang.Package
return elem.getAnnotation(annotType);
}
}
public static Annotation jlrAccessibleObjectGetAnnotation(AccessibleObject obj, Class extends Annotation> annotType) {
if (obj instanceof Method) {
return jlrMethodGetAnnotation((Method) obj, annotType);
} else if (obj instanceof Field) {
return jlrFieldGetAnnotation((Field) obj, annotType);
} else if (obj instanceof Constructor>) {
return jlrConstructorGetAnnotation((Constructor>) obj, annotType);
} else {
//Some other type of member which we don't support reloading...
return obj.getAnnotation(annotType);
}
}
public static Field jlClassGetField(Class> clazz, String name) throws SecurityException, NoSuchFieldException {
ReloadableType rtype = getRType(clazz);
if (name.startsWith(Constants.PREFIX)) {
throw Exceptions.noSuchFieldException(name);
}
if (rtype == null) {
//Not reloadable
return clazz.getField(name);
} else {
//Reloadable
Field f = GetFieldLookup.lookup(rtype, name);
if (f != null) {
return f;
}
throw Exceptions.noSuchFieldException(name);
}
}
public static Field jlClassGetDeclaredField(Class> clazz, String name) throws SecurityException, NoSuchFieldException {
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
return clazz.getDeclaredField(name);
} else if (name.startsWith(Constants.PREFIX)) {
throw Exceptions.noSuchFieldException(name);
} else if (!rtype.hasBeenReloaded()) {
Field f = clazz.getDeclaredField(name);
fixModifier(rtype.getLatestTypeDescriptor(), f);
return f;
} else {
Field f = GetDeclaredFieldLookup.lookup(rtype, name);
if (f == null) {
throw Exceptions.noSuchFieldException(name);
} else {
return f;
}
}
}
public static Field[] jlClassGetDeclaredFields(Class> clazz) {
Field[] fields = clazz.getDeclaredFields();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
return fields;
} else {
if (!rtype.hasBeenReloaded()) {
//Not reloaded yet...
fields = removeMetaFields(fields);
fixModifiers(rtype, fields);
return fields;
} else {
// Was reloaded, it's up to us to create the field objects
TypeDescriptor typeDesc = rtype.getLatestTypeDescriptor();
FieldMember[] members = typeDesc.getFields();
fields = new Field[members.length];
int i = 0;
for (FieldMember f : members) {
String fieldTypeDescriptor = f.getDescriptor();
Class> type;
try {
type = Utils.toClass(Type.getType(fieldTypeDescriptor), rtype.typeRegistry.getClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
fields[i++] = JVM.newField(clazz, type, f.getModifiers(), f.getName(), f.getGenericSignature());
}
if (GlobalConfiguration.assertsMode) {
Utils.assertTrue(i == fields.length, "Bug: unexpected number of fields");
}
return fields;
}
}
}
/**
* Given a list of fields filter out those fields that are created by springloaded (leaving only the "genuine" fields)
*/
private static Field[] removeMetaFields(Field[] fields) {
Field[] realFields = new Field[fields.length - 1];
//We'll delete at least one, sometimes more than one field (because there's at least the r$type field).
int i = 0;
for (Field field : fields) {
if (!field.getName().startsWith(Constants.PREFIX)) {
realFields[i++] = field;
}
}
if (i < realFields.length) {
realFields = Utils.arrayCopyOf(realFields, i);
}
if (GlobalConfiguration.assertsMode) {
Utils.assertTrue(i == realFields.length, "Bug in removeMetaFields, created array of wrong length");
}
return realFields;
}
/**
* Although fields are not reloadable, we have to intercept this because otherwise we'll return the r$type field as a result
* here.
* @param clazz the class for which to retrieve the fields
* @return array of fields in the class
*/
public static Field[] jlClassGetFields(Class> clazz) {
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
return clazz.getFields();
} else {
List allFields = new ArrayList();
gatherFields(clazz, allFields, new HashSet>());
return allFields.toArray(new Field[allFields.size()]);
}
}
/**
* Gather up all (public) fields in an interface and all its super interfaces recursively.
* @param clazz the class for which to collect up fields
* @param collected a collector that has fields added to it as this method runs (recursively)
* @param visited a set recording which types have already been visited
*/
private static void gatherFields(Class> clazz, List collected, HashSet> visited) {
if (visited.contains(clazz)) {
return;
}
visited.add(clazz);
Field[] fields = jlClassGetDeclaredFields(clazz);
for (Field f : fields) {
if (Modifier.isPublic(f.getModifiers())) {
collected.add(f);
}
}
if (!clazz.isInterface()) {
Class> supr = clazz.getSuperclass();
if (supr != null) {
gatherFields(supr, collected, visited);
}
}
for (Class> itf : clazz.getInterfaces()) {
gatherFields(itf, collected, visited);
}
}
public static Object jlrFieldGet(Field field, Object target) throws IllegalArgumentException, IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
field = asAccessibleField(field, target, true);
return field.get(target);
} else {
asAccessibleField(field, target, false);
return rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers()));
}
}
public static int jlrFieldGetInt(Field field, Object target) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
field = asAccessibleField(field, target, true);
return field.getInt(target);
} else {
asAccessibleField(field, target, false);
typeCheckFieldGet(field, int.class);
Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers()));
if (value instanceof Character) {
return ((Character) value).charValue();
} else {
return ((Number) value).intValue();
}
}
}
public static byte jlrFieldGetByte(Field field, Object target) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
field = asAccessibleField(field, target, true);
return field.getByte(target);
} else {
asAccessibleField(field, target, false);
typeCheckFieldGet(field, byte.class);
Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers()));
return ((Number) value).byteValue();
}
}
public static char jlrFieldGetChar(Field field, Object target) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
field = asAccessibleField(field, target, true);
return field.getChar(target);
} else {
asAccessibleField(field, target, false);
typeCheckFieldGet(field, char.class);
Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers()));
return ((Character) value).charValue();
}
}
public static short jlrFieldGetShort(Field field, Object target) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
field = asAccessibleField(field, target, true);
return field.getShort(target);
} else {
asAccessibleField(field, target, false);
typeCheckFieldGet(field, short.class);
Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers()));
if (value instanceof Character) {
return (short) ((Character) value).charValue();
} else {
return ((Number) value).shortValue();
}
}
}
public static double jlrFieldGetDouble(Field field, Object target) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
field = asAccessibleField(field, target, true);
return field.getDouble(target);
} else {
asAccessibleField(field, target, false);
typeCheckFieldGet(field, double.class);
Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers()));
if (value instanceof Character) {
return ((Character) value).charValue();
} else {
return ((Number) value).doubleValue();
}
}
}
public static float jlrFieldGetFloat(Field field, Object target) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
field = asAccessibleField(field, target, true);
return field.getFloat(target);
} else {
asAccessibleField(field, target, false);
typeCheckFieldGet(field, float.class);
Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers()));
if (value instanceof Character) {
return ((Character) value).charValue();
} else {
return ((Number) value).floatValue();
}
}
}
public static boolean jlrFieldGetBoolean(Field field, Object target) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
field = asAccessibleField(field, target, true);
return field.getBoolean(target);
} else {
asAccessibleField(field, target, false);
typeCheckFieldGet(field, boolean.class);
Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers()));
return ((Boolean) value).booleanValue();
}
}
public static long jlrFieldGetLong(Field field, Object target) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
field = asAccessibleField(field, target, true);
return field.getLong(target);
} else {
asAccessibleField(field, target, false);
typeCheckFieldGet(field, long.class);
Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers()));
if (value instanceof Character) {
return ((Character) value).charValue();
} else {
return ((Number) value).longValue();
}
}
}
private static void typeCheckFieldGet(Field field, Class> returnType) {
Class> fieldType = field.getType();
if (!Utils.isConvertableFrom(returnType, fieldType)) {
throw Exceptions.illegalGetFieldType(field, returnType);
}
}
public static void jlrFieldSet(Field field, Object target, Object value) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Not reloadable
field = asSetableField(field, target, valueType(value), value, true);
field.set(target, value);
} else {
asSetableField(field, target, valueType(value), value, false);
rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value);
}
}
public static void jlrFieldSetInt(Field field, Object target, int value) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Not reloadable
field = asSetableField(field, target, int.class, value, true);
field.setInt(target, value);
} else {
asSetableField(field, target, int.class, value, false);
rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value);
}
}
public static void jlrFieldSetByte(Field field, Object target, byte value) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Not reloadable
field = asSetableField(field, target, byte.class, value, true);
field.setByte(target, value);
} else {
asSetableField(field, target, byte.class, value, false);
rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value);
}
}
public static void jlrFieldSetChar(Field field, Object target, char value) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Not reloadable
field = asSetableField(field, target, char.class, value, true);
field.setChar(target, value);
} else {
asSetableField(field, target, char.class, value, false);
rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value);
}
}
public static void jlrFieldSetShort(Field field, Object target, short value) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Not reloadable
field = asSetableField(field, target, short.class, value, true);
field.setShort(target, value);
} else {
asSetableField(field, target, short.class, value, false);
rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value);
}
}
public static void jlrFieldSetDouble(Field field, Object target, double value) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Not reloadable
field = asSetableField(field, target, double.class, value, true);
field.setDouble(target, value);
} else {
asSetableField(field, target, double.class, value, false);
rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value);
}
}
public static void jlrFieldSetFloat(Field field, Object target, float value) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Not reloadable
field = asSetableField(field, target, float.class, value, true);
field.setFloat(target, value);
} else {
asSetableField(field, target, float.class, value, false);
rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value);
}
}
public static void jlrFieldSetLong(Field field, Object target, long value) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Not reloadable
field = asSetableField(field, target, long.class, value, true);
field.setLong(target, value);
} else {
asSetableField(field, target, long.class, value, false);
rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value);
}
}
public static void jlrFieldSetBoolean(Field field, Object target, boolean value) throws IllegalAccessException {
Class> clazz = field.getDeclaringClass();
ReloadableType rtype = getRType(clazz);
if (rtype == null) {
// Not reloadable
field = asSetableField(field, target, boolean.class, value, true);
field.setBoolean(target, value);
} else {
asSetableField(field, target, boolean.class, value, false);
rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value);
}
}
/**
* What's the "boxed" version of a given primtive type.
*/
private static Class> boxTypeFor(Class> primType) {
if (primType == int.class) {
return Integer.class;
} else if (primType == boolean.class) {
return Boolean.class;
} else if (primType == byte.class) {
return Byte.class;
} else if (primType == char.class) {
return Character.class;
} else if (primType == double.class) {
return Double.class;
} else if (primType == float.class) {
return Float.class;
} else if (primType == long.class) {
return Long.class;
} else if (primType == short.class) {
return Short.class;
}
throw new IllegalStateException("Forgotten a case in this method?");
}
}