2

How with minimum code uglification can I write a debugging hook in a Swing program that would tell me which component in the hierarchy is actually handling each KeyStroke or mouse click and performing the action mapped to it in the component's action map? We are writing a complicated GUI and it would be very useful to know this information.

Steve Cohen
  • 4,679
  • 9
  • 51
  • 89
  • Have you looked at AOP solution? You should be able to intercept the handling methods of your codebase, and you can weave the aspect in without changing your original code. – Atreys Jun 23 '11 at 20:57
  • Sounds interesting ... links to get me started in this? – Steve Cohen Jun 23 '11 at 22:44
  • [AspectJ](www.eclipse.org/aspectj) is a standard AOP tool for Java. The [pointcut](http://www.eclipse.org/aspectj/doc/released/progguide/starting-aspectj.html) would be something of the form `call(public * KeyListener.keyPressed(*))` and the advice would be to output the component that is handling the event. You might need to handle `cflow` to find the originator and skip the dispatching middle calls before your listener's method is called. – Atreys Jun 24 '11 at 04:59
  • Well, what it seems I want to do would be more like `call(public * ActionListener.actionPerformed(*))` but this seems like a useful approach. However, I am new to AspectJ/AOP although I have had it on my list of things to try for several years and this seems like a pretty good place. However, in playing with the Eclipse AJDT it seems there is no convenient way to add AspectJ to an existing Java project. Am I missing something? I don't want to modify the build process, introduce Ant scripts, etc. The effort involved would exceed the benefits, cool as this capability might be. – Steve Cohen Jun 24 '11 at 18:37
  • Is it possible to add Aspect J to an existing Java project in Eclipse? – Steve Cohen Jun 24 '11 at 18:39
  • 1
    Possibilities that occur to me would be 1) create a separate AspectJ project with a project dependency on the Java project that would somehow launch the Java application 2) create a separate AspectJ project and import the AspectJ project and use it as a library. Would either work? – Steve Cohen Jun 24 '11 at 18:44
  • @Steve, i think that the first approach should work. I am a relative newb with AspectJ as well, otherwise I could flesh this out as an answer. You're right, this approach may take a bit of work to get in place. The benefits is you don't have to modify the code base, and you can weave the aspect in or not depending on whether you're interested analysing the calls or not. – Atreys Jun 26 '11 at 03:36
  • Apparently, the AOP solution, while appealing, conceptually, is not going to work. Getting it to do what I want it to would involve weaving in the javax.swing.* and java.awt.* classes and this is not recommended. See [link] http://www.eclipse.org/forums/index.php/t/206028/ in which trying to do this is very strongly discouraged. When a swing class handles my keystroke, the call is within swing, not weavable and I don't get the info I want. Still, this gave a reason to finally try AOP. – Steve Cohen Jun 27 '11 at 18:07
  • If weaving into javax.swing.* is necessary for your case, than I'd agree. Thanks for the update. The link you have suggests using call pointcuts rather than execute pointcuts to preclude weaving into java classes. I would look at `cflow` within the execution of your actionListener and `cflowbelow` any calls from packages you can actually weave into. While weaving into the base libraries is a pain, AspectJ should handle calls that go into it and then come out of it again. I'll look at it more tonight, as this has my interest. – Atreys Jun 27 '11 at 18:58
  • I saw the "call" idea but didnt' see how it would help much. If the point of interest is one layer deeper in swing it wouldn't help. Would have to check cflow and cflowbelow. I'm sort of a newbie at this. – Steve Cohen Jun 27 '11 at 20:22
  • I came across what I think may be a solution. The cflow isn't going to do it because the action events are all handled internal to the java code (I realized, after doing a stacktrace in an ActionListener). The advice will be something like `before(): execution(* *.actionPerformed(ActionEvent) && args(event)) {log(event.getSource();}`. With the appropriate `log` action taken. I had forgotten about the argument. This should match all weaveable actionListeners. I'll check it out and post an example – Atreys Jun 28 '11 at 02:35
  • @Steve, I was so close on my last comment. I finally downloaded AspectJ (I think I deleted it to clear up some space on my HD) and tested it out. I was missing an argument in the before section. the answer should be usable to match source components to listeners. – Atreys Jun 28 '11 at 03:30

2 Answers2

0

Put in a custom event dispatcher: http://tips4java.wordpress.com/2009/09/06/global-event-dispatching/

Also look up AWTEvent.getID(), MouseEvent, KeyEvent in the API docs.

This is how the software I work on monitors mouse and keyboard to see if the user is busy working, and locks the window if they're not.

Jim
  • 3,476
  • 4
  • 23
  • 33
  • But I don't want to hook the event, I want to hook the action of the event. I want to know which object handles the event - which is not known by the event itself. – Steve Cohen Jun 23 '11 at 22:43
  • How about making a proxy event object and passing that along from the custom event dispatcher? Your proxy can notify you who is "doing stuff" with it. – Jim Jun 30 '11 at 21:37
0

Using AspectJ to weave a logging aspect into your code base can be done. Below is an example of advice on a joinpoint within execution of any objects with the method actionPerformed(ActionEvent) you have in your code base. Similar constructions can be used to advise other Listeners.

Below is the aspect to advise button presses and other components having ActionListeners. It simply outputs the class name of the source of the action and the signature of the actionPerformed method.

import java.awt.event.ActionEvent;

import org.aspectj.lang.Signature;

public aspect Logger {
  before(ActionEvent e) : execution(* *.actionPerformed(ActionEvent)) && args(e) {
    Signature sig = thisJoinPoint.getSignature();
    System.out.println(e.getSource().getClass() + " lead to " + sig);
  }
}

A test class which produces two buttons of different classes (in file StackTraceExample.java):

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

class MyButton extends JButton {
  public MyButton(String string) {
    super(string);
  }
}

class MyOtherButton extends JButton {
  public MyOtherButton(String string) {
    super(string);
  }
}

class ButtonStackDisplay implements ActionListener {
  private final JTextArea stackTraceText;

  ButtonStackDisplay(JTextArea textArea) {
    this.stackTraceText = textArea;
  }

  public void actionPerformed(ActionEvent e) {
    String endl = System.getProperty("line.separator");
    StringBuilder b = new StringBuilder();

    // you can see the source of the event 
    b.append(e.getSource()).append(endl).append(endl);

    // the stack trace shows that events don't propagate through the components
    // originating them, but instead processed in a different thread
    for (StackTraceElement se : new Throwable().getStackTrace()) {
      b.append(se.toString());
      b.append(endl);
    }
    stackTraceText.setText(b.toString());
  }
}

public class StackTraceExample {
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.setLayout(new BorderLayout());
        JPanel top = new JPanel();
        JButton button = new MyButton("Stack Trace");
        top.setLayout(new GridLayout(2, 1));
        top.add(button);
        JButton otherButton = new MyOtherButton("Stack Trace");
        top.add(otherButton);
        f.getContentPane().add(top, BorderLayout.NORTH);
        JTextArea stackTraceText = new JTextArea();
        f.add(stackTraceText, BorderLayout.CENTER);
        ButtonStackDisplay bsd = new ButtonStackDisplay(stackTraceText);
        button.addActionListener(bsd);
        otherButton.addActionListener(bsd);
        f.setSize(400, 400);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
        f.toFront();
      }
    });
  }
}
Atreys
  • 3,741
  • 1
  • 17
  • 27