+ improved LocalVariableTableParameterNameDiscoverer discovery and memory usage
+ added extra tests
This commit is contained in:
parent
06cd013b31
commit
c9975504c6
|
|
@ -16,14 +16,12 @@
|
|||
|
||||
package org.springframework.core;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
|
@ -43,225 +41,163 @@ import org.springframework.util.ClassUtils;
|
|||
* <code>null</code> if the class file was compiled without debug information.
|
||||
*
|
||||
* <p>Uses ObjectWeb's ASM library for analyzing class files. Each discoverer
|
||||
* instance caches the ASM ClassReader for each introspected Class, in a
|
||||
* instance caches the ASM discovered information for each introspected Class, in a
|
||||
* thread-safe manner. It is recommended to reuse discoverer instances
|
||||
* as far as possible.
|
||||
*
|
||||
* @author Adrian Colyer
|
||||
* @author Juergen Hoeller
|
||||
* @author Costin Leau
|
||||
* @since 2.0
|
||||
*/
|
||||
public class LocalVariableTableParameterNameDiscoverer implements ParameterNameDiscoverer {
|
||||
|
||||
private static Log logger = LogFactory.getLog(LocalVariableTableParameterNameDiscoverer.class);
|
||||
|
||||
private final Map<Member, String[]> parameterNamesCache = new ConcurrentHashMap<Member, String[]>();
|
||||
|
||||
private final Map<Class, ClassReader> classReaderCache = new HashMap<Class, ClassReader>();
|
||||
// the cache uses a nested index (value is a map) to keep the top level cache relatively small in size
|
||||
private final Map<Class<?>, Map<Member, String[]>> parameterNamesCache = new ConcurrentHashMap<Class<?>, Map<Member, String[]>>();
|
||||
|
||||
// marker object for members that have been visited yet no params have been discovered for it
|
||||
private static final String[] EMPTY_NAMES_ARRAY = new String[0];
|
||||
// marker object for classes that do not have any debug info
|
||||
private static final Map<Member, String[]> EMPTY_MAP = Collections.emptyMap();
|
||||
|
||||
public String[] getParameterNames(Method method) {
|
||||
String[] paramNames = this.parameterNamesCache.get(method);
|
||||
if (paramNames == null) {
|
||||
try {
|
||||
paramNames = visitMethod(method).getParameterNames();
|
||||
if (paramNames != null) {
|
||||
this.parameterNamesCache.put(method, paramNames);
|
||||
Class<?> declaringClass = method.getDeclaringClass();
|
||||
Map<Member, String[]> map = parameterNamesCache.get(declaringClass);
|
||||
if (map == null) {
|
||||
// initialize cache
|
||||
map = cacheClass(declaringClass);
|
||||
}
|
||||
if (map != EMPTY_MAP) {
|
||||
String[] paramNames = map.get(method);
|
||||
return (paramNames == EMPTY_NAMES_ARRAY ? null : paramNames);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// We couldn't load the class file, which is not fatal as it
|
||||
// simply means this method of discovering parameter names won't work.
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("IOException whilst attempting to read '.class' file for class [" +
|
||||
method.getDeclaringClass().getName() +
|
||||
"] - unable to determine parameter names for method: " + method, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
return paramNames;
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public String[] getParameterNames(Constructor ctor) {
|
||||
String[] paramNames = this.parameterNamesCache.get(ctor);
|
||||
if (paramNames == null) {
|
||||
try {
|
||||
paramNames = visitConstructor(ctor).getParameterNames();
|
||||
if (paramNames != null) {
|
||||
this.parameterNamesCache.put(ctor, paramNames);
|
||||
Class<?> declaringClass = ctor.getDeclaringClass();
|
||||
Map<Member, String[]> map = parameterNamesCache.get(declaringClass);
|
||||
if (map == null) {
|
||||
// initialize cache
|
||||
map = cacheClass(declaringClass);
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// We couldn't load the class file, which is not fatal as it
|
||||
// simply means this method of discovering parameter names won't work.
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("IOException whilst attempting to read '.class' file for class [" +
|
||||
ctor.getDeclaringClass().getName() +
|
||||
"] - unable to determine parameter names for constructor: " + ctor, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
return paramNames;
|
||||
String[] paramNames = map.get(ctor);
|
||||
return (paramNames == EMPTY_NAMES_ARRAY ? null : paramNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit the given method and discover its parameter names.
|
||||
* Caches the results of the introspected class. Exceptions will be logged and swallowed.
|
||||
*
|
||||
* @param declaringClass class about to be inspected
|
||||
* @return cached value
|
||||
*/
|
||||
private ParameterNameDiscoveringVisitor visitMethod(Method method) throws IOException {
|
||||
ClassReader classReader = getClassReader(method.getDeclaringClass());
|
||||
FindMethodParameterNamesClassVisitor classVisitor = new FindMethodParameterNamesClassVisitor(method);
|
||||
classReader.accept(classVisitor, false);
|
||||
return classVisitor;
|
||||
}
|
||||
private Map<Member, String[]> cacheClass(Class<?> clazz) {
|
||||
|
||||
/**
|
||||
* Visit the given constructor and discover its parameter names.
|
||||
*/
|
||||
private ParameterNameDiscoveringVisitor visitConstructor(Constructor ctor) throws IOException {
|
||||
ClassReader classReader = getClassReader(ctor.getDeclaringClass());
|
||||
FindConstructorParameterNamesClassVisitor classVisitor = new FindConstructorParameterNamesClassVisitor(ctor);
|
||||
classReader.accept(classVisitor, false);
|
||||
return classVisitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a (cached) ClassReader for the given class.
|
||||
*/
|
||||
private ClassReader getClassReader(Class clazz) throws IOException {
|
||||
synchronized (this.classReaderCache) {
|
||||
ClassReader classReader = (ClassReader) this.classReaderCache.get(clazz);
|
||||
if (classReader == null) {
|
||||
InputStream is = clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz));
|
||||
if (is == null) {
|
||||
throw new FileNotFoundException("Class file for class [" + clazz.getName() + "] not found");
|
||||
// We couldn't load the class file, which is not fatal as it
|
||||
// simply means this method of discovering parameter names won't work.
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Cannot find '.class' file for class [" + clazz
|
||||
+ "] - unable to determine constructors/methods parameter names");
|
||||
}
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
try {
|
||||
classReader = new ClassReader(is);
|
||||
this.classReaderCache.put(clazz, classReader);
|
||||
ClassReader classReader = new ClassReader(is);
|
||||
Map<Member, String[]> map = new ConcurrentHashMap<Member, String[]>();
|
||||
classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), false);
|
||||
return map;
|
||||
} catch (IOException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Exception thrown while reading '.class' file for class [" + clazz
|
||||
+ "] - unable to determine constructors/methods parameter names", ex);
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
}
|
||||
}
|
||||
return classReader;
|
||||
} catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class that looks for a given member name and descriptor, and then
|
||||
* Helper class that inspects all methods (constructor included) and then
|
||||
* attempts to find the parameter names for that member.
|
||||
*/
|
||||
private static abstract class ParameterNameDiscoveringVisitor extends EmptyVisitor {
|
||||
private static class ParameterNameDiscoveringVisitor extends EmptyVisitor {
|
||||
|
||||
private String methodNameToMatch;
|
||||
private final Class<?> clazz;
|
||||
private final Map<Member, String[]> memberMap;
|
||||
private static final String STATIC_CLASS_INIT = "<clinit>";
|
||||
|
||||
private String descriptorToMatch;
|
||||
public ParameterNameDiscoveringVisitor(Class<?> clazz, Map<Member, String[]> memberMap) {
|
||||
this.clazz = clazz;
|
||||
this.memberMap = memberMap;
|
||||
}
|
||||
|
||||
private int numParamsExpected;
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
// exclude synthetic + bridged && static class initialization
|
||||
if (!isSyntheticOrBridged(access) && !STATIC_CLASS_INIT.equals(name)) {
|
||||
return new LocalVariableTableVisitor(clazz, memberMap, name, desc, isStatic(access));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isSyntheticOrBridged(int access) {
|
||||
return (((access & Opcodes.ACC_SYNTHETIC) | (access & Opcodes.ACC_BRIDGE)) > 0);
|
||||
}
|
||||
|
||||
private static boolean isStatic(int access) {
|
||||
return ((access & Opcodes.ACC_STATIC) > 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static class LocalVariableTableVisitor extends EmptyVisitor {
|
||||
|
||||
private static final String CONSTRUCTOR = "<init>";
|
||||
|
||||
private final Class<?> clazz;
|
||||
private final Map<Member, String[]> memberMap;
|
||||
private final String name;
|
||||
private final Type[] args;
|
||||
private final boolean isStatic;
|
||||
|
||||
private String[] parameterNames;
|
||||
private boolean hasLvtInfo = false;
|
||||
|
||||
/*
|
||||
* The nth entry contains the slot index of the LVT table entry holding the
|
||||
* argument name for the nth parameter.
|
||||
*/
|
||||
private int[] lvtSlotIndex;
|
||||
private final int[] lvtSlotIndex;
|
||||
|
||||
private String[] parameterNames;
|
||||
|
||||
public ParameterNameDiscoveringVisitor(String name, boolean isStatic, Class[] paramTypes) {
|
||||
this.methodNameToMatch = name;
|
||||
this.numParamsExpected = paramTypes.length;
|
||||
computeLvtSlotIndices(isStatic, paramTypes);
|
||||
}
|
||||
|
||||
public void setDescriptorToMatch(String descriptor) {
|
||||
this.descriptorToMatch = descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
if (name.equals(this.methodNameToMatch) && desc.equals(this.descriptorToMatch)) {
|
||||
return new LocalVariableTableVisitor(this, isStatic(access));
|
||||
}
|
||||
else {
|
||||
// Not interested in this method...
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStatic(int access) {
|
||||
return ((access & Opcodes.ACC_STATIC) > 0);
|
||||
}
|
||||
|
||||
public String[] getParameterNames() {
|
||||
return this.parameterNames;
|
||||
}
|
||||
|
||||
private void computeLvtSlotIndices(boolean isStatic, Class[] paramTypes) {
|
||||
this.lvtSlotIndex = new int[paramTypes.length];
|
||||
int nextIndex = (isStatic ? 0 : 1);
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
this.lvtSlotIndex[i] = nextIndex;
|
||||
if (isWideType(paramTypes[i])) {
|
||||
nextIndex += 2;
|
||||
}
|
||||
else {
|
||||
nextIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isWideType(Class aType) {
|
||||
return (aType == Long.TYPE || aType == Double.TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class FindMethodParameterNamesClassVisitor extends ParameterNameDiscoveringVisitor {
|
||||
|
||||
public FindMethodParameterNamesClassVisitor(Method method) {
|
||||
super(method.getName(), Modifier.isStatic(method.getModifiers()), method.getParameterTypes());
|
||||
setDescriptorToMatch(Type.getMethodDescriptor(method));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class FindConstructorParameterNamesClassVisitor extends ParameterNameDiscoveringVisitor {
|
||||
|
||||
public FindConstructorParameterNamesClassVisitor(Constructor ctor) {
|
||||
super("<init>", false, ctor.getParameterTypes());
|
||||
Type[] pTypes = new Type[ctor.getParameterTypes().length];
|
||||
for (int i = 0; i < pTypes.length; i++) {
|
||||
pTypes[i] = Type.getType(ctor.getParameterTypes()[i]);
|
||||
}
|
||||
setDescriptorToMatch(Type.getMethodDescriptor(Type.VOID_TYPE, pTypes));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class LocalVariableTableVisitor extends EmptyVisitor {
|
||||
|
||||
private final ParameterNameDiscoveringVisitor memberVisitor;
|
||||
|
||||
private final boolean isStatic;
|
||||
|
||||
private String[] parameterNames;
|
||||
|
||||
private boolean hasLvtInfo = false;
|
||||
|
||||
public LocalVariableTableVisitor(ParameterNameDiscoveringVisitor memberVisitor, boolean isStatic) {
|
||||
this.memberVisitor = memberVisitor;
|
||||
public LocalVariableTableVisitor(Class<?> clazz, Map<Member, String[]> map, String name, String desc,
|
||||
boolean isStatic) {
|
||||
this.clazz = clazz;
|
||||
this.memberMap = map;
|
||||
this.name = name;
|
||||
// determine args
|
||||
args = Type.getArgumentTypes(desc);
|
||||
this.parameterNames = new String[args.length];
|
||||
this.isStatic = isStatic;
|
||||
this.parameterNames = new String[memberVisitor.numParamsExpected];
|
||||
this.lvtSlotIndex = computeLvtSlotIndices(isStatic, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(
|
||||
String name, String description, String signature, Label start, Label end, int index) {
|
||||
public void visitLocalVariable(String name, String description, String signature, Label start, Label end,
|
||||
int index) {
|
||||
this.hasLvtInfo = true;
|
||||
int[] lvtSlotIndices = this.memberVisitor.lvtSlotIndex;
|
||||
for (int i = 0; i < lvtSlotIndices.length; i++) {
|
||||
if (lvtSlotIndices[i] == index) {
|
||||
for (int i = 0; i < lvtSlotIndex.length; i++) {
|
||||
if (lvtSlotIndex[i] == index) {
|
||||
this.parameterNames[i] = name;
|
||||
}
|
||||
}
|
||||
|
|
@ -274,9 +210,47 @@ public class LocalVariableTableParameterNameDiscoverer implements ParameterNameD
|
|||
// which doesn't use any local variables.
|
||||
// This means that hasLvtInfo could be false for that kind of methods
|
||||
// even if the class has local variable info.
|
||||
this.memberVisitor.parameterNames = this.parameterNames;
|
||||
}
|
||||
memberMap.put(resolveMember(), parameterNames);
|
||||
}
|
||||
}
|
||||
|
||||
private Member resolveMember() {
|
||||
ClassLoader loader = clazz.getClassLoader();
|
||||
Class<?>[] classes = new Class<?>[args.length];
|
||||
|
||||
// resolve args
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
classes[i] = ClassUtils.resolveClassName(args[i].getClassName(), loader);
|
||||
}
|
||||
try {
|
||||
if (CONSTRUCTOR.equals(name)) {
|
||||
return clazz.getDeclaredConstructor(classes);
|
||||
}
|
||||
|
||||
return clazz.getDeclaredMethod(name, classes);
|
||||
} catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("Method [" + name
|
||||
+ "] was discovered in the .class file but cannot be resolved in the class object", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] computeLvtSlotIndices(boolean isStatic, Type[] paramTypes) {
|
||||
int[] lvtIndex = new int[paramTypes.length];
|
||||
int nextIndex = (isStatic ? 0 : 1);
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
lvtIndex[i] = nextIndex;
|
||||
if (isWideType(paramTypes[i])) {
|
||||
nextIndex += 2;
|
||||
} else {
|
||||
nextIndex++;
|
||||
}
|
||||
}
|
||||
return lvtIndex;
|
||||
}
|
||||
|
||||
private static boolean isWideType(Type aType) {
|
||||
// float is not a wide type
|
||||
return (aType == Type.LONG_TYPE || aType == Type.DOUBLE_TYPE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,8 +16,12 @@
|
|||
|
||||
package org.springframework.core;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
|
|
@ -30,7 +34,6 @@ public class LocalVariableTableParameterNameDiscovererTests extends TestCase {
|
|||
|
||||
private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
|
||||
public void testMethodParameterNameDiscoveryNoArgs() throws NoSuchMethodException {
|
||||
Method getName = TestBean.class.getMethod("getName", new Class[0]);
|
||||
String[] names = discoverer.getParameterNames(getName);
|
||||
|
|
@ -39,7 +42,7 @@ public class LocalVariableTableParameterNameDiscovererTests extends TestCase {
|
|||
}
|
||||
|
||||
public void testMethodParameterNameDiscoveryWithArgs() throws NoSuchMethodException {
|
||||
Method setName = TestBean.class.getMethod("setName", new Class[]{String.class});
|
||||
Method setName = TestBean.class.getMethod("setName", new Class[] { String.class });
|
||||
String[] names = discoverer.getParameterNames(setName);
|
||||
assertNotNull("should find method info", names);
|
||||
assertEquals("one argument", 1, names.length);
|
||||
|
|
@ -54,7 +57,7 @@ public class LocalVariableTableParameterNameDiscovererTests extends TestCase {
|
|||
}
|
||||
|
||||
public void testConsParameterNameDiscoveryArgs() throws NoSuchMethodException {
|
||||
Constructor twoArgCons = TestBean.class.getConstructor(new Class[]{String.class, int.class});
|
||||
Constructor twoArgCons = TestBean.class.getConstructor(new Class[] { String.class, int.class });
|
||||
String[] names = discoverer.getParameterNames(twoArgCons);
|
||||
assertNotNull("should find cons info", names);
|
||||
assertEquals("one argument", 2, names.length);
|
||||
|
|
@ -72,14 +75,14 @@ public class LocalVariableTableParameterNameDiscovererTests extends TestCase {
|
|||
public void testOverloadedStaticMethod() throws Exception {
|
||||
Class clazz = this.getClass();
|
||||
|
||||
Method m1 = clazz.getMethod("staticMethod", new Class[]{Long.TYPE, Long.TYPE});
|
||||
Method m1 = clazz.getMethod("staticMethod", new Class[] { Long.TYPE, Long.TYPE });
|
||||
String[] names = discoverer.getParameterNames(m1);
|
||||
assertNotNull("should find method info", names);
|
||||
assertEquals("two arguments", 2, names.length);
|
||||
assertEquals("x", names[0]);
|
||||
assertEquals("y", names[1]);
|
||||
|
||||
Method m2 = clazz.getMethod("staticMethod", new Class[]{Long.TYPE, Long.TYPE, Long.TYPE});
|
||||
Method m2 = clazz.getMethod("staticMethod", new Class[] { Long.TYPE, Long.TYPE, Long.TYPE });
|
||||
names = discoverer.getParameterNames(m2);
|
||||
assertNotNull("should find method info", names);
|
||||
assertEquals("three arguments", 3, names.length);
|
||||
|
|
@ -91,13 +94,13 @@ public class LocalVariableTableParameterNameDiscovererTests extends TestCase {
|
|||
public void testOverloadedStaticMethodInInnerClass() throws Exception {
|
||||
Class clazz = InnerClass.class;
|
||||
|
||||
Method m1 = clazz.getMethod("staticMethod", new Class[]{Long.TYPE});
|
||||
Method m1 = clazz.getMethod("staticMethod", new Class[] { Long.TYPE });
|
||||
String[] names = discoverer.getParameterNames(m1);
|
||||
assertNotNull("should find method info", names);
|
||||
assertEquals("one argument", 1, names.length);
|
||||
assertEquals("x", names[0]);
|
||||
|
||||
Method m2 = clazz.getMethod("staticMethod", new Class[]{Long.TYPE, Long.TYPE});
|
||||
Method m2 = clazz.getMethod("staticMethod", new Class[] { Long.TYPE, Long.TYPE });
|
||||
names = discoverer.getParameterNames(m2);
|
||||
assertNotNull("should find method info", names);
|
||||
assertEquals("two arguments", 2, names.length);
|
||||
|
|
@ -108,14 +111,14 @@ public class LocalVariableTableParameterNameDiscovererTests extends TestCase {
|
|||
public void testOverloadedMethod() throws Exception {
|
||||
Class clazz = this.getClass();
|
||||
|
||||
Method m1 = clazz.getMethod("instanceMethod", new Class[]{Double.TYPE, Double.TYPE});
|
||||
Method m1 = clazz.getMethod("instanceMethod", new Class[] { Double.TYPE, Double.TYPE });
|
||||
String[] names = discoverer.getParameterNames(m1);
|
||||
assertNotNull("should find method info", names);
|
||||
assertEquals("two arguments", 2, names.length);
|
||||
assertEquals("x", names[0]);
|
||||
assertEquals("y", names[1]);
|
||||
|
||||
Method m2 = clazz.getMethod("instanceMethod", new Class[]{Double.TYPE, Double.TYPE, Double.TYPE});
|
||||
Method m2 = clazz.getMethod("instanceMethod", new Class[] { Double.TYPE, Double.TYPE, Double.TYPE });
|
||||
names = discoverer.getParameterNames(m2);
|
||||
assertNotNull("should find method info", names);
|
||||
assertEquals("three arguments", 3, names.length);
|
||||
|
|
@ -127,13 +130,13 @@ public class LocalVariableTableParameterNameDiscovererTests extends TestCase {
|
|||
public void testOverloadedMethodInInnerClass() throws Exception {
|
||||
Class clazz = InnerClass.class;
|
||||
|
||||
Method m1 = clazz.getMethod("instanceMethod", new Class[]{String.class});
|
||||
Method m1 = clazz.getMethod("instanceMethod", new Class[] { String.class });
|
||||
String[] names = discoverer.getParameterNames(m1);
|
||||
assertNotNull("should find method info", names);
|
||||
assertEquals("one argument", 1, names.length);
|
||||
assertEquals("aa", names[0]);
|
||||
|
||||
Method m2 = clazz.getMethod("instanceMethod", new Class[]{String.class, String.class});
|
||||
Method m2 = clazz.getMethod("instanceMethod", new Class[] { String.class, String.class });
|
||||
names = discoverer.getParameterNames(m2);
|
||||
assertNotNull("should find method info", names);
|
||||
assertEquals("two arguments", 2, names.length);
|
||||
|
|
@ -141,6 +144,71 @@ public class LocalVariableTableParameterNameDiscovererTests extends TestCase {
|
|||
assertEquals("bb", names[1]);
|
||||
}
|
||||
|
||||
public void testGenerifiedClass() throws Exception {
|
||||
Class clazz = GenerifiedClass.class;
|
||||
|
||||
Constructor ctor = clazz.getDeclaredConstructor(Object.class);
|
||||
String[] names = discoverer.getParameterNames(ctor);
|
||||
assertEquals(1, names.length);
|
||||
assertEquals("key", names[0]);
|
||||
|
||||
ctor = clazz.getDeclaredConstructor(Object.class, Object.class);
|
||||
names = discoverer.getParameterNames(ctor);
|
||||
assertEquals(2, names.length);
|
||||
assertEquals("key", names[0]);
|
||||
assertEquals("value", names[1]);
|
||||
|
||||
Method m = clazz.getMethod("generifiedStaticMethod", Object.class);
|
||||
names = discoverer.getParameterNames(m);
|
||||
assertEquals(1, names.length);
|
||||
assertEquals("param", names[0]);
|
||||
|
||||
m = clazz.getMethod("generifiedMethod", Object.class, long.class, Object.class, Object.class);
|
||||
names = discoverer.getParameterNames(m);
|
||||
assertEquals(4, names.length);
|
||||
assertEquals("param", names[0]);
|
||||
assertEquals("x", names[1]);
|
||||
assertEquals("key", names[2]);
|
||||
assertEquals("value", names[3]);
|
||||
|
||||
m = clazz.getMethod("voidStaticMethod", Object.class, long.class, int.class);
|
||||
names = discoverer.getParameterNames(m);
|
||||
assertEquals(3, names.length);
|
||||
assertEquals("obj", names[0]);
|
||||
assertEquals("x", names[1]);
|
||||
assertEquals("i", names[2]);
|
||||
|
||||
m = clazz.getMethod("nonVoidStaticMethod", Object.class, long.class, int.class);
|
||||
names = discoverer.getParameterNames(m);
|
||||
assertEquals(3, names.length);
|
||||
assertEquals("obj", names[0]);
|
||||
assertEquals("x", names[1]);
|
||||
assertEquals("i", names[2]);
|
||||
|
||||
m = clazz.getMethod("getDate", null);
|
||||
names = discoverer.getParameterNames(m);
|
||||
assertEquals(0, names.length);
|
||||
}
|
||||
|
||||
public void testMemUsage() throws Exception {
|
||||
// JDK classes don't have debug information (usually)
|
||||
Class clazz = Component.class;
|
||||
String methodName = "list";
|
||||
|
||||
Method m = clazz.getMethod(methodName, null);
|
||||
String[] names = discoverer.getParameterNames(m);
|
||||
assertNull(names);
|
||||
|
||||
m = clazz.getMethod(methodName, PrintStream.class);
|
||||
names = discoverer.getParameterNames(m);
|
||||
assertNull(names);
|
||||
|
||||
m = clazz.getMethod(methodName, PrintStream.class, int.class);
|
||||
names = discoverer.getParameterNames(m);
|
||||
assertNull(names);
|
||||
|
||||
//System.in.read()
|
||||
}
|
||||
|
||||
public static void staticMethodNoLocalVars() {
|
||||
}
|
||||
|
|
@ -165,7 +233,6 @@ public class LocalVariableTableParameterNameDiscovererTests extends TestCase {
|
|||
return u;
|
||||
}
|
||||
|
||||
|
||||
public static class InnerClass {
|
||||
|
||||
public int waz = 0;
|
||||
|
|
@ -198,4 +265,48 @@ public class LocalVariableTableParameterNameDiscovererTests extends TestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public static class GenerifiedClass<K, V> {
|
||||
private static long date;
|
||||
|
||||
private K key;
|
||||
private V value;
|
||||
|
||||
static {
|
||||
// some custom static bloc or <clinit>
|
||||
date = new Date().getTime();
|
||||
}
|
||||
|
||||
public GenerifiedClass() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public GenerifiedClass(K key) {
|
||||
this(key, null);
|
||||
}
|
||||
|
||||
public GenerifiedClass(K key, V value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static <P> long generifiedStaticMethod(P param) {
|
||||
return date;
|
||||
}
|
||||
|
||||
public <P> void generifiedMethod(P param, long x, K key, V value) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
public static void voidStaticMethod(Object obj, long x, int i) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
public static long nonVoidStaticMethod(Object obj, long x, int i) {
|
||||
return date;
|
||||
}
|
||||
|
||||
public static long getDate() {
|
||||
return date;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue