package org.infinispan.cdi.common.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import javax.enterprise.inject.spi.Annotated;
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;
/**
* <p>
* Utilities for working with {@link Annotated}s.
* </p>
* <p/>
* <p>
* Includes utilities to check the equality of and create unique id's for
* <code>Annotated</code> instances.
* </p>
*
* @author Stuart Douglas <stuart@baileyroberts.com.au>
*/
public class Annotateds {
/**
* Does the first stage of comparing AnnoatedCallables, however it cannot
* compare the method parameters
*/
private static class AnnotatedCallableComparator<T> implements Comparator<AnnotatedCallable<? super T>> {
public int compare(AnnotatedCallable<? super T> arg0, AnnotatedCallable<? super T> arg1) {
// compare the names first
int result = (arg0.getJavaMember().getName().compareTo(arg1.getJavaMember().getName()));
if (result != 0) {
return result;
}
result = arg0.getJavaMember().getDeclaringClass().getName().compareTo(arg1.getJavaMember().getDeclaringClass().getName());
if (result != 0) {
return result;
}
result = arg0.getParameters().size() - arg1.getParameters().size();
return result;
}
}
private static class AnnotatedMethodComparator<T> implements Comparator<AnnotatedMethod<? super T>> {
public static <T> Comparator<AnnotatedMethod<? super T>> instance() {
return new AnnotatedMethodComparator<T>();
}
private AnnotatedCallableComparator<T> callableComparator = new AnnotatedCallableComparator<T>();
public int compare(AnnotatedMethod<? super T> arg0, AnnotatedMethod<? super T> arg1) {
int result = callableComparator.compare(arg0, arg1);
if (result != 0) {
return result;
}
for (int i = 0; i < arg0.getJavaMember().getParameterTypes().length; ++i) {
Class<?> p0 = arg0.getJavaMember().getParameterTypes()[i];
Class<?> p1 = arg1.getJavaMember().getParameterTypes()[i];
result = p0.getName().compareTo(p1.getName());
if (result != 0) {
return result;
}
}
return 0;
}
}
private static class AnnotatedConstructorComparator<T> implements Comparator<AnnotatedConstructor<? super T>> {
public static <T> Comparator<AnnotatedConstructor<? super T>> instance() {
return new AnnotatedConstructorComparator<T>();
}
private AnnotatedCallableComparator<T> callableComparator = new AnnotatedCallableComparator<T>();
public int compare(AnnotatedConstructor<? super T> arg0, AnnotatedConstructor<? super T> arg1) {
int result = callableComparator.compare(arg0, arg1);
if (result != 0) {
return result;
}
for (int i = 0; i < arg0.getJavaMember().getParameterTypes().length; ++i) {
Class<?> p0 = arg0.getJavaMember().getParameterTypes()[i];
Class<?> p1 = arg1.getJavaMember().getParameterTypes()[i];
result = p0.getName().compareTo(p1.getName());
if (result != 0) {
return result;
}
}
return 0;
}
}
private static class AnnotatedFieldComparator<T> implements Comparator<AnnotatedField<? super T>> {
public static <T> Comparator<AnnotatedField<? super T>> instance() {
return new AnnotatedFieldComparator<T>();
}
public int compare(AnnotatedField<? super T> arg0, AnnotatedField<? super T> arg1) {
if (arg0.getJavaMember().getName().equals(arg1.getJavaMember().getName())) {
return arg0.getJavaMember().getDeclaringClass().getName().compareTo(arg1.getJavaMember().getDeclaringClass().getName());
}
return arg0.getJavaMember().getName().compareTo(arg1.getJavaMember().getName());
}
}
private static class AnnotationComparator implements Comparator<Annotation> {
public static final Comparator<Annotation> INSTANCE = new AnnotationComparator();
public int compare(Annotation arg0, Annotation arg1) {
return arg0.annotationType().getName().compareTo(arg1.annotationType().getName());
}
}
private static class MethodComparator implements Comparator<Method> {
public static final Comparator<Method> INSTANCE = new MethodComparator();
public int compare(Method arg0, Method arg1) {
return arg0.getName().compareTo(arg1.getName());
}
}
private static final char SEPERATOR = ';';
private Annotateds() {
}
/**
* Generates a deterministic signature for an {@link AnnotatedType}. Two
* <code>AnnotatedType</code>s that have the same annotations and underlying
* type will generate the same signature.
* <p/>
* This can be used to create a unique bean id for a passivation capable bean
* that is added directly through the SPI.
*
* @param annotatedType The type to generate a signature for
* @return A string representation of the annotated type
*/
public static <X> String createTypeId(AnnotatedType<X> annotatedType) {
return createTypeId(annotatedType.getJavaClass(), annotatedType.getAnnotations(), annotatedType.getMethods(), annotatedType.getFields(), annotatedType.getConstructors());
}
/**
* Generates a unique signature for a concrete class. Annotations are not
* read directly from the class, but are read from the
* <code>annotations</code>, <code>methods</code>, <code>fields</code> and
* <code>constructors</code> arguments
*
* @param clazz The java class tyoe
* @param annotations Annotations present on the java class
* @param methods The AnnotatedMethods to include in the signature
* @param fields The AnnotatedFields to include in the signature
* @param constructors The AnnotatedConstructors to include in the signature
* @return A string representation of the type
*/
public static <X> String createTypeId(Class<X> clazz, Collection<Annotation> annotations, Collection<AnnotatedMethod<? super X>> methods, Collection<AnnotatedField<? super X>> fields, Collection<AnnotatedConstructor<X>> constructors) {
StringBuilder builder = new StringBuilder();
builder.append(clazz.getName());
builder.append(createAnnotationCollectionId(annotations));
builder.append("{");
// now deal with the fields
List<AnnotatedField<? super X>> sortedFields = new ArrayList<AnnotatedField<? super X>>();
sortedFields.addAll(fields);
Collections.sort(sortedFields, AnnotatedFieldComparator.<X>instance());
for (AnnotatedField<? super X> field : sortedFields) {
if (!field.getAnnotations().isEmpty()) {
builder.append(createFieldId(field));
builder.append(SEPERATOR);
}
}
// methods
List<AnnotatedMethod<? super X>> sortedMethods = new ArrayList<AnnotatedMethod<? super X>>();
sortedMethods.addAll(methods);
Collections.sort(sortedMethods, AnnotatedMethodComparator.<X>instance());
for (AnnotatedMethod<? super X> method : sortedMethods) {
if (!method.getAnnotations().isEmpty() || hasMethodParameters(method)) {
builder.append(createCallableId(method));
builder.append(SEPERATOR);
}
}
// constructors
List<AnnotatedConstructor<? super X>> sortedConstructors = new ArrayList<AnnotatedConstructor<? super X>>();
sortedConstructors.addAll(constructors);
Collections.sort(sortedConstructors, AnnotatedConstructorComparator.<X>instance());
for (AnnotatedConstructor<? super X> constructor : sortedConstructors) {
if (!constructor.getAnnotations().isEmpty() || hasMethodParameters(constructor)) {
builder.append(createCallableId(constructor));
builder.append(SEPERATOR);
}
}
builder.append("}");
return builder.toString();
}
/**
* Generates a deterministic signature for an {@link AnnotatedField}. Two
* <code>AnnotatedField</code>s that have the same annotations and
* underlying field will generate the same signature.
*/
public static <X> String createFieldId(AnnotatedField<X> field) {
return createFieldId(field.getJavaMember(), field.getAnnotations());
}
/**
* Creates a deterministic signature for a {@link Field}.
*
* @param field The field to generate the signature for
* @param annotations The annotations to include in the signature
*/
public static <X> String createFieldId(Field field, Collection<Annotation> annotations) {
StringBuilder builder = new StringBuilder();
builder.append(field.getDeclaringClass().getName());
builder.append('.');
builder.append(field.getName());
builder.append(createAnnotationCollectionId(annotations));
return builder.toString();
}
/**
* Generates a deterministic signature for an {@link AnnotatedCallable}. Two
* <code>AnnotatedCallable</code>s that have the same annotations and
* underlying callable will generate the same signature.
*/
public static <X> String createCallableId(AnnotatedCallable<X> method) {
StringBuilder builder = new StringBuilder();
builder.append(method.getJavaMember().getDeclaringClass().getName());
builder.append('.');
builder.append(method.getJavaMember().getName());
builder.append(createAnnotationCollectionId(method.getAnnotations()));
builder.append(createParameterListId(method.getParameters()));
return builder.toString();
}
/**
* Generates a unique string representation of a list of
* {@link AnnotatedParameter}s.
*/
public static <X> String createParameterListId(List<AnnotatedParameter<X>> parameters) {
StringBuilder builder = new StringBuilder();
builder.append("(");
for (int i = 0; i < parameters.size(); ++i) {
AnnotatedParameter<X> ap = parameters.get(i);
builder.append(createParameterId(ap));
if (i + 1 != parameters.size()) {
builder.append(',');
}
}
builder.append(")");
return builder.toString();
}
/**
* Creates a string representation of an {@link AnnotatedParameter}.
*/
public static <X> String createParameterId(AnnotatedParameter<X> annotatedParameter) {
return createParameterId(annotatedParameter.getBaseType(), annotatedParameter.getAnnotations());
}
/**
* Creates a string representation of a given type and set of annotations.
*/
public static <X> String createParameterId(Type type, Set<Annotation> annotations) {
StringBuilder builder = new StringBuilder();
if (type instanceof Class<?>) {
Class<?> c = (Class<?>) type;
builder.append(c.getName());
} else {
builder.append(type.toString());
}
builder.append(createAnnotationCollectionId(annotations));
return builder.toString();
}
private static <X> boolean hasMethodParameters(AnnotatedCallable<X> callable) {
for (AnnotatedParameter<X> parameter : callable.getParameters()) {
if (!parameter.getAnnotations().isEmpty()) {
return true;
}
}
return false;
}
private static String createAnnotationCollectionId(Collection<Annotation> annotations) {
if (annotations.isEmpty()) {
return "";
}
StringBuilder builder = new StringBuilder();
builder.append('[');
List<Annotation> annotationList = new ArrayList<Annotation>(annotations.size());
annotationList.addAll(annotations);
Collections.sort(annotationList, AnnotationComparator.INSTANCE);
for (Annotation a : annotationList) {
builder.append('@');
builder.append(a.annotationType().getName());
builder.append('(');
Method[] declaredMethods = a.annotationType().getDeclaredMethods();
List<Method> methods = new ArrayList<Method>(declaredMethods.length);
for (Method m : declaredMethods) {
methods.add(m);
}
Collections.sort(methods, MethodComparator.INSTANCE);
for (int i = 0; i < methods.size(); ++i) {
Method method = methods.get(i);
try {
Object value = method.invoke(a);
builder.append(method.getName());
builder.append('=');
builder.append(value.toString());
} catch (NullPointerException e) {
throw new RuntimeException("NullPointerException accessing annotation member, annotation:" + a.annotationType().getName() + " member: " + method.getName(), e);
} catch (IllegalArgumentException e) {
throw new RuntimeException("IllegalArgumentException accessing annotation member, annotation:" + a.annotationType().getName() + " member: " + method.getName(), e);
} catch (IllegalAccessException e) {
throw new RuntimeException("IllegalAccessException accessing annotation member, annotation:" + a.annotationType().getName() + " member: " + method.getName(), e);
} catch (InvocationTargetException e) {
throw new RuntimeException("InvocationTargetException accessing annotation member, annotation:" + a.annotationType().getName() + " member: " + method.getName(), e);
}
if (i + 1 != methods.size()) {
builder.append(',');
}
}
builder.append(')');
}
builder.append(']');
return builder.toString();
}
}