/*
* JBoss, by Red Hat.
* Copyright 2010, Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.forge.bus.cdi;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AnnotatedCallable;
import javax.enterprise.inject.spi.AnnotatedConstructor;
import javax.enterprise.inject.spi.AnnotatedField;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedParameter;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.inject.Singleton;
import org.jboss.forge.bus.event.BusEvent;
import org.jboss.forge.bus.util.Annotations;
/**
* @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
*/
@Singleton
@SuppressWarnings({ "rawtypes", "unchecked" })
public class ObserverCaptureExtension implements Extension
{
private final Map<Class<?>, List<BusManaged>> eventQualifierMap = new HashMap<Class<?>, List<BusManaged>>();
private int rollingIdentifier = 0;
public void scan(@Observes final ProcessAnnotatedType<Object> event)
{
AnnotatedType<Object> originalType = event.getAnnotatedType();
AnnotatedType<Object> newType;
List<AnnotatedMethod> obsoleteMethods = new ArrayList<AnnotatedMethod>();
List<AnnotatedMethod> replacementMethods = new ArrayList<AnnotatedMethod>();
for (AnnotatedMethod<?> method : getOrderedMethods(originalType))
{
for (AnnotatedParameter<?> param : method.getParameters())
{
if (param.isAnnotationPresent(Observes.class))
{
Set<Type> typeClosure = param.getTypeClosure();
for (Type type : typeClosure) {
if (type instanceof Class)
{
if (Annotations.isAnnotationPresent((Class<?>) type, BusEvent.class))
{
replacementMethods.add(qualifyObservedEvent(method, param));
obsoleteMethods.add(method);
break;
}
}
else if (type instanceof ParameterizedType)
{
Type rawType = ((ParameterizedType) type).getRawType();
if (rawType instanceof Class)
{
if (Annotations.isAnnotationPresent((Class<?>) rawType, (BusEvent.class)))
{
replacementMethods.add(qualifyObservedEvent(method, param));
obsoleteMethods.add(method);
break;
}
}
}
}
}
}
}
newType = removeMethodsFromType(originalType, obsoleteMethods);
newType = addReplacementMethodsToType(newType, replacementMethods);
event.setAnnotatedType(newType);
}
private List<AnnotatedMethod<? super Object>> getOrderedMethods(final AnnotatedType<Object> originalType)
{
Set<AnnotatedMethod<? super Object>> methods = originalType.getMethods();
List<AnnotatedMethod<? super Object>> result = new ArrayList<AnnotatedMethod<? super Object>>();
result.addAll(methods);
Collections.sort(result, new Comparator<Object>()
{
@Override
public int compare(final Object left, final Object right)
{
String lid = ((AnnotatedMethod<? super Object>) left).getJavaMember().toGenericString();
String rid = ((AnnotatedMethod<? super Object>) right).getJavaMember().toGenericString();
return lid.compareTo(rid);
}
});
return result;
}
private AnnotatedType<Object> removeMethodsFromType(final AnnotatedType type,
final List<AnnotatedMethod> targetedMethods)
{
final Set<AnnotatedMethod> methods = new HashSet<AnnotatedMethod>();
methods.addAll(type.getMethods());
methods.removeAll(targetedMethods);
return new AnnotatedType()
{
@Override
public Class getJavaClass()
{
return type.getJavaClass();
}
@Override
public Set<AnnotatedConstructor> getConstructors()
{
return type.getConstructors();
}
@Override
public Set<AnnotatedMethod> getMethods()
{
return methods;
}
@Override
public Set<AnnotatedField> getFields()
{
return type.getFields();
}
@Override
public Type getBaseType()
{
return type.getBaseType();
}
@Override
public Set<Type> getTypeClosure()
{
return type.getTypeClosure();
}
@Override
public <T extends Annotation> T getAnnotation(final Class<T> annotationType)
{
return type.getAnnotation(annotationType);
}
@Override
public Set<Annotation> getAnnotations()
{
return type.getAnnotations();
}
@Override
public boolean isAnnotationPresent(final Class<? extends Annotation> annotationType)
{
return type.isAnnotationPresent(annotationType);
}
};
}
private AnnotatedType<Object> addReplacementMethodsToType(final AnnotatedType newType,
final List<AnnotatedMethod> replacementMethods)
{
newType.getMethods().addAll(replacementMethods);
return newType;
}
private AnnotatedMethod<Object> qualifyObservedEvent(
final AnnotatedMethod method, final AnnotatedParameter param)
{
final List<AnnotatedParameter> parameters = new ArrayList<AnnotatedParameter>();
parameters.addAll(method.getParameters());
parameters.set(parameters.indexOf(param), addUniqueQualifier(method, param));
parameters.remove(param);
return new AnnotatedMethod()
{
@Override
public List<AnnotatedParameter> getParameters()
{
return parameters;
}
@Override
public AnnotatedType<Object> getDeclaringType()
{
return method.getDeclaringType();
}
@Override
public boolean isStatic()
{
return method.isStatic();
}
@Override
public <T extends Annotation> T getAnnotation(final Class<T> annotation)
{
return method.getAnnotation(annotation);
}
@Override
public Set<Annotation> getAnnotations()
{
return method.getAnnotations();
}
@Override
public Type getBaseType()
{
return method.getBaseType();
}
@Override
public Set<Type> getTypeClosure()
{
return method.getTypeClosure();
}
@Override
public boolean isAnnotationPresent(final Class<? extends Annotation> annotation)
{
return method.isAnnotationPresent(annotation);
}
@Override
public Method getJavaMember()
{
return method.getJavaMember();
}
};
}
private AnnotatedParameter addUniqueQualifier(final AnnotatedMethod method,
final AnnotatedParameter param)
{
final String identifier = String.valueOf(rollingIdentifier++);
final String methodName = method.getJavaMember().getName();
final BusManaged qualifier = new BusManaged()
{
@Override
public Class<? extends Annotation> annotationType()
{
return BusManaged.class;
}
@Override
public String value()
{
return identifier;
}
@Override
public String method()
{
return methodName;
}
};
addQualifierToMap(method, param, qualifier);
final Set<Annotation> annotations = new HashSet<Annotation>();
annotations.addAll(param.getAnnotations());
annotations.add(qualifier);
return new AnnotatedParameter<Object>()
{
@Override
public <T extends Annotation> T getAnnotation(final Class<T> clazz)
{
if (BusManaged.class.isAssignableFrom(clazz))
{
return (T) qualifier;
}
return param.getAnnotation(clazz);
}
@Override
public Set<Annotation> getAnnotations()
{
return annotations;
}
@Override
public Type getBaseType()
{
return param.getBaseType();
}
@Override
public Set<Type> getTypeClosure()
{
return param.getTypeClosure();
}
@Override
public boolean isAnnotationPresent(final Class<? extends Annotation> clazz)
{
if (BusManaged.class.isAssignableFrom(clazz))
{
return true;
}
return param.isAnnotationPresent(clazz);
}
@Override
public AnnotatedCallable<Object> getDeclaringCallable()
{
return param.getDeclaringCallable();
}
@Override
public int getPosition()
{
return param.getPosition();
}
};
}
private void addQualifierToMap(final AnnotatedMethod annotatedMethod,
final AnnotatedParameter param, final BusManaged qualifier)
{
Method method = annotatedMethod.getJavaMember();
Class<?> clazz = method.getParameterTypes()[param.getPosition()];
List<BusManaged> qualifiers = eventQualifierMap.get(clazz);
if (qualifiers == null)
{
qualifiers = new ArrayList<BusManaged>();
}
qualifiers.add(qualifier);
eventQualifierMap.put(clazz, qualifiers);
}
/**
* Return a list containing instances of {@link BusManaged} annotations corresponding to the given event type.
*/
public List<BusManaged> getEventQualifiers(final Class<?> clazz)
{
List<BusManaged> result = new ArrayList<BusManaged>();
for (Entry<Class<?>, List<BusManaged>> entry : eventQualifierMap.entrySet())
{
Class<?> key = entry.getKey();
List<BusManaged> value = entry.getValue();
if (key.isAssignableFrom(clazz))
{
result.addAll(value);
}
}
return result;
}
/**
* Return the entire map of Event Types and their corresponding lists of {@link BusManaged} annotation instances.
* This map can be used to implement a strategy for custom firing of events.
*/
public Map<Class<?>, List<BusManaged>> getEventQualifierMap()
{
return eventQualifierMap;
}
}