Duly Noted is a Java library that facilitates finding Java annotations on program elements at runtime. It wraps the basic annotation-finding capabilities of the JDK and offers similar functionality to Spring's annotation utilities.
-
Presence levels as a first-class concept. Rather than having to remember which method of AnnotatedElement corresponds to which presence level, or which of the Spring or JUnit utility methods bake in certain levels of presence, instead you can use methods like
find()
,findAll()
, andall()
from a clearly-named implementation of a presence level onPresences
to perform the operation on the element. -
Meta-presence, as exploited in Spring and JUnit 5, as a first-class concept. An annotation is meta-present on an element if it is presence-level on the element, or presence-level on any annotations that are presence-level on the element, and so on up the annotation "hierarchy". Meta-presence encourages creation of composed annotations. A
MetaPresence
wraps anotherPresence
to allow for finding meta-present annotations on program elements. -
Annotated path: a sequence of program elements along which Duly Noted looks for and merges annotations. Many Spring and JUnit 5 annotation helper methods prescribe a search path for the desired annotations; if you want a separate path, find a separate method. Duly Noted takes the approach of allowing for building a search path for annotations, then calling methods on the path such as
find(Class, SingleByTypeDetector)
andall(AllDetector)
to perform the desired operations. The presence level implementations fromPresences
support these detector types.
(Note: If only a single instance of a repeatable annotation of type A
is declared on a program element, it is directly present on the element.
If more than one such instance is declared on the element, those instances
become indirectly present on the element, and an instance of its container
element is directly present on the element.)
- Find a single annotation of type
A
directly present on a programelement
:
Optional<A> a = Presences.DIRECT.find(A.class, element);
- Find all the annotations directly present on a program
element
:
List<Annotation> all = Presences.DIRECT.all(element);
- Find all the annotations of type
A
directly or indirectly present on a programelement
:
List<A> as = Presences.DIRECT_OR_INDIRECT.findAll(A.class, element);
- Find a single annotation of type
A
present on a programelement
:
Optional<A> a = Presences.PRESENT.find(A.class, element);
- Find all the annotations present on a program
element
:
List<Annotation> all = Presences.PRESENT.all(element);
- Find all the annotations of type
A
associated on a programelement
:
List<A> as = Presences.ASSOCIATED.findAll(A.class, element);
- Find the first annotation of type
A
either directly present on a programelement
, or declared recursively on any of the annotations that are directly present on `element:
Optional<A> a = Presences.META_DIRECT.find(A.class, element);
- Find all the annotations either directly present on a program
element
, or declared recursively on any of the annotations that are on a programelement
, or declared recursively on any of the annotations that are directly present onelement
:
List<Annotation> all = Presences.META_DIRECT.all(element);
- Find all the annotations of type
A
directly or indirectly present on a programelement
, or declared recursively on any of the annotations that are directly or indirectly present onelement
:
List<A> as = Presences.META_DIRECT_OR_INDIRECT.findAll(A.class, element);
- Find a single annotation of type
A
present on a programelement
, or declared recursively on any of the annotations that are present onelement
:
Optional<A> a = Presences.META_PRESENT.find(A.class, element);
- Find all the annotations present on a program
element
, or declared recursively on any of the annotations that are present onelement
:
List<Annotation> all = Presences.META_PRESENT.all(element);
- Find all the annotations of type
A
associated on a programelement
, or declared recursively on any of the annotations that are associated onelement
:
List<A> as = Presences.META_ASSOCIATED.findAll(A.class, element);
- Give an annotation of type
A
meta-present along a path of program elementspath
starting at a method parameterp
, where attribute values ofA
found earlier in the path supersede attribute values ofA
found later (an explicit non-default value for an attribute always supersedes a default value):
AnnotatedPath path =
AnnotatedPath.fromParameter(p)
.toDeclaringMethod()
.toDeclaringClass()
.toDepthHierarchy()
.build();
A merged = path.merge(A.class, Presences.META_DIRECT);
-
Direct presence, direct-or-indirect presence, presence, associated
- On non-classes and classes
-
find-one by type, find-all by type, all: as appropriate for above
-
Meta-presence: either on an element, or recursively on one of the annotations that are on the element (TODO: need more tests)
-
Model
AnnotatedPath
as an abstraction over a sequence ofAnnotatedElements
- Along such a path, support the following operations:
- find first occurrence of a non-repeatable annotation along the path
- find every occurrence of a non-repeatable annotation along the path
- corresponding merge operation along the path: give a synthesized annotation, with attributes at front of path superseding attributes further back
- find all occurrences of a repeatable annotation along the path
- find all annotations along the path
- corresponding merge operation along the path: give synthesized annotations, with attributes at front of path superseding attributes further back
- Figure out a way to handle merging of repeatable annotations, if we even want to do that.
- Along such a path, support the following operations:
-
Implement several ways of producing meaningful
AnnotatedPath
s, outward in program element hierarchy- From method parameter, from field, from method, from class, from package, from module
- From method parameter to declaring method
- From method to declaring class
- From class to package
- From class to module
- From field to declaring class
- From class to class enclosure
- From class to superclass/interface hierarchy
- Depth-first superclasses, then depth-first interfaces
- Breadth-first: superclass, then interfaces, ...
- From method to methods it overrides
- Depth-first or breadth-first thru superclass/interfaces
- For path-building stages that yield multiples (classes, methods), offer a way to go "then" to next elements for each (e.g. all these methods, then all their declaring classes), or "depth-first" (from each of those methods, interweave calls out to declaring classes, then chain from each of those etc. so that method1/class1/...1 come before method2/class2/...2)
-
Would it be useful to be able to create
AnnotatedPath
s in the other direction: e.g. class to declared fields? Maybe distinguish between inward and outwardAnnotatedPath
s?- Answer: I'm going with "no".
-
empty class enclosure test
-
empty class hierarchy test
-
empty method override test
-
Is it ok for these to be empty, and extended if they are (with the "then" items as above)?
- Answer: yes; test for these
-
empty class enclosure test with
then
-
empty class hierarchy test with
then
-
empty method override test with
then
-
JPMS-modularize