]> source.dussan.org Git - javassist.git/commitdiff
Fix the annotation hashCode and equals implementation.
authoradrian <adrian@30ef5769-5b8d-40dd-aea6-55b5d6557bb3>
Wed, 31 Jan 2007 16:17:42 +0000 (16:17 +0000)
committeradrian <adrian@30ef5769-5b8d-40dd-aea6-55b5d6557bb3>
Wed, 31 Jan 2007 16:17:42 +0000 (16:17 +0000)
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

src/main/javassist/bytecode/annotation/Annotation.java
src/main/javassist/bytecode/annotation/AnnotationImpl.java

index 4b00a318c7924525686756272f065590f77e9330..34f021e10bf66a25876e2933b2cb353573e02a8f 100644 (file)
@@ -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 <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 {
@@ -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 {
      * <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.
      *
@@ -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);
+    }
 }
index 00bf318a1a716b06acd5fdee7a80463c61db9467..e83812f2e5e1ebc3b4ba54f4d25fd4887c0275dc 100644 (file)
 
 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
- * <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.
      *
@@ -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;
+    }
 }