Thanks for all the tips, here is the solution I came up with in the end. It was easier than I thought. This solution uses both AspectJ and Annotations. It works like this: simply add one of the annotations (defined below) to a method or a class, and a simple check for EDT rule violations will be inserted into it at the beginning. Especially if you mark whole classes like this, you can do a whole lot of testing with only a tiny amount of extra code.
First I downloaded AspectJ and added it to my project (In eclipse you can use AJDT)
Then I defined two new Annotations:
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* Indicates that this class or method should only be accessed by threads
* other than the Event Dispatch Thread
* <p>
* Add this annotation to methods that perform potentially blocking operations,
* such as disk, network or database access.
*/
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR})
public @interface WorkerThreadOnly {}
and
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* Indicates that this class or method should only be accessed by the
* Event Dispatch Thread
* <p>
* Add this annotation to methods that call (swing) GUI methods
*/
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR})
public @interface EventDispatchThreadOnly {}
After that, I defined the Aspect that does the actual checking:
import javax.swing.SwingUtilities;
/** Check methods / classes marked as WorkerThreadOnly or EventDispatchThreadOnly */
public aspect ThreadChecking {
/** you can adjust selection to a subset of methods / classes */
pointcut selection() : execution (* *(..));
pointcut edt() : selection() &&
(within (@EventDispatchThreadOnly *) ||
@annotation(EventDispatchThreadOnly));
pointcut worker() : selection() &&
(within (@WorkerThreadOnly *) ||
@annotation(WorkerThreadOnly));
before(): edt() {
assert (SwingUtilities.isEventDispatchThread());
}
before(): worker() {
assert (!SwingUtilities.isEventDispatchThread());
}
}
Now add @EventDispatchThreadOnly or @WorkerThreadOnly to the methods or classes that should be thread-confined. Don't add anything to thread safe methods.
Finally, Simply run with assertions enabled (JVM option -ea) and you'll find out soon enough where the violations are, if any.
For reference purposes, here is the solution of Alexander Potochkin, which Mark referred to. It's a similar approach, but it checks calls to swing methods from your app, instead of calls within your app. Both approaches are complimentary and can be used together.
import javax.swing.*;
aspect EdtRuleChecker {
private boolean isStressChecking = true;
public pointcut anySwingMethods(JComponent c):
target(c) && call(* *(..));
public pointcut threadSafeMethods():
call(* repaint(..)) ||
call(* revalidate()) ||
call(* invalidate()) ||
call(* getListeners(..)) ||
call(* add*Listener(..)) ||
call(* remove*Listener(..));
//calls of any JComponent method, including subclasses
before(JComponent c): anySwingMethods(c) &&
!threadSafeMethods() &&
!within(EdtRuleChecker) {
if(!SwingUtilities.isEventDispatchThread() &&
(isStressChecking || c.isShowing()))
{
System.err.println(thisJoinPoint.getSourceLocation());
System.err.println(thisJoinPoint.getSignature());
System.err.println();
}
}
//calls of any JComponent constructor, including subclasses
before(): call(JComponent+.new(..)) {
if (isStressChecking && !SwingUtilities.isEventDispatchThread()) {
System.err.println(thisJoinPoint.getSourceLocation());
System.err.println(thisJoinPoint.getSignature() +
" *constructor*");
System.err.println();
}
}
}