How can I get the complete Call Hierarchy of a Java source code?

Tarek picture Tarek · Dec 13, 2013 · Viewed 14.7k times · Source

This is a bit tricky to explain. I have a class A:

public class A {
    private Integer a1;
    private Integer a2;
    // getters and setters.
}

There is a static class B that returns my class A:

public static class B {
    public static A getCurrentA() {
        return a;
    }
}

I need to find all usages of class A returned by B. So let's say class C calls c.setA(B.getCurrentA()) and then further along there's a call to c.getA().getA2();, I'd want to find all of these.

In the real scenario, I have 217 different classes that call B.getCurrentA(). I can't manually follow all the calls in Eclipse and find out which methods are getting called.

Eclipse call hierarchy view only shows me all calls to B.getCurrentA().

How can I achieve this?


EDIT

Chris Hayes understood what I want to do. In order to refactor some really bad legacy code without breaking the whole system, I need to first fine-tune some queries using Hibernate's projections (every mapped entity in the system is eagerly loaded, and many entities are related, so some queries take a LONG time fetching everything). But first I need to find which properties are used so that I don't get a NullPointerException somewhere...

Here's an example of what I'd have to do manually:

  1. Use Eclipse's Search to find all calls to B.getCurrentA();
  2. Open the first method found, let's say it's the one below:

    public class CController {
        C c = new C();
        CFacade facade = new CFacade();
        List<C> Cs = new ArrayList<C>();
    
        public void getAllCs() {
            c.setA(B.getCurrentA()); // found it!
            facade.search(c);
        }
    }
    
  3. Open the search method in the CFacade class:

    public class CFacade {
        CBusinessObject cBo = new CBusinessObject();
    
        public List<C> search(C c) {
            // doing stuff...
            cBo.verifyA(c);
            cBo.search(c); // yes, the system is that complicated
        }
    }
    
  4. Open the verifyA method in the CBusinessObject class and identify that field a2 is used:

    public class CBusinessObject {
        public void verifyA(c) {
            if (Integer.valueOf(1).equals(c.getA().getA2())) {
                // do stuff
            else {
                // something else
            }
        }
    }
    
  5. Repeat steps 2-4 for the next 216 matches... Yay.

Please help.

Answer

Satheesh Cheveri picture Satheesh Cheveri · Dec 19, 2013

If you want to make any source code changes/refactoring you will have to manually find all usages and apply your code changes;

Any way, I have two different aproach

  1. Static search You can simply do Text Search in eclipse to find the occurance of getA2() . It will directly take you to the Caller method (here CBusinessObject.verifyA()) -but it will give you every getA2() occurances, may be from different class

  2. Run time search Use java instrumentation API to change the byte code at run time on your required method to find invoking class and run as java agent - Enable you to identify the caller with out touching the existing code base and very useful especially when you don't have access to source code.

Here you go how to implement

Step 1- Write Agent main class to initiate instrumentation

public class BasicAgent {
                public static void premain(String agentArguments, Instrumentation instrumentation){
                    System.out.println("Simple Agent");
                    FindUsageTransformer transformer = new FindUsageTransformer ();
                    instrumentation.addTransformer(transformer,true);
                }
            }

Step 2 -Write a ClassFileTransformer implementation and capture the method

public class FindUsageTransformer implements ClassFileTransformer{

        Class clazz = null;
        public byte[] transform(ClassLoader loader,String className,Class<?>  classBeingRedefined,  ProtectionDomain    protectionDomain,
                byte[]              classfileBuffer)    throws IllegalClassFormatException {
            if(className.equals("A")){
                doClass(className, classBeingRedefined, classfileBuffer);
            }
            return classfileBuffer;
        }
        private byte[] doClass(String name, Class clazz, byte[] b) {
            ClassPool pool = ClassPool.getDefault();
            CtClass cl = null;
            try {
              cl = pool.makeClass(new java.io.ByteArrayInputStream(b));
              CtMethod method =  cl.getDeclaredMethod("getA2");
              // here you have lot of options to explore
              method.insertBefore("System.out.println(Thread.currentThread().getStackTrace()[0].getClassName()+ Thread.currentThread().getStackTrace()[0].getMethodName());");
              b = cl.toBytecode();
            } catch (Exception e) {
              System.err.println("Could not instrument  " + name
                  + ",  exception : " + e.getMessage());
            } finally {
              if (cl != null) {
                cl.detach();
              }
            }
            return b;
          }

Step 3- create jar file for agent classes ( you have to set manifest file with premain class, and add javaassit jar) snippet of build file is given - you can do it by manually as well

<jar destfile="build/jar/BasicAgent.jar" basedir="build/classes">
                <manifest>
                    <attribute name="Manifest-Version" value="1.0"/>
                    <attribute name="Premain-Class" value="com.sk.agent.basic.BasicAgent"/>
                    <attribute name="Boot-Class-Path" value="../lib/javassist.jar"/>
                </manifest>
            </jar>

Step 4- Run your main application with java agent - before that set VM arguments to load agent

            -`javaagent:D:\softwares\AgentProject\AgentLib\build\jar\BasicAgent.jar`

Pre requisite : you would need javassist.jar in the class path.