I think I have found an acceptable, relatively easy solution for compound predicate annotations. There are two problems:
- Each element can only have one annotation of each type, so it’s impossible to prefix a method with
@OnlyThreadWithName("foo") @OnlyThreadWithName("bar")
. - There is no subtyping for annotations, so two annotations of different types do not have a common subtype, and I can’t specify an array of annotations that can contain annotations of different types.
Because of these problems, I at first couldn’t figure out how to produce compound annotations that combine other annotations using “and” and “or”, and perhaps “not”. I really would have liked to be able to write the following to specify that method fee()
could be run either by a thread with name “foo” or by a thread with name “bar” that is also the event thread:
@Or({@OnlyThreadWithName("foo"),
@And({@OnlyEventThread,
@OnlyThreadWithName("bar")})})
void fee() { ... }
But there’s no way to do that. I was close to giving up on finding an easy solution, because it’s not really necessary. I could always just write a new annotation and write a new predicate method that just does exactly this or-and combination. But it just looked like there should be an easier way…
I think I found it. Earlier, I wondered if I had programmed handling annotations as member values in annotations correctly, and how those values would be passed to the predicate method. In my new idea, they won’t be passed to a predicate method: An annotation member value instead represents the composition of that annotation’s predicate method with whatever else is in the annotation. Whether the composition is done using “and” or “or” is decided using a meta-annotation; by default, it is “or”, because I expect that will occur more often.
That means that you still have to write new annotations, but at least you do not have to write new predicates. The above annotation can now actually be written as:
@And
@interface MyAndAnnotation {
OnlyEventThread a1()
default @OnlyEventThread;
OnlyThreadWithName a2()
default @OnlyThreadWithName("bar");
}
@interface MyCompoundAnnotation {
OnlyThreadWithName a1()
default @OnlyThreadWithName("foo");
MyAndAnnotation a2()
default @MyAndAnnotation();
}
@MyCompoundAnnotation
void fee() { ... }
It’s still a little bit of work, because the compound cannot be declared just where it is used, but I think this is still very acceptable. After the annotation has been defined once, it can be reused, and with default parameters, it becomes very short.
I’ll now add the restriction that annotation member values themselves have to be Thread Checker annotations, i.e. they have to have a @PredicateLink
meta-annotation. I’ll also have to check for the @And
and @Or
meta-annotations. Then I’ll do normal processing of the member values, except for annotations and arrays of annotations. If there are member values left, then I will use those to make the kind of predicate method call that I am making now.
For all annotation member values and all the annotations in array members, I will call their predicate methods and combine the results together using whatever mode was selected. That also gets combined with the result of the original predicate call from normal data.
If a predicate annotation has only annotations or arrays of annotations, and no normal data, then it is not required to have a @PredicateLink
, even though I need something that tells me this is an anntotation for the Thread Checker. Maybe I’ll use a meta-annotation like @Compound(CompoundMode.AND)
and @Compound(CompoundMode.OR)
instead and then require an annotation for the Thread Checker to either have a @PredicateLink
or @Compound
meta-annotation.
I’m convinced this will work, it will work because quite frankly… I fell asleep again in the middle of this sentence. By the way, here are pictures of my scribbling on the office whiteboard: 1 2 3.