views:

8291

answers:

8

I need to find the caller of a method. Is it possible using stacktrace or reflection?

A: 

I believe the only method to do this is to throw an exception, immediately catch it and parse the stacktrace. It's a fairly costly operation

krosenvold
Since C# allows you to find stacktrace i thought Java might allow it too outside the context of an Exception/catch block. Cool trick anyways. Thanks
Sathish
I believe the depth of the stacktrace and thus the index of the caller method is VM dependent and in particular it does vary between 1.4/1.5/1.6
Draemon
No, since Java 5 there is a method on Thread to get the current stack as an array of StackTraceElements; it's still not cheap, but cheaper than the old exception-parsing solution.
Software Monkey
Cool. Now if there was a use-case for it too ;)
krosenvold
+1  A: 

I've done this before. You can just create a new exception and grab the stack trace on it without throwing it, then examine the stack trace. As the other answer says though, it's extremely costly--don't do it in a tight loop.

I've done it before for a logging utility on an app where performance didn't matter much (Performance rarely matters much at all, actually--as long as you display the result to an action such as a button click quickly).

It was before you could get the stack trace, exceptions just had .printStackTrace() so I had to redirect System.out to a stream of my own creation, then (new Exception()).printStackTrace(); Redirect System.out back and parse the stream. Fun stuff.

Bill K
Cool; you don't have to throw it ?
krosenvold
Nope, At least that's how I remember it, I haven't done it in a few years, but I'm pretty sure that newing an exception is just creating an object, and throwing the exception doesn't do anything to it except pass it to the catch() clause.
Bill K
Neat. I was inclined on throwing it to simulate an actual exception.
Sathish
No, since Java 5 there is a method on Thread to get the current stack as an array of StackTraceElements; it's still not cheap, but cheaper than the old exception-parsing solution.
Software Monkey
@Software Monkey Although I'm sure it's more appropriate, what makes you say that it's cheaper? I'd assume the same mechanism would be used, and if not, why make one slower when it does the same thing?
Bill K
+15  A: 
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()

According to the Javadocs:

The last element of the array represents the bottom of the stack, which is the least recent method invocation in the sequence.

A StackTraceElement has getClassName(), getFileName(), getLineNumber() and getMethodName().

You will have to experiment to determine which index you want (probably stackTraceElements[1] or [2]).

Adam Paynter
I should note that getStackTrace() still creates an Exception, so this isn't really faster--just more convenient.
Michael Myers
Note that this method will not give you the caller, but only the *type of the caller*. You will not have a reference to the object calling your method.
Joachim Sauer
@mmyers: That's good to know, but I think it expresses the intent better than creating the throw-away exception, anyway.
Software Monkey
Just a side note, but on a 1.5 JVM Thread.currentThread().getStackTrace() seems to be a lot slower than creating a new Exception() (approximately 3 times slower). But as already noted, you shouldn't be using code like this in a performance-critical area anyway. ;)A 1.6 JVM only seems to be ~10% slower and, as Software Monkey said, it expresses the intent better than the "new Exception" way.
GaZ
But GaZ, that implies that Thread.currentThread is a very expensive method indeed (which is called twice here, the only other code executed is the test whether the current thread equals this thread). I found that hard to believe frankly.
Eelco
+6  A: 

Sounds like you're trying to avoid passing a reference to this into the method. Passing this is way better than finding the caller through the current stack trace. Refactoring to a more OO design is even better. You shouldn't need to know the caller. Pass a callback object if necessary.

Craig P. Motlin
++ Knowing the caller is too much information. If you must, you could pass in an interface, but there is a good chance that a major refactoring is needed. @satish should post his code and let us have some fun with it :)
Bill K
Valid reasons for wanting to do this exist. I've had a few occasions where I found it helpful during testing for instance.
Eelco
@chillenious I know :)I've done it myself to create a method like `LoggerFactory.getLogger(MyClass.class)` where I didn't have to pass in the class literal. It's still rarely the right thing to do.
Craig P. Motlin
A: 
     /**
       * Get the method name for a depth in call stack. <br />
       * Utility function
       * @param depth depth in the call stack (0 means current method, 1 means call method, ...)
       * @return method name
       */
      public static String getMethodName(final int depth)
      {
        final StackTraceElement[] ste = new Throwable().getStackTrace();

        //System. out.println(ste[ste.length-depth].getClassName()+"#"+ste[ste.length-depth].getMethodName());
        return ste[ste.length - depth].getMethodName();
      }


