Monday, September 8, 2008

Debug Listeners on AWT/Swing components

Being a newbie to the whole Swing stuff, I often wonder what events are fired on a given component for any given gesture or event.

I wonder no more!

Copy the following (ctrl-C), then activate some package node in a project in Eclipse (e.g. the obvious "com.example") and paste (ctrl-V).

import java.awt.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.swing.JButton;

public class DebugStatics {

public interface LineTaker {
void line(String msg);
}

public static final void main(String[] args) {
LineTaker out = new LineTaker() {
@Override
public void line(String msg) {
System.out.println(msg);
}
};
addDebugListeners(out, new JButton(), false);
}

public static void addDebugListeners(final LineTaker out, final Object component,
final boolean includeAllMoveEvents) {

// :: Find all add*Listener methods for supplied component.

List<Class<?>> listenerInterfaces = new ArrayList<Class<?>>();
List<Method> addListenerMethods = new ArrayList<Method>();
Method[] methods = component.getClass().getMethods();
for (Method method : methods) {
String name = method.getName();
if (name.startsWith("add") && name.endsWith("Listener")) {
Class<?>[] parameters = method.getParameterTypes();
if (parameters.length == 1) {
Class<?> interfaze = parameters[0];
if (interfaze.isInterface()) {
listenerInterfaces.add(interfaze);
addListenerMethods.add(method);
}
}
}
}

// :: Make handler for "super listener"

InvocationHandler handler = new InvocationHandler() {
long _lastEvent;
String _lastMethodName;
boolean _threadFired;
InvocationHandler _handler = this;

@Override
public synchronized Object invoke(@SuppressWarnings ("unused") Object proxy, Method method, Object[] args)
throws Throwable {
_lastEvent = System.currentTimeMillis();
String methodName = method.getName();
if (includeAllMoveEvents || !(methodName.endsWith("Moved") && methodName.equals(_lastMethodName))) {
out.line("event:[" + method.getName() + "] - on - [" + method.getDeclaringClass().getName()
+ "] - with - " + Arrays.asList(args) + ".");
if (!_threadFired) {
_threadFired = true;
new Thread("debug:Event Stream Breaker") {
@Override
public void run() {
while (true) {
long millisLeft;
synchronized (_handler) {
millisLeft = 400 - (System.currentTimeMillis() - _lastEvent);
if (millisLeft < 0) {
out.line("===== event stream break.");
_threadFired = false;
_lastMethodName = null;
break;
}
}
try {
Thread.sleep(millisLeft + 2);
continue;
}
catch (InterruptedException e) {
break;
}
}
}
}.start();
}
}
_lastMethodName = methodName;
return null;
}
};

// :: Make "super listener", "implementing" all the Listener interfaces

Object superListener = Proxy.newProxyInstance(DebugStatics.class.getClassLoader(), listenerInterfaces
.toArray(new Class<?>[0]), handler);

// :: Attach "super listener" using all add*Listener methods on supplied component

for (Method method : addListenerMethods) {
try {
method.invoke(component, superListener);
out.line(" ++ add*Listener: [" + method + "].");
}
catch (Throwable e) {
out.line("Got error when trying to invoke add*Listener method:[" + method + "]." + e);
}
}
}
}
Code discussion: The LineTaker comes from the need to have some flexibility regarding where the textual stream of events should end up (e.g. to System.out.println(...) or to some logger.debug(...)). The driver (main method) is just to check out the listener adding (and possibly also to find out which types of add*Listener methods a given AWT/Swing/Whatever class has). The reason for the Thread-forking is so that you more easily can understand which events "belongs" to each actual, physical gesture: Move the mouse into a button, wait a second, then press down mouse button, wait a second, release mouse button, wait a second, move out. Now the result event stream have embedded a break on every second-wait. The thread only lives long enough to write the break, then exits. The reason for the code regarding move-events suppression is that you get an awful lot of move-events without it - this code results in only one move event being recorded in a consecutive run of the same move event.

While dumping the code into this post, it hit me that the above method actually works for any class having add*Listener(AnyInterface listener) methods. That's why the type of the component argument is Object.

There's a pretty impressive amount of events being fired on a JButton for the simple act of using the mouse to click it!

1 comment:

  1. Công việc văn phòng luôn tạo cho nhân viên căng thẳng áp lực.Nên cần thay đổi không gian làm việc bằng cách tựtạo cảm hứng làm việc với bàn làm việc
    Bạn đang có nnhu cầu mua bàn làm việc thì việc đầu tiên bạn nên trang bị kiến thứccách chọn bàn làm việc
    Tìm hiểu thông tin và tiêu chỉ để có thể chọn bàn làm việc văn phòng
    Nội thất văn phòng trong công ty doanh nghiệp ghế, tủ, hộc hồ sơ...và không thể thiếubàn làm việc trong văn phòng công ty
    Cũng như các sản phẩm văn phòng khác truóc khi mua bạn nên xem qua và chọn mẫu ưng ý.Và đối với bàn làm việc cũng thế khi mua bạn nênnên chọn mẫu bàn làm việc

    ReplyDelete