aboutsummaryrefslogtreecommitdiff
path: root/src/main/javassist/bytecode/annotation/AnnotationImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/javassist/bytecode/annotation/AnnotationImpl.java')
-rw-r--r--src/main/javassist/bytecode/annotation/AnnotationImpl.java304
1 files changed, 304 insertions, 0 deletions
diff --git a/src/main/javassist/bytecode/annotation/AnnotationImpl.java b/src/main/javassist/bytecode/annotation/AnnotationImpl.java
new file mode 100644
index 0000000..dfd23bb
--- /dev/null
+++ b/src/main/javassist/bytecode/annotation/AnnotationImpl.java
@@ -0,0 +1,304 @@
+/*
+ * Javassist, a Java-bytecode translator toolkit.
+ * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. Alternatively, the contents of this file may be used under
+ * the terms of the GNU Lesser General Public License Version 2.1 or later.
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ */
+
+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.AnnotationDefaultAttribute;
+import javassist.bytecode.ClassFile;
+import javassist.bytecode.MethodInfo;
+
+/**
+ * Internal-use only. This is a helper class internally used for implementing
+ * <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", (Class[])null);
+ }
+ catch (Exception ignored) {
+ // Probably not JDK5+
+ }
+ }
+
+ /**
+ * Constructs an annotation object.
+ *
+ * @param cl class loader for obtaining annotation types.
+ * @param clazz the annotation type.
+ * @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;
+ classLoader = loader;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Executes a method invocation on a proxy instance.
+ * The implementations of <code>toString()</code>, <code>equals()</code>,
+ * and <code>hashCode()</code> are directly supplied by the
+ * <code>AnnotationImpl</code>. The <code>annotationType()</code> method
+ * is also available on the proxy instance.
+ */
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable
+ {
+ String name = method.getName();
+ if (Object.class == method.getDeclaringClass()) {
+ if ("equals".equals(name)) {
+ Object obj = args[0];
+ return new Boolean(checkEquals(obj));
+ }
+ else if ("toString".equals(name))
+ return annotation.toString();
+ else if ("hashCode".equals(name))
+ return new Integer(hashCode());
+ }
+ else if ("annotationType".equals(name)
+ && method.getParameterTypes().length == 0)
+ return getAnnotationType();
+
+ MemberValue mv = annotation.getMemberValue(name);
+ if (mv == null)
+ return getDefault(name, method);
+ else
+ return mv.getValue(classLoader, pool, method);
+ }
+
+ private Object getDefault(String name, Method method)
+ throws ClassNotFoundException, RuntimeException
+ {
+ String classname = annotation.getTypeName();
+ if (pool != null) {
+ try {
+ CtClass cc = pool.get(classname);
+ ClassFile cf = cc.getClassFile2();
+ MethodInfo minfo = cf.getMethod(name);
+ if (minfo != null) {
+ AnnotationDefaultAttribute ainfo
+ = (AnnotationDefaultAttribute)
+ minfo.getAttribute(AnnotationDefaultAttribute.tag);
+ if (ainfo != null) {
+ MemberValue mv = ainfo.getDefaultValue();
+ return mv.getValue(classLoader, pool, method);
+ }
+ }
+ }
+ catch (NotFoundException e) {
+ throw new RuntimeException("cannot find a class file: "
+ + classname);
+ }
+ }
+
+ throw new RuntimeException("no default value: " + classname + "."
+ + name + "()");
+ }
+
+ /**
+ * Returns a hash code value for this object.
+ */
+ 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, (Object[])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, (Object[])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;
+ }
+}