Link to home
Start Free TrialLog in
Avatar of wabar
wabar

asked on

Java Calling Class

I have an application where I want to get a reference back to the calling class in Java.

The calling class does not identify itself and I want to go back to the calling class and find out its name using the java.lang.reflect forName() function to get the name of the calling class.

How can I get the reference to the calling class?

Avatar of rwoodruf
rwoodruf

Can you provide details as to what you are planning on doing with the calling object once you able to figure out what it is?  

I am confused as to how you expect to get the calling object when you don't pass a reference of the calling object to the object that is being called.

Hope this makes sense.
Avatar of CEHJ
You can't AFAIK
Avatar of wabar

ASKER

I have a logging utilty that is instantiated as a singleton. It would be great to know which class is writing to the log w/o going back to re-write the log API and updating 1000's of log calls to pass in the data.

If I could get a reference back to the calling class, then I could do .forName() call on that class (as an object).

You'll have to pass a reference
ASKER CERTIFIED SOLUTION
Avatar of agb6w
agb6w

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of wabar

ASKER

Wow, that is great! However, isn't throwing an exception  costly for perfomance? If I do this with every log will I not grind my application down to a crawl?

This maybe the only solution though ...
You could provide a parameter for passing the calling object in your logging api.

public void log(Object caller, ....);
True. Exceptions can be fairly expensive.  But that is usually in comparison to regular flow control.  If you are logging to a file then you I/O time is probably going to be so much more expensive that it really wouldn't matter.  Unfortunately, I don't know of any other way to access the call stack.

If you are using this for logging have you considered using something like log4j?  It is pretty powerful.
Or use the logging in 1.4 if you're using that version of Java
yes, the only way i can see a how a singleton instantiation could work would be to pass a reference to the logger each time like 'object' suggested.
Another way would be to instantiate a new logger for each application that requests logging. Then the application would need to call a logger method like setCaller(Object o) or in the constructor once before starting logging to identify itself, with the object reference being stored in each logger instance. This way, a reference would not need to be passed each time a log is made, just once at initialisation.
>>to pass a reference to the logger each time like 'object' suggested

and what about

>>You'll have to pass a reference

;-)
yep, credit to you too for simplicity
I think exception stack trace is populated when you create the exception, not when you throw it.

So, you could just do

Exception e = new Exception();
StackTraceElement[] ste = e.getStackTrace();
if (ste == null || ste.length < 3)
  return null;
else
  return ste[2].getClassName();

BTW, if you don't have 1.4, you can still do something similar using e.printStackTrace(), and parse the stack trace for the class name. It's just much more tricky and JVM-dependent.

But as a matter of clean code, it's a better idea to pass the reference to the calling class :)

- Eugene
> I think exception stack trace is populated when you
> create the exception

You're still creating a new object everytime you add an entry to the log.
> then I could do .forName() call on that class (as an object).

What exactly do you plan to do with this Class object anyway?
yes. sorry, that's the best I can do :)
> I think exception stack trace is populated when you create the exception, not when you throw it.

The stack is populated with stack frames anytime the VM is running, and the StackTraceElement[] is just a dump of this. It's not really that big of a deal ( except for the creation of the Exception object ).

To finally and definitively answer your question,

> How can I get the reference to the calling class?

You can get it, but you gotta want it. You can access the stack using JDI ( Java 1.3 or higher ). Essentially, what you want is to find the MethodEntryEvent in the VM's EventQueue, at at that point, momentarily suspend the VM, looking at the previous frame on the stack and get the method, class, etc..  The jump to the next frame ( current frame ), set your values, and let the VM go again.

Example: ( i may have 2 posts just so it fits )
Class 1 -

package vm;
import com.sun.jdi.*;
import com.sun.jdi.connect.*;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;
import java.util.*;
public class TestMain {
  public TestMain() {}
  public static void main(String[] args) throws Exception {
    TestMain test = new TestMain();
    VirtualMachineManager manager = Bootstrap.virtualMachineManager();
    LaunchingConnector connector = manager.defaultConnector();
    Map map = connector.defaultArguments();
    ((Connector.Argument)map.get("main")).setValue( "-classpath \"c:/vm/classes\" vm.LogMain" );
    VirtualMachine vm = connector.launch(map);
    EventRequestManager erm = vm.eventRequestManager();
    MethodEntryRequest mer = erm.createMethodEntryRequest();
    mer.addClassFilter( "vm.Log" );
    mer.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
    mer.enable();
    vm.resume();
    EventQueue queue = vm.eventQueue();
    try {
      while( true ) {
        EventSet es = queue.remove();
        for ( EventIterator ei = es.eventIterator(); ei.hasNext(); ) {
          Event e = ei.nextEvent();
          if ( e instanceof MethodEntryEvent ) {
            MethodEntryEvent event = (MethodEntryEvent)e;
            if ( event.method().name().equals( "log" ) ) {
              List frameList = event.thread().frames( 0, 2 );
              StackFrame frame = (StackFrame)frameList.get(1);
              Location loc = frame.location();
              String callString = loc.method() + ": line[" + loc.lineNumber() + "]";
              frame = (StackFrame)frameList.get(0);
              ObjectReference ref = frame.thisObject();
              LocalVariable locVar = frame.visibleVariableByName( "message" );
              Value value = vm.mirrorOf( callString + "-->" + frame.getValue( locVar ));
              frame.setValue( locVar, value );
            }
            vm.resume();
          }
          else if( e instanceof VMDeathEvent ) return;
        }
      }
    }
    catch ( InterruptedException ie ) { ie.printStackTrace(); }
  }
}

