How can one obtain objects of the following types from specific instances of their represented language feature in the code:
Ideally the providing method would be a caller of the obtaining method or the obtaining method itself.
Note that I have found two kinds of answers on SO:
Neither helps to obtain the Parameter to a particular method whose name is not unique.
Consider in particular how this answer and this one to similar questions gloss over the selection of the desired method.
Now, there exists Class.getMethod(String, Class<?>... types)
but it does not seem that one could generate the "types" parameter automatically from an existing method definition?
Use case:
public class MyAssertions
{
private static final String DEFAULT_MESSAGE_NOT_NULL = "Parameter must not be null: ";
/* Ideal. */
public static void notNull(Object ref, Parameter param)
{
if(ref == null)
throw new IllegalArgumentException(DEFAULT_MESSAGE_NOT_NULL + param.getName());
}
/* Still okay. */
public static void notNull(Object ref, Executable caller, int param)
{
if(ref == null)
throw new IllegalArgumentException(DEFAULT_MESSAGE_NOT_NULL
+ caller.getParameters()[param].getName());
}
/* Hell no! */
public static void notNull(Object ref, Class<?> caller, String method, Object[] paramTypes, int param)
{
if(ref == null)
throw new IllegalArgumentException(DEFAULT_MESSAGE_NOT_NULL
+ caller.getMethod(method, paramTypes)
.getParameters()[param].getName());
}
}
You can use bytecode utilities to get the bytecode infomation. And then use the line number info in StackTraceElement
to get the method
.
Here I use javassist.
private static Optional<Method> get(StackTraceElement e) throws NotFoundException, ClassNotFoundException {
Class<?> clz = Class.forName(e.getClassName());
int line = e.getLineNumber();
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get(clz.getName());
return Observable.fromArray(cc.getDeclaredMethods())
.sorted(Comparator.comparing(m -> m.getMethodInfo().getLineNumber(0)))
.filter(m -> m.getMethodInfo().getLineNumber(0) <= line)
.map(Optional::of)
.blockingLast(Optional.empty())
.map(m -> uncheck(() -> clz.getDeclaredMethod(m.getName(),
Arrays.stream(m.getParameterTypes()).map(c -> uncheck(() -> nameToClass(c.getName()))).toArray(Class[]::new))));
}
private static Class<?> nameToClass(String name) throws ClassNotFoundException {
switch (name) {
case "int":
return int.class;
case "short":
return short.class;
case "long":
return long.class;
case "double":
return double.class;
case "float":
return float.class;
case "boolean":
return boolean.class;
case "char":
return char.class;
case "byte":
return byte.class;
case "void":
return void.class;
}
if (name.endsWith("[]")) {
return Array.newInstance(nameToClass(name.substring(0, name.length() - 2)), 0).getClass();
}
return Class.forName(name);
}
uncheck
is my utility only ignore the exception in lambda, you can simply use try-catch. see source here
then we test our code
public static void main(String[] args) throws Exception {
// test normal
System.out.println(get(new Exception().getStackTrace()[0]));
// test lambda
Runnable r = () -> System.out.println(uncheck(() -> get(new Exception().getStackTrace()[0])));
r.run();
// test function
testHere(1);
}
private static void testHere(int i) throws Exception {
System.out.println(get(new Exception().getStackTrace()[0]));
}
and output
Optional[public static void xdean.stackoverflow.java.reflection.Q44563354.main(java.lang.String[]) throws java.lang.Exception]
Optional[private static java.util.Optional xdean.stackoverflow.java.reflection.Q44563354.lambda$1() throws java.lang.Exception]
Optional[private static void xdean.stackoverflow.java.reflection.Q44563354.testHere(int) throws java.lang.Exception]
But note that this approach only works for class has bytecode in classpath. If you use dynamic proxy, it doesn't work.
Find complete sample code here
EDIT: You can filter synthetic and bridge methods to get the actual method.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With