Here is a description of the syntax for the Thread Checker annotations that use predicates. The syntax was formulated to make using the annotations as easy as possible for the “user programmer”; it should just boil down to a single line, one annotation, with as few values as possible, for example:
@OnlyEventThread
public void foo() {
// only allowed to be run by event thread
}
@OnlyThreadWithName("auxThread")
public void bar() {
// only allowed to be run by thread with name "auxThread"
}
To allow this and make the system extensible, we provide a meta-annotation that signals to the instrumentor that a certain annotation on a class, method or constructor is a Thread Checker annotation. It also specifies what method should be executed as predicate. This meta-annotation is currently called @PredicateLink
:
public @interface PredicateLink {
Class value();
String method() default "check";
}
The class is specified using a class’ .class
constant. This makes my job much easier, since the Java compiler will completely resolve the class. The method name is optional; if it isn’t specified, we assume the name of the predicate method is “check”.
This annotation is then applied to the annotations that are actually being used to annotate classes, methods and constructors. Here are two examples, one annotation to specify that only the event thread may execute something, and another one to specify the allowed thread by name:
@PredicateLink(ThreadCheckPredicates.class)
public @interface OnlyEventThread { }
@PredicateLink(value = ThreadCheckPredicates.class, method = "checkName")
public @interface OnlyThreadWithName {
String value();
boolean regex() default false;
}
The first annotation is just a marker annotation, i.e. it does not contain any data. The @PredicateLink
meta-annotation tells us that this is indeed an annotation for the Thread Checker and that the method to execute is in the ThreadCheckPredicates
class. Since no method name is given, the method name “check” is assumed.
The second annotation contains two values, a string and a boolean that controls whether the string is treated as a regular expression or as a plain string. That boolean is optional and defaults to false
, though, making it very easy to use if the regular expression behavior is not desired. The @PredicateLink
meta-annotation links this annotation with the ThreadCheckPredicates.checkName
method.
The methods that serve as predicates have several requirements:
- They need to be static. That also means that they cannot be in an inner (non-static) class. We may be able to relax this requirement later and allow non-static method as long as there’s a zero-ary constructor or a singleton field with a particular name.
- They need to return
boolean
. - They need to accept at least one argument for the value of
this
(ornull
if used in a static context), and one additional argument with the correct type and name for each member value of the annotation that uses the predicate method.
Here is an example of the ThreadCheckPredicates
class that contains the predicate methods for the two annotations defined above:
class ThreadCheckPredicates {
public static boolean check(Object thisObject) {
return EventQueue.isDispatchThread();
}
public static boolean checkName(Object thisObject, String value, boolean regex) {
if (regex) {
return Thread.currentThread().getName().matches(value);
}
else {
return Thread.currentThread().getName().equals(value);
}
}
}
Both methods are static and return boolean
. The first method, check
has just one parameter of type Object
since the annotation linked to it, @OnlyEventThread
is just a marker annotation without data. That parameter will contain the value of this
or null
, depending on whether it’s used in a non-static or static context. The second method, checkName
, has three parameters: The first one is for the value of this
or null
again; the second parameter is String value
and the third is boolean regex
since the annotation that links to the predicate has two member values with the corresponding types and names.
I’m not sure yet, but I think I’ll insist that the names of the method parameters match the names of the member values in the annotation. The alternative would be to say that the parameters must be listed in the same order as the member values in the annotation, and while that’s easier for me to program, it seems a lot more brittle in actual use.
Using predicate methods written in Java adds a lot of flexibility to the system. The meta-annotation that provides the link to the annotation simplifies the actual annotation used by the “user programmer” to the bare minimum. There are still a few things that could be better. For example, accessing fields could be easier. To access fields, the predicate methods have to be enclosed by the scope the fields are defined in, or we have to resort to reflection. Reflection is powerful but also slow.
Accessing local variables directly seems impossible right now. I just noticed that the compiler fails to compile an annotation interface inside an anonymous inner class, even when I’ve relaxed the requirement that predicate methods must be static. When I compile the following, I get a NullPointerException
inside the compiler and an “modifier interface not allowed here” error:
public void fuzz() {
final String name = "fuzzThread";
Thread t = new Thread(new Runnable() {
class AnonymousInnerPredicate {
public boolean check(Object thisObject) {
return (Thread.currentThread().getName().equals(name));
}
}
@PredicateLink(AnonymousInnerPredicate.class)
@interface OnlyThreadWithNameInVariable{ }
@OnlyThreadWithNameInVariable
public void run() {
// only allowed to be run by a thread with name
// equal to string in local variable
}
}, name);
}
The current alternative to this is, of course, to use reflection: The name of the local variable is prefixed with “val$”. That way, the predicate method can be declared outside. However, because an anonymous inner class is anonymous, we can’t get a .class
constant at compile time, so the lookup has to be by name, which isn’t nearly as nice and can’t be automated, because the numbering of the anonymous inner class depends on the location in the source file:
public void fuzz() {
final String name = "fuzzThread";
Thread t = new Thread(new Runnable() {
@OnlyThreadWithNameInField(fieldClassName="TCTest3\$1",
fieldName="val\$name",
name="fuzzThread")
public void run() {
// only allowed to be run by a thread with name
// equal to string in local variable
}
}, name);
}
For situations like this, it would just be nice to allow actual Java code in a string in the annotation, i.e. choose the fourth option I described:
public void fuzz() {
final String name = "fuzzThread";
Thread t = new Thread(new Runnable() {
@CodeThreadPredicate("return Thread.currentThread().getName().equals(name)")
public void run() {
// only allowed to be run by a thread with name
// equal to string in local variable
}
}, name);
}
I know this is possible. I’m pretty sure I could do it. But I also know that it’s a can of worms. So it’ll remain closed for now.