How to create an annotation with an interface that has a generic? - java

I am trying to create an annotation that will allow me to wrap a (spring) bean with an instance of the supplied class. The interface has a type parameter which is (should not, if possible) not specified by the wrapping class. See the code below for an example of what I mean.
I managed to get a (compilation, haven't tried runtime yet) fix by making MyWrapperImpl implement the type parameter with super class of the class used by MyWrappedClass, however I would rather not specify it.
How can I keep the type parameter? In other words how can I keep MyWrapperImpl as generic as possible?
Annotation:
#Documented
#Target(ElementType.TYPE)
#Inherited
#Retention(RetentionPolicy.Runtime)
public #interface Wrap {
Class<? extends MyInterface<?>> classToWrapWith();
}
Interface:
public interface MyInterface<T> {
T getSomething();
}
A wrapper class:
public class MyWrapperImpl<T> implements MyInterface<T> {
private MyInterface<T> wrapped;
public T getSometing() {
// Do something special, such as:
System.out.println("Calling get something from wrapped object");
return wrapped.getSomething(); // MyWrapperImpl should "use" the type from the wrapped instance.
}
}
Annotated class:
// Attempt 1
#Wrap(classToWrapWith = MyWrapperImpl.class) // <-- Compile error "found class<MyWrapperImpl>, required class<? extends MyInterface<?>>"
// Attempt 2
#Wrap(classToWrapWith = MyWrapperImpl<T>.class) // <-- Compile error, cannot select from parameterized type.
public class MyWrappedClass implements MyInterface<SubObject> {
public SubObject getSomething() {
return new SubObject();
}
}
Wrapper class with a working fix (Where SuperObject is a parent class of SubObject, which is used in MyWrappedClass (see above)):
public class MyWrapperImpl<SuperObject> implements MyInterface<T> {
private MyInterface<SuperObject> wrapped;
public SuperObject getSometing() {
return wrapped.getSomething();
}
}

Related

How to call method from a reference send to annotation in java

I have a Interface I and a Abstract Class A , I have My custom annotation MyAnnotation which should take parameter as subclass S of A, now while processing annotation I want to call method of concrete class S
public interface I{
void m1();
}
public abstract class A implements I {
public abstract void m1();
}
public #interface MyAnnotation {
public Class< ? extends A> ref();
public Class< ? super A> ref2();
}
public S extends A{
public void m1() {}
}
I am annotating method like
#MyAnnotation(ref= new XX() ) or #MyAnnotation(ref= XX.class )
#MyAnnotation(ref= new yy() ) or #MyAnnotation(ref= yy.class )
whichever works
//In spring aspect before processing I am getting method annotation and trying to call m1()
annotation.ref().m1() //Error
annotation.ref2().m1() //Error
You can't use new XX() in an annotation. Annotations parameters can use a very specific set of types:
primitive
String
Class
an Enum
another Annotation
an array of any of the above
See this answer.
So to accomplish what you're trying to accomplish, you'd have to use a class.
You would then have to use reflection to create an instance and invoke the method.
Class<?> clazz = annotation.ref();
I instance = (I) cls.getConstructor().newInstance();
instance.m1();
See this answer.
Your classes must all have no-argument constructors, else you'll only be able to instantiate some this way but not others (leading you to have to conditionally branch based on the class).
You can't do that simply like that. You need an instance of the class first.
If your A class is a Spring's bean, you can inject ApplicationContext and get the bean from there. Then you can call a method.
#Autowired
private ApplicationContext context;
void test(MyAnnotation annotation) {
A bean = context.getBean(annotation.ref());
bean.m1();
}

Generics with annotation annotated by annotation

I am trying to do this, I have some "base" annotation
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.ANNOTATION_TYPE})
public #interface A
{
}
and I have annotaion B which is annotated by A
#A
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.METHOD })
public #interface B {
String value();
}
I want to have interface which behaves something like this, being sure that T is annotation which is annotated by A.
interface SomeInterface<T extends A>
{
void method(T argument);
}
So that I implement that something like this
public class Implementation implements SomeInterface<B>
{
public void method(B argument);
}
How to do that? When I use "T extends A" in SomeInterface, when I implement that, it says that B is not a valid substitue.
Thanks!
B is not a valid substitution for <T extends A> because B does not extend A.
Java does not include a way to require that a generic type parameter has a particular annotation.
If you can refactor SomeInterface to be a class instead of an interface, you could put a runtime check in the constructor:
protected SomeInterface(Class<T> classOfT) {
if(classOfT.getAnnotation(A.class) == null)
throw new RuntimeException("T must be annotated with #A");
}
Annotation inheritance is not possible in Java.
What you have written is simply an annotation annotated by another annotation, not an annotation extending another one.
If annotation inheritance would have been possible, I guess it would have been something like this:
public #interface B extends A {
String value();
}
But it just does not exist.
Check also this link and this link

Use an annotation as a typed method parameter

