Add support for meta annotations

This commit is contained in:
Arne Vandamme 2014-07-05 11:11:49 +02:00
parent 9a81992a0a
commit 1834145fe7
10 changed files with 273 additions and 95 deletions

View File

@ -1,100 +1,138 @@
package net.engio.mbassy.common;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.*;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* @author bennidi
* Date: 2/16/12
* Time: 12:14 PM
*/
public class ReflectionUtils {
public class ReflectionUtils
{
public static List<Method> getMethods(IPredicate<Method> condition, Class<?> target) {
List<Method> methods = new LinkedList<Method>();
try {
for (Method method : target.getDeclaredMethods()) {
if (condition.apply(method)) {
methods.add(method);
}
}
} catch (Exception e) {
//nop
}
if (!target.equals(Object.class)) {
methods.addAll(getMethods(condition, target.getSuperclass()));
}
return methods;
}
public static List<Method> getMethods( IPredicate<Method> condition, Class<?> target ) {
List<Method> methods = new LinkedList<Method>();
try {
for ( Method method : target.getDeclaredMethods() ) {
if ( condition.apply( method ) ) {
methods.add( method );
}
}
}
catch ( Exception e ) {
//nop
}
if ( !target.equals( Object.class ) ) {
methods.addAll( getMethods( condition, target.getSuperclass() ) );
}
return methods;
}
/**
* Traverses the class hierarchy upwards, starting at the given subclass, looking
* for an override of the given methods -> finds the bottom most override of the given
* method if any exists
*
* @param overridingMethod
* @param subclass
* @return
*/
public static Method getOverridingMethod(final Method overridingMethod, final Class subclass) {
Class current = subclass;
while (!current.equals(overridingMethod.getDeclaringClass())) {
try {
return current.getDeclaredMethod(overridingMethod.getName(), overridingMethod.getParameterTypes());
} catch (NoSuchMethodException e) {
current = current.getSuperclass();
}
}
return null;
}
/**
* Traverses the class hierarchy upwards, starting at the given subclass, looking
* for an override of the given methods -> finds the bottom most override of the given
* method if any exists
*
* @param overridingMethod
* @param subclass
* @return
*/
public static Method getOverridingMethod( final Method overridingMethod, final Class subclass ) {
Class current = subclass;
while ( !current.equals( overridingMethod.getDeclaringClass() ) ) {
try {
return current.getDeclaredMethod( overridingMethod.getName(), overridingMethod.getParameterTypes() );
}
catch ( NoSuchMethodException e ) {
current = current.getSuperclass();
}
}
return null;
}
public static Set<Class> getSuperclasses(Class from) {
Set<Class> superclasses = new HashSet<Class>();
collectInterfaces(from, superclasses);
while (!from.equals(Object.class) && !from.isInterface()) {
superclasses.add(from.getSuperclass());
from = from.getSuperclass();
collectInterfaces(from, superclasses);
}
return superclasses;
}
public static Set<Class> getSuperclasses( Class from ) {
Set<Class> superclasses = new HashSet<Class>();
collectInterfaces( from, superclasses );
while ( !from.equals( Object.class ) && !from.isInterface() ) {
superclasses.add( from.getSuperclass() );
from = from.getSuperclass();
collectInterfaces( from, superclasses );
}
return superclasses;
}
public static void collectInterfaces(Class from, Set<Class> accumulator){
for(Class intface : from.getInterfaces()){
accumulator.add(intface);
collectInterfaces(intface, accumulator);
}
}
public static void collectInterfaces( Class from, Set<Class> accumulator ) {
for ( Class intface : from.getInterfaces() ) {
accumulator.add( intface );
collectInterfaces( intface, accumulator );
}
}
public static boolean containsOverridingMethod(final List<Method> allMethods, final Method methodToCheck) {
for (Method method : allMethods) {
if (isOverriddenBy(methodToCheck, method)) {
return true;
}
}
return false;
}
public static boolean containsOverridingMethod( final List<Method> allMethods, final Method methodToCheck ) {
for ( Method method : allMethods ) {
if ( isOverriddenBy( methodToCheck, method ) ) {
return true;
}
}
return false;
}
private static boolean isOverriddenBy(Method superclassMethod, Method subclassMethod) {
// if the declaring classes are the same or the subclass method is not defined in the subclass
// hierarchy of the given superclass method or the method names are not the same then
// subclassMethod does not override superclassMethod
if (superclassMethod.getDeclaringClass().equals(subclassMethod.getDeclaringClass())
|| !superclassMethod.getDeclaringClass().isAssignableFrom(subclassMethod.getDeclaringClass())
|| !superclassMethod.getName().equals(subclassMethod.getName())) {
return false;
}
public static <A extends Annotation> A getAnnotation( Method method, Class<A> annotationType ) {
return getAnnotation( (AnnotatedElement) method, annotationType );
}
Class[] superClassMethodParameters = superclassMethod.getParameterTypes();
Class[] subClassMethodParameters = subclassMethod.getParameterTypes();
// method must specify the same number of parameters
//the parameters must occur in the exact same order
for (int i = 0; i < subClassMethodParameters.length; i++) {
if (!superClassMethodParameters[i].equals(subClassMethodParameters[i])) {
return false;
}
}
return true;
}
public static <A extends Annotation> A getAnnotation( Class from, Class<A> annotationType ) {
return getAnnotation( (AnnotatedElement) from, annotationType );
}
/**
* Searches for an Annotation of the given type on the class. Supports meta annotations.
*
* @param from AnnotatedElement (class, method...)
* @param annotationType Annotation class to look for.
* @param <A> Annotation class
* @return Annotation instance or null
*/
public static <A extends Annotation> A getAnnotation( AnnotatedElement from, Class<A> annotationType ) {
A ann = from.getAnnotation( annotationType );
if ( ann == null ) {
for ( Annotation metaAnn : from.getAnnotations() ) {
ann = metaAnn.annotationType().getAnnotation( annotationType );
if ( ann != null ) {
break;
}
}
}
return ann;
}
private static boolean isOverriddenBy( Method superclassMethod, Method subclassMethod ) {
// if the declaring classes are the same or the subclass method is not defined in the subclass
// hierarchy of the given superclass method or the method names are not the same then
// subclassMethod does not override superclassMethod
if ( superclassMethod.getDeclaringClass().equals(
subclassMethod.getDeclaringClass() ) || !superclassMethod.getDeclaringClass().isAssignableFrom(
subclassMethod.getDeclaringClass() ) || !superclassMethod.getName().equals(
subclassMethod.getName() ) ) {
return false;
}
Class[] superClassMethodParameters = superclassMethod.getParameterTypes();
Class[] subClassMethodParameters = subclassMethod.getParameterTypes();
// method must specify the same number of parameters
//the parameters must occur in the exact same order
for ( int i = 0; i < subClassMethodParameters.length; i++ ) {
if ( !superClassMethodParameters[i].equals( subClassMethodParameters[i] ) ) {
return false;
}
}
return true;
}
}

View File

@ -15,7 +15,7 @@ import java.lang.annotation.Target;
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Inherited
@Target(value = {ElementType.METHOD})
@Target(value = {ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface Enveloped {
/**

View File

@ -14,7 +14,7 @@ import java.lang.annotation.*;
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Inherited
@Target(value = {ElementType.METHOD})
@Target(value = {ElementType.METHOD,ElementType.ANNOTATION_TYPE})
public @interface Handler {
/**

View File

@ -13,7 +13,7 @@ import java.lang.annotation.*;
* @author bennidi
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Target(value = {ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Inherited
public @interface Listener {

View File

@ -1,5 +1,6 @@
package net.engio.mbassy.listener;
import net.engio.mbassy.common.ReflectionUtils;
import net.engio.mbassy.dispatch.HandlerInvocation;
import net.engio.mbassy.dispatch.el.ElFilter;
@ -48,7 +49,7 @@ public class MessageHandler {
if(filter == null){
filter = new IMessageFilter[]{};
}
net.engio.mbassy.listener.Enveloped enveloped = handler.getAnnotation(Enveloped.class);
net.engio.mbassy.listener.Enveloped enveloped = ReflectionUtils.getAnnotation( handler, Enveloped.class );
Class[] handledMessages = enveloped != null
? enveloped.messages()
: handler.getParameterTypes();
@ -76,7 +77,7 @@ public class MessageHandler {
properties.put(Enveloped, enveloped != null);
properties.put(AcceptSubtypes, !handlerConfig.rejectSubtypes());
properties.put(Listener, listenerConfig);
properties.put(IsSynchronized, handler.getAnnotation(Synchronized.class) != null);
properties.put(IsSynchronized, ReflectionUtils.getAnnotation( handler, Synchronized.class) != null);
properties.put(HandledMessages, handledMessages);
return properties;
}

View File

@ -1,6 +1,7 @@
package net.engio.mbassy.listener;
import net.engio.mbassy.common.IPredicate;
import net.engio.mbassy.common.ReflectionUtils;
import java.util.ArrayList;
import java.util.Collection;
@ -42,7 +43,7 @@ public class MessageListener<T> {
public MessageListener(Class<T> listenerDefinition) {
this.listenerDefinition = listenerDefinition;
listenerAnnotation = listenerDefinition.getAnnotation(Listener.class);
listenerAnnotation = ReflectionUtils.getAnnotation( listenerDefinition, Listener.class );
}

View File

@ -22,7 +22,7 @@ public class MetadataReader {
private static final IPredicate<Method> AllMessageHandlers = new IPredicate<Method>() {
@Override
public boolean apply(Method target) {
return target.getAnnotation(Handler.class) != null;
return ReflectionUtils.getAnnotation(target, Handler.class) != null;
}
};
@ -52,7 +52,6 @@ public class MetadataReader {
return filters;
}
// get all listeners defined by the given class (includes
// listeners defined in super classes)
public MessageListener getMessageListener(Class target) {
@ -70,7 +69,7 @@ public class MetadataReader {
// for each handler there will be no overriding method that specifies @Handler annotation
// but an overriding method does inherit the listener configuration of the overwritten method
for (Method handler : bottomMostHandlers) {
Handler handlerConfig = handler.getAnnotation(Handler.class);
Handler handlerConfig = ReflectionUtils.getAnnotation( handler, Handler.class);
if (!handlerConfig.enabled() || !isValidMessageHandler(handler)) {
continue; // disabled or invalid listeners are ignored
}
@ -89,7 +88,7 @@ public class MetadataReader {
private boolean isValidMessageHandler(Method handler) {
if (handler == null || handler.getAnnotation(Handler.class) == null) {
if (handler == null || ReflectionUtils.getAnnotation( handler, Handler.class) == null) {
return false;
}
if (handler.getParameterTypes().length != 1) {
@ -98,7 +97,7 @@ public class MetadataReader {
+ "]. A messageHandler must define exactly one parameter");
return false;
}
Enveloped envelope = handler.getAnnotation(Enveloped.class);
Enveloped envelope = ReflectionUtils.getAnnotation( handler, Enveloped.class);
if (envelope != null && !MessageEnvelope.class.isAssignableFrom(handler.getParameterTypes()[0])) {
System.out.println("Message envelope configured but no subclass of MessageEnvelope found as parameter");
return false;

View File

@ -18,6 +18,6 @@ import java.lang.annotation.*;
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Inherited
@Target(value = {ElementType.METHOD})
@Target(value = {ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface Synchronized {
}

View File

@ -163,7 +163,7 @@ public class SubscriptionManager {
readWriteLock.readLock().lock();
if (subscriptionsPerMessage.get(messageType) != null) {
subscriptions.addAll(subscriptionsPerMessage.get(messageType));
subscriptions.addAll(subscriptionsPerMessage.get(messageType));
}
for (Class eventSuperType : ReflectionUtils.getSuperclasses(messageType)) {
Collection<Subscription> subs = subscriptionsPerMessage.get(eventSuperType);

View File

@ -0,0 +1,139 @@
package net.engio.mbassy;
import net.engio.mbassy.bus.MBassador;
import net.engio.mbassy.bus.config.BusConfiguration;
import net.engio.mbassy.common.MessageBusTest;
import net.engio.mbassy.common.ReflectionUtils;
import net.engio.mbassy.listener.*;
import net.engio.mbassy.subscription.MessageEnvelope;
import org.junit.Test;
import java.lang.annotation.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Tests a custom handler annotation with a @Handler meta annotation and a default filter.
*/
public class CustomHandlerAnnotationTest extends MessageBusTest
{
/**
* Handler annotation that adds a default filter on the NamedMessage.
* Enveloped is in no way required, but simply added to test a meta enveloped annotation.
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Inherited
@Handler(filters = { @Filter(NamedMessageFilter.class) })
@Synchronized
@Target(value = { ElementType.METHOD, ElementType.ANNOTATION_TYPE })
static @interface NamedMessageHandler
{
/**
* @return The message names supported.
*/
String[] value();
}
/**
* Test enveloped meta annotation.
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Inherited
@Handler(filters = { @Filter(NamedMessageFilter.class) })
@Enveloped(messages = NamedMessage.class)
static @interface EnvelopedNamedMessageHandler
{
/**
* @return The message names supported.
*/
String[] value();
}
/**
* Searches for a NamedMessageHandler annotation on the handler method.
* The annotation specifies the supported message names.
*/
public static class NamedMessageFilter implements IMessageFilter<NamedMessage>
{
@Override
public boolean accepts( NamedMessage message, MessageHandler metadata ) {
NamedMessageHandler namedMessageHandler =
ReflectionUtils.getAnnotation( metadata.getHandler(), NamedMessageHandler.class );
if ( namedMessageHandler != null ) {
return Arrays.asList( namedMessageHandler.value() ).contains( message.getName() );
}
EnvelopedNamedMessageHandler envelopedHandler =
ReflectionUtils.getAnnotation( metadata.getHandler(), EnvelopedNamedMessageHandler.class );
return envelopedHandler != null && Arrays.asList( envelopedHandler.value() ).contains( message.getName() );
}
}
static class NamedMessage
{
private String name;
NamedMessage( String name ) {
this.name = name;
}
public String getName() {
return name;
}
}
static class NamedMessageListener
{
final Set<NamedMessage> handledByOne = new HashSet<NamedMessage>();
final Set<NamedMessage> handledByTwo = new HashSet<NamedMessage>();
final Set<NamedMessage> handledByThree = new HashSet<NamedMessage>();
@NamedMessageHandler({ "messageOne", "messageTwo" })
void handlerOne( NamedMessage message ) {
handledByOne.add( message );
}
@EnvelopedNamedMessageHandler({ "messageTwo", "messageThree" })
void handlerTwo( MessageEnvelope envelope ) {
handledByTwo.add( (NamedMessage) envelope.getMessage() );
}
@NamedMessageHandler("messageThree")
void handlerThree( NamedMessage message ) {
handledByThree.add( message );
}
}
@Test
public void testMetaHandlerFiltering() {
MBassador bus = getBus( BusConfiguration.Default() );
NamedMessageListener listener = new NamedMessageListener();
bus.subscribe( listener );
NamedMessage messageOne = new NamedMessage( "messageOne" );
NamedMessage messageTwo = new NamedMessage( "messageTwo" );
NamedMessage messageThree = new NamedMessage( "messageThree" );
bus.publish( messageOne );
bus.publish( messageTwo );
bus.publish( messageThree );
assertTrue( listener.handledByOne.contains( messageOne ) );
assertTrue( listener.handledByOne.contains( messageTwo ) );
assertFalse( listener.handledByOne.contains( messageThree ) );
assertFalse( listener.handledByTwo.contains( messageOne ) );
assertTrue( listener.handledByTwo.contains( messageTwo ) );
assertTrue( listener.handledByTwo.contains( messageThree ) );
assertFalse( listener.handledByThree.contains( messageOne ) );
assertFalse( listener.handledByThree.contains( messageTwo ) );
assertTrue( listener.handledByThree.contains( messageThree ) );
}
}