com.strobel.reflection.emit.AnnotationSupport Maven / Gradle / Ivy
/*
* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.strobel.reflection.emit;
import com.strobel.core.Fences;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.AnnotationTypeMismatchException;
import java.lang.annotation.IncompleteAnnotationException;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
final class AnnotationSupport {
static A annotationForMap(
final Class annotationClass,
final Map memberValues) {
return AccessController.doPrivileged(new PrivilegedAction() {
public A run() {
@SuppressWarnings("unchecked")
final A annotationProxy = (A) Proxy.newProxyInstance(
annotationClass.getClassLoader(), new Class>[] { annotationClass },
new AnnotationInvocationHandler(annotationClass, memberValues)
);
return annotationProxy;
}
});
}
}
/**
* Represents an annotation type at run time. Used to type-check annotations
* and apply member defaults.
*
* @author Josh Bloch
* @since 1.5
*/
@SuppressWarnings({ "WeakerAccess", "unused" })
final class AnnotationType {
private final static ClassValue ANNOTATION_TYPES = new ClassValue() {
@Override
protected AnnotationType computeValue(final Class> clazz) {
if (clazz.isAnnotation()) {
@SuppressWarnings("unchecked")
final Class extends Annotation> annotationClass = (Class extends Annotation>) clazz;
return new AnnotationType(annotationClass);
}
return null;
}
};
/**
* Member name -> type mapping. Note that primitive types are represented by the class objects for the
* corresponding wrapper types. This matches the return value that must be used for a dynamic proxy,
* allowing for a simple isInstance test.
*/
private final Map> memberTypes;
/**
* Member name -> default value mapping.
*/
private final Map memberDefaults;
/**
* Member name -> Method object mapping. This (and its associated accessor) are used only to generate
* AnnotationTypeMismatchExceptions.
*/
private final Map members;
/**
* The retention policy for this annotation type.
*/
private final RetentionPolicy retention;
/**
* Whether this annotation type is inherited.
*/
private final boolean inherited;
/**
* Returns an AnnotationType instance for the specified annotation type.
*
* @throws IllegalArgumentException
* if the specified class object for does not represent a valid annotation type
*/
public static AnnotationType getInstance(final Class extends Annotation> annotationClass) {
if (!annotationClass.isAnnotation()) {
throw new IllegalArgumentException("Not an annotation type");
}
return ANNOTATION_TYPES.get(annotationClass);
}
/**
* Sole constructor.
*
* @param annotationClass
* the class object for the annotation type
*
* @throws IllegalArgumentException
* if the specified class object for does not represent a valid annotation type
*/
private AnnotationType(final Class extends Annotation> annotationClass) {
if (!annotationClass.isAnnotation()) {
throw new IllegalArgumentException("Not an annotation type");
}
final Method[] methods =
AccessController.doPrivileged(new PrivilegedAction() {
public Method[] run() {
// Initialize memberTypes and defaultValues
return annotationClass.getDeclaredMethods();
}
});
memberTypes = new HashMap<>(methods.length + 1, 1.0f);
memberDefaults = new HashMap<>(0);
members = new HashMap<>(methods.length + 1, 1.0f);
for (final Method method : methods) {
if (method.getParameterTypes().length != 0) {
throw new IllegalArgumentException(method + " has params");
}
final String name = method.getName();
final Class> type = method.getReturnType();
memberTypes.put(name, invocationHandlerReturnType(type));
members.put(name, method);
final Object defaultValue = method.getDefaultValue();
if (defaultValue != null) {
memberDefaults.put(name, defaultValue);
}
}
// Initialize retention, & inherited fields. Special treatment
// of the corresponding annotation types breaks infinite recursion.
if (annotationClass != Retention.class && annotationClass != Inherited.class) {
final Retention ret = annotationClass.getAnnotation(Retention.class);
retention = (ret == null ? RetentionPolicy.CLASS : ret.value());
inherited = annotationClass.getAnnotation(Inherited.class) != null;
}
else {
retention = RetentionPolicy.RUNTIME;
inherited = false;
}
}
/**
* Returns the type that must be returned by the invocation handler
* of a dynamic proxy in order to have the dynamic proxy return
* the specified type (which is assumed to be a legal member type
* for an annotation).
*/
public static Class> invocationHandlerReturnType(final Class> type) {
// Translate primitives to wrappers
if (type == byte.class) {
return Byte.class;
}
if (type == char.class) {
return Character.class;
}
if (type == double.class) {
return Double.class;
}
if (type == float.class) {
return Float.class;
}
if (type == int.class) {
return Integer.class;
}
if (type == long.class) {
return Long.class;
}
if (type == short.class) {
return Short.class;
}
if (type == boolean.class) {
return Boolean.class;
}
// Otherwise, just return declared type
return type;
}
/**
* Returns member types for this annotation type
* (member name -> type mapping).
*/
public Map> memberTypes() {
return memberTypes;
}
/**
* Returns members of this annotation type
* (member name -> associated Method object mapping).
*/
public Map members() {
return members;
}
/**
* Returns the default values for this annotation type
* (Member name -> default value mapping).
*/
public Map memberDefaults() {
return memberDefaults;
}
/**
* Returns the retention policy for this annotation type.
*/
public RetentionPolicy retention() {
return retention;
}
/**
* Returns true if this annotation type is inherited.
*/
public boolean isInherited() {
return inherited;
}
/**
* For debugging.
*/
public String toString() {
return "Annotation Type:\n" +
" Member types: " + memberTypes + "\n" +
" Member defaults: " + memberDefaults + "\n" +
" Retention policy: " + retention + "\n" +
" Inherited: " + inherited;
}
}
/**
* InvocationHandler for dynamic proxy implementation of Annotation.
*
* @author Josh Bloch
* @since 1.5
*/
final class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class extends Annotation> type;
private final Map memberValues;
AnnotationInvocationHandler(final Class extends Annotation> type, final Map memberValues) {
this.type = type;
this.memberValues = memberValues;
}
public Object invoke(final Object proxy, final Method method, final Object[] args) {
final String member = method.getName();
final Class>[] paramTypes = method.getParameterTypes();
//
// Handle Object and Annotation methods.
//
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class) {
return equalsImpl(args[0]);
}
assert paramTypes.length == 0;
if (member.equals("toString")) {
return toStringImpl();
}
if (member.equals("hashCode")) {
return hashCodeImpl();
}
if (member.equals("annotationType")) {
return type;
}
//
// Handle annotation member accessors
//
Object result = memberValues.get(member);
if (result == null) {
throw new IncompleteAnnotationException(type, member);
}
if (result instanceof ExceptionProxy) {
throw ((ExceptionProxy) result).generateException();
}
if (result.getClass().isArray() && Array.getLength(result) != 0) {
result = cloneArray(result);
}
return result;
}
/**
* This method, which clones its array argument, would not be necessary if Cloneable had a
* public clone method.
*/
private static Object cloneArray(final Object array) {
final Class> type = array.getClass();
if (type == byte[].class) {
final byte[] byteArray = (byte[]) array;
return byteArray.clone();
}
if (type == char[].class) {
final char[] charArray = (char[]) array;
return charArray.clone();
}
if (type == double[].class) {
final double[] doubleArray = (double[]) array;
return doubleArray.clone();
}
if (type == float[].class) {
final float[] floatArray = (float[]) array;
return floatArray.clone();
}
if (type == int[].class) {
final int[] intArray = (int[]) array;
return intArray.clone();
}
if (type == long[].class) {
final long[] longArray = (long[]) array;
return longArray.clone();
}
if (type == short[].class) {
final short[] shortArray = (short[]) array;
return shortArray.clone();
}
if (type == boolean[].class) {
final boolean[] booleanArray = (boolean[]) array;
return booleanArray.clone();
}
final Object[] objectArray = (Object[]) array;
return objectArray.clone();
}
/**
* Implementation of dynamicProxy.toString()
*/
private String toStringImpl() {
final StringBuilder result = new StringBuilder(128);
result.append('@');
result.append(type.getName());
result.append('(');
boolean firstMember = true;
for (final Map.Entry e : memberValues.entrySet()) {
if (firstMember) {
firstMember = false;
}
else {
result.append(", ");
}
result.append(e.getKey());
result.append('=');
result.append(memberValueToString(e.getValue()));
}
return result.append(')').toString();
}
/**
* Translates a member value (in "dynamic proxy return form") into a string
*/
private static String memberValueToString(final Object value) {
final Class> type = value.getClass();
if (!type.isArray()) {
// primitive, string, class, enum const, or annotation
return value.toString();
}
if (type == byte[].class) {
return Arrays.toString((byte[]) value);
}
if (type == char[].class) {
return Arrays.toString((char[]) value);
}
if (type == double[].class) {
return Arrays.toString((double[]) value);
}
if (type == float[].class) {
return Arrays.toString((float[]) value);
}
if (type == int[].class) {
return Arrays.toString((int[]) value);
}
if (type == long[].class) {
return Arrays.toString((long[]) value);
}
if (type == short[].class) {
return Arrays.toString((short[]) value);
}
if (type == boolean[].class) {
return Arrays.toString((boolean[]) value);
}
return Arrays.toString((Object[]) value);
}
/**
* Implementation of dynamicProxy.equals(Object o)
*/
private Boolean equalsImpl(final Object o) {
if (o == this) {
return true;
}
if (!type.isInstance(o)) {
return false;
}
for (final Method memberMethod : getMemberMethods()) {
final String member = memberMethod.getName();
final Object ourValue = memberValues.get(member);
final Object hisValue;
final AnnotationInvocationHandler hisHandler = asOneOfUs(o);
if (hisHandler != null) {
hisValue = hisHandler.memberValues.get(member);
}
else {
try {
hisValue = memberMethod.invoke(o);
}
catch (final InvocationTargetException e) {
return false;
}
catch (final IllegalAccessException e) {
throw new AssertionError(e);
}
}
if (!memberValueEquals(ourValue, hisValue)) {
return false;
}
}
return true;
}
/**
* Returns an object's invocation handler if that object is a dynamic
* proxy with a handler of type AnnotationInvocationHandler.
* Returns null otherwise.
*/
private AnnotationInvocationHandler asOneOfUs(final Object o) {
if (Proxy.isProxyClass(o.getClass())) {
final InvocationHandler handler = Proxy.getInvocationHandler(o);
if (handler instanceof AnnotationInvocationHandler) {
return (AnnotationInvocationHandler) handler;
}
}
return null;
}
/**
* Returns true iff the two member values in "dynamic proxy return form"
* are equal using the appropriate equality function depending on the
* member type. The two values will be of the same type unless one of
* the containing annotations is ill-formed. If one of the containing
* annotations is ill-formed, this method will return false unless the
* two members are identical object references.
*/
private static boolean memberValueEquals(final Object v1, final Object v2) {
final Class> type = v1.getClass();
// Check for primitive, string, class, enum const, annotation,
// or ExceptionProxy
if (!type.isArray()) {
return v1.equals(v2);
}
// Check for array of string, class, enum const, annotation,
// or ExceptionProxy
if (v1 instanceof Object[] && v2 instanceof Object[]) {
return Arrays.equals((Object[]) v1, (Object[]) v2);
}
// Check for ill formed annotation(s)
if (v2.getClass() != type) {
return false;
}
// Deal with array of primitives
if (type == byte[].class) {
return Arrays.equals((byte[]) v1, (byte[]) v2);
}
if (type == char[].class) {
return Arrays.equals((char[]) v1, (char[]) v2);
}
if (type == double[].class) {
return Arrays.equals((double[]) v1, (double[]) v2);
}
if (type == float[].class) {
return Arrays.equals((float[]) v1, (float[]) v2);
}
if (type == int[].class) {
return Arrays.equals((int[]) v1, (int[]) v2);
}
if (type == long[].class) {
return Arrays.equals((long[]) v1, (long[]) v2);
}
if (type == short[].class) {
return Arrays.equals((short[]) v1, (short[]) v2);
}
assert type == boolean[].class;
return Arrays.equals((boolean[]) v1, (boolean[]) v2);
}
/**
* Returns the member methods for our annotation type. These are
* obtained lazily and cached, as they're expensive to obtain, and
* we only need them if our `equals` method is invoked (which should
* be rare).
*/
private Method[] getMemberMethods() {
if (memberMethods == null) {
final Method[] mm = type.getDeclaredMethods();
AccessibleObject.setAccessible(mm, true);
memberMethods = Fences.orderWrites(mm);
}
return memberMethods;
}
private transient volatile Method[] memberMethods = null;
/**
* Implementation of dynamicProxy.hashCode()
*/
private int hashCodeImpl() {
int result = 0;
for (final Map.Entry e : memberValues.entrySet()) {
result += (127 * e.getKey().hashCode()) ^
memberValueHashCode(e.getValue());
}
return result;
}
/**
* Computes hashCode of a member value (in "dynamic proxy return form")
*/
private static int memberValueHashCode(final Object value) {
final Class> type = value.getClass();
if (!type.isArray()) {
// primitive, string, class, enum const, or annotation
return value.hashCode();
}
if (type == byte[].class) {
return Arrays.hashCode((byte[]) value);
}
if (type == char[].class) {
return Arrays.hashCode((char[]) value);
}
if (type == double[].class) {
return Arrays.hashCode((double[]) value);
}
if (type == float[].class) {
return Arrays.hashCode((float[]) value);
}
if (type == int[].class) {
return Arrays.hashCode((int[]) value);
}
if (type == long[].class) {
return Arrays.hashCode((long[]) value);
}
if (type == short[].class) {
return Arrays.hashCode((short[]) value);
}
if (type == boolean[].class) {
return Arrays.hashCode((boolean[]) value);
}
return Arrays.hashCode((Object[]) value);
}
private void readObject(final java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
final AnnotationType annotationType;
try {
annotationType = AnnotationType.getInstance(type);
}
catch (final IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
final Map> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (final Map.Entry memberValue : memberValues.entrySet()) {
final String name = memberValue.getKey();
final Class> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
final Object value = memberValue.getValue();
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name))
);
}
}
}
}
}
/**
* An instance of this class is stored in an AnnotationInvocationHandler's
* "memberValues" map in lieu of a value for an annotation member that
* cannot be returned due to some exceptional condition (typically some
* form of illegal evolution of the annotation class). The ExceptionProxy
* instance describes the exception that the dynamic proxy should throw if
* it is queried for this member.
*
* @author Josh Bloch
* @since 1.5
*/
abstract class ExceptionProxy implements java.io.Serializable {
protected abstract RuntimeException generateException();
}
/**
* ExceptionProxy for AnnotationTypeMismatchException.
*
* @author Josh Bloch
* @since 1.5
*/
final class AnnotationTypeMismatchExceptionProxy extends ExceptionProxy {
private static final long serialVersionUID = 7844069490309503934L;
private Method member;
private final String foundType;
/**
* It turns out to be convenient to construct these proxies in
* two stages. Since this is a private implementation class, we
* permit ourselves this liberty even though it's normally a very
* bad idea.
*/
AnnotationTypeMismatchExceptionProxy(final String foundType) {
this.foundType = foundType;
}
AnnotationTypeMismatchExceptionProxy setMember(final Method member) {
this.member = member;
return this;
}
protected RuntimeException generateException() {
return new AnnotationTypeMismatchException(member, foundType);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy