5

Suppose we have:

@WebserviceUrl("/withObj")
public void caller(Object obj){
      called();
}

@WebserviceUrl("/withoutObj")
public void caller(){
      called();
}

as you can see, caller has two signatures. In order to get the stack trace we can use:

StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();

But it only returns the name of the method. How can I find the actual true caller?

UPDATE:
The main intention of the question is to read the webservice url which is declared in the method's annotation. wrong detection of caller method, cause calling the wrong webservice.

Pshemo
  • 122,468
  • 25
  • 185
  • 269
Khoda
  • 955
  • 2
  • 14
  • 42
  • I don't think you will be able to do this unless you do something crazy using the "line number" field (i.e. parse the source code and find out what method has that line number in!) - Why do you want this? Is it just to distinguish between the two calls? If so, wouldn't the line number do? – BretC Jul 25 '15 at 11:28
  • Also see http://stackoverflow.com/questions/12834887/how-to-get-the-line-number-of-a-method - might give you some ideas (without having to parse the source code!) Beware though that line number information is not always available – BretC Jul 25 '15 at 11:31
  • Please read the update. – Khoda Jul 25 '15 at 11:33

3 Answers3

2

Interesting. A possible approach I would think of is doing the same as a human being would do : read the line number in the stacktrace and then go to the class. It seems it is doable like this : How to get the line number of a method?. This is not directly applicable because CtClass.getDeclaredMethod only gives you one signature of the method. Yet, you can do something like this :

String className;
String methodName;
int lineNumber;
// parse the stacktrace to get the name of the class, the name of the method and its line number

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get(className);
CtMethod methodWhereExceptionOccurred = 
    Stream.of(cc.getDeclaredMethods())
          .filter(method -> method.getName().equals(methodName))
          .filter(method -> method.getMethodInfo().getLineNumber(0) == lineNumber)
          .findFirst()
          .get();
Community
  • 1
  • 1
Dici
  • 25,226
  • 7
  • 41
  • 82
2

Answer above beat me to it, but I'd add that you might have to go through several stack frames before you get the class or method with the annotations you are looking for, even if the source code is in the same class (but you probably know this).

For example, here was my "Test" class...

import javassist.CtMethod;

public class Test {

    private void method(String s) {
        called();
    }

    private void method() {
        called();
    }

    private static void called() {
        CtMethod caller = StackTraceUtil.getCaller();
        System.out.println(caller);
    }

    private interface Widgit {
        void call();
    }

    private static void call(Widgit w) {
        w.call();
    }

    public static void main(String[] args) {
        new Test().method();
        new Test().method("[]");
        new Widgit() {
            @Override
            public void call() {
                called();
            }
        }.call();
        call(() -> {
            called();
        });
    }
}

The output from this was...

javassist.CtMethod@e59521a2[private method ()V]
javassist.CtMethod@abb88b98[private method (Ljava/lang/String;)V]
javassist.CtMethod@bbd779f1[static access$0 ()V]
javassist.CtMethod@67f92ed4[private static lambda$0 ()V]

Here's StackTraceUtil for completeness. You see that I hard-coded "3" to get the the stace trace element from the array. You probably need to loop through everything from element 3 onwards until you find your annotation.

(This is not as elegant as the answer above, but as I'd nearly finished it I thought I'd post it anyway...)

import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

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

public class StackTraceUtil {

    private static Map<Class<?>, SortedMap<Integer, CtMethod>> cache = new HashMap<>();

    public static CtMethod getCaller() {
        StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
        StackTraceElement stackTraceElement = stacktrace[3];
        int lineNumber = stackTraceElement.getLineNumber();
        try {
            return findMethod(Class.forName(stackTraceElement.getClassName()), lineNumber);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    public static CtMethod findMethod(Class<?> clazz, int lineNumber) {
        SortedMap<Integer, CtMethod> classInfo = cache.get(clazz);

        if (classInfo == null) {
            classInfo = populateClass(clazz);
            cache.put(clazz, classInfo);
        }

        if(classInfo != null) {
            SortedMap<Integer, CtMethod> map = classInfo.tailMap(lineNumber);

            if(!map.isEmpty()) {
                return map.values().iterator().next();
            }
        }

        return null;
    }

    private static SortedMap<Integer, CtMethod> populateClass(Class<?> clazz) {
        SortedMap<Integer, CtMethod> result;

        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass cc = pool.get(clazz.getCanonicalName());
            CtMethod[] methods = cc.getDeclaredMethods();

            result = new TreeMap<>();

            for (CtMethod ctMethod : methods) {
                result.put(ctMethod.getMethodInfo().getLineNumber(0), ctMethod);
            }
        } catch (NotFoundException ex) {
            result = null;
        }

        return result;
    }
}

Hope this helps.

BretC
  • 4,141
  • 13
  • 22
1

If your aim is reading @WebserviceUrl do something and call method then why not use proxy ? .. here is an example .. i dont understand why u need to read the line of the code .. it can be changed anytime ... if your aims is reading webservice url .. maybe this will be helpful

Interface for your caller Class

public interface Test {
    @WebServiceUrl("/withObj")
    public void caller(Object obj);
    @WebServiceUrl("/withoutObj")
    public void caller();
}

Implentation of That Class

public class TestImpl implements Test {


    @Override
    public void caller(Object obj) {
        System.out.println("Object with Called");
    }


    @Override
    public void caller() {
        System.out.println("Object without Called");
    }
}

Invocation Handler Class

public class TestProxyHandler implements InvocationHandler {

    private final Object obj;

    public TestProxyHandler(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            final WebServiceUrl annotation = method.getAnnotation(WebServiceUrl.class);
            if (annotation != null) {
                System.out.println("Value Of annotation  :" + annotation.value());
                //Do What you want ; 
            }
            return method.invoke(obj, args);
        } catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) {
            throw e;
        }


    }

}

ProxyFactory Class

public class TestProxyFactory {

    public static Object newInstance(Object ob) {
        return Proxy.newProxyInstance(ob.getClass().getClassLoader(),
                new Class<?>[]{Test.class}, new TestProxyHandler(ob));
    }

}

And the last your main class

 public static void main(String[] args) {

     Test  tester =    (Test) TestProxyFactory.newInstance(new TestImpl()); 
     tester.caller();
     tester.caller("Test Value");        
    }
Saltuk
  • 1,159
  • 9
  • 12