import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
+import javassist.NotFoundException;
import java.io.IOException;
import java.util.HashMap;
*
* @author <a href="mailto:bill@jboss.org">Bill Burke</a>
* @author Shigeru Chiba
+ * @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
*/
public class Annotation {
static class Pair {
*
* @param cp the constant pool table.
* @param clazz the interface.
+ * @throws NotFoundException when the clazz is not found
*/
public Annotation(ConstPool cp, CtClass clazz)
- throws javassist.NotFoundException
+ throws NotFoundException
{
// todo Enums are not supported right now.
this(cp.addUtf8Info(Descriptor.of(clazz.getName())), cp);
"Only interfaces are allowed for Annotation creation.");
CtMethod methods[] = clazz.getDeclaredMethods();
- if (methods.length > 0)
+ if (methods.length > 0) {
members = new HashMap();
+ }
for (int i = 0; i < methods.length; i++) {
CtClass returnType = methods[i].getReturnType();
addMemberValue(methods[i].getName(),
createMemberValue(cp, returnType));
+
}
}
*
* @param cp the constant pool table.
* @param type the type of the member.
+ * @return the member value
+ * @throws NotFoundException when the type is not found
*/
public static MemberValue createMemberValue(ConstPool cp, CtClass type)
- throws javassist.NotFoundException
+ throws NotFoundException
{
if (type == CtClass.booleanType)
return new BooleanMemberValue(cp);
members.put(name, pair);
}
- /**
- * Returns a string representation of this object.
- */
public String toString() {
StringBuffer buf = new StringBuffer("@");
buf.append(getTypeName());
/**
* Obtains the name of the annotation type.
+ *
+ * @return the type name
*/
public String getTypeName() {
return Descriptor.toClassName(pool.getUtf8Info(typeIndex));
* <code>MemberValue</code> with the default value.
* The default value can be obtained from the annotation type.
*
+ * @param name the member name
* @return null if the member cannot be found or if the value is
* the default value.
*
*
* @param cl class loader for loading an annotation type.
* @param cp class pool for obtaining class files.
+ * @return the annotation
+ * @throws ClassNotFoundException when the class cannot found
*/
public Object toAnnotationType(ClassLoader cl, ClassPool cp)
throws ClassNotFoundException
* Writes this annotation.
*
* @param writer the output.
+ * @throws IOException for an error during the write
*/
public void write(AnnotationsWriter writer) throws IOException {
String typeName = pool.getUtf8Info(typeIndex);
pair.value.write(writer);
}
}
+
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+ if (obj == null || obj instanceof Annotation == false)
+ return false;
+
+ Annotation other = (Annotation) obj;
+
+ if (getTypeName().equals(other.getTypeName()) == false)
+ return false;
+
+ if (members == null && other.members != null)
+ return false;
+ if (members != null && other.members == null)
+ return false;
+ return members.equals(other.members);
+ }
}
package javassist.bytecode.annotation;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import javassist.bytecode.ClassFile;
import javassist.bytecode.MethodInfo;
-import java.lang.reflect.*;
-
/**
* Internal-use only. This is a helper class internally used for implementing
- * <code>toAnnotationType()</code> in <code>Annotation</code>.
+ * <code>toAnnotationType()</code> in <code>Annotation</code>.
+ *
+ * @author Shigeru Chiba
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
*/
public class AnnotationImpl implements InvocationHandler {
+ private static final String JDK_ANNOTATION_CLASS_NAME = "java.lang.annotation.Annotation";
+ private static Method JDK_ANNOTATION_TYPE_METHOD = null;
+
private Annotation annotation;
private ClassPool pool;
private ClassLoader classLoader;
+ private transient Class annotationType;
+ private transient int cachedHashCode = Integer.MIN_VALUE;
+ static {
+ // Try to resolve the JDK annotation type method
+ try {
+ Class clazz = Class.forName(JDK_ANNOTATION_CLASS_NAME);
+ JDK_ANNOTATION_TYPE_METHOD = clazz.getMethod("annotationType", null);
+ }
+ catch (Exception ignored) {
+ // Probably not JDK5+
+ }
+ }
+
/**
* Constructs an annotation object.
*
* @param cp class pool for containing an annotation
* type (or null).
* @param anon the annotation.
+ * @return the annotation
*/
public static Object make(ClassLoader cl, Class clazz, ClassPool cp,
Annotation anon) {
AnnotationImpl handler = new AnnotationImpl(anon, cp, cl);
return Proxy.newProxyInstance(cl, new Class[] { clazz }, handler);
}
-
+
private AnnotationImpl(Annotation a, ClassPool cp, ClassLoader loader) {
annotation = a;
pool = cp;
/**
* Obtains the name of the annotation type.
+ *
+ * @return the type name
*/
public String getTypeName() {
return annotation.getTypeName();
}
+ /**
+ * Get the annotation type
+ *
+ * @return the annotation class
+ * @throws NoClassDefFoundError when the class could not loaded
+ */
+ private Class getAnnotationType() {
+ if (annotationType == null) {
+ String typeName = annotation.getTypeName();
+ try {
+ annotationType = classLoader.loadClass(typeName);
+ }
+ catch (ClassNotFoundException e) {
+ NoClassDefFoundError error = new NoClassDefFoundError("Error loading annotation class: " + typeName);
+ error.setStackTrace(e.getStackTrace());
+ throw error;
+ }
+ }
+ return annotationType;
+ }
+
/**
* Obtains the internal data structure representing the annotation.
+ *
+ * @return the annotation
*/
public Annotation getAnnotation() {
return annotation;
if (Object.class == method.getDeclaringClass()) {
if ("equals".equals(name)) {
Object obj = args[0];
- if (obj == null || obj instanceof Proxy == false)
- return Boolean.FALSE;
-
- Object other = Proxy.getInvocationHandler(obj);
- if (this.equals(other))
- return Boolean.TRUE;
- else
- return Boolean.FALSE;
+ return new Boolean(checkEquals(obj));
}
else if ("toString".equals(name))
- return annotation.getTypeName() + '@' + hashCode();
+ return annotation.toString();
else if ("hashCode".equals(name))
return new Integer(hashCode());
}
else if ("annotationType".equals(name)
&& method.getParameterTypes().length == 0)
- return classLoader.loadClass(getTypeName());
+ return getAnnotationType();
MemberValue mv = annotation.getMemberValue(name);
if (mv == null)
else
return mv.getValue(classLoader, pool, method);
}
-
+
private Object getDefault(String name, Method method)
throws ClassNotFoundException, RuntimeException
{
String classname = annotation.getTypeName();
- if (pool != null)
+ if (pool != null) {
try {
CtClass cc = pool.get(classname);
ClassFile cf = cc.getClassFile2();
throw new RuntimeException("cannot find a class file: "
+ classname);
}
+ }
throw new RuntimeException("no default value: " + classname + "."
+ name + "()");
}
+
+ public int hashCode() {
+ if (cachedHashCode == Integer.MIN_VALUE) {
+ int hashCode = 0;
+
+ // Load the annotation class
+ getAnnotationType();
+
+ Method[] methods = annotationType.getDeclaredMethods();
+ for (int i = 0; i < methods.length; ++ i) {
+ String name = methods[i].getName();
+ int valueHashCode = 0;
+
+ // Get the value
+ MemberValue mv = annotation.getMemberValue(name);
+ Object value = null;
+ try {
+ if (mv != null)
+ value = mv.getValue(classLoader, pool, methods[i]);
+ if (value == null)
+ value = getDefault(name, methods[i]);
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Error retrieving value " + name + " for annotation " + annotation.getTypeName(), e);
+ }
+
+ // Calculate the hash code
+ if (value != null) {
+ if (value.getClass().isArray())
+ valueHashCode = arrayHashCode(value);
+ else
+ valueHashCode = value.hashCode();
+ }
+ hashCode += 127 * name.hashCode() ^ valueHashCode;
+ }
+
+ cachedHashCode = hashCode;
+ }
+ return cachedHashCode;
+ }
+
+ /**
+ * Check that another annotation equals ourselves
+ *
+ * @param obj the other annotation
+ * @return the true when equals false otherwise
+ * @throws Exception for any problem
+ */
+ private boolean checkEquals(Object obj) throws Exception {
+ if (obj == null)
+ return false;
+
+ // Optimization when the other is one of ourselves
+ if (obj instanceof Proxy) {
+ InvocationHandler ih = Proxy.getInvocationHandler(obj);
+ if (ih instanceof AnnotationImpl) {
+ AnnotationImpl other = (AnnotationImpl) ih;
+ return annotation.equals(other.annotation);
+ }
+ }
+
+ Class otherAnnotationType = (Class) JDK_ANNOTATION_TYPE_METHOD.invoke(obj, null);
+ if (getAnnotationType().equals(otherAnnotationType) == false)
+ return false;
+
+ Method[] methods = annotationType.getDeclaredMethods();
+ for (int i = 0; i < methods.length; ++ i) {
+ String name = methods[i].getName();
+
+ // Get the value
+ MemberValue mv = annotation.getMemberValue(name);
+ Object value = null;
+ Object otherValue = null;
+ try {
+ if (mv != null)
+ value = mv.getValue(classLoader, pool, methods[i]);
+ if (value == null)
+ value = getDefault(name, methods[i]);
+ otherValue = methods[i].invoke(obj, null);
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Error retrieving value " + name + " for annotation " + annotation.getTypeName(), e);
+ }
+
+ if (value == null && otherValue != null)
+ return false;
+ if (value != null && value.equals(otherValue) == false)
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Calculates the hashCode of an array using the same
+ * algorithm as java.util.Arrays.hashCode()
+ *
+ * @param object the object
+ * @return the hashCode
+ */
+ private static int arrayHashCode(Object object)
+ {
+ if (object == null)
+ return 0;
+
+ int result = 1;
+
+ Object[] array = (Object[]) object;
+ for (int i = 0; i < array.length; ++i) {
+ int elementHashCode = 0;
+ if (array[i] != null)
+ elementHashCode = array[i].hashCode();
+ result = 31 * result + elementHashCode;
+ }
+ return result;
+ }
}