/*
* 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
* <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)
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.
*
* @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
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.
*/
@
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);
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()
*
* @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;
}
}