views:

89

answers:

3

I would like to get the calling method java.lang.reflect.Method. NOT the name of the method.

Here is an example how to get the callers Class.

// find the callers class
Thread t = Thread.getCurrentThread();
Class<?> klass = Class.forName(t.getStackTrace()[2].getClassName());
// do something with the class (like processing its annotations)
... 

It's for testing purpose only!

+1  A: 

quite easy: just get the corresponding Class object first and then use Class.getMethod(String name,params...)

check here for the javadoc

public class GetMethod {
    public static void main(String[] args){
        new GetMethod().checkMethod();
    }

    public void checkMethod(){
        Thread t=Thread.currentThread();
        StackTraceElement element=t.getStackTrace()[1];
        System.out.println(element.getClassName());
        System.out.println(element.getMethodName());
        try{
            Method m=Class.forName(element.getClassName()).getMethod(element.getMethodName(),null);
            System.out.println("Method: " + m.getName());
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

hope that helped....

smeg4brains
Doesn't work! If we have several method with the same or with parameters it won't work.. I'll add an example to my question (thanks for the example!)
dacwe
Well yes that works if the method takes no params. It's also rather worthless to find the current method instead of the calling method.
seanizer
+1  A: 

We can almost get there, here's a method that works in many cases. The problem is: it won't work reliably if there are overloaded methods (multiple methods with the same name). The stack trace does not provide the arguments, unfortunately.

private static Method getCallingMethod() throws ClassNotFoundException{
    final Thread t = Thread.currentThread();
    final StackTraceElement[] stackTrace = t.getStackTrace();
    final StackTraceElement ste = stackTrace[2];
    final String methodName = ste.getMethodName();
    final String className = ste.getClassName();
    Class<?> kls = Class.forName(className);
    do{
        for(final Method candidate : kls.getDeclaredMethods()){
            if(candidate.getName().equals(methodName)){
                return candidate;
            }
        }
        kls = kls.getSuperclass();
    } while(kls != null);
    return null;
}

Test code:

public static void main(final String[] args) throws Exception{
    System.out.println(getCallingMethod());
}

Output:

public static void foo.bar.Phleem.main(java.lang.String[]) throws java.lang.Exception


OK, here is a solution using ASM. It works for almost all cases:

private static Method getCallingMethod() throws ClassNotFoundException,
    IOException{
    final Thread t = Thread.currentThread();
    final StackTraceElement[] stackTrace = t.getStackTrace();
    final StackTraceElement ste = stackTrace[2];
    final String methodName = ste.getMethodName();
    final int lineNumber = ste.getLineNumber();
    final String className = ste.getClassName();
    final Class<?> kls = Class.forName(className);
    final ClassReader cr = new ClassReader(className);
    final EmptyVisitor empty = new EmptyVisitor();
    final AtomicReference<Method> holder = new AtomicReference<Method>();

    cr.accept(new ClassAdapter(empty){

        @Override
        public MethodVisitor visitMethod(

        final int access,
            final String name,
            final String desc,
            final String signature,
            final String[] exceptions){

            return name.equals(methodName) ? new MethodAdapter(empty){

                @Override
                public void visitLineNumber(final int line,
                    final Label start){
                    if(line >= lineNumber && holder.get() == null){

                        final Type[] argumentTypes =
                            Type.getArgumentTypes(desc);
                        final Class<?>[] argumentClasses =
                            new Class[argumentTypes.length];
                        try{
                            for(int i = 0; i < argumentTypes.length; i++){
                                final Type type = argumentTypes[i];
                                final String dd = type.getDescriptor();

                                argumentClasses[i] = getClassFromType(type);
                            }
                            holder.set(kls.getDeclaredMethod(methodName,
                                argumentClasses));
                        } catch(final ClassNotFoundException e){
                            throw new IllegalStateException(e);
                        } catch(final SecurityException e){
                            throw new IllegalStateException(e);
                        } catch(final NoSuchMethodException e){
                            throw new IllegalStateException(e);
                        }
                    }
                    super.visitLineNumber(line, start);
                }

                private Class<?> getClassFromType(final Type type) throws ClassNotFoundException{
                    Class<?> javaType;
                    final String descriptor = type.getDescriptor();
                    if(type.equals(Type.INT_TYPE)){
                        javaType = Integer.TYPE;
                    } else if(type.equals(Type.LONG_TYPE)){
                        javaType = Long.TYPE;
                    } else if(type.equals(Type.DOUBLE_TYPE)){
                        javaType = Double.TYPE;
                    } else if(type.equals(Type.FLOAT_TYPE)){
                        javaType = Float.TYPE;
                    } else if(type.equals(Type.BOOLEAN_TYPE)){
                        javaType = Boolean.TYPE;
                    } else if(type.equals(Type.BYTE_TYPE)){
                        javaType = Byte.TYPE;
                    } else if(type.equals(Type.CHAR_TYPE)){
                        javaType = Float.TYPE;
                    } else if(type.equals(Type.SHORT_TYPE)){
                        javaType = Short.TYPE;
                    } else if(descriptor.startsWith("[")){
                        final Class<?> elementType =
                            getClassFromType(type.getElementType());
                        javaType =
                            Array.newInstance(elementType, 0).getClass();

                    } else{
                        javaType = Class.forName(type.getClassName());
                    }
                    return javaType;
                }
            }
                : null;
        }
    },
        0);
    return holder.get();

}

I'll leave it to you to refactor this into something readable. And it won't work if the signature of the calling method contains primitive arrays or multidimensional arrays. Obviously it only works if the class file contains line numbers.

Argghh, I work for ages and then I see that someone has come up with an almost identical solution!!! Anyway, I'll leave mine, because I developed it independently.

seanizer
Not sufficiant but thanks for the example..
dacwe
see my updated answer
seanizer
@seanizer: Wow, that's eerily similar to mine! For the record, mine returns the actual `Method` and not just a String representation (although it is true that I use the method's descriptor internally, which is itself a String). Isn't ASM fantastic?! :)
Adam Paynter
@seanizer: We even both used `AtomicReference` and `EmptyVisitor`!
Adam Paynter
@Adam nice trick with the Method descriptor. Saves a lot of work...
seanizer
+3  A: 

If it's just for testing, then this may work. It assumes that the class files are accessible via the calling class's ClassLoader and that the class files were compiled with debugging symbols (which I hope they are for testing!). This code relies on the ASM bytecode library.

public static Method getMethod(final StackTraceElement stackTraceElement) throws Exception {
    final String stackTraceClassName = stackTraceElement.getClassName();
    final String stackTraceMethodName = stackTraceElement.getMethodName();
    final int stackTraceLineNumber = stackTraceElement.getLineNumber();
    Class<?> stackTraceClass = Class.forName(stackTraceClassName);

    // I am only using AtomicReference as a container to dump a String into, feel free to ignore it for now
    final AtomicReference<String> methodDescriptorReference = new AtomicReference<String>();

    String classFileResourceName = "/" + stackTraceClassName.replaceAll("\\.", "/") + ".class";
    InputStream classFileStream = stackTraceClass.getResourceAsStream(classFileResourceName);

    if (classFileStream == null) {
        throw new RuntimeException("Could not acquire the class file containing for the calling class");
    }

    try {
        ClassReader classReader = new ClassReader(classFileStream);
        classReader.accept(
                new EmptyVisitor() {
                    @Override
                    public MethodVisitor visitMethod(int access, final String name, final String desc, String signature, String[] exceptions) {
                        if (!name.equals(stackTraceMethodName)) {
                            return null;
                        }

                        return new EmptyVisitor() {
                            @Override
                            public void visitLineNumber(int line, Label start) {
                                if (line == stackTraceLineNumber) {
                                    methodDescriptorReference.set(desc);
                                }
                            }
                        };
                    }
                },
                0
            );
    } finally {
        classFileStream.close();
    }

    String methodDescriptor = methodDescriptorReference.get();

    if (methodDescriptor == null) {
        throw new RuntimeException("Could not find line " + stackTraceLineNumber);
    }

    for (Method method : stackTraceClass.getMethods()) {
        if (stackTraceMethodName.equals(method.getName()) && methodDescriptor.equals(Type.getMethodDescriptor(method))) {
            return method;
        }
    }

    throw new RuntimeException("Could not find the calling method");
}
Adam Paynter
Great, finally a solution! Just a bit bad to have to go through ASM..
dacwe
@dacwe: Yeah, it's a shame that the functionality isn't built into the standard library. You can use any bytecode library if ASM isn't acceptable.
Adam Paynter
Yeah, but I like ASM so it's fine. Just thought someone had a one-line-standard-java-solution, but this is acceptable.. But if you do it with ASM, couldn't you "inject" some kind of line -> method information in the class file directly so that the lookups are faster?
dacwe