Java annotation dynamic typecast - java

I have 2 java annotation types, let's say XA and YA. Both have some method(). I parse the source code and retrieve Annotation object. Now I'd like to dynamicaly cast the annotation to the real type of it to be able to call the method(). How can I do it without the instanceof statement? I really want to avoid switch-like source. I need something like this:
Annotation annotation = getAnnotation(); // I recieve the Annotation object here
String annotationType = annotation.annotationType().getName();
?_? myAnnotation = (Class.forName(annotationType)) annotation;
annotation.method(); // this is what I need, get the method() called
?_? means I have no idea what would be myAnnotation type. I cannot use the base class for my XA and YA annotations since the inheritance in annotations is not allowed. Or is it possible to do somehow?
Thanks for any suggestion or help.

Why don't you use the typesafe way to retrieve your annotation ?
final YourAnnotationType annotation = classType.getAnnotation(YourAnnotationType.class);
annotation.yourMethod();
If your annotation can't be found, null is returned.
Please note that this also works with fields and methods.

One way is to invoke the method dynamically using the name of it:
Annotation annotation = getAnnotation();
Class<? extends Annotation> annotationType = annotation.annotationType();
Object result = annotationType.getMethod("method").invoke(annotation);
This approach is quite risky and totally compromise the code refactoring if needed.

Related

JSON Binding #JsonbTypeDeserializer annotation ignored on enums?

I'm converting a JAXB application to JSON-B and I've run into an issue while trying to deserialize a Java enum using a custom JsonbDeserializer inside one of my tests.
The original JSON I need to deserialize contains ints referencing the enum's constants. Therefore my custom JsonbDeserializer needs to take the int and return the enum constant with the matching ordinal. It looks like this:
#JsonbTypeDeserializer(Region.RegionDeserializer.class)
public enum Region implements BaseEnum {
REGION_A,
REGION_B;
static final class RegionDeserializer implements JsonbDeserializer<Region> {
// deserialize() method returns REGION_A for 0 and REGION_B for 1.
}
}
Then I run it like this:
try (var jsonb = JsonbBuilder.create()) {
var result = jsonb.fromJson(text, Region.class);
} catch (final Exception ex) {
fail(ex);
}
Unfortunately, here's what I get back:
java.lang.IllegalArgumentException: No enum constant Region.1
at java.base/java.lang.Enum.valueOf(Enum.java:266)
at org.eclipse.yasson.internal.serializer.EnumTypeDeserializer.deserialize(EnumTypeDeserializer.java:40)
As you can see, RegionDeserializer is not used. Instead, the default enum deserializer is used. Looking into the JSON-B docs, I see I should register the deserializer manually:
JsonbConfig config = new JsonbConfig()
.withDeserializer(RegionDeserializer.class);
Jsonb jsonb = JsonbBuilder.create(config);
...
And when I do that, the code in fact works. But here's my question - what can I do to have the JsonbTypeDeserializer annotation registered automatically? Considering I have a lot of enums I need custom deserializers for, registering them manually really doesn't scale.
EDIT 1: I have tried to use #JsonbCreator-annotated static method instead, and the result was the same. The default enum deserializer was still used.
The JSON-B specification mentions both ways of registering the custom deserializer:
There are two ways how to register JsonbSerializer/JsonbDeserializer:
Using JsonbConfig::withSerializers/JsonbConfig::withDeserializers method;
Annotating a type with JsonbSerializer/JsonbDeserializer annotation.
The fact that the annotation does not work is a bug. I could reproduce this on Yasson 1.0.6, but not on Yasson 2.0.0-M1. Perhaps updating to the latest version solves your problem?

How to get type annotations & attribute values for VariableElement with Java8?