Class 2-
package vm;
import java.io.*;
public class Log {
  private PrintWriter out;
  private static Log instance;
  public static Log getInstance() {
    if ( instance == null ) instance = new Log();
    return instance;
  }
  private Log() {
    try { out = new PrintWriter( new FileOutputStream( "logtest" ) ); }
    catch( Exception e ) { e.printStackTrace(); }
  }
  public synchronized void log( String message ) {
    out.println( message );
    out.flush();
  }
}

Class 3-
package vm;
public class LogMain {
  public LogMain() {}
  public static void main(String[] args) {
    LogMain logMain1 = new LogMain();
    Log log = Log.getInstance();
    new Thread( new OtherThread() ).start();
    for ( int i = 0; i < 10; i ++ ) {
      try { Thread.sleep( 1000 ); }
      catch ( Exception e ) { e.printStackTrace(); }
      log.log( "this is message #" + i );
    }
  }
}
Class 4-
package vm;
public class OtherThread implements Runnable {
  public OtherThread() {}
  public void run() {
    Log log = Log.getInstance();
    for( int i = 0; i < 10; i++ ) {
      try { Thread.sleep( 1000 ); } catch(Exception e) {e.printStackTrace();}
      log.log( "this is from left field.");
    }
  }
}


If you compile and run this example, you'll get the idea. The output looks like this.

vm.LogMain.main(java.lang.String[]): line[11]-->"this is message #0"
vm.OtherThread.run(): line[8]-->"this is from left field."
vm.LogMain.main(java.lang.String[]): line[11]-->"this is message #1"
vm.OtherThread.run(): line[8]-->"this is from left field."
vm.LogMain.main(java.lang.String[]): line[11]-->"this is message #2"
vm.OtherThread.run(): line[8]-->"this is from left field."
vm.LogMain.main(java.lang.String[]): line[11]-->"this is message #3"
vm.OtherThread.run(): line[8]-->"this is from left field."

etc, etc....

Yeah, its kind complex, but hey...  you asked.

IMO, unless you want to implement this kind of complexity, it would be alot easier to just force the caller to identify himself to the log method.

BUT as far as
>>You'll have to pass a reference
Definitely not. You CAN do what you originally asked.  It is just a bit difficult.
BTW, if you DO decide to play with this, make sure you modify the line
  ((Connector.Argument)map.get("main")).setValue( "-classpath \"c:/vm/classes\" vm.LogMain" );
to suit your classpath so it will work. :)

> It is just a bit difficult.

And most probably impractical for the required purpose due to performance reasons.
Using stacktraces and reflection is rediculous, IMO.  There is a much simpler way.  

interface UsesLogging
{
  String whoIsLogging();
}

class a implements UsesLogging
{
..
  logger.write(this, "message");
}

class Logger
{
  public void write(UsesLogging who, String message)
  {
     finalMessage = who.whoIsLogging() + ...
}

cheers,
brian
>>And most probably impractical for the required purpose due to performance reasons.

objects,
Actually, the performance isn't as bad as you would think.
I would think a better dig on it would be "but you have to launch your app in another vm". ;) Either way, I was just answering the question.


>Using stacktraces and reflection is rediculous, IMO.

bkrahmer,
IMO, the only thing riduculous here is how many people FAIL to read what has been posted before and continue to post the same answers OVER and OVER.
>CEHJ:  You'll have to pass a reference
CEHJ posted that solution a long time ago.  Everyone agrees it is the right course of action. Either way, I was just answering the question.
FYI, it probably isn't helping wabar telling him his suggestion of using reflection is 'rediculous'. I am not positive, but confident, that's not what he's looking for.
Avatar of wabar

ASKER

Thank you everyone for the excellent repsonses over the weekend. I will further analyze the potential solutions and let everyone know what I choose to do : )
You can try this approach:

At the begining of your class (the one that has to identify who's calling it) add the following code:

  // Static method to find out which class called our method..
  private static Method sysGetCallerClass;
  protected static int opOrder = 0;

  static {
    try {
      sysGetCallerClass = System.class.getDeclaredMethod("getCallerClass", null);
      sysGetCallerClass.setAccessible(true);
    } catch (Exception ex) {
    }
  }

Then, when you need to identify the calling class, add this code to get it:

    Class callingClass = null;
    try {
      callingClass = (Class)sysGetCallerClass.invoke(null, null);
    } catch (Exception ex) {
    }

WARNING: This works because of an undocumented method in the java.lang.System class, newer versions of the JDK (higher than 1.4) or non-Sun JDK implementations may not include this method.

Hope this helps.
Doron
P.S. please disregard the opOrder, its not needed... sorry :)
wabar, just wondering if you saw my post..