[JSR308] Suggestion (and example) for annotations that take any annotation or for annotation inheritance
Jonathan Aldrich
jonathan.aldrich at cs.cmu.edu
Tue Oct 7 00:33:39 EDT 2008
Hello,
I appreciate Joe sharing his perspective on this issue. I work with
Ciera and Nels at CMU; they and others in my group have found that
annotation inheritance and/or annotations that take another annotation
are useful for a wide range of analyses, and so I wanted to respond to
some of the points Joe made and better explain why we think this
extension would be worth its weight.
Regarding JSR 175, I definitely appreciate its focus on the needs of
"Enterprise Edition" Java. On the other hand, JSR 308 is at least in
part aimed at adapting annotations, in minor ways, to make them more
suitable for program analysis. Whether annotation-based program
analysis becomes mainstream is unknown--but if it does, as I and many
other believe it will, it will benefit all programmers, not just
Enterprise programmers. Thus JSR 308 could have a wider impact than JSR
175. Furthermore, although I'm not an Enterprise edition expert, there
are good reasons for thinking that Enterprise users might benefit from
annotations that take another annotation as well (see below).
I believe allowing annotations to take arbitrary annotations as
arguments is a smaller change than allowing annotations wherever types
go (the primary focus of the proposal at present). Furthermore, it will
eventually be needed by almost all of the program analyses/type systems
targeted by JSR 308. Finally, this one change, by itself, can express
much of the the JSR 308 existing proposal (I'm not arguing we should
throw the rest out, only that this piece carries its weight well). Let
me explain these one by one.
FIRST: annotations taking other annotations as parameters is a
(comparatively) small change. The existing JSR 308 proposal changes the
Java language syntax. Merely permitting annotations to extend other
annotations would not change the language syntax. The existing JSR 308
proposal changes the class file format. Joe raised a great question
about whether annotations taking other annotations as parameters would
require changing the class file format as well. As far as I can tell,
from the specification of the Java 5 classfile format, it would not:
http://docs.oracle.com/javase/specs/jvms/se5.0/ClassFileFormat-Java5.pdf
Specifically, annotations are stored in an "annotation" structure in the
classfile. This structure contains a type index (the type of the
annotation) and a list of element_value_pairs. The value in the
element_value_pair is an element_value, which is a tagged union, and if
the tag is "@" for annotation, then the union stores an "annotation"
structure. That's the annotation structure above--which in fact
contains a type index, that in the case of "annotations taking arbitrary
annotations" tells us what type the nested annotation is. Ironically,
in the current classfile format, this is wasted space, as it could be
inferred from the surrounding annotation type, but it nicely supports
this extension. Thus, "annotations containing arbitrary annotations"
need change neither the syntax of the language nor the classfile format.
It could be that there are other issues lurking, and Joe is right that
we need to investigate this in detail, but to me it initially looks like
a simpler change than the other elements of JSR 308.
SECOND: such an extension would be of very wide utility. In the last
week, four people have suggested this. Two of them are my students, who
are developing entirely different type systems, with independent and
somewhat different needs for annotations within annotations. I have at
least one more example that I'll give below. This, and that two others
unrelated to me have proposed variants of the same idea, suggests we're
on to something general.
In fact, the most important use case for "annotations taking arbitrary
annotations" is annotation parameters that don't correspond to a type
parameter. Imagine that you have developed some kind of container class
that is designed to hold things of a specific type, not of an arbitrary
type. This may be realistic in cases where the container needs to
interact with the things it contains in domain-specific ways. In this
case, there is no reason to have a type parameter on the container.
However, it may still make lots of sense to have annotations on the
contained thing.
As a concrete example, another one of my students is using annotations
to implement an ownership type system. Ownership types can be used for
lots of things, including finding design errors, eliminating race
conditions, etc. Imagine a MySubject object that has a list of
MyObserver objects. The MySubject knows the MyObserver type, and
doesn't need a type parameter for the particular kind of observer, but
it does need to know who owns its observers. Right now, we have to
express this in an ugly string-based syntax:
@OwnedBy("owned<shared>") MySubject subject
Here "owned" is the owner of MySubject, while "shared" is the owner of
the observers stored in MySubject. We could add a dummy parameter to
MySubject and use JSR 308 as follows:
@Owned
MySubject< @Shared MyObserver> subject
First, note the benefits of JSR308: I was able to avoid parsing a
string, and I can use the more structured @Owned and @Shared instead of
"owned" and "shared." But it is very unpleasant, to the point of being
unmaintainable in large systems where there might be multiple such
parameters, to add unneeded type parameters just to add an annotation
parameter. A much better solution is to use annotations:
@Owned
@OwnershipArgs({@Shared})
MySubject subject
Almost any analysis is likely to come up against this issue. To pick a
few from the JSR 308 spec, containers without type parameters could
benefit in:
* Titanium, where contained elements are @local
* dependent types, where the contained elements are arrays of a @Size
* javari/immutability, where the contained elements are immutable
* non-nullness, where the contained elements are non-null
* taintedness, where the contained elements are tainted
* etc. for just about any type qualifier
Another great example in the JSR 308 spec, which is independent of the
"annotation parameterization without type parameterization" is units of
measurement. Instead of @Units("m/s") it might be nice to expose the
structure of the units with annotations: @Units(value={@Meters},
dividedBy={@Seconds}) This won't work without "annotations taking
arbitrary annotations."
As a further (conceptual) motivation, JSR 175 was originally developed
for "Enterprise Edition" Java--but when Enterprise users aren't using
annotations, they're using XML configuration files. Interestingly, the
most important semantic difference between JSR 175 annotations and XML
files is that XML files allow recursive structure--XML elements
containing arbitrary XML elements--whereas JSR 175 does not allow
annotations to contain arbitrary annotations. I am not an Enterprise
Java user, but I suspect that there would be a lot of use cases in
Enterprise Java for recursively structured annotations--in particular I
imagine complex issues like database persistence and security could
probably benefit.
So if JSR 308 is designed to facilitate (at least in part) static
checkers, nearly all of them would benefit from "annotations taking
arbitrary annotations," and although I don't have examples, it's
reasonable to think that other uses of annotations would benefit as well.
THIRD, "annotations taking arbitrary annotations" can express much of
what's in JSR 308--not as cleanly, to be sure, but still allowing users
to express annotations on most uses of a type. Instead of the current
proposal:
Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
we could write:
@Params({@NonNull, @AnnotAndParams(@NonEmpty, {@Readonly})})
Map<String, List<Document>> files;
Similar ideas would work for arrays, type bounds, wildcards, class
inheritance, throws clauses, and method receivers. It would not work
for new statements, casts, or generic type arguments; syntax extensions
a la JSR 308 are needed there.
Now, the JSR 308 syntax is *definitely* more readable, and I am not for
a moment suggesting that we give it up. But the fact that "annotations
taking arbitrary annotations" is by itself, likely simpler than the rest
of JSR 308 to implement, is broadly applicable, and can express much of
what the rest of JSR 308 does, seems to me like a good argument that it
pulls its own weight. It arguably may fall higher than the rest of JSR
308 on the "worse is better" scale.
Joe also said that work needs to be done in ensuring this can be
feasibly implemented, beyond the short analysis above; I agree, and I
hope we can contribute to that. But first I'd like to get a broader
sense for if the JSR 308 community sees this as a valuable proposal.
Thanks,
Jonathan
P.S. Regarding "anything that requires instanceof is suspicious" -
functional programmers who use pattern matching would disagree, and I
suspect instanceof is harmless in many of the use cases for this
extension. But from an OO extensibility perspective, the "right change"
is actually not "annotations that take arbitrary annotations" but rather
"annotations that inherit from other annotations." In the latter case,
no instanceof is necessary, as ordinary dispatch or the visitor pattern
can be used. But "annotations that inherit from other annotations" is
more complex than "annotations that take arbitrary annotations", so a
"worse is better" perspective suggests that we might prefer "annotations
that take arbitrary annotations" despite the instanceof issue. If
there's a groundswell of support for annotations that inherit from other
annotations, and the technical issues are solvable, I'd be delighted.
More information about the JSR308
mailing list