For example, if you try to get the calling method line for debug purpose, you need to get past the Utility class in which you code those static methods:
(old java1.4 code, just to illustrate a potential StackTraceElement usage)

     /**
       * Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils". <br />
       * From the Stack Trace.
       * @return "[class#method(line)]: " (never empty, first class past StackTraceUtils)
       */
     public static String getClassMethodLine()
     {
      return getClassMethodLine(null);
     }

     /**
       * Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils" and aclass. <br />
       * Allows to get past a certain class.
       * @param aclass class to get pass in the stack trace. If null, only try to get past StackTraceUtils. 
       * @return "[class#method(line)]: " (never empty, because if aclass is not found, returns first class past StackTraceUtils)
       */
     public static String getClassMethodLine(final Class aclass)
     {
      final StackTraceElement st = getCallingStackTraceElement(aclass);
      final String amsg = "[" + st.getClassName() + "#" + st.getMethodName() + "(" + st.getLineNumber()
      +")] <" + Thread.currentThread().getName() + ">: ";
      return amsg;
     }

     /**
       * Returns the first stack trace element of the first class not equal to "StackTraceUtils" or "LogUtils" and aClass. <br />
       * Stored in array of the callstack. <br />
       * Allows to get past a certain class.
       * @param aclass class to get pass in the stack trace. If null, only try to get past StackTraceUtils. 
       * @return stackTraceElement (never null, because if aClass is not found, returns first class past StackTraceUtils)
       * @throws AssertionFailedException if resulting statckTrace is null (RuntimeException)
       */
      public static StackTraceElement getCallingStackTraceElement(final Class aclass)
      {
     final Throwable           t         = new Throwable();
     final StackTraceElement[] ste       = t.getStackTrace();
        int index = 1;
        final int limit = ste.length;
     StackTraceElement   st        = ste[index];
     String              className = st.getClassName();
     boolean aclassfound = false;
     if(aclass == null)
     {
      aclassfound = true;
     }
     StackTraceElement   resst = null;
        while(index < limit)
        {
         if(shouldExamine(className, aclass) == true)
         {
          if(resst == null)
          {
           resst = st;
          }
          if(aclassfound == true)
          {
           final StackTraceElement ast = onClassfound(aclass, className, st);
           if(ast != null)
           {
            resst = ast;
            break;
           }
          }
          else
          {
           if(aclass != null && aclass.getName().equals(className) == true)
           {
            aclassfound = true;
           }
          }
         }
         index = index + 1;
         st        = ste[index];
            className = st.getClassName();
        }
        if(resst == null) 
        {
         //Assert.isNotNull(resst, "stack trace should null"); //NO OTHERWISE circular dependencies 
         throw new AssertionFailedException(StackTraceUtils.getClassMethodLine() + " null argument:" + "stack trace should null"); //$NON-NLS-1$
        }
     return resst;
      }

      static private boolean shouldExamine(String className, Class aclass)
      {
       final boolean res = StackTraceUtils.class.getName().equals(className) == false && (className.endsWith("LogUtils"
      ) == false || (aclass !=null && aclass.getName().endsWith("LogUtils")));
       return res;
      }

      static private StackTraceElement onClassfound(Class aclass, String className, StackTraceElement st)
      {
       StackTraceElement   resst = null;
       if(aclass != null && aclass.getName().equals(className) == false)
       {
        resst = st;
       }
       if(aclass == null)
       {
        resst = st;
       }
       return resst;
      }
VonC
A: 

This method does the same thing but a little more simply and possibly a little more performant and in the event you are using reflection, it skips those frames automatically. The only issue is it may not be present in non-Sun JVMs, although it is included in the runtime classes of JRockit 1.4-->1.6. (Point is, it is not a public class).

sun.reflect.Reflection

    /** Returns the class of the method <code>realFramesToSkip</code>
        frames up the stack (zero-based), ignoring frames associated
        with java.lang.reflect.Method.invoke() and its implementation.
        The first frame is that associated with this method, so
        <code>getCallerClass(0)</code> returns the Class object for
        sun.reflect.Reflection. Frames associated with
        java.lang.reflect.Method.invoke() and its implementation are
        completely ignored and do not count toward the number of "real"
        frames skipped. */
    public static native Class getCallerClass(int realFramesToSkip);

