How to find out superclass in an annotation processor? - java

I'm building an annotation processor. How can I find out the superclass of an annotated class?
#Retention(SOURCE)
#Target({ METHOD, TYPE })
public #interface Auditable {
}
class A {
#Auditable
Integer getInt() { ... }
}
#Auditable
class B extends A { ... }
Annotation processor provides two annotated elements. One for method getInt in class A and one for class B.
How can I find out that B extends A?
Partial code of the annotation processor:
private void process( Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv ) {
Set<? extends Element> annotatedElements =
roundEnv.getElementsAnnotatedWith( Auditable.class );
for ( Element element : annotatedElements ) {
ElementKind kind = element.getKind();
if ( ElementKind.CLASS.equals( kind ) ) {
// --- here I need to find out superclass ---
} else if ( ElementKind.METHOD.equals( kind ) ) {
workOnMethod( (ExecutableElement) element );
} else {
processingEnv.getMessager().printMessage( Kind.WARNING,
String.format( "WARNING: #Auditable must be applied "
"to a JPA entity or method but found at %s", element.toString()));
}}}

Related

Find Type of a class implementing a generic interface

I have a generic interface:
public interface Validator<T, M extends Serializable> {
...
}
And several classes implementing that interface:
public class DocumentValidator implements Validator<DocumentDto, Serializable> {
...
}
public class ContractValidator implements Validator<ContractDto, Serializable> {
...
}
public class AccountValidator implements Validator<AccountDto, Serializable> {
...
}
How can I programmatically find out the parameter T? For example, here I'm trying to filter out a specific validator out of a list of validators by passing the T class.
class SomeClass {
#Autowired
List<Validator> validators;
public Validator getValidatorForObject(Object object) {
validators.stream()
.filter(v -> ???)
.findFirst()
.orElseThrow(() -> new
GenericRuntimeException(ERROR_TYPE_VALIDATOR_NOT_FOUND.getText(object.getClass()));
}
}
You can find the T type by java reflection, first, get the getGenericInterfaces then get getActualTypeArguments
validators.stream().filter((v) -> {
for (Type t : v.getClass().getGenericInterfaces()) {
ParameterizedType parameterizedType = (ParameterizedType) t;
Type type = parameterizedType.getActualTypeArguments()[0];
System.out.println(type);
}
// continue your code
// should return boolean value
}).findFirst().orElseThrow(() -> new GenericRuntimeException(ERROR_TYPE_VALIDATOR_NOT_FOUND.getText(object.getClass())));
, output
class com.example.DocumentDto
class com.example.ContractDto

Extending an Annotation [duplicate]

I'm exploring annotations and came to a point where some annotations seems to have a hierarchy among them.
I'm using annotations to generate code in the background for Cards. There are different Card types (thus different code and annotations) but there are certain elements that are common among them like a name.
#Target(value = {ElementType.TYPE})
public #interface Move extends Page{
String method1();
String method2();
}
And this would be the common Annotation:
#Target(value = {ElementType.TYPE})
public #interface Page{
String method3();
}
In the example above I would expect Move to inherit method3 but I get a warning saying that extends is not valid with annotations. I was trying to have an Annotation extends a common base one but that doesn't work. Is that even possible or is just a design issue?
You can annotate your annotation with a base annotation instead of inheritance. This is used in Spring framework.
To give an example
#Target(value = {ElementType.ANNOTATION_TYPE})
public #interface Vehicle {
}
#Target(value = {ElementType.TYPE})
#Vehicle
public #interface Car {
}
#Car
class Foo {
}
You can then check if a class is annotated with Vehicle using Spring's AnnotationUtils:
Vehicle vehicleAnnotation = AnnotationUtils.findAnnotation (Foo.class, Vehicle.class);
boolean isAnnotated = vehicleAnnotation != null;
This method is implemented as:
public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
return findAnnotation(clazz, annotationType, new HashSet<Annotation>());
}
#SuppressWarnings("unchecked")
private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, Set<Annotation> visited) {
try {
Annotation[] anns = clazz.getDeclaredAnnotations();
for (Annotation ann : anns) {
if (ann.annotationType() == annotationType) {
return (A) ann;
}
}
for (Annotation ann : anns) {
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
A annotation = findAnnotation(ann.annotationType(), annotationType, visited);
if (annotation != null) {
return annotation;
}
}
}
}
catch (Exception ex) {
handleIntrospectionFailure(clazz, ex);
return null;
}
for (Class<?> ifc : clazz.getInterfaces()) {
A annotation = findAnnotation(ifc, annotationType, visited);
if (annotation != null) {
return annotation;
}
}
Class<?> superclass = clazz.getSuperclass();
if (superclass == null || Object.class == superclass) {
return null;
}
return findAnnotation(superclass, annotationType, visited);
}
AnnotationUtils also contains additional methods for searching for annotations on methods and other annotated elements. The Spring class is also powerful enough to search through bridged methods, proxies, and other corner-cases, particularly those encountered in Spring.
Unfortunately, no. Apparently it has something to do with programs that read the annotations on a class without loading them all the way. See Why is it not possible to extend annotations in Java?
However, types do inherit the annotations of their superclass if those annotations are #Inherited.
Also, unless you need those methods to interact, you could just stack the annotations on your class:
#Move
#Page
public class myAwesomeClass {}
Is there some reason that wouldn't work for you?
In addition to Grygoriys answer of annotating annotations.
You can check e.g. methods for containing a #Qualifier annotation (or an annotation annotated with #Qualifier) by this loop:
for (Annotation a : method.getAnnotations()) {
if (a.annotationType().isAnnotationPresent(Qualifier.class)) {
System.out.println("found #Qualifier annotation");//found annotation having Qualifier annotation itself
}
}
What you're basically doing, is to get all annotations present on the method and of those annotations you get their types and check those types if they're annotated with #Qualifier. Your annotation needs to be Target.Annotation_type enabled as well to get this working.
Check out https://github.com/blindpirate/annotation-magic , which is a library I developed when I had the same question.
#interface Animal {
boolean fluffy() default false;
String name() default "";
}
#Extends(Animal.class)
#Animal(fluffy = true)
#interface Pet {
String name();
}
#Extends(Pet.class)
#interface Cat {
#AliasFor("name")
String value();
}
#Extends(Pet.class)
#interface Dog {
String name();
}
#interface Rat {
#AliasFor(target = Animal.class, value = "name")
String value();
}
#Cat("Tom")
class MyClass {
#Dog(name = "Spike")
#Rat("Jerry")
public void foo() {
}
}
Pet petAnnotation = AnnotationMagic.getOneAnnotationOnClassOrNull(MyClass.class, Pet.class);
assertEquals("Tom", petAnnotation.name());
assertTrue(AnnotationMagic.instanceOf(petAnnotation, Animal.class));
Animal animalAnnotation = AnnotationMagic.getOneAnnotationOnClassOrNull(MyClass.class, Animal.class);
assertTrue(animalAnnotation.fluffy());
Method fooMethod = MyClass.class.getMethod("foo");
List<Animal> animalAnnotations = AnnotationMagic.getAnnotationsOnMethod(fooMethod, Animal.class);
assertEquals(Arrays.asList("Spike", "Jerry"), animalAnnotations.stream().map(Animal::name).collect(toList()));

