I have a custom java annotation like below
#customelement(folder = "/path/")
public testMethod() {
}
I want to validate the folder path ("/path/") for the folder member variable and report error inside java source. How I can do that?
Java Annotation Element Method return
#Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(CustomAnnotation.class);
for(Element te : elements){
CustomAnnotation annotation = te.getAnnotation(CustomAnnotation.class);
String folder = annotation.folder();
//... do sth with folder in context of te.
//te represent annotated method, e.g. testMethod from your example
}
return true;
}
Also remember to annotate your CompileTimeAnnotationProcessor with #SupportedAnnotationTypes("package.CustomAnnotation") and #SupportedSourceVersion(SourceVersion.RELEASE_7) (or whatever version of Java syntax are you going to support).
In case of further problems, I recommend to read this great tutorial blog post.
Related
I have written a custom annotation that I use to find methods that can be invoked via a IoT platform. It's a method level annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface DirectMethod {
String value();
}
I look this annotation up in runtime, and to have the call succeed, the expected signature must be:
#DeviceMethod("metod")
public ReturnType methodName(final String data) {...}
i.e, the return type and the input parameters are crucial.
Is there any way to have an annotation be "smart" when its target type is METHOD? Like integrated IDE warnings and such. Or do I simply have to process each annotation manually at startup and have the startup procedure fail if any method breaks my intended method contract?
Yes, you can write annotation processor to validate your calls, the only downside of this is that annotation processors needs to be passed to javac (gradle and maven support easy syntax to register them) so someone could just not do it and not see any warnings/errors.
But otherwise all you need to do is create special annotation and processor, like that:
#SupportedAnnotationTypes("com.gotofinal.direct.DirectMethod")
#SupportedSourceVersion(SourceVersion.RELEASE_8)
public class DirectAnnProcessor extends AbstractProcessor {
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
TypeElement stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String");
TypeElement expectedReturnType = processingEnv.getElementUtils().getTypeElement("com.gotofinal.direct.ReturnType");
for (Element element : roundEnv.getElementsAnnotatedWith(DirectMethod.class)) {
if (! (element instanceof ExecutableElement)) {
processingEnv.getMessager().printMessage(Kind.ERROR, "Annotation should be on method.");
continue;
}
ExecutableElement executableElement = (ExecutableElement) element;
if (! executableElement.getReturnType().equals(expectedReturnType)) {
processingEnv.getMessager().printMessage(Kind.ERROR, "Method should return ReturnType");
}
List<? extends VariableElement> parameters = executableElement.getParameters();
if (parameters.size() != 1 && parameters.get(0).asType().equals(stringType)) {
processingEnv.getMessager().printMessage(Kind.ERROR, "Method should have single String argument");
}
}
return true; // no further processing of this annotation type
}
}
And register it in META-INF/services/javax.annotation.processing.Processor file:
com.gotofinal.direct.DirectAnnProcessor
And then you can add such lib to maven/gradle as annotation processor and it should report any issues. In gradle such library must be added using annotationProcessor "my:lib:0.1" declaration.
Question:
Is it possible to access elements annotated with a #Target(ElementType.TYPE_USE) annotation via an annotation processor?
Is it possible to access the annotated type bounds via an annotation processor?
Links to related documentation I missed are highly appreciated.
Context:
The annotation:
#Target(ElementType.TYPE_USE)
#Retention(RetentionPolicy.SOURCE)
public #interface TypeUseAnno {}
An example class:
public class SomeClass extends HashMap<#TypeUseAnno String, String> {}
The processor:
#SupportedSourceVersion(SourceVersion.RELEASE_8)
#SupportedAnnotationTypes("base.annotations.TypeUseAnno")
public class Processor extends AbstractProcessor {
#Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Initialized.");
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Invoked.");
for (TypeElement annotation : annotations) {
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "" + roundEnv.getElementsAnnotatedWith(annotation));
}
return true;
}
}
Compiling the above SomeClass with Processor on the classpath will show the "Intialized" message but the process(...) method is never invoked.
Adding another annotation to the processor with #Target(ElementType.PARAMETER) works fine when the annotation is present on a method parameter. If the method parameter is annotated with #TypeUseAnno the process will again ignore the element.
The TYPE_USE annotations are a bit tricky, because the compiler treats them differently, than the "old usage" annotations.
So as you correctly observed, they are not passed to annotation processor, and your process() method will never receive them.
So how to use them at compilation time?
In Java 8, where these annotations got introduced, there was also introduced new way to attach to java compilation. You can now attach listener to compilation tasks, and trigger your own traversal of the source code. So your task to access the annotation splits into two.
Hook to the compiler.
Implement your analyzer.
Ad 1.
There are 2 options to hook on the compiler in Java 8:
Using new compiler plugin API.
Using annotation processor.
I haven't used option #1 much, because it needs to be explicitely specified as javac parameter. So I'll describe option #1:
You have to attach TaskListener to the propper compilation phase. There are various phases. Following one is the only one, during which you have accessible syntax tree representing full source code including method bodies (remember, that TYPE_USE annotations can be used even on local variable declarations.
#SupportedSourceVersion(SourceVersion.RELEASE_8)
public class EndProcessor extends AbstractProcessor {
#Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
Trees trees = Trees.instance(env);
JavacTask.instance(env).addTaskListener(new TaskListener() {
#Override
public void started(TaskEvent taskEvent) {
// Nothing to do on task started event.
}
#Override
public void finished(TaskEvent taskEvent) {
if(taskEvent.getKind() == ANALYZE) {
new MyTreeScanner(trees).scan(taskEvent.getCompilationUnit(), null);
}
}
});
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// We don't care about this method, as it will never be invoked for our annotation.
return false;
}
}
Ad 2.
Now the MyTreeScanner can scan the full source code, and find the annotations. That applies no matter if you used the Plugin or AnnotationProcessor approach. This is still tricky. You have to implement the TreeScanner, or typically extend the TreePathScanner.
This represents a visitor pattern, where you have to properly analyze, which elements are of your interest to be visited.
Let's give simple example, that can somehow react on local variable declaration (give me 5 minutes):
class MyTreeScanner extends TreePathScanner<Void, Void> {
private final Trees trees;
public MyTreeScanner(Trees trees) {
this.trees = trees;
}
#Override
public Void visitVariable(VariableTree tree, Void aVoid) {
super.visitVariable(variableTree, aVoid);
// This method might be invoked in case of
// 1. method field definition
// 2. method parameter
// 3. local variable declaration
// Therefore you have to filter out somehow what you don't need.
if(tree.getKind() == Tree.Kind.VARIABLE) {
Element variable = trees.getElement(trees.getPath(getCurrentPath().getCompilationUnit(), tree));
MyUseAnnotation annotation = variable.getAnnotation(MyUseAnnotation.class);
// Here you have your annotation.
// You can process it now.
}
return aVoid;
}
}
This is very brief introduction. For real examples you can have a look at following project source code:
https://github.com/c0stra/fluent-api-end-check/tree/master/src/main/java/fluent/api/processors
It's also very important to have good tests while developing such features, so you can debug, reverse engineer and solve all the tricky issues you'll face in this area ;)
For that you can also get inspired here:
https://github.com/c0stra/fluent-api-end-check/blob/master/src/test/java/fluent/api/EndProcessorTest.java
Maybe my last remark, as the annotations are really used differently by the javac, there are some limitations. E.g. it's not suitable for triggering java code generation, because the compiler doesn't pick files created during this phase for further compilation.
I am trying to come up with a custom annotation, wanted to see if my use-case fit a allowed way of using custom annotation.
I want to replicate what Spring #Value does, but instead of reading a property off of a property, i want to my custom thing.
#Documented
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#SupportedSourceVersion(SourceVersion.RELEASE_8)
public #interface EncryptedValue {
String value();
}
public Class TestEncrypted {
#EncryptedValue("dGVzdCBzdHJpbmc=");
public String someEncryptedValue;
}
I am hoping in annotation processor, i decrypt value and set to the field someEncryptedValue.
/**
*
*/
#SupportedAnnotationTypes("annotation.EncryptedValue")
#SupportedSourceVersion(SourceVersion.RELEASE_8)
public class CustomProcessor extends AbstractProcessor{
private Types typeUtils;
private Elements elementUtils;
private Filer filer;
private Messager messager;
#Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
typeUtils = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);
for(Element ele : annotatedElements) {
EncryptedValue encryptedValue = ele.getAnnotation(EncryptedValue.class);
if(!ele.getKind().isField()){
messager.printMessage(Diagnostic.Kind.ERROR,"EncryptedValue is supported for field");
return false;
}
String annotationValue = encryptedValue.value();
// now get the enclosing type
Set<Modifier> modifiers = ele.getModifiers();
String nameOfVariable = ele.getSimpleName().toString();
// check to see what fields we can modify (i think we can't modify static).
messager.printMessage(Diagnostic.Kind.NOTE,"ClassType: "+ele.getSimpleName().toString()+", nameOf="+annotationValue);
String simpleName = ele.getEnclosingElement().getSimpleName().toString();
for (Element elem : roundEnv.getRootElements()) {
messager.printMessage(Diagnostic.Kind.NOTE, "Enclosing ClassName: "+elem.getSimpleName().toString());
if (elem.getSimpleName().toString().equals(simpleName)) {
for (Element variableDeclaration : elem.getEnclosedElements()) {
if (variableDeclaration instanceof VariableElement) {
messager.printMessage(Diagnostic.Kind.NOTE, "variable: "+((VariableElement) variableDeclaration).getSimpleName().toString());
}
}
}
}
}
}
return true;
}
}
I get the variable, its return types and everything, but not sure how to set value of the variable from this annotation, even if i figure it out, is it good way of using custom annotations.
*Note: This might be sample, what I am planning to do is much more complicated than above sample.
There's no way to modify existing source files via the current publicly-available API. Tools like Lombok which do this are using undocumented internal Javac features to edit the abstract syntax tree. For example, you could use the Sun compiler tree API to obtain a VariableTree, cast it to a JCVariableDecl, then modify it and hope there are no unforeseen consequences. There's no guarantee that tools like Lombok will actually work, and they could break tomorrow with no warning.
What you could do instead is have the annotated classes reference a class which your annotation processor generates, as in the following example:
public class TestEncrypted {
#EncryptedValue("dGVzdCBzdHJpbmc=");
public String someEncryptedValue =
TestEncryptedDecryptedValues.someEncryptedValue;
}
// then generate this class with the annotation processor
final class TestEncryptedDecryptedValues {
static final String someEncryptedValue = "test string";
}
Another way to do something like this would be to use the annotation processor to generate a factory object or method which creates instances of e.g. TestEncrypted with the field assigned to the decrypted value.
A good tutorial for code generation with annotation processors is here: https://deors.wordpress.com/2011/10/08/annotation-processors/
Also, as a side note in case you don't know this, String literals and names appear in the compiled class file, so none of these examples which decrypt the data at compile-time provide any security.
I'm currently writing an annotation processor in which I want it to scan the available methods in classes that their parameters are annotated as NonNull and generate some code inside that methods.
During my search, I didn't find any kind of specification regarding my problem.
What steps do I follow at this stage?
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(NonNull.class)) {
}
return true;
}
I have created an annotation #EnableEntityProcessingthat is intended to be used in the tests (src/test/java).
Also I have created the associated annotation processor EnableEntityProcessingProcessor.
The purpose of this EnableEntityProcessingProcessor is to scan for #Entity (javax.persistence.Entity) annotations found in src/main/java and to generate code based on these annotations. I would want the code to be generated under test resource because it meant to be used only in tests.
The main issue is that RoundEnvironment object points to the test code because the supported annotation is #EnableEntityProcessing (used only in tests).
public final class MyProcessor extends EnableEntityProcessingProcessor {
private static final Class<EnableEntityProcessing> TEST_ANNOTATION_CLASS =
EnableEntityProcessing.class;
private static final Class<Entity> PROD_ANNOTATION_CLASS = Entity.class;
#Override
public final Set<String> getSupportedAnnotationTypes() {
return ImmutableSet.of(TEST_ANNOTATION_CLASS.getCanonicalName());
}
#Override
public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
final Set<? extends Element> elementsAnnotated = roundEnv.getElementsAnnotatedWith(TEST_ANNOTATION_CLASS);
if (!elementsAnnotated.isEmpty()) {
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(PROD_ANNOTATION_CLASS)){
// code generation based on properties from classes annotated with PROD_ANNOTATION_CLASS
}
}
}
}
From the code sample, you can see that I am scanning for elements annotated with EnableEntityProcessing, and if one of the exists, then I want to look for classes annotated with Entity. The problem is that the roundEnv does not find them because it is linked to the test sources.
You could implement your processor so that it gets triggered by #Entity, apply it to the main code of your compilation unit but then use something like target/generated-test-sources as the directory for the sources generated by the processor.