Consider the following code:
public class SimpleTest {
private Map<#JSON Integer,Map<#Frozen Integer,#Enumerated(value = Enumerated.Encoding.NAME, test = "123") String>> map;
}
With the latest JDK8 API for annotation processing, how can I access the list of annotations (#JSON, #Frozen & #Enumerated) and their corresponding attributes (value & test for #Enumerated) from the VariableElement ?
final VariableElement mapElm = els.stream().filter(x -> x.getSimpleName().contentEquals("map")).findFirst().get();
???
???
I've tried many tricks, like mapElm.getTypeArguments().get(0) for the #Json Integer but I never succeed to get my hand on the annotation #JSON...
Edit: By accessing internal classes of the JDK, I can have access to those annotations but it's so hacky and sensitive to impl change that I'm wondering whether there is a better way
public static class SimpleEntityCodecFactoryTest {
private Map<#JSON Integer,Map<#Frozen Integer,#Enumerated(value = Enumerated.Encoding.NAME, test = "123") String>> map;
}
final TypeElement typeElement = elementUtils.getTypeElement(SimpleEntityCodecFactoryTest.class.getCanonicalName());
final List<VariableElement> els = ElementFilter.fieldsIn(typeElement.getEnclosedElements());
final VariableElement mapElt = els.stream().filter(x -> x.getSimpleName().contentEquals("map")).findFirst().get();
final com.sun.tools.javac.util.List<Attribute.TypeCompound> typeAttributes = ((Symbol.VarSymbol) mapElt).getMetadata().getTypeAttributes();
for (Attribute.TypeCompound typeAttribute : typeAttributes) {
final DeclaredType annotationType = typeAttribute.getAnnotationType();
System.out.println(format("Accessing annotation '%s' at location : %s",annotationType.toString(),typeAttribute.getPosition().location));
for (Map.Entry<Symbol.MethodSymbol,Attribute> entry : typeAttribute.getElementValues().entrySet()) {
final Symbol.MethodSymbol methodSymbol = entry.getKey();
final Attribute attribute = entry.getValue();
System.out.println(format("Attribute '%s' for annotation '%s' : %s", methodSymbol.name, annotationType.toString(), attribute.toString()));
}
}
The output display:
Accessing annotation 'info.archinnov.achilles.annotations.JSON' at location : TYPE_ARGUMENT(0)
Accessing annotation 'info.archinnov.achilles.annotations.Frozen' at location : TYPE_ARGUMENT(1),TYPE_ARGUMENT(0)
Accessing annotation 'info.archinnov.achilles.annotations.Enumerated' at location : TYPE_ARGUMENT(1),TYPE_ARGUMENT(1)
Attribute 'value' for annotation 'info.archinnov.achilles.annotations.Enumerated' : info.archinnov.achilles.annotations.Enumerated.Encoding.NAME
Attribute 'test' for annotation 'info.archinnov.achilles.annotations.Enumerated' : "123"
The above code is working fine in IntelliJ, but because of the dirty cast ((Symbol.VarSymbol) mapElt).getMetadata(), it is working with Oracle JDK but fails miserably with Eclipse compiler.
Right now, I don't find any other solution than the dirty cast to access annotations in generic types. Any idea is welcomed
Solution:
Thanks to Werner (wmdietl), I can access the nested annotations using the Tree API instead of Elements or TypeMirror
However I'm quite stuck because once I get there, it is not possible to convert any subclass of Tree back to Element or TypeMirror (my real target).
All of my annotation processing is using heavily JavaPoet (https://github.com/square/javapoet) to generate clean source code and this framework only handles TypeMirror, not Tree
In the https://github.com/typetools/checker-framework/blob/master/javacutil/src/org/checkerframework/javacutil/TreeUtils.java class, there are some methods to convert Tree back to Element but it is relying on InternalUtils, which I can't use because it won't be compatible with Eclipse ECJ compiler.
I guess I will have to wait for JDK 9 before having an usable Element API that will be compatible with ECJ compiler
Edit: To make the type annotation work for Eclipse Compiler, I had to cast to internal compiler classes like here: https://github.com/doanduyhai/Achilles/blob/master/achilles-core/src/main/java/info/archinnov/achilles/internals/parser/AnnotationTree.java#L83-L85. It's ugly but that is the only way for now until JDK9.
You shouldn't look at the Element (or Symbol), but at the TypeMirror (javax.lang.model.type.TypeMirror).
The annotations you are interested in are type use annotations, so you can't access them (easily, there are hacky ways) through the Element.
Once you have the TypeMirror, you can use the methods in javax.lang.model.AnnotatedConstruct to query for all or particular annotations.
Another aspect to be aware of: usual annotation processing runs early in the compiler and not all types might have been set.
See
https://github.com/typetools/checker-framework/blob/master/javacutil/src/org/checkerframework/javacutil/AbstractTypeProcessor.java
for a way to run your processor after code attribution.
Alternatively, you can use the new "plugin" mechanism in com.sun.source.util.Plugin, but that is OpenJDK specific.
#JSON must be decorated with #Retention(RetentionPolicy.RUNTIME) so that Reflections can be used.
AnnotatedParameterizedType mapField = (AnnotatedParameterizedType)SimpleTest.class.getDeclaredFields()[0].getAnnotatedType();
AnnotatedType integerType = mapField.getAnnotatedActualTypeArguments()[0];
System.out.println(integerType.getAnnotations()[0]);
// -> #JSON()
see AnnotatedParameterizedType

Implementing Marker Interfaces in Java

I want to implement a marker interface that suggests user to define a value like serialVersionUID that is suggested when a Class implements java.io.Serializable.
How can I achieve it in Java?
Thanks in advance!!
If you can go the annotation way you can implement a custom annotation with a (required) element. Elements that do not have a default value are required.
For instance look at the following annotation:
public #interface Deprecated {
String version(); //In which version this was deprecated?
String reason() default "[not documented]";
}
Here version is a required field, while you can omit the reason field when annotating something.
For more information, you can look at this tutorial:
http://howtodoinjava.com/2014/06/09/complete-java-annotations-tutorial/

Spring Injection and Annotations

Have looked around a lot but am unable to figure out an approach or if it is even possible; here is the question, any directions/word of advice would be very helpful.
Is it possible to have a method annotation say, #TestAnnotation that could be used as follows?
#TestAnnotation(element="something" id="someId")
public void someMethod() {
AnObj anObj = id.getAnObj();
}
"someId" is a wrapper around "AnObj" class; 'id' is an instance of "someId". And whatever is passed to 'element' is to be accepted as a parameter or configuration element to be used in creating an instance of "someId" and assigning it to 'id'.
Thanks and regards
And Why dont simple use AOP and do all that you need in #Before method
http://www.journaldev.com/2583/spring-aop-example-tutorial-aspect-advice-pointcut-joinpoint-annotations-xml-configuration
Change it to this:
#TestAnnotation(element="something" id="someId")
public void someMethod(Object something, AnObj anObj) {
}
Then you can set up an aspect to intercept methods with that annotation and assign the necessary objects to the parameters of the method before calling.

Java annotation returns cryptic class names

I am somewhat new to Java so perhaps I misunderstand the use cases for annotations in java. My issue is the following:
After annotating a method I receive class names such as $Proxy31 when inspecting the annotations on the method. I am curious why I am receiving class names for my annotations that are similar to this, and what I can do to fix this problem.
Method m = this.remoteServiceClass.getMethod(rpcRequest.getMethod().getName());
RequiredPermission a = m.getAnnotation(RequiredPermission.class);
This returns a null annotation even though I know that the method it is looking up has the RequiredPermission annotation implemented.
for(Annotation a : m.getAnnotations())
{
System.out.println(a.getClass().getName());
}
This prints out the $Proxy31 class names.
Given Annotation a, you need to call annotationType(), not getClass() to determine the type of the annotation. An Annotation object is just a proxy that represents that instance of the annotation on that class.
Object o = ...;
Class c = o.getClass();
Annotation[] as = c.getAnnotations();
for (Annotation a : as) {
// prints out the proxy class name
System.out.println(a.getClass().getName());
// prints out the name of the actual annotation
System.out.println(a.annotationType().getName());
}
When you add annotations in the source code, Java actually creates a bunch of interfaces and classes "under the hood" to allow you (or your tools) to ask the program things about the annotations using restrictions. Method annotations create "dyanmic proxies", and accordingly Java creates classes for you, probably with the name Proxy.
If you are interested in this, read on java.lang.reflect.InvocationHandler and its subtype, AnnotationInvocationHandler
That being said, you should not have to worry about what Java actually generates. I suspect you are not using reflection correctly to inspect your annotations from within a Java program.
also.. remember to set this:
#Retention(RetentionPolicy.RUNTIME)
on your annotation so that it lives beyond the compile.

Categories

Resources