Swing and Roundabouts 1P: Epoxy DTs
"Gooey Beans"
contents page
for links to the revised article, its sequels, and other Gooey stories :)
Some background
See the prequel Event DTs.

Also see Alexander Potochkin's "Debugging Swing - is it really difficult?"
and "Debugging Swing, the summary #1"
for a discussion on approaches to EDT issues.
Using dynamic proxies to run methods in the EDT
"Ssh, I'm trying to fix this camera. Easy, easy. No, I'll need a bigger drill." Homer Simpson
The Spin framework uses
dynamic proxies to make code EDT-safe.
So we gonna try to do the same.
Say we have a some methods that need to run in the EDT, as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> NDialogHelper {
<font color=#000099><b>public</b></font> <font color=#000099><b>int</b></font> <b>showConfirmDialog</b>(Component parentComponent, Object message, String title, <font color=#000099><b>int</b></font> option) {
<font color=#000099><b>return</b></font> JOptionPane.<b>showConfirmDialog</b>(parentComponent, message, title, option);
}
...
}
But we also want to run these methods from code running in the background, ie. outside the EDT.
For example an actionPerformed() method might start a
SwingWorker thread
to run a long task outside the EDT, so as not to block the EDT.
This is important, so that the application does not appear to be hung
while this long task is executing, ie. the user can still click on buttons
and move windows around and such, since the Swing EDT is free
to respond to these events.
In order to use dynamic proxies (to intercept method invocations to our target object),
we refactor out an interface corresponding to our class, and our class becomes an implementation
of that interface.
<font color=#000099><b>public</b></font> <font color=#000099><b>interface</b></font> NDialogHelper {
<font color=#000099><b>public</b></font> <font color=#000099><b>int</b></font> <b>showConfirmDialog</b>(Component parentComponent, Object message, String title, <font color=#000099><b>int</b></font> option);
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>showMessageDialog</b>(Component parentComponent, Object message, String title, <font color=#000099><b>int</b></font> option);
<font color=#000099><b>public</b></font> <font color=#000099><b>boolean</b></font> <b>showConfirmDialog</b>(Object message);
<font color=#000099><b>public</b></font> <font color=#000099><b>boolean</b></font> <b>showConfirmDialog</b>(String format, Object ... args);
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>showMessageDialog</b>(Object message);
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>showMessageDialog</b>(String format, Object ... args);
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>showExceptionDialog</b>(Throwable exception, String message, Object ... args);
...
}
where the implementation class is as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> NDialogHelperImpl <font color=#000099><b>implements</b></font> NDialogHelper {
<font color=#000099><b>public</b></font> <font color=#000099><b>static</b></font> <font color=#000099><b>final</b></font> NDialogHelper dialogHelper = (NDialogHelper)
NProxyFactory.<b>createProxy</b>(<font color=#000099><b>new</b></font> NDialogHelperImpl(), <font color=#000099><b>new</b></font> NEdtInvoker());
<font color=#000099><b>private</b></font> NDialogHelperImpl() {
}
<font color=#000099><b>public</b></font> <font color=#000099><b>int</b></font> <b>showConfirmDialog</b>(Component parentComponent, Object message, String title, <font color=#000099><b>int</b></font> option) {
<font color=#000099><b>return</b></font> JOptionPane.<b>showConfirmDialog</b>(parentComponent, message, title, option);
}
<font color=#000099><b>public</b></font> <font color=#000099><b>boolean</b></font> <b>showConfirmDialog</b>(Object message) {
<font color=#000099><b>return</b></font> <b>showConfirmDialog</b>(<font color=#000099><b>null</b></font>, message, <font color=#000099><b>null</b></font>, JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION;
}
...
}
We expose this implementation to the application via a proxy object, namely the
static dialogHelper singleton constructed above, using an NProxyFactory as below.
The constructor above is private, since we only access the target via this proxy,
using the NDialogHelper interface.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> NProxyFactory {
<font color=#000099><b>public</b></font> <font color=#000099><b>static</b></font> Object <b>createProxy</b>(Object target, NMethodInvoker invoker) {
NProxyInvocationHandler invocationHandler =
<font color=#000099><b>new</b></font> NProxyInvocationHandler(target, invoker);
Object proxyObject = Proxy.<b>newProxyInstance</b>(
target.<b>getClass</b>().<b>getClassLoader</b>(),
target.<b>getClass</b>().<b>getInterfaces</b>(),
invocationHandler);
<font color=#000099><b>return</b></font> proxyObject;
}
}
which is nothing out of the ordinary.
Our createProxy() method takes an NMethodInvoker parameter,
which is an interface as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>interface</b></font> NMethodInvoker {
<font color=#000099><b>public</b></font> Object <b>invoke</b>(Object target, Method method, Object[] args) <font color=#000099><b>throws</b></font> Exception;
}
Our NProxyInvocationHandler will delegate method invocation to the given NMethodInvoker,
as we'll see in a minute.
Our NEdtInvoker is implemented as follows.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> NEdtInvoker <font color=#000099><b>implements</b></font> NMethodInvoker {
<font color=#000099><b>public</b></font> Object <b>invoke</b>(Object target, Method method, Object[] args) <font color=#000099><b>throws</b></font> Exception {
Callable callable = <b>createCallable</b>(target, method, args);
<font color=#000099><b>return</b></font> <b>callEdt</b>(callable);
}
<font color=#000099><b>protected</b></font> Callable <b>createCallable</b>(<font color=#000099><b>final</b></font> Object target, <font color=#000099><b>final</b></font> Method method, <font color=#000099><b>final</b></font> Object[] args) {
<font color=#000099><b>return</b></font> <font color=#000099><b>new</b></font> Callable() {
<font color=#000099><b>public</b></font> Object <b>call</b>() <font color=#000099><b>throws</b></font> Exception {
<font color=#000099><b>return</b></font> method.<b>invoke</b>(target, args);
}
};
}
<font color=#000099><b>protected</b></font> Object <b>call</b>(Callable callable) {
<font color=#000099><b>return</b></font> <b>call</b>(callable, <font color=#000099><b>false</b></font>);
}
<font color=#000099><b>protected</b></font> Object <b>callEdt</b>(Callable callable) {
<font color=#000099><b>return</b></font> <b>call</b>(callable, <font color=#000099><b>true</b></font>);
}
<font color=#000099><b>protected</b></font> Object <b>call</b>(Callable callable, <font color=#000099><b>boolean</b></font> inEdt) {
<font color=#000099><b>final</b></font> FutureTask task = <font color=#000099><b>new</b></font> FutureTask(callable);
<font color=#000099><b>try</b></font> {
<font color=#000099><b>if</b></font> (inEdt && !SwingUtilities.<b>isEventDispatchThread</b>()) {
SwingUtilities.<b>invokeAndWait</b>(task);
} <font color=#000099><b>else</b></font> {
task.<b>run</b>();
}
<font color=#000099><b>return</b></font> task.<b>get</b>();
} <font color=#000099><b>catch</b></font> (InterruptedException ie) {
<font color=#000099><b>throw</b></font> <font color=#000099><b>new</b></font> NWrappedRuntimeException(ie, <font color=#000099><b>null</b></font>);
} <font color=#000099><b>catch</b></font> (InvocationTargetException ite) {
<font color=#000099><b>throw</b></font> <font color=#000099><b>new</b></font> NWrappedRuntimeException(ite.<b>getCause</b>(), <font color=#000099><b>null</b></font>);
} <font color=#000099><b>catch</b></font> (ExecutionException ee) {
<font color=#000099><b>throw</b></font> <font color=#000099><b>new</b></font> NWrappedRuntimeException(ee.<b>getCause</b>(), <font color=#000099><b>null</b></font>);
}
}
<font color=#000099><b>public</b></font> <font color=#000099><b>void</b></font> <b>runBackground</b>(<font color=#000099><b>final</b></font> Runnable runnable) {
SwingWorker worker = <font color=#000099><b>new</b></font> SwingWorker() {
<font color=#000099><b>protected</b></font> Object <b>doInBackground</b>() <font color=#000099><b>throws</b></font> Exception {
<font color=#000099><b>try</b></font> {
runnable.<b>run</b>();
} <font color=#000099><b>catch</b></font> (Exception e) {
dialogHelper.<b>showExceptionDialog</b>(e, <font color=#000099><b>null</b></font>);
}
<font color=#000099><b>return</b></font> <font color=#000099><b>null</b></font>;
}
};
worker.<b>execute</b>();
}
}
where for callEdt(), we check if isEventDispatchThread(), and if not then we invokeAndWait().
If we are gonna have classes with only some methods we wanna run inside the EDT,
then we could annotate those methods, and implement the above invoke()
method as follows.
<font color=#000099><b>public</b></font> Object <b>invoke</b>(Object target, Method method, Object[] args) <font color=#000099><b>throws</b></font> Exception {
Callable callable = <b>createCallable</b>(target, method, args);
<font color=#000099><b>if</b></font> (method.<b>getAnnotation</b>(InEdtAnnotation.<font color=#000099><b>class</b></font>) == <font color=#000099><b>null</b></font>) <font color=#000099><b>return</b></font> <b>call</b>(callable);
<font color=#000099><b>return</b></font> <b>callEdt</b>(callable);
}
which we might implement in an alternative NMethodInvoker, eg. NAnnotatedEdtInvoker,
which might extend NEdtInvoker to override the invoke() method as above.
Our InvocationHandler below delegates to the given NMethodInvoker, eg. NEdtInvoker.
<font color=#000099><b>public</b></font> <font color=#000099><b>class</b></font> NProxyInvocationHandler <font color=#000099><b>implements</b></font> InvocationHandler {
<font color=#000099><b>protected</b></font> Object target;
<font color=#000099><b>protected</b></font> NMethodInvoker invoker;
<font color=#000099><b>public</b></font> NProxyInvocationHandler(Object target, NMethodInvoker invoker) {
<font color=#000099><b>super</b></font>();
<font color=#000099><b>this</b></font>.target = target;
<font color=#000099><b>this</b></font>.invoker = invoker;
}
<font color=#000099><b>public</b></font> Object <b>invoke</b>(Object proxy, Method method, Object[] args) <font color=#000099><b>throws</b></font> Throwable {
Class declaringClass = method.<b>getDeclaringClass</b>();
<font color=#000099><b>if</b></font> (declaringClass == Object.<font color=#000099><b>class</b></font>) <font color=#000099><b>return</b></font> <b>invokeObject</b>(proxy, method, args);
Method targetMethod = target.<b>getClass</b>().<b>getMethod</b>(method.<b>getName</b>(), method.<b>getParameterTypes</b>());
<font color=#000099><b>return</b></font> invoker.<b>invoke</b>(target, targetMethod, args);
}
...
}
which knows only about NMethodInvoker and doesn't have any DTs,
ie. is reusable beyond our NEdtInvoker.
OK, let's see if this works, by popping up a dialog, from a background (non-EDT) SwingWorker thread.
Supoib!
Using EventBus for EDT-safety
An alternative approach is made possible by eventbus.dev.java.net.
In this case, our dialog displayer would be implemented as a service,
which subscribes on the EventBus to "dialog display events." For example,
we might implement MessageDialogEvent, which contains
the string to display in the dialog.
The dialog displayer would be invoked by EventBus in the EDT.
So our application can then just publish events to be displayed
onto the event bus, no matter whether we are in the EDT or not.
This is quite elegant, so i think i'll write me a simplistic EventBus,
to ensure i have a Quite Eventful weekend. I'll blog it into
Swing and Roundabouts 1M: Emission DTs.
Conclusion
"I hope I didn't brain my damage." Homer Simpson
So this is one approach to building some EDT-safe helper classes,
which we can use from code running in SwingWorker background threads.
We avoid the isEventDispatchThread() and invokeAndWait() boiler-plate code,
using dynamic proxies. The price we pay is having to maintain an interface
for the implementation.
The code can be found lurking in vellum.dev.java.net for now.
Coming Up
Swing and Roundabouts 5: Quite Gooey will present some reusable helper classes
(into which the DTs of this blog are bundled), for building quick GUIs,
with some tooling, beans binding by convention, validation and configuration via annotation
and resource bundles.
Before that, Plumber's Hack 2: Quite Writer will take quite a trip through
the warez i wrote to write these blog articles.
Please help! I'm working on Trip and Tick 4, about writing a Netbeans module to generate
code in the editor, ie. manipulate java classes, and create new java classes,
and such, along the lines of the following Web Start tool. If anyone has any pointers
for me, eg. to sample module source code that does such manipulation and generation
of java classes in Netbeans, please drop me a line (evanx at dev.java.net) - thanks!

(BeanieGenie, Java5, 320k, unsandboxed)
- Login or register to post comments
- Printer-friendly version
- evanx's blog
- 658 reads