How to find class name of ExecutableElement?

I'm writing an annotation processor which i use PostConstruct annotation only for methods. Assume that i have a class like this:
public MyClass{
#PostConstruct
public void onCreate(){
}
}
So inside my annotation processor i can get onCreate method:
for (Element element : roundEnv.getElementsAnnotatedWith(PostConstruct.class)) {
if (element.getKind() != ElementKind.METHOD) {
return false;
}
ExecutableElement method = (ExecutableElement) element;
}
And also i need to know about the class name of onCreate method (MyClass) but ExecutableElement didn't provide any methods to do that. Is there any other utility classes to give me the class name?
The class declaring a method is the method's enclosing element:
TypeElement declaringClass =
(TypeElement) method.getEnclosingElement();
String className =
// also getSimpleName()
declaringClass.getQualifiedName().toString();
When casting gets involved, it usually means one of the visitor API should be used instead. While they are more verbose, they are the only safe option.
The following example triggers a compilation error if something else than a type element is visited, you can obviously adapt that part to your need.
public class TypeElementVisitor extends SimpleElementVisitor8<TypeElement, Void>
{
private final Messager messager;
public TypeElementVisitor( Messager messager )
{
this.messager = messager;
}
#Override
public TypeElement visitType( TypeElement e, Void ignored )
{
return e;
}
#Override
public TypeElement visitUnknown( Element e, Void ignored )
{
messager.printMessage( Diagnostic.Kind.ERROR, "Expected an enclosing class, got: " + e.toString(), e );
return null;
}
}
You can then use it like this:
ExecutableElement execElement = [...];
ElementVisitor<TypeElement, Void> enclosingTypeVisitor = new TypeElementVisitor( processingEnvironment.getMessager() );
TypeElement enclosingType = enclosingTypeVisitor.visit( execElement.getEnclosingElement() )

Java - Annotation Processing

I have an annotation which marks classes that contain an inner class which implements a named interface.
Here's an example of how this annotation is used:
public interface Implementable {}
#Deserializable(Implementable.class)
public class ImplementableFactory {
public static Implementable getImplementable() {
return new Impl();
}
private class Impl implements Implementable {}
}
And here's the annotation itself:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#SupportedSourceVersion(SourceVersion.RELEASE_8)
public #interface Deserializable {
Class value();
}
I'd like to do some annotation processing to ensure this contract. I've created an annotation processor class for that purpose:
public class DeserializableProcessor extends AbstractProcessor {
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for(Element element : roundEnv.getElementsAnnotatedWith(Deserializable.class)){
TypeMirror expected = getDeserializableValue(element);
if (expected != null) {
Boolean found = false;
for (Element enclosed : element.getEnclosedElements()) {
if (enclosed.getKind().equals(ElementKind.CLASS)) {
//This next bit doesn't compile.
//I'm looking for the same functionality.
if (expected.isAssignableFrom(enclosed)) {
found = true;
break;
}
}
}
if (!found) {
String message = String.format("Classes marked with the Deserializable annotation must contain an inner class with implements the value of the annotation. %s does not contain a class which implements %s.",
element.getSimpleName().toString(),
expected.toString());
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message);
}
}
}
return true;
}
private TypeMirror getDeserializableValue(Element element) {
...
}
}
How can I achieve similar functionality to Class::isAssignableFrom within reflection possible via annotation processing?
This can be done with the aid of AbstractProcessor's protected processingEnvironment. It exposes an implementation of TypeUtils, a utility class that enables a lot of reflection functionality.
if (processingEnv.getTypeUtils().isAssignable(enclosed.asType(), expected)) {
found = true;
break;
}

