/*
 * 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.
 */

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 toAnnotationType() in Annotation.
Author:Shigeru Chiba, Bill Burke, Adrian Brock
/** * 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.
Params:
  • cl – class loader for obtaining annotation types.
  • clazz – the annotation type.
  • cp – class pool for containing an annotation type (or null).
  • anon – the annotation.
Returns:the annotation
/** * 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) throws IllegalArgumentException { 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.
Returns:the type name
/** * Obtains the name of the annotation type. * * @return the type name */
public String getTypeName() { return annotation.getTypeName(); }
Get the annotation type
Throws:
Returns:the annotation class
/** * 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.
Returns:the annotation
/** * 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 toString(), equals(), and hashCode() are directly supplied by the AnnotationImpl. The annotationType() method is also available on the proxy instance.
/** * 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 Boolean.valueOf(checkEquals(obj)); } else if ("toString".equals(name)) return annotation.toString(); else if ("hashCode".equals(name)) return Integer.valueOf(hashCode()); } else if ("annotationType".equals(name) && method.getParameterTypes().length == 0) return getAnnotationType(); MemberValue mv = annotation.getMemberValue(name); if (mv == null) return getDefault(name, method); 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.
/** * 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.
Params:
  • obj – the other annotation
Throws:
Returns:the true when equals false otherwise
/** * 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); 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); } 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()
Params:
  • object – the object
Returns:the hashCode
/** * 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; } }