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 16package javassist.bytecode.annotation; 17 18import java.lang.reflect.InvocationHandler; 19import java.lang.reflect.Method; 20import java.lang.reflect.Proxy; 21 22import javassist.ClassPool; 23import javassist.CtClass; 24import javassist.NotFoundException; 25import javassist.bytecode.AnnotationDefaultAttribute; 26import javassist.bytecode.ClassFile; 27import 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 */ 37public 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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