views:

1024

answers:

9

I need to get a list of all caller methods for a method of interest for me in Java. Is there a tool that can help me with this?

Edit: I forgot to mention that I need to do this from a program. I'm usig Java Pathfinder and I want to run it an all the methods that call my method of interest.

+5  A: 

Edit: the original question was edited to indicate a runtime solution was needed - this answer was given before that edit and only indicates how to do it during development.

If you are using Eclipse you can right click the method and choose "Open call hierarchy" to get this information.

Updated after reading comments: Other IDEs support this as well in a similar fashion (at least Netbeans and IntelliJ do)

Simon Groenewolt
I don't recall the exact name, but in NetBeans it's pretty much the same thing.
Michael Myers
Most IDEs support this. IntelliJ supports this too.
Peter Lawrey
thanks, I'll update my answer.
Simon Groenewolt
A: 

Yes, most modern IDE:s will let you either search for usages of a method or variable. Alternatively, you could use a debugger and set a trace point on the method entry, printing a stack trace or whatever every time the method is invoked. Finally, you could use some simple shell util to just grep for the method, such as

find . -name '*.java' -exec grep -H methodName {} ;

The only method that will let you find invokations made through some reflection method, though, would be using the debugger.

Christoffer
A: 

The closest that I could find was the method described in this StackOverflow questions selected answer.check this out

Soldier.moth
A: 

You can do this with something in your IDE such as "Find Usages" (which is what it is called in Netbeans and JDeveloper). A couple of things to note:

  1. If your method implements a method from an interface or base class, you can only know that your method is POSSIBLY called.
  2. A lot of Java frameworks use Reflection to call your method (IE Spring, Hibernate, JSF, etc), so be careful of that.
  3. On the same note, your method could be called by some framework, reflectively or not, so again be careful.
GreenieMeanie
+1  A: 

There isn't a way to do this (programmatically) via the Java reflection libraries - you can't ask a java.lang.reflect.Method "which methods do you call?"

That leaves two other options I can think of:

  1. Static analysis of the source code. I'm sure this is what the Eclipse Java toolset does - you could look at the Eclipse source behind the JDT, and find what it does when you ask Eclipse to "Find References" to a method.

  2. Bytecode analysis. You could inspect the bytecode for calls to the method. I'm not sure what libraries or examples are out there to help with this - but I can't imagine that something doesn't exist.

Jared
+1  A: 

Annotate the method with @Deprecated ( or tag it with @deprecated ), turn on deprecation warnings, run your compile and see which warnings get triggered.

The run your compile bit can be done either by invoking an external ant process or by using the Java 6 compiler API.

Robert Munteanu
+11  A: 

For analyzing bytecode, I would recommend ASM. Given a list of Classes to analyze, a visitors can be made which find method calls your interested in. One implementation which analyses classes in a jar file is below.

Note that ASM uses internalNames with '/' instead of '.' as a separator. Specify the target method as a standard declaration without modifiers.

For example, to list methods that could be calling System.out.println("foo") in the java runtime jar:

java -cp "classes;asm-3.1.jar;asm-commons-3.1.jar" App \
    c:/java/jdk/jre/lib/rt.jar \
    java/io/PrintStream  "void println(String)"

Edit: source and line numbers added: Note that this only indicates the last target method invocation per calling method - the original q only wanted to know which methods. I leave it as an exercise for the reader to show line numbers of the calling method declaration, or the line numbers of every target invocation, depending on what you're actually after. :)

results in:

