0

Briefly speaking, I am working on developing a system which is able to give you information about the results provided by the execution of a java program. I have considered the following problem, and I do not know if it is possible to solve it in java.

I have the following classes:

public class ClassA {
    ClassB classB= new ClassB();
    public Integer method1(){
       return classB.method2();
    }
}

public class ClassB {
    ClassC classC = new ClassC();
    public Integer method2() {
        return this.classC.method3() + this.classC.method4();
    }
}

public class ClassC {
    public Integer method3() {
        return 3;
    }
    public Integer method4() {
        return 4;
    }
}

So far I can capture each invocation of the methods by using dynamic proxies. In particular, I am using the Proxy and the InvocationHandler objects from the package java.lang.reflect. Here there is the example I followed (https://www.concretepage.com/java/dynamic-proxy-with-proxy-and-invocationhandler-in-java).

My question is if someone knows how can I give information such as: "the return of method1() is generated from the return of method2(), and the return of method2() is in turn generated from the return of method3() and the return of method4()".

csadan
  • 291
  • 1
  • 3
  • 13
  • There is a debugger api and other related low-level apis. You are fairly limited in what you can do internally from the runtime the program is running in itself. – pvg Nov 09 '17 at 17:01
  • 1
    Another option, if you have control over the JVM, is instrumentation (`java.lang.instrumentation`). As your classes are loaded you can inject code into each method that builds up a call tree if you wish (with relevant indentation) so you get something like: `method1 -> method2 -> method3,method4`. So you can add a little bit of code at the start of each method that populates said call tree and at the end of the method pop children off the tree. – Mr Rho Nov 09 '17 at 17:24

3 Answers3

0

You need to utilize ThreadLocals. You thread local will have a Map which will be filled by each method. Here is a sample code:

public static void main(String... args){
        try{
            new ClassA().method1();

            TrackingThreadLocal.tracker.get().entrySet().stream().forEach( (e) -> System.out.println( 
            "the return of " + e.getKey() + " is generated from the return of " + e.getValue().stream().collect( Collectors.joining(", ") ) ) );

        }finally{
            //make sut to clean up to avoid ThreadLocal memoty leak
            TrackingThreadLocal.tracker.remove();
        }
    }

    public class TrackingThreadLocal{

        public static ThreadLocal< Map<String, List<String> > > tracker = new ThreadLocal< Map< String, List<String> > >(){
            @Override 
            public Map< String, List<String> > initialValue() {
                return new HashMap<>();
            }
        };
    }

    public class ClassA {
        ClassB classB= new ClassB();
        public Integer method1(){
           TrackingThreadLocal.tracker.get().put("method1", Arrays.asList("method2") );
           return classB.method2();
        }
    }

    public class ClassB {
        ClassC classC = new ClassC();
        public Integer method2() {
            TrackingThreadLocal.tracker.get().put( "method2",  Arrays.asList("method3", "method4") );
            return this.classC.method3() + this.classC.method4();
        }
    }

    public class ClassC {
        public Integer method3() {
            return 3;
        }
        public Integer method4() {
            return 4;
        }
    }
tsolakp
  • 5,858
  • 1
  • 22
  • 28
  • Why ThreadLocal? OP didn't mention threads, so I'd rather assume a single-threaded solution first... Also, would this handle transitive dependencies between the methods? (i.e. "method1 is a result of method2 that is a result of method3") – david a. Nov 09 '17 at 18:18
  • @david a. ThreadLocals are meant for exact situation like I described. They can be used in either single or multi threaded code. – tsolakp Nov 09 '17 at 18:21
  • In a single-threaded case, thread locals won't be necesary. A simple Map would do for the solution you're describing. – david a. Nov 09 '17 at 18:28
0

I have used instrumentation to solve a similar problem before.

Disclaimer: You can only do the following if you have control over the JVM and you can specify it to run with a javaagent.

An agent is fairly simple to implement, all you need is a class which implements a method with the premain(String, java.lang.Instrumentation) signature. An example follows:

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.instrumentation.ClassFileTransformer;
import java.lang.instrumentation.IllegalClassFormatException;
import java.lang.instrumentation.Instrumentation;
import java.security.ProtectionDomain;

public class MyAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new ClassTransformer() {
            public byte[] transform(ClassLoader loader, String className,
                    Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
                    byte[] classfileBuffer) throws IllegalClassFormatException {
            // if you want to target specific classes or packages you can filter
            // on the class name, just remember that it is the JVM class format string
            if(className.startWith("com/my/package/SomeThing")) {
                // javassist provides methods to access classes and generate bytecode
                ClassPool cp = ClassPool.getDefault();
                // you can access the class with the following
                CtClass cc = cp.get("com.my.package.MyClass$InnerClass");
                // access specific methods with
                CtMethod m = cc.getDeclaredMethod("someMethod");
                m.setBody("{MyCallTree tree = com.my.package.TreeSingleton.getTree();\ntree.addCall(" + m.getLongName + ");\n}");
                return cc.toByteCode();
            }
            else {
                // return null so that you don't break methods or classes you
                // don't want to
                return null;
            }
        });
    }
}

In the snippet above, I replaced the entire method body with the code passed as a string to CtMethod.setBody. However, you can really do almost anything with javassist, prepend code, append code, etc. A detailed tutorial on using javassist can be found here. It is a very powerful library.

The details of how you implement the code to build up the call information is really just Java code, maybe write the code as part of your project first and then just use the bits that does the footwork in the premain method. You can even let it write the code suggested by tsolakp in his answer.

The next step is to package the above code in a jar file and then inject it into your JVM when you start it up, as done in this answer.

Mr Rho
  • 578
  • 3
  • 16
-1

One thing that comes to my mind is to retrieve a stacktrace of a thread and see what you can do with.

You can do it with eg. Thread.currentThread().getStackTrace() method and receive an array of StackTraceElement, finally use them to receive a method name with a getMethodName() method.

It is just an idea, I am not sure if you can get the information you want to.

staszko032
  • 802
  • 6
  • 16
  • Unfortunately the stacktrace will only yield `method1 -> method2 -> method3` and `method1 -> method2 -> method4` and only if the stack traces are invoked inside `method3` and `method4` respectively. I believe the OP wants to evaluate Java code which is a different kettle of fish. I have worked with [JavaParser](https://javaparser.org/) before but then you need a proper understanding of the EBNF of Java and the language structures. Analysis of the language tokens can get very hairy. – Mr Rho Nov 09 '17 at 17:20