• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Javassist, a Java-bytecode translator toolkit.
3  * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License.  Alternatively, the contents of this file may be used under
8  * the terms of the GNU Lesser General Public License Version 2.1 or later.
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  */
15 
16 package javassist.bytecode.annotation;
17 
18 import java.lang.reflect.InvocationHandler;
19 import java.lang.reflect.Method;
20 import java.lang.reflect.Proxy;
21 
22 import javassist.ClassPool;
23 import javassist.CtClass;
24 import javassist.NotFoundException;
25 import javassist.bytecode.AnnotationDefaultAttribute;
26 import javassist.bytecode.ClassFile;
27 import javassist.bytecode.MethodInfo;
28 
29 /**
30  * Internal-use only.  This is a helper class internally used for implementing
31  * <code>toAnnotationType()</code> in <code>Annotation</code>.
32  *
33  * @author Shigeru Chiba
34  * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
35  * @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
36  */
37 public class AnnotationImpl implements InvocationHandler {
38     private static final String JDK_ANNOTATION_CLASS_NAME = "java.lang.annotation.Annotation";
39     private static Method JDK_ANNOTATION_TYPE_METHOD = null;
40 
41     private Annotation annotation;
42     private ClassPool pool;
43     private ClassLoader classLoader;
44     private transient Class annotationType;
45     private transient int cachedHashCode = Integer.MIN_VALUE;
46 
47     static {
48         // Try to resolve the JDK annotation type method
49         try {
50             Class clazz = Class.forName(JDK_ANNOTATION_CLASS_NAME);
51             JDK_ANNOTATION_TYPE_METHOD = clazz.getMethod("annotationType", (Class[])null);
52         }
53         catch (Exception ignored) {
54             // Probably not JDK5+
55         }
56     }
57 
58     /**
59      * Constructs an annotation object.
60      *
61      * @param cl        class loader for obtaining annotation types.
62      * @param clazz     the annotation type.
63      * @param cp        class pool for containing an annotation
64      *                  type (or null).
65      * @param anon      the annotation.
66      * @return the annotation
67      */
make(ClassLoader cl, Class clazz, ClassPool cp, Annotation anon)68     public static Object make(ClassLoader cl, Class clazz, ClassPool cp,
69                               Annotation anon) {
70         AnnotationImpl handler = new AnnotationImpl(anon, cp, cl);
71         return Proxy.newProxyInstance(cl, new Class[] { clazz }, handler);
72     }
73 
AnnotationImpl(Annotation a, ClassPool cp, ClassLoader loader)74     private AnnotationImpl(Annotation a, ClassPool cp, ClassLoader loader) {
75         annotation = a;
76         pool = cp;
77         classLoader = loader;
78     }
79 
80     /**
81      * Obtains the name of the annotation type.
82      *
83      * @return the type name
84      */
getTypeName()85     public String getTypeName() {
86         return annotation.getTypeName();
87     }
88 
89     /**
90      * Get the annotation type
91      *
92      * @return the annotation class
93      * @throws NoClassDefFoundError when the class could not loaded
94      */
getAnnotationType()95     private Class getAnnotationType() {
96         if (annotationType == null) {
97             String typeName = annotation.getTypeName();
98             try {
99                 annotationType = classLoader.loadClass(typeName);
100             }
101             catch (ClassNotFoundException e) {
102                 NoClassDefFoundError error = new NoClassDefFoundError("Error loading annotation class: " + typeName);
103                 error.setStackTrace(e.getStackTrace());
104                 throw error;
105             }
106         }
107         return annotationType;
108     }
109 
110     /**
111      * Obtains the internal data structure representing the annotation.
112      *
113      * @return the annotation
114      */
getAnnotation()115     public Annotation getAnnotation() {
116         return annotation;
117     }
118 
119     /**
120      * Executes a method invocation on a proxy instance.
121      * The implementations of <code>toString()</code>, <code>equals()</code>,
122      * and <code>hashCode()</code> are directly supplied by the
123      * <code>AnnotationImpl</code>.  The <code>annotationType()</code> method
124      * is also available on the proxy instance.
125      */
invoke(Object proxy, Method method, Object[] args)126     public Object invoke(Object proxy, Method method, Object[] args)
127         throws Throwable
128     {
129         String name = method.getName();
130         if (Object.class == method.getDeclaringClass()) {
131             if ("equals".equals(name)) {
132                 Object obj = args[0];
133                 return new Boolean(checkEquals(obj));
134             }
135             else if ("toString".equals(name))
136                 return annotation.toString();
137             else if ("hashCode".equals(name))
138                 return new Integer(hashCode());
139         }
140         else if ("annotationType".equals(name)
141                  && method.getParameterTypes().length == 0)
142            return getAnnotationType();
143 
144         MemberValue mv = annotation.getMemberValue(name);
145         if (mv == null)
146             return getDefault(name, method);
147         else
148             return mv.getValue(classLoader, pool, method);
149     }
150 
getDefault(String name, Method method)151     private Object getDefault(String name, Method method)
152         throws ClassNotFoundException, RuntimeException
153     {
154         String classname = annotation.getTypeName();
155         if (pool != null) {
156             try {
157                 CtClass cc = pool.get(classname);
158                 ClassFile cf = cc.getClassFile2();
159                 MethodInfo minfo = cf.getMethod(name);
160                 if (minfo != null) {
161                     AnnotationDefaultAttribute ainfo
162                         = (AnnotationDefaultAttribute)
163                           minfo.getAttribute(AnnotationDefaultAttribute.tag);
164                     if (ainfo != null) {
165                         MemberValue mv = ainfo.getDefaultValue();
166                         return mv.getValue(classLoader, pool, method);
167                     }
168                 }
169             }
170             catch (NotFoundException e) {
171                 throw new RuntimeException("cannot find a class file: "
172                                            + classname);
173             }
174         }
175 
176         throw new RuntimeException("no default value: " + classname + "."
177                                    + name + "()");
178     }
179 
180     /**
181      * Returns a hash code value for this object.
182      */
hashCode()183     public int hashCode() {
184         if (cachedHashCode == Integer.MIN_VALUE) {
185             int hashCode = 0;
186 
187             // Load the annotation class
188             getAnnotationType();
189 
190             Method[] methods = annotationType.getDeclaredMethods();
191             for (int i = 0; i < methods.length; ++ i) {
192                 String name = methods[i].getName();
193                 int valueHashCode = 0;
194 
195                 // Get the value
196                 MemberValue mv = annotation.getMemberValue(name);
197                 Object value = null;
198                 try {
199                    if (mv != null)
200                        value = mv.getValue(classLoader, pool, methods[i]);
201                    if (value == null)
202                        value = getDefault(name, methods[i]);
203                 }
204                 catch (RuntimeException e) {
205                     throw e;
206                 }
207                 catch (Exception e) {
208                     throw new RuntimeException("Error retrieving value " + name + " for annotation " + annotation.getTypeName(), e);
209                 }
210 
211                 // Calculate the hash code
212                 if (value != null) {
213                     if (value.getClass().isArray())
214                         valueHashCode = arrayHashCode(value);
215                     else
216                         valueHashCode = value.hashCode();
217                 }
218                 hashCode += 127 * name.hashCode() ^ valueHashCode;
219             }
220 
221             cachedHashCode = hashCode;
222         }
223         return cachedHashCode;
224     }
225 
226     /**
227      * Check that another annotation equals ourselves.
228      *
229      * @param obj the other annotation
230      * @return the true when equals false otherwise
231      * @throws Exception for any problem
232      */
checkEquals(Object obj)233     private boolean checkEquals(Object obj) throws Exception {
234         if (obj == null)
235             return false;
236 
237         // Optimization when the other is one of ourselves
238         if (obj instanceof Proxy) {
239             InvocationHandler ih = Proxy.getInvocationHandler(obj);
240             if (ih instanceof AnnotationImpl) {
241                 AnnotationImpl other = (AnnotationImpl) ih;
242                 return annotation.equals(other.annotation);
243             }
244         }
245 
246         Class otherAnnotationType = (Class) JDK_ANNOTATION_TYPE_METHOD.invoke(obj, (Object[])null);
247         if (getAnnotationType().equals(otherAnnotationType) == false)
248            return false;
249 
250         Method[] methods = annotationType.getDeclaredMethods();
251         for (int i = 0; i < methods.length; ++ i) {
252             String name = methods[i].getName();
253 
254             // Get the value
255             MemberValue mv = annotation.getMemberValue(name);
256             Object value = null;
257             Object otherValue = null;
258             try {
259                if (mv != null)
260                    value = mv.getValue(classLoader, pool, methods[i]);
261                if (value == null)
262                    value = getDefault(name, methods[i]);
263                otherValue = methods[i].invoke(obj, (Object[])null);
264             }
265             catch (RuntimeException e) {
266                 throw e;
267             }
268             catch (Exception e) {
269                 throw new RuntimeException("Error retrieving value " + name + " for annotation " + annotation.getTypeName(), e);
270             }
271 
272             if (value == null && otherValue != null)
273                 return false;
274             if (value != null && value.equals(otherValue) == false)
275                 return false;
276         }
277 
278         return true;
279     }
280 
281     /**
282      * Calculates the hashCode of an array using the same
283      * algorithm as java.util.Arrays.hashCode()
284      *
285      * @param object the object
286      * @return the hashCode
287      */
arrayHashCode(Object object)288     private static int arrayHashCode(Object object)
289     {
290        if (object == null)
291           return 0;
292 
293        int result = 1;
294 
295        Object[] array = (Object[]) object;
296        for (int i = 0; i < array.length; ++i) {
297            int elementHashCode = 0;
298            if (array[i] != null)
299               elementHashCode = array[i].hashCode();
300            result = 31 * result + elementHashCode;
301        }
302        return result;
303     }
304 }
305