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!

16 comments:

  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
  2. Really this article is truly one of the best in article history and am a collector of old "items" and sometimes read new items if i find them interesting which is one that I found quite fascinating and should be part of my collection. Very good work!
    Data Scientist Course in Gurgaon

    ReplyDelete
  3. I am hoping the same best effort from you in the future as well and in fact your creative writing skills has inspired me.
    Data Science Course near me

    ReplyDelete
  4. I would like to say that this blog really convinced me to do it and thanks for informative post and bookmarked to check out new things of your post…
    Data Science Institute in Noida

    ReplyDelete
  5. Nice Post thank you very much for sharing such a useful information and will definitely saved and revisit your site and i have bookmarked to check out new things frm your post.
    Data Science Course

    ReplyDelete
  6. Just a shine from you here and have never expected anything less from you and have not disappointed me at all which i guess you will continue the quality work. Great post.
    Data Science Training in Gurgaon

    ReplyDelete
  7. Interesting post. which i wondered about this issue so thanks for posting and very good article which is a really very nice and useful article. Thank you
    Data Science Course in Noida

    ReplyDelete
  8. Very great post which I really enjoy reading this and it is not everyday that I have the possibility to see something like this. Thank You.
    Best Online Data Science Courses

    ReplyDelete
  9. I read your excellent blog post. It's a great job. I enjoyed reading your post for the first time, thank you.
    Data Science Institutes in Bangalore

    ReplyDelete
  10. Well done for this excellent article. and really enjoyed reading this article today it might be one of the best articles I have read so far and please keep this work of the same quality.
    Data Analytics Course in Noida

    ReplyDelete
  11. Informative Post. The information you have posted is very useful and sites you have referred was good. Thanks for sharing.
    Data Science Course with Placement

    ReplyDelete
  12. This is an informative and knowledgeable article. therefore, I would like to thank you for your effort in writing this article.
    Data Science Course in Bangalore

    ReplyDelete
  13. I am always searching online for articles that can help me and you made some good points in Features also. Keep working, great job
    Data Science Training

    ReplyDelete
  14. Really nice and amazing post. I was looking for this kind of information, Keep posting. Thanks for sharing.
    Data Science Courses in Bangalore

    ReplyDelete
  15. Wonderful blog post. It's absolute magic on your part! Hope you continue like this!
    Data Analytics Training in Bangalore

    ReplyDelete