15

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!

2
  • 2
    Why? It's also for my personal satisfaction.
    – EMMERICH
    Commented Oct 26, 2010 at 14:17
  • Ok ok, it's for personal satisfaction of knowing if it could be done...
    – dacwe
    Commented Oct 26, 2010 at 14:19

4 Answers 4

9

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");
}
3
  • 1
    @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. Commented Oct 26, 2010 at 15:15
  • 1
    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
    Commented Oct 26, 2010 at 15:21
  • Does it also print visited instructions' line number under each method? @AdamPaynter
    – alper
    Commented Apr 24, 2017 at 9:10
6

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 = Character.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.

5
  • 1
    @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?! :) Commented Oct 26, 2010 at 22:05
  • 1
    @seanizer: We even both used AtomicReference and EmptyVisitor! Commented Oct 26, 2010 at 22:12
  • Beware the bug in the code above with CHAR_TYPE - it's mapped to Float.TYPE not Character.TYPE. Commented Feb 21, 2011 at 2:04
  • There's also a problem with multidimensional arrays. This will force a single-dimensional array type. I posted an update that works a little differently below. Commented Feb 21, 2011 at 15:30
  • @Jeff true, that's why I wrote: And it won't work if the signature of the calling method contains primitive arrays or multidimensional arrays Commented Feb 21, 2011 at 15:40
1

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....

2
  • 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
    Commented Oct 26, 2010 at 14:47
  • 1
    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. Commented Oct 26, 2010 at 14:47
1

Here is a modified version of Sean Patrick Floyd's posted method for getting a Java Class from an ASM Type. It fixes a problem with multidimensional arrays and another problem with classes loaded by other classloaders.

public static Class<?> getClassFromType(Class<?> clazz, final Type type) throws ClassNotFoundException{
    Class<?> javaType = null;
    switch( type.getSort() ) {
        case Type.VOID      : javaType = Void.TYPE; break;
        case Type.BOOLEAN   : javaType = Boolean.TYPE; break;
        case Type.CHAR      : javaType = Character.TYPE; break;
        case Type.BYTE      : javaType = Byte.TYPE; break;
        case Type.SHORT     : javaType = Short.TYPE; break;
        case Type.INT       : javaType = Integer.TYPE; break;
        case Type.FLOAT     : javaType = Float.TYPE; break;
        case Type.LONG      : javaType = Long.TYPE; break;
        case Type.DOUBLE    : javaType = Double.TYPE; break;
        case Type.ARRAY     : javaType = Array.newInstance( getClassFromType( clazz, type.getElementType()), new int[type.getDimensions()] ).getClass(); break; 
        case Type.OBJECT    : javaType = Class.forName( type.getClassName(), false, clazz.getClassLoader() ); break;
    }
    if ( javaType != null ) return javaType;
    throw new ClassNotFoundException( "Couldn't find class for type " + type );
}
0

Not the answer you're looking for? Browse other questions tagged or ask your own question.