diff options
-rw-r--r-- | src/main/javassist/bytecode/annotation/Annotation.java | 40 | ||||
-rw-r--r-- | src/main/javassist/bytecode/annotation/AnnotationImpl.java | 196 |
2 files changed, 214 insertions, 22 deletions
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 <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); + } } 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 - * <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; + } } |