In Java is it possible to use a class annotation as a typed method parameter.
For example - this is your annotation
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface Entity {
}
then
#Entity
public class Car {
...
}
and then do
interface Persister {
void persist(Entity entity);
}
You can do
public #interface Entity {
String name();
}
public class Car implements Entity{
public String name(){ return "car"; }
}
but that's just odd. Entity should be an ordinary interface instead.
---
It is possible though that through annotation processing, we can require that an argument to a method must have a static type that contains certain annotation. Not sure if someone has done that.
You can do this but it won't do what you expect. This persist(Entity) method can only take your Entity annotation, not an instance of a class you want to use.
Instead what you can do is
interface Entity { }
interface Car extends Entity {
interface Persister {
void persist(Entity entity);
}
This will work as expected and you can pass an instance of a Car to the persist method.

How to get the class of a field of type T?

I write JUnit tests for some Spring MVC Controllers. The initialization of the JUnit test is common for all my Controllers tests, so I wanted to create an abstract class that does this initialization.
Thus, I created the following code:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath*:spring/applicationContext-test.xml", "classpath*:spring/spring-mvc-test.xml" })
#Transactional
public abstract class AbstractSpringMVCControllerTest<T> {
#Autowired
protected ApplicationContext applicationContext;
protected MockHttpServletRequest request;
protected MockHttpServletResponse response;
protected HandlerAdapter handlerAdapter;
protected T controller;
#SuppressWarnings("unchecked")
#Before
public void initContext() throws SecurityException, NoSuchFieldException {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
handlerAdapter = applicationContext.getBean(AnnotationMethodHandlerAdapter.class);
// Does not work, the problem is here...
controller = applicationContext.getBean(T);
}
}
The idea is to create, for each controller I want to test a JUnit test class that extends my AbstractSpringMVCControllerTest. The type given in the extends declaration is the class of the Controller.
For example, if I want to test my AccountController, I will create the AccountControllerTest class like that:
public class AccountControllerTest extends AbstractSpringMVCControllerTest<AccountController> {
#Test
public void list_accounts() throws Exception {
request.setRequestURI("/account/list.html");
ModelAndView mav = handlerAdapter.handle(request, response, controller);
...
}
}
My problem is located in the last line of the initContext() method of the abstract class. This abstract class declares the controller object as a T object, but how can say to the Spring Application Context to return the bean of type T?
I've tried something like that:
Class<?> controllerClass = this.getClass().getSuperclass().getDeclaredField("controller").getType();
controller = (T) applicationContext.getBean(controllerClass);
but controllerClass returns the java.lang.Object.class class, not AccountController.class.
Of course, I can create a public abstract Class<?> getControllerClass(); method, which will be overriden by each JUnit Controller test class, but I prefer to avoid this solution.
So, any idea?
This is possible if your subclasses of AbstractSpringMVCControllerTest bind T at compile time. That is, you have something like
public class DerpControllerTest extends AbstractSpringMVCControllerTest<DerpController> { }
rather than
public class AnyControllerTest<T> extends AbstractSpringMVCControllerTest<T> { }
I'm guessing you probably have the former. In this case, the type of T is erased from the Class object for AbstractSpringMVCControllerTest at runtime, but the Class object for DerpControllerTest does provide a way to know what T is, since it bound T at compile time.
The following classes demonstrate how to access the type of T:
Super.java
import java.lang.reflect.ParameterizedType;
public abstract class Super<T> {
protected T object;
#SuppressWarnings("unchecked")
public Class<T> getObjectType() {
// This only works if the subclass directly subclasses this class
return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
}
Sub.java
public class Sub extends Super<Double> {
}
Test.java
public class Test {
public static void main(String...args) {
Sub s = new Sub();
System.out.println(s.getObjectType()); // prints "Class java.lang.Double"
}
}
This is different from the type erasure we normally see. With type erasure, we don't know the parameter of the current class (the one you get with getClass()), but you can get those in super class / super interface (those you get with getGenericSuperxxxxx()) because this is part of the type declaration.
This won't give your the type of controller field, but I hope this is enough for your purpose.
Code:
public class A<P> {
}
import java.lang.reflect.ParameterizedType;
public class B extends A<String> {
public static void main(String[] arg) {
System.out.println(
((ParameterizedType)B.class.getGenericSuperclass()).getActualTypeArguments()[0]);
}
}
Output:
class java.lang.String
In your case, it would be
Class controllerClass = (Class)( ((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
Something to notes:
If the class B is also parameterized like this:
public class B<X> extends A<X> {}
This won't work. Or if you have another class extends B, it will have problem too. I won't go into all those cases, but you should get the idea.
You can't because at runtime, due to ERASURE, the JVM cannot know the class of your "controller" attribute. It is considered as Object...

Generics and Qualifiers

Hi im trying to implement an EventBuilder based in weld (CDI) Events.
I created the following method to build my event with the chosen qualifier (especified inf the parameter qualifierClass.
#SuppressWarnings("serial")
public static <T extends custom.Event, Q extends Qualifier>
Event<T> buildEvent(Event<T> event, Class<Q> qualifierClass) {
return event.select(new AnnotationLiteral<Q>(){});
}
My qualifier has the following code:
#Qualifier
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface TicketSuccessfulValidation {
}
Then i try to use the methodo like this:
#Inject Event<TicketEvent> event;
private Map<String, User> loggedUsers = new HashMap<String, User>(0);
public User retrieveUserByTicket(String ticket) {
User user = this.loggedUsers.get(ticket);
if (user != null) {
buildEvent(event, TicketSuccessfulValidation.class).fire(new TicketEvent("12345"));
return user;
} else {
throw new TicketNotFoundException();
}
}
My eclipse then gives me the following message:
Bound mismatch: The generic method buildEvent(Event<T>, Class<Q>) of type AbstractEventBuilder is not applicable for the arguments (Event<TicketEvent>, Class<TicketSuccessfulValidation>). The inferred type TicketSuccessfulValidation is not a valid substitute for the bounded parameter <Q extends Qualifier>
If my TicketSuccessfulValidation is annotated with #Qualifier its not right to say that is extends Qualifier? Why TicketSuccessfulValidation is not a valid substitute for "Q extends Qualifier" ?
Thanks in advance for any help.
Q extends Qualifier obviously means that the class you pass has to extend Qualifier. :)
However, TicketSuccessfulValidation doesn't extend Qualifier but is annotated to be one. Annotations are not evaluated by Generics.

Categories

Resources