Fork of the legendary hickory annotation processor. Hickory has served pretty well since it was created in 2010, but it's unmaintained and doesn't have module support.
When writing annotation processors the two conventional mechanisms to access the annotations are both awkward. Element.getAnnotation()
can throw Exceptions if the annotation or its members are not semantically correct, and it can also fail to work on some modular projects. (This is one the reasons why <annotationProcessorPaths>
is required for modular projects but it is seriously limited and technically not correct either (See MCOMPILER-412) Moreover, when calling a member with a Class
return type, you need to catch an exception to extract the TypeMirror
.
On the other hand, AnnotationMirror
and AnnotationValue
do a good job of modeling both correct and incorrect annotations, but provide no simple mechanism to determine whether it is correct or incorrect, and provide no convenient functionality to access the member values in a simple type-specific way. While AnnotationMirror
and AnnotationValue
provide an ideal mechanism for dealing with unknown annotations, they are inconvenient for reading member values from known annotations.
A Prism provides a solution to this problem by combining the advantages of the pure reflective model of AnnotationMirror
and the runtime (real) model provided by Element.getAnnotation(), hence the term Prism to capture this idea of partial reflection.
A prism has the same member methods as the annotation except that the return types are translated from runtime types to compile time types as follows...
- Primitive members return their equivalent wrapper class in the prism.
- Class members return a TypeMirror from the mirror API.
- Enum members return a String being the name of the enum constant (because the constant value in the mirror API might not match those available in the runtime it cannot consistently return the appropriate enum).
- String members return Strings.
- Annotation members return a Prism of the annotation. If a prism for that annotation is generated from the same @GeneratePrisms annotation as the prism that uses it, then an instance of that prism will be returned. Otherwise, a Prism for that annotation is supplied as an inner class of the dependant Prism. the name of which is the simple name of the referenced annotation type.
- Array members return a List where X is the appropriate prism mapping of the array component as above.
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-prisms</artifactId>
<version>${avaje.prism.version}</version>
<optional>true</optional>
<scope>provided</scope>
</dependency>
When working with Java modules you need to add prisms as a static dependency.
module my.processor {
requires static io.avaje.prisms;
}
2. In your AP's pom.xml, replace <compilerArgument>-proc:none</compilerArgument>
with this annotation processor
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.avaje</groupId>
<artifactId>avaje-prisms</artifactId>
<version>${avaje.prism.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
This ensures that only the prism generator will run at build time.
// package-info.java
@GeneratePrism(MyExampleAnnotation.class)
//@GenerateUtils optionally can add this to generate a helper class
package org.example
void someFunction(Element element) {
MyExampleAnnotationPrism exampleAnnotation = MyExampleAnnotationPrism.getInstanceOn(element);
//can get the original annotation type as a string
String annotationQualifiedType = MyExampleAnnotationPrism.PRISM_TYPE
//can easily retrieve the annotation values as if the annotation was present on the classpath.
exampleAnnotation.getValue()
...
}
Avaje prisms will try to detect your processor class and register an entry to META-INF/services/javax.annotation.processing.Processor
after compilation. Doing this means you can omit the compiler plugin configuration. (<compilerArgument>-proc:none</compilerArgument>
and step 2 in the how to use section)
Services entries will be added if a concrete processor class has any of the following annotations:
- Any avaje prism annotation
@SupportedAnnotationTypes
@SupportedOptions
@SupportedSourceVersion
If you add the annotation, this Helper Class will be generated into your project to allow you to access the processing environment statically from anywhere.
To initialize/cleanup the generated APContext
do the below:
@GenerateAPContext
public final class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
APContext.init(env);
}
@Override
public boolean process(Set<? extends TypeElement> tes, RoundEnvironment renv) {
if (renv.processingOver()) {
APContext.clear();
return false;
}
//do whatever processing you need
}
}
- Upgrades from JDK 6 to 11
- Adds modular support via module-info
@GeneratedPrism
is now repeatable- Can choose what classes the generated Prisms inherit.
- Generates an
isPresent
method to check if an element has the target annotation easily - Generates
Optional
factory methods - Generates a
getAllInstances
method to retrieve a list of prisms from an element (@Repeatable
annotations only) - Generates a
getAllOnMetaAnnotations
method to retrieve a list of prisms from an element's annotations (Meta annotations only) - Exposes the fully qualified type of the target annotation as a string.
getInstance
returns null instead of throwing exceptions when the provided mirror doesn't match the prism target- null annotation array values are returned as empty lists
- META-INF/services generation