LogSupport.java:44 com/sun/activation/registries/LogSupport log (Ljava/lang/String;)V
LogSupport.java:50 com/sun/activation/registries/LogSupport log (Ljava/lang/String;Ljava/lang/Throwable;)V
...
Throwable.java:498 java/lang/Throwable printStackTraceAsCause (Ljava/io/PrintStream;[Ljava/lang/StackTraceElement;)V
--
885 methods invoke java/io/PrintStream println (Ljava/lang/String;)V

source:

public class App {
    private String targetClass;
    private Method targetMethod;

    private AppClassVisitor cv;

    private ArrayList<Callee> callees = new ArrayList<Callee>();

    private static class Callee {
        String className;
        String methodName;
        String methodDesc;
        String source;
        int line;

        public Callee(String cName, String mName, String mDesc, String src, int ln) {
            className = cName; methodName = mName; methodDesc = mDesc; source = src; line = ln;
        }
    }

    private class AppMethodVisitor extends MethodAdapter {

        boolean callsTarget;
        int line;

        public AppMethodVisitor() { super(new EmptyVisitor()); }

        public void visitMethodInsn(int opcode, String owner, String name, String desc) {
            if (owner.equals(targetClass)
                    && name.equals(targetMethod.getName())
                    && desc.equals(targetMethod.getDescriptor())) {
                callsTarget = true;
            }
        }

        public void visitCode() {
            callsTarget = false;
        }

        public void visitLineNumber(int line, Label start) {
            this.line = line;
        }

        public void visitEnd() {
            if (callsTarget)
                callees.add(new Callee(cv.className, cv.methodName, cv.methodDesc, 
                        cv.source, line));
        }
    }

    private class AppClassVisitor extends ClassAdapter {

        private AppMethodVisitor mv = new AppMethodVisitor();

        public String source;
        public String className;
        public String methodName;
        public String methodDesc;

        public AppClassVisitor() { super(new EmptyVisitor()); }

        public void visit(int version, int access, String name,
                          String signature, String superName, String[] interfaces) {
            className = name;
        }

        public void visitSource(String source, String debug) {
            this.source = source;
        }

        public MethodVisitor visitMethod(int access, String name, 
                                         String desc, String signature,
                                         String[] exceptions) {
            methodName = name;
            methodDesc = desc;

            return mv;
        }
    }


    public void findCallingMethodsInJar(String jarPath, String targetClass,
                                        String targetMethodDeclaration) throws Exception {

        this.targetClass = targetClass;
        this.targetMethod = Method.getMethod(targetMethodDeclaration);

        this.cv = new AppClassVisitor();

        JarFile jarFile = new JarFile(jarPath);
        Enumeration<JarEntry> entries = jarFile.entries();

        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();

            if (entry.getName().endsWith(".class")) {
                InputStream stream = new BufferedInputStream(jarFile.getInputStream(entry), 1024);
                ClassReader reader = new ClassReader(stream);

                reader.accept(cv, 0);

                stream.close();
            }
        }
    }


    public static void main( String[] args ) {
        try {
            App app = new App();

            app.findCallingMethodsInJar(args[0], args[1], args[2]);

            for (Callee c : app.callees) {
                System.out.println(c.source+":"+c.line+" "+c.className+" "+c.methodName+" "+c.methodDesc);
            }

            System.out.println("--\n"+app.callees.size()+" methods invoke "+
                    app.targetClass+" "+
                    app.targetMethod.getName()+" "+app.targetMethod.getDescriptor());
        } catch(Exception x) {
            x.printStackTrace();
        }
    }

}
Chadwick
This is exactly what I'm looking for. Is there any way to also find the source line of the methods?
With debug info in the jar, you'll see the visitSource and visitLineNumber methods invoked (be sure not to disable DEBUG info in the accept call, as I originally had). Not sure why full path to the source is missing, that may be a compiler issue, or maybe there's an annotation for it. Note that this only shows the last target method invocation per method - your original q only wanted to know which methods. Minor effort would show the line number of the method declaration if thats what you're after.
Chadwick
This code only returns one call for each calling method; this was probably originally intended, but with the addition of the line numbers, it now seems more like a bug...
Daphna Shezaf
@Daphna Shezaf That fact is highlighted in my comment just before yours, but I'll edit the answer above to include that caveat. I leave it as an exercise for the reader to customize this sample code for any real-world situations :)
Chadwick
A: 

Structure 101 excels at this kind of analysis.

moffdub
A: 

In eclipse, highlight the method name and then Ctrl+Shift+G

Niyaz