From: adrian Date: Wed, 31 Jan 2007 16:17:42 +0000 (+0000) Subject: Fix the annotation hashCode and equals implementation. X-Git-Tag: rel_3_17_1_ga~292 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=57dcd65cab1449ee755c3e40007a66a23e1febfe;p=javassist.git Fix the annotation hashCode and equals implementation. Use the annotation.toString() for the real annotation since it is better. Also some javadoc tidyup. The default annotation value processing is still ugly. git-svn-id: http://anonsvn.jboss.org/repos/javassist/trunk@343 30ef5769-5b8d-40dd-aea6-55b5d6557bb3 --- diff --git a/src/main/javassist/bytecode/annotation/Annotation.java b/src/main/javassist/bytecode/annotation/Annotation.java index 4b00a318..34f021e1 100644 --- a/src/main/javassist/bytecode/annotation/Annotation.java +++ b/src/main/javassist/bytecode/annotation/Annotation.java @@ -20,6 +20,7 @@ import javassist.bytecode.Descriptor; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; +import javassist.NotFoundException; import java.io.IOException; import java.util.HashMap; @@ -41,6 +42,7 @@ import java.util.Iterator; * * @author Bill Burke * @author Shigeru Chiba + * @author Adrian Brock */ public class Annotation { static class Pair { @@ -91,9 +93,10 @@ public class Annotation { * * @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); @@ -103,13 +106,15 @@ public class Annotation { "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)); + } } @@ -118,9 +123,11 @@ public class Annotation { * * @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); @@ -201,9 +208,6 @@ public class Annotation { members.put(name, pair); } - /** - * Returns a string representation of this object. - */ public String toString() { StringBuffer buf = new StringBuffer("@"); buf.append(getTypeName()); @@ -224,6 +228,8 @@ public class Annotation { /** * Obtains the name of the annotation type. + * + * @return the type name */ public String getTypeName() { return Descriptor.toClassName(pool.getUtf8Info(typeIndex)); @@ -250,6 +256,7 @@ public class Annotation { * MemberValue 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. * @@ -274,6 +281,8 @@ public class Annotation { * * @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 @@ -287,6 +296,7 @@ public class Annotation { * 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); @@ -303,4 +313,22 @@ public class Annotation { 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); + } } diff --git a/src/main/javassist/bytecode/annotation/AnnotationImpl.java b/src/main/javassist/bytecode/annotation/AnnotationImpl.java index 00bf318a..e83812f2 100644 --- a/src/main/javassist/bytecode/annotation/AnnotationImpl.java +++ b/src/main/javassist/bytecode/annotation/AnnotationImpl.java @@ -15,6 +15,10 @@ 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; @@ -22,17 +26,35 @@ import javassist.bytecode.AnnotationDefaultAttribute; import javassist.bytecode.ClassFile; import javassist.bytecode.MethodInfo; -import java.lang.reflect.*; - /** * Internal-use only. This is a helper class internally used for implementing - * toAnnotationType() in Annotation. + * toAnnotationType() in Annotation. + * + * @author Shigeru Chiba + * @author Bill Burke + * @author Adrian Brock */ 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. * @@ -41,13 +63,14 @@ public class AnnotationImpl implements InvocationHandler { * @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; @@ -56,13 +79,38 @@ public class AnnotationImpl implements InvocationHandler { /** * 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; @@ -82,23 +130,16 @@ public class AnnotationImpl implements InvocationHandler { 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) @@ -106,12 +147,12 @@ public class AnnotationImpl implements InvocationHandler { 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(); @@ -130,8 +171,131 @@ public class AnnotationImpl implements InvocationHandler { 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; + } }