As far as what the realFramesToSkip value should be, the Sun 1.5 and 1.6 VM versions of java.lang.System, there is a package protected method called getCallerClass() which calls sun.reflect.Reflection.getCallerClass(3), but in my helper utility class I used 4 since there is the added frame of the helper class invocation.

Nicholas
Use of JVM implementation classes is a *really* bad idea.
Software Monkey
Noted. I did specify that it is not a public class, and the protected method getCallerClass() in java.lang.System is present across all the 1.5+ VMs I have looked at, including IBM, JRockit and Sun, but your assertion is conservatively sound.
Nicholas
+3  A: 

An alternative solution can be found in a comment to this request for enhancement. It uses the getClassContext() method of a custom SecurityManager and seems to be faster than the stack trace method.

The following program tests the speed of the different suggested methods (the most interesting bit is in the inner class SecurityManagerMethod):

/**
 * Test the speed of various methods for getting the caller class name
 */
public class TestGetCallerClassName {

  /**
   * Abstract class for testing different methods of getting the caller class name
   */
  private static abstract class GetCallerClassNameMethod {
      public abstract String getCallerClassName(int callStackDepth);
      public abstract String getMethodName();
  }

  /**
   * Uses the internal Reflection class
   */
  private static class ReflectionMethod extends GetCallerClassNameMethod {
      public String getCallerClassName(int callStackDepth) {
          return sun.reflect.Reflection.getCallerClass(callStackDepth).getName();
      }

      public String getMethodName() {
          return "Reflection";
      }
  }

  /**
   * Get a stack trace from the current thread
   */
  private static class ThreadStackTraceMethod extends GetCallerClassNameMethod {
      public String  getCallerClassName(int callStackDepth) {
          return Thread.currentThread().getStackTrace()[callStackDepth].getClassName();
      }

      public String getMethodName() {
          return "Current Thread StackTrace";
      }
  }

  /**
   * Get a stack trace from a new Throwable
   */
  private static class ThrowableStackTraceMethod extends GetCallerClassNameMethod {

      public String getCallerClassName(int callStackDepth) {
          return new Throwable().getStackTrace()[callStackDepth].getClassName();
      }

      public String getMethodName() {
          return "Throwable StackTrace";
      }
  }

  /**
   * Use the SecurityManager.getClassContext()
   */
  private static class SecurityManagerMethod extends GetCallerClassNameMethod {
      public String  getCallerClassName(int callStackDepth) {
          return mySecurityManager.getCallerClassName(callStackDepth);
      }

      public String getMethodName() {
          return "SecurityManager";
      }

      /** 
       * A custom security manager that exposes the getClassContext() information
       */
      static class MySecurityManager extends SecurityManager {
          public String getCallerClassName(int callStackDepth) {
              return getClassContext()[callStackDepth].getName();
          }
      }

      private final static MySecurityManager mySecurityManager =
          new MySecurityManager();
  }

  /**
   * Test all four methods
   */
  public static void main(String[] args) {
      testMethod(new ReflectionMethod());
      testMethod(new ThreadStackTraceMethod());
      testMethod(new ThrowableStackTraceMethod());
      testMethod(new SecurityManagerMethod());
  }

  private static void testMethod(GetCallerClassNameMethod method) {
      long startTime = System.nanoTime();
      String className = null;
      for (int i = 0; i < 1000000; i++) {
          className = method.getCallerClassName(2);
      }
      printElapsedTime(method.getMethodName(), startTime);
      System.out.println(className);
  }

  private static void printElapsedTime(String title, long startTime) {
      System.out.println(title + ": " + ((double)(System.nanoTime() - startTime))/1000000 + " ms.");
  }
}

An example of the output from my 2.4 GHz Intel Core 2 Duo MacBook running Java 1.6.0_17:

Reflection: 10.195 ms.
TestGetCallerClassName
Current Thread StackTrace: 5886.964 ms.
TestGetCallerClassName
Throwable StackTrace: 4700.073 ms.
TestGetCallerClassName
SecurityManager: 1046.804 ms.
TestGetCallerClassName

The internal Reflection method is much faster than the others. Getting a stack trace from a newly created Throwable is faster than getting it from the current Thread. And among the non-internal ways of finding the caller class the custom SecurityManager seems to be the fastest.

Johan Kaving