/* * Javassist, a Java-bytecode translator toolkit. * Copyright (C) 1999- 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, * or the Apache License Version 2.0. * * 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. * * MODIFIED BY James Nelson of We The Internet, 2013. * Repackaged to avoid conflicts with different versions of Javassist, * and modified Javassist APIs to make them more accessible to outside code. */ package xapi.bytecode.annotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import xapi.bytecode.ClassFile; import xapi.bytecode.ClassPool; import xapi.bytecode.CtClass; import xapi.bytecode.MethodInfo; import xapi.bytecode.NotFoundException; 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. */ @Override 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. */ @Override 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; } }