Bind byte buddy method delegation only to methods with annotated parameters

I want to decorate existing objects so that method calls are automatically validated. I already managed to delegate method call to an interceptor that calls Hibernate validator and so far it works fine:
public class HibernateBeanValidator implements BeanValidator{
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
#Override
public <T> T addMethodValidation(T object) {
ExecutableValidator executableValidator = factory.getValidator().forExecutables();
Class<? extends T> dynamicType = (Class<? extends T>)new ByteBuddy()
.subclass(object.getClass())
.method(isPublic()).intercept(MethodDelegation.to(new ValidationInterceptor(object, executableValidator)).andThen(SuperMethodCall.INSTANCE))
.make()
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
try {
T validatedObject = dynamicType.newInstance();
return validatedObject;
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static class ValidationInterceptor {
private final Object validatedObject;
private final ExecutableValidator executableValidator;
public <T> ValidationInterceptor(T object, ExecutableValidator executableValidator) {
this.validatedObject = object;
this.executableValidator = executableValidator;
}
public void validate(#Origin Method method, #AllArguments Object[] arguments)
throws Exception {
Set<ConstraintViolation<Object>> constraintViolations = executableValidator.validateParameters(validatedObject, method, arguments);
if(! constraintViolations.isEmpty()) {
throw new ValidationException(constraintViolations);
}
}
}
}
What I would like to improve is to bind method calls only to methods that have at least one parameter annotated with a constraint annotation, such as:
class Echo {
public String repeat(#NotNull String word) { /* should bind validation here */
return word;
}
public String notAnnotated(String word) { /* should not bind validation */
return word;
}
}
How could I specify an ElementMatcher in Byte Buddy so that it would bind only to methods with parameters annotated with annotations that are annotated with #Constraint, such as #NotNull (taken from javax.validation.constraints):
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { })
public #interface NotNull {
String message() default "{javax.validation.constraints.NotNull.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
/**
* Defines several {#link NotNull} annotations on the same element.
*
* #see javax.validation.constraints.NotNull
*/
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#interface List {
NotNull[] value();
}
}
Your problem can be solved by implementing a custom ElementMatcher which is used to identify methods to be intercepted. Currently, you are using the predefined isPublic() interceptor which does not consider annotations but only the public modifier of a method. As the predefined annotations can be chained, you can build a suitable matcher as follows:
isPublic().and(hasParameter(hasAnnotation(nameStartsWith("javax."))))
Of course, you can simply implement your own matchers without using the predfined ones.
Actually instead of just checking for an annotation out of the javax.validation.constraints namespace, it is probably better to use the Bean Validation meta data API. Constraints do not need to come from this namespace, but can also originate from Hibernate Validator (org.hibernate.validator.constraints) or be a custom constraint. A possible implementation of ElementMatcher which makes use of the meta data API could look like this:
public static class BeanValidationMatcher implements ElementMatcher {
private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
#Override
public boolean matches(Object target) {
// handle different descriptors and potentially use generic MethodDescription
if ( !( target instanceof MethodDescription.ForLoadedMethod ) ) {
return false;
}
MethodDescription.ForLoadedMethod methodDescription = (MethodDescription.ForLoadedMethod) target;
Method method = methodDescription.getLoadedMethod();
boolean isGetter = ReflectionHelper.isGetterMethod( method );
boolean needsValidation;
BeanDescriptor beanDescriptor = validator.getConstraintsForClass( method.getDeclaringClass() );
if ( isGetter ) {
needsValidation = isGetterConstrained( method, beanDescriptor );
}
else {
needsValidation = isNonGetterConstrained( method, beanDescriptor );
}
return needsValidation;
}
private boolean isNonGetterConstrained(Method method, BeanDescriptor beanDescriptor) {
return beanDescriptor.getConstraintsForMethod( method.getName(), method.getParameterTypes() ) != null;
}
private boolean isGetterConstrained(Method method, BeanDescriptor beanDescriptor) {
String propertyName = ReflectionHelper.getPropertyName( method );
PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty( propertyName );
return propertyDescriptor != null && propertyDescriptor.findConstraints()
.declaredOn( ElementType.METHOD )
.hasConstraints();
}
}

Categories