Merge pull request #21 from serverperformance/master

Fixes in AccessClassLoader and MethodAccess
This commit is contained in:
Nathan Sweet 2014-01-26 05:24:44 -08:00
commit d918153ffe
6 changed files with 390 additions and 77 deletions

View File

@ -1,35 +1,69 @@
package com.esotericsoftware.reflectasm; package com.esotericsoftware.reflectasm;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.security.ProtectionDomain; import java.security.ProtectionDomain;
import java.util.ArrayList; import java.util.WeakHashMap;
class AccessClassLoader extends ClassLoader { class AccessClassLoader extends ClassLoader {
static private final ArrayList<AccessClassLoader> accessClassLoaders = new ArrayList(); // Weak-references to ClassLoaders, to avoid PermGen memory leaks for example
// in AppServers/WebContainters if the reflectasm framework (including this class)
// is loaded outside the deployed applications (WAR/EAR) using ReflectASM/Kryo
// (exts, user classpath, etc).
//
// The key is the parent ClassLoader and the value is the AccessClassLoader
// Both are weak-referenced in the HashTable.
static private final WeakHashMap<ClassLoader, WeakReference<AccessClassLoader>> accessClassLoaders = new WeakHashMap<ClassLoader, WeakReference<AccessClassLoader>>();
// Fast-path for classes loaded in the same ClassLoader than this Class
static private final ClassLoader selfContextParentClassLoader = getParentClassLoader(AccessClassLoader.class);
static private volatile AccessClassLoader selfContextAccessClassLoader = new AccessClassLoader(selfContextParentClassLoader);
static AccessClassLoader get (Class type) { static AccessClassLoader get (Class type) {
ClassLoader parent = type.getClassLoader(); ClassLoader parent = getParentClassLoader(type);
synchronized (accessClassLoaders) { // 1. fast-path:
for (int i = 0, n = accessClassLoaders.size(); i < n; i++) { if (selfContextParentClassLoader.equals(parent)) {
AccessClassLoader accessClassLoader = accessClassLoaders.get(i); if (selfContextAccessClassLoader==null) {
if (accessClassLoader.getParent() == parent) return accessClassLoader; // DCL with volatile semantics
synchronized (accessClassLoaders) {
if (selfContextAccessClassLoader==null)
selfContextAccessClassLoader = new AccessClassLoader(selfContextParentClassLoader);
}
}
return selfContextAccessClassLoader;
}
// 2. normal search:
synchronized (accessClassLoaders) {
WeakReference<AccessClassLoader> ref = accessClassLoaders.get(parent);
if (ref!=null) {
AccessClassLoader accessClassLoader = ref.get();
if (accessClassLoader!=null) return accessClassLoader;
else accessClassLoaders.remove(parent); // the value has been GC-reclaimed, but still not the key (defensive sanity)
} }
if(parent == null) parent = ClassLoader.getSystemClassLoader();
AccessClassLoader accessClassLoader = new AccessClassLoader(parent); AccessClassLoader accessClassLoader = new AccessClassLoader(parent);
accessClassLoaders.add(accessClassLoader); accessClassLoaders.put(parent, new WeakReference<AccessClassLoader>(accessClassLoader));
return accessClassLoader; return accessClassLoader;
} }
} }
static void remove (ClassLoader parent) { public static void remove (ClassLoader parent) {
synchronized (accessClassLoaders) { // 1. fast-path:
for (int i = accessClassLoaders.size() - 1; i >= 0; i--) { if (selfContextParentClassLoader.equals(parent)) {
AccessClassLoader accessClassLoader = accessClassLoaders.get(i); selfContextAccessClassLoader = null;
if (accessClassLoader.getParent() == parent) accessClassLoaders.remove(i); }
else {
// 2. normal search:
synchronized (accessClassLoaders) {
accessClassLoaders.remove(parent);
} }
} }
} }
public static int activeAccessClassLoaders() {
int sz = accessClassLoaders.size();
if (selfContextAccessClassLoader!=null) sz++;
return sz;
}
private AccessClassLoader (ClassLoader parent) { private AccessClassLoader (ClassLoader parent) {
super(parent); super(parent);
@ -49,11 +83,17 @@ class AccessClassLoader extends ClassLoader {
// Attempt to load the access class in the same loader, which makes protected and default access members accessible. // Attempt to load the access class in the same loader, which makes protected and default access members accessible.
Method method = ClassLoader.class.getDeclaredMethod("defineClass", new Class[] {String.class, byte[].class, int.class, Method method = ClassLoader.class.getDeclaredMethod("defineClass", new Class[] {String.class, byte[].class, int.class,
int.class, ProtectionDomain.class}); int.class, ProtectionDomain.class});
method.setAccessible(true); if (!method.isAccessible()) method.setAccessible(true);
return (Class)method.invoke(getParent(), new Object[] {name, bytes, Integer.valueOf(0), Integer.valueOf(bytes.length), return (Class)method.invoke(getParent(), new Object[] {name, bytes, Integer.valueOf(0), Integer.valueOf(bytes.length),
getClass().getProtectionDomain()}); getClass().getProtectionDomain()});
} catch (Exception ignored) { } catch (Exception ignored) {
} }
return defineClass(name, bytes, 0, bytes.length, getClass().getProtectionDomain()); return defineClass(name, bytes, 0, bytes.length, getClass().getProtectionDomain());
} }
private static ClassLoader getParentClassLoader(Class type) {
ClassLoader parent = type.getClassLoader();
if (parent == null) parent = ClassLoader.getSystemClassLoader();
return parent;
}
} }

View File

@ -1,6 +1,7 @@
package com.esotericsoftware.reflectasm; package com.esotericsoftware.reflectasm;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import org.objectweb.asm.ClassWriter; import org.objectweb.asm.ClassWriter;
@ -46,20 +47,29 @@ public abstract class ConstructorAccess<T> {
String classNameInternal = className.replace('.', '/'); String classNameInternal = className.replace('.', '/');
String enclosingClassNameInternal; String enclosingClassNameInternal;
boolean isPrivate = false;
if (!isNonStaticMemberClass) { if (!isNonStaticMemberClass) {
enclosingClassNameInternal = null; enclosingClassNameInternal = null;
try { try {
type.getDeclaredConstructor((Class[])null); Constructor<T> constructor = type.getDeclaredConstructor((Class[])null);
isPrivate = Modifier.isPrivate(constructor.getModifiers());
} catch (Exception ex) { } catch (Exception ex) {
throw new RuntimeException("Class cannot be created (missing no-arg constructor): " + type.getName()); throw new RuntimeException("Class cannot be created (missing no-arg constructor): " + type.getName(), ex);
}
if (isPrivate) {
throw new RuntimeException("Class cannot be created (the no-arg constructor is private): " + type.getName());
} }
} else { } else {
enclosingClassNameInternal = enclosingType.getName().replace('.', '/'); enclosingClassNameInternal = enclosingType.getName().replace('.', '/');
try { try {
type.getDeclaredConstructor(enclosingType); // Inner classes should have this. Constructor<T> constructor = type.getDeclaredConstructor(enclosingType); // Inner classes should have this.
isPrivate = Modifier.isPrivate(constructor.getModifiers());
} catch (Exception ex) { } catch (Exception ex) {
throw new RuntimeException("Non-static member class cannot be created (missing enclosing class constructor): " throw new RuntimeException("Non-static member class cannot be created (missing enclosing class constructor): "
+ type.getName()); + type.getName(), ex);
}
if (isPrivate) {
throw new RuntimeException("Non-static member class cannot be created (the enclosing class constructor is private): " + type.getName());
} }
} }

View File

@ -17,12 +17,18 @@ import static org.objectweb.asm.Opcodes.*;
public abstract class MethodAccess { public abstract class MethodAccess {
private String[] methodNames; private String[] methodNames;
private Class[][] parameterTypes; private Class[][] parameterTypes;
private Class[] returnTypes;
abstract public Object invoke (Object object, int methodIndex, Object... args); abstract public Object invoke (Object object, int methodIndex, Object... args);
/** Invokes the first method with the specified name. */ /** Invokes the method with the specified name and the specified param types. */
public Object invoke (Object object, String methodName, Class[] paramTypes, Object... args) {
return invoke(object, getIndex(methodName, paramTypes), args);
}
/** Invokes the first method with the specified name and the specified number of arguments. */
public Object invoke (Object object, String methodName, Object... args) { public Object invoke (Object object, String methodName, Object... args) {
return invoke(object, getIndex(methodName), args); return invoke(object, getIndex(methodName, args==null ? 0 : args.length), args);
} }
/** Returns the index of the first method with the specified name. */ /** Returns the index of the first method with the specified name. */
@ -32,10 +38,18 @@ public abstract class MethodAccess {
throw new IllegalArgumentException("Unable to find public method: " + methodName); throw new IllegalArgumentException("Unable to find public method: " + methodName);
} }
/** Returns the index of the first method with the specified name and param types. */
public int getIndex (String methodName, Class... paramTypes) { public int getIndex (String methodName, Class... paramTypes) {
for (int i = 0, n = methodNames.length; i < n; i++) for (int i = 0, n = methodNames.length; i < n; i++)
if (methodNames[i].equals(methodName) && Arrays.equals(paramTypes, parameterTypes[i])) return i; if (methodNames[i].equals(methodName) && Arrays.equals(paramTypes, parameterTypes[i])) return i;
throw new IllegalArgumentException("Unable to find public method: " + methodName + " " + Arrays.toString(parameterTypes)); throw new IllegalArgumentException("Unable to find public method: " + methodName + " " + Arrays.toString(paramTypes));
}
/** Returns the index of the first method with the specified name and the specified number of arguments. */
public int getIndex (String methodName, int paramsCount) {
for (int i = 0, n = methodNames.length; i < n; i++)
if (methodNames[i].equals(methodName) && parameterTypes[i].length==paramsCount) return i;
throw new IllegalArgumentException("Unable to find public method: " + methodName + " with " + paramsCount + " params.");
} }
public String[] getMethodNames () { public String[] getMethodNames () {
@ -45,34 +59,40 @@ public abstract class MethodAccess {
public Class[][] getParameterTypes () { public Class[][] getParameterTypes () {
return parameterTypes; return parameterTypes;
} }
public Class[] getReturnTypes () {
return returnTypes;
}
static public MethodAccess get (Class type) { static public MethodAccess get (Class type) {
ArrayList<Method> methods = new ArrayList(); ArrayList<Method> methods = new ArrayList<Method>();
Class nextClass = type; boolean isInterface = type.isInterface();
while (nextClass != Object.class) { if (!isInterface) {
Method[] declaredMethods = nextClass.getDeclaredMethods(); Class nextClass = type;
for (int i = 0, n = declaredMethods.length; i < n; i++) { while (nextClass != Object.class) {
Method method = declaredMethods[i]; addDeclaredMethodsToList(nextClass, methods);
int modifiers = method.getModifiers(); nextClass = nextClass.getSuperclass();
if (Modifier.isStatic(modifiers)) continue;
if (Modifier.isPrivate(modifiers)) continue;
methods.add(method);
} }
nextClass = nextClass.getSuperclass(); }
else {
recursiveAddInterfaceMethodsToList(type, methods);
} }
Class[][] parameterTypes = new Class[methods.size()][]; int n = methods.size();
String[] methodNames = new String[methods.size()]; String[] methodNames = new String[n];
for (int i = 0, n = methodNames.length; i < n; i++) { Class[][] parameterTypes = new Class[n][];
Class[] returnTypes = new Class[n];
for (int i = 0; i < n; i++) {
Method method = methods.get(i); Method method = methods.get(i);
methodNames[i] = method.getName(); methodNames[i] = method.getName();
parameterTypes[i] = method.getParameterTypes(); parameterTypes[i] = method.getParameterTypes();
returnTypes[i] = method.getReturnType();
} }
String className = type.getName(); String className = type.getName();
String accessClassName = className + "MethodAccess"; String accessClassName = className + "MethodAccess";
if (accessClassName.startsWith("java.")) accessClassName = "reflectasm." + accessClassName; if (accessClassName.startsWith("java.")) accessClassName = "reflectasm." + accessClassName;
Class accessClass = null; Class accessClass;
AccessClassLoader loader = AccessClassLoader.get(type); AccessClassLoader loader = AccessClassLoader.get(type);
synchronized (loader) { synchronized (loader) {
@ -106,14 +126,14 @@ public abstract class MethodAccess {
mv.visitVarInsn(ASTORE, 4); mv.visitVarInsn(ASTORE, 4);
mv.visitVarInsn(ILOAD, 2); mv.visitVarInsn(ILOAD, 2);
Label[] labels = new Label[methods.size()]; Label[] labels = new Label[n];
for (int i = 0, n = labels.length; i < n; i++) for (int i = 0; i < n; i++)
labels[i] = new Label(); labels[i] = new Label();
Label defaultLabel = new Label(); Label defaultLabel = new Label();
mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels); mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels);
StringBuilder buffer = new StringBuilder(128); StringBuilder buffer = new StringBuilder(128);
for (int i = 0, n = labels.length; i < n; i++) { for (int i = 0; i < n; i++) {
mv.visitLabel(labels[i]); mv.visitLabel(labels[i]);
if (i == 0) if (i == 0)
mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] {classNameInternal}, 0, null); mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] {classNameInternal}, 0, null);
@ -124,8 +144,9 @@ public abstract class MethodAccess {
buffer.setLength(0); buffer.setLength(0);
buffer.append('('); buffer.append('(');
Method method = methods.get(i); String methodName = methodNames[i];
Class[] paramTypes = method.getParameterTypes(); Class[] paramTypes = parameterTypes[i];
Class returnType = returnTypes[i];
for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) { for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
mv.visitVarInsn(ALOAD, 3); mv.visitVarInsn(ALOAD, 3);
mv.visitIntInsn(BIPUSH, paramIndex); mv.visitIntInsn(BIPUSH, paramIndex);
@ -175,10 +196,10 @@ public abstract class MethodAccess {
} }
buffer.append(')'); buffer.append(')');
buffer.append(Type.getDescriptor(method.getReturnType())); buffer.append(Type.getDescriptor(returnType));
mv.visitMethodInsn(INVOKEVIRTUAL, classNameInternal, method.getName(), buffer.toString()); mv.visitMethodInsn(isInterface ? INVOKEINTERFACE : INVOKEVIRTUAL, classNameInternal, methodName, buffer.toString());
switch (Type.getType(method.getReturnType()).getSort()) { switch (Type.getType(returnType).getSort()) {
case Type.VOID: case Type.VOID:
mv.visitInsn(ACONST_NULL); mv.visitInsn(ACONST_NULL);
break; break;
@ -237,9 +258,28 @@ public abstract class MethodAccess {
MethodAccess access = (MethodAccess)accessClass.newInstance(); MethodAccess access = (MethodAccess)accessClass.newInstance();
access.methodNames = methodNames; access.methodNames = methodNames;
access.parameterTypes = parameterTypes; access.parameterTypes = parameterTypes;
access.returnTypes = returnTypes;
return access; return access;
} catch (Exception ex) { } catch (Exception ex) {
throw new RuntimeException("Error constructing method access class: " + accessClassName, ex); throw new RuntimeException("Error constructing method access class: " + accessClassName, ex);
} }
} }
private static void addDeclaredMethodsToList(Class type, ArrayList<Method> methods) {
Method[] declaredMethods = type.getDeclaredMethods();
for (int i = 0, n = declaredMethods.length; i < n; i++) {
Method method = declaredMethods[i];
int modifiers = method.getModifiers();
if (Modifier.isStatic(modifiers)) continue;
if (Modifier.isPrivate(modifiers)) continue;
methods.add(method);
}
}
private static void recursiveAddInterfaceMethodsToList(Class interfaceType, ArrayList<Method> methods) {
addDeclaredMethodsToList(interfaceType, methods);
for (Class nextInterface : interfaceType.getInterfaces()) {
recursiveAddInterfaceMethodsToList(nextInterface, methods);
}
}
} }

View File

@ -4,42 +4,16 @@ package com.esotericsoftware.reflectasm;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import junit.framework.TestCase; import junit.framework.TestCase;
public class ClassLoaderTest extends TestCase { public class ClassLoaderTest extends TestCase {
public void testDifferentClassloaders () throws Exception { public void testDifferentClassloaders () throws Exception {
// This classloader can see only the Test class and core Java classes. // This classloader can see only the Test class and core Java classes.
ClassLoader testClassLoader = new ClassLoader() { ClassLoader testClassLoader = new TestClassLoader1();
protected synchronized Class<?> loadClass (String name, boolean resolve) throws ClassNotFoundException {
Class c = findLoadedClass(name);
if (c != null) return c;
if (name.startsWith("java.")) return super.loadClass(name, resolve);
if (!name.equals("com.esotericsoftware.reflectasm.ClassLoaderTest$Test"))
throw new ClassNotFoundException("Class not found on purpose: " + name);
ByteArrayOutputStream output = new ByteArrayOutputStream(32 * 1024);
InputStream input = ClassLoaderTest.class.getResourceAsStream("/" + name.replace('.', '/') + ".class");
if (input == null) return null;
try {
byte[] buffer = new byte[4096];
int total = 0;
while (true) {
int length = input.read(buffer, 0, buffer.length);
if (length == -1) break;
output.write(buffer, 0, length);
}
} catch (IOException ex) {
throw new ClassNotFoundException("Error reading class file.", ex);
} finally {
try {
input.close();
} catch (IOException ignored) {
}
}
byte[] buffer = output.toByteArray();
return defineClass(name, buffer, 0, buffer.length);
}
};
Class testClass = testClassLoader.loadClass("com.esotericsoftware.reflectasm.ClassLoaderTest$Test"); Class testClass = testClassLoader.loadClass("com.esotericsoftware.reflectasm.ClassLoaderTest$Test");
Object testObject = testClass.newInstance(); Object testObject = testClass.newInstance();
@ -49,6 +23,90 @@ public class ClassLoaderTest extends TestCase {
assertEquals("first", testObject.toString()); assertEquals("first", testObject.toString());
assertEquals("first", access.get(testObject, "name")); assertEquals("first", access.get(testObject, "name"));
} }
public void testAutoUnloadClassloaders () throws Exception {
int initialCount = AccessClassLoader.activeAccessClassLoaders();
ClassLoader testClassLoader1 = new TestClassLoader1();
Class testClass1 = testClassLoader1.loadClass("com.esotericsoftware.reflectasm.ClassLoaderTest$Test");
Object testObject1 = testClass1.newInstance();
FieldAccess access1 = FieldAccess.get(testObject1.getClass());
access1.set(testObject1, "name", "first");
assertEquals("first", testObject1.toString());
assertEquals("first", access1.get(testObject1, "name"));
ClassLoader testClassLoader2 = new TestClassLoader2();
Class testClass2 = testClassLoader2.loadClass("com.esotericsoftware.reflectasm.ClassLoaderTest$Test");
Object testObject2 = testClass2.newInstance();
FieldAccess access2 = FieldAccess.get(testObject2.getClass());
access2.set(testObject2, "name", "second");
assertEquals("second", testObject2.toString());
assertEquals("second", access2.get(testObject2, "name"));
assertEquals(access1.getClass().toString(), access2.getClass().toString()); // Same class names
assertFalse(access1.getClass().equals(access2.getClass())); // But different classes
assertEquals(initialCount+2, AccessClassLoader.activeAccessClassLoaders());
testClassLoader1 = null;
testClass1 = null;
testObject1 = null;
access1 = null;
testClassLoader2 = null;
testClass2 = null;
testObject2 = null;
access2 = null;
// Force GC to reclaim unreachable (or only weak-reachable) objects
System.gc();
try {
Object[] array = new Object[(int) Runtime.getRuntime().maxMemory()];
System.out.println(array.length);
} catch (Throwable e) {
// Ignore OME
}
System.gc();
int times = 0;
while (AccessClassLoader.activeAccessClassLoaders()>1 && times < 50) { // max 5 seconds, should be instant
Thread.sleep(100); // test again
times++;
}
// Yeah, both reclaimed!
assertEquals(1, AccessClassLoader.activeAccessClassLoaders());
}
public void testRemoveClassloaders () throws Exception {
int initialCount = AccessClassLoader.activeAccessClassLoaders();
ClassLoader testClassLoader1 = new TestClassLoader1();
Class testClass1 = testClassLoader1.loadClass("com.esotericsoftware.reflectasm.ClassLoaderTest$Test");
Object testObject1 = testClass1.newInstance();
FieldAccess access1 = FieldAccess.get(testObject1.getClass());
access1.set(testObject1, "name", "first");
assertEquals("first", testObject1.toString());
assertEquals("first", access1.get(testObject1, "name"));
ClassLoader testClassLoader2 = new TestClassLoader2();
Class testClass2 = testClassLoader2.loadClass("com.esotericsoftware.reflectasm.ClassLoaderTest$Test");
Object testObject2 = testClass2.newInstance();
FieldAccess access2 = FieldAccess.get(testObject2.getClass());
access2.set(testObject2, "name", "second");
assertEquals("second", testObject2.toString());
assertEquals("second", access2.get(testObject2, "name"));
assertEquals(access1.getClass().toString(), access2.getClass().toString()); // Same class names
assertFalse(access1.getClass().equals(access2.getClass())); // But different classes
assertEquals(initialCount+2, AccessClassLoader.activeAccessClassLoaders());
AccessClassLoader.remove(testObject1.getClass().getClassLoader());
assertEquals(initialCount+1, AccessClassLoader.activeAccessClassLoaders());
AccessClassLoader.remove(testObject2.getClass().getClassLoader());
assertEquals(initialCount+0, AccessClassLoader.activeAccessClassLoaders());
AccessClassLoader.remove(this.getClass().getClassLoader());
assertEquals(initialCount-1, AccessClassLoader.activeAccessClassLoaders());
}
static public class Test { static public class Test {
public String name; public String name;
@ -57,4 +115,38 @@ public class ClassLoaderTest extends TestCase {
return name; return name;
} }
} }
static public class TestClassLoader1 extends ClassLoader {
protected synchronized Class<?> loadClass (String name, boolean resolve) throws ClassNotFoundException {
Class c = findLoadedClass(name);
if (c != null) return c;
if (name.startsWith("java.")) return super.loadClass(name, resolve);
if (!name.equals("com.esotericsoftware.reflectasm.ClassLoaderTest$Test"))
throw new ClassNotFoundException("Class not found on purpose: " + name);
ByteArrayOutputStream output = new ByteArrayOutputStream(32 * 1024);
InputStream input = ClassLoaderTest.class.getResourceAsStream("/" + name.replace('.', '/') + ".class");
if (input == null) return null;
try {
byte[] buffer = new byte[4096];
int total = 0;
while (true) {
int length = input.read(buffer, 0, buffer.length);
if (length == -1) break;
output.write(buffer, 0, length);
}
} catch (IOException ex) {
throw new ClassNotFoundException("Error reading class file.", ex);
} finally {
try {
input.close();
} catch (IOException ignored) {
}
}
byte[] buffer = output.toByteArray();
return defineClass(name, buffer, 0, buffer.length);
}
}
static public class TestClassLoader2 extends TestClassLoader1 {
}
} }

View File

@ -1,6 +1,8 @@
package com.esotericsoftware.reflectasm; package com.esotericsoftware.reflectasm;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import junit.framework.TestCase; import junit.framework.TestCase;
public class ConstructorAccessTest extends TestCase { public class ConstructorAccessTest extends TestCase {
@ -20,6 +22,70 @@ public class ConstructorAccessTest extends TestCase {
assertEquals(someObject, access.newInstance()); assertEquals(someObject, access.newInstance());
} }
public void testHasArgumentConstructor () {
try {
ConstructorAccess.get(HasArgumentConstructor.class);
assertTrue(false);
}
catch (RuntimeException re) {
System.out.println("Expected exception happened: " + re);
}
catch (Throwable t) {
System.out.println("Unexpected exception happened: " + t);
assertTrue(false);
}
}
public void testHasPrivateConstructor () {
try {
ConstructorAccess.get(HasPrivateConstructor.class);
assertTrue(false);
}
catch (RuntimeException re) {
System.out.println("Expected exception happened: " + re);
}
catch (Throwable t) {
System.out.println("Unexpected exception happened: " + t);
assertTrue(false);
}
}
public void testHasProtectedConstructor () {
try {
ConstructorAccess<HasProtectedConstructor> access = ConstructorAccess.get(HasProtectedConstructor.class);
HasProtectedConstructor newInstance = access.newInstance();
assertEquals("cow", newInstance.getMoo());
}
catch (Throwable t) {
System.out.println("Unexpected exception happened: " + t);
assertTrue(false);
}
}
public void testHasPackageProtectedConstructor () {
try {
ConstructorAccess<HasPackageProtectedConstructor> access = ConstructorAccess.get(HasPackageProtectedConstructor.class);
HasPackageProtectedConstructor newInstance = access.newInstance();
assertEquals("cow", newInstance.getMoo());
}
catch (Throwable t) {
System.out.println("Unexpected exception happened: " + t);
assertTrue(false);
}
}
public void testHasPublicConstructor () {
try {
ConstructorAccess<HasPublicConstructor> access = ConstructorAccess.get(HasPublicConstructor.class);
HasPublicConstructor newInstance = access.newInstance();
assertEquals("cow", newInstance.getMoo());
}
catch (Throwable t) {
System.out.println("Unexpected exception happened: " + t);
assertTrue(false);
}
}
static class PackagePrivateClass { static class PackagePrivateClass {
public String name; public String name;
public int intValue; public int intValue;
@ -73,4 +139,51 @@ public class ConstructorAccessTest extends TestCase {
return true; return true;
} }
} }
static public class HasArgumentConstructor {
public String moo;
public HasArgumentConstructor (String moo) {
this.moo = moo;
}
public boolean equals (Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
HasArgumentConstructor other = (HasArgumentConstructor)obj;
if (moo == null) {
if (other.moo != null) return false;
} else if (!moo.equals(other.moo)) return false;
return true;
}
public String getMoo() {
return moo;
}
}
static public class HasPrivateConstructor extends HasArgumentConstructor {
private HasPrivateConstructor () {
super("cow");
}
}
static public class HasProtectedConstructor extends HasPrivateConstructor {
protected HasProtectedConstructor () {
super();
}
}
static public class HasPackageProtectedConstructor extends HasProtectedConstructor {
HasPackageProtectedConstructor () {
super();
}
}
static public class HasPublicConstructor extends HasPackageProtectedConstructor {
HasPublicConstructor () {
super();
}
}
} }

View File

@ -1,7 +1,9 @@
package com.esotericsoftware.reflectasm; package com.esotericsoftware.reflectasm;
import com.esotericsoftware.reflectasm.FieldAccessTest.EmptyClass; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static junit.framework.Assert.assertEquals;
import junit.framework.TestCase; import junit.framework.TestCase;
@ -71,6 +73,21 @@ public class MethodAccessTest extends TestCase {
} }
} }
public void testInvokeInterface () {
MethodAccess access = MethodAccess.get(ConcurrentMap.class);
ConcurrentHashMap<String, String> someMap = new ConcurrentHashMap<String, String>();
someMap.put("first", "one");
someMap.put("second", "two");
Object value;
// invoke a method declared directly in the ConcurrentMap interface
value = access.invoke(someMap, "replace", "first", "foo");
assertEquals("one", value);
// invoke a method declared in the Map superinterface
value = access.invoke(someMap, "size");
assertEquals(someMap.size(), value);
}
static public class EmptyClass { static public class EmptyClass {
} }
@ -98,4 +115,5 @@ public class MethodAccessTest extends TestCase {
return "test"; return "test";
} }
} }
} }