/*
 * Copyright (c) 2003, 2014, 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 sun.reflect.annotation;

import java.io.ObjectInputStream;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.io.Serializable;
import java.util.*;
import java.security.AccessController;
import java.security.PrivilegedAction;

InvocationHandler for dynamic proxy implementation of Annotation.
Author: Josh Bloch
Since: 1.5
/** * InvocationHandler for dynamic proxy implementation of Annotation. * * @author Josh Bloch * @since 1.5 */
class AnnotationInvocationHandler implements InvocationHandler, Serializable { private static final long serialVersionUID = 6182022883658399397L; private final Class<? extends Annotation> type; private final Map<String, Object> memberValues; AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) { Class<?>[] superInterfaces = type.getInterfaces(); if (!type.isAnnotation() || superInterfaces.length != 1 || superInterfaces[0] != java.lang.annotation.Annotation.class) throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type."); this.type = type; this.memberValues = memberValues; } public Object invoke(Object proxy, Method method, Object[] args) { String member = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); // Handle Object and Annotation methods if (member.equals("equals") && paramTypes.length == 1 && paramTypes[0] == Object.class) return equalsImpl(args[0]); if (paramTypes.length != 0) throw new AssertionError("Too many parameters for an annotation method"); if ("toString".equals(member)) { return toStringImpl(); } else if ("hashCode".equals(member)) { return hashCodeImpl(); } else if ("annotationType".equals(member)) { 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.
/** * This method, which clones its array argument, would not be necessary * if Cloneable had a public clone method. */
private Object cloneArray(Object array) { Class<?> type = array.getClass(); if (type == byte[].class) { byte[] byteArray = (byte[])array; return byteArray.clone(); } if (type == char[].class) { char[] charArray = (char[])array; return charArray.clone(); } if (type == double[].class) { double[] doubleArray = (double[])array; return doubleArray.clone(); } if (type == float[].class) { float[] floatArray = (float[])array; return floatArray.clone(); } if (type == int[].class) { int[] intArray = (int[])array; return intArray.clone(); } if (type == long[].class) { long[] longArray = (long[])array; return longArray.clone(); } if (type == short[].class) { short[] shortArray = (short[])array; return shortArray.clone(); } if (type == boolean[].class) { boolean[] booleanArray = (boolean[])array; return booleanArray.clone(); } Object[] objectArray = (Object[])array; return objectArray.clone(); }
Implementation of dynamicProxy.toString()
/** * Implementation of dynamicProxy.toString() */
private String toStringImpl() { StringBuilder result = new StringBuilder(128); result.append('@'); result.append(type.getName()); result.append('('); boolean firstMember = true; for (Map.Entry<String, Object> e : memberValues.entrySet()) { if (firstMember) firstMember = false; else result.append(", "); result.append(e.getKey()); result.append('='); result.append(memberValueToString(e.getValue())); } result.append(')'); return result.toString(); }
Translates a member value (in "dynamic proxy return form") into a string
/** * Translates a member value (in "dynamic proxy return form") into a string */
private static String memberValueToString(Object value) { 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)
/** * Implementation of dynamicProxy.equals(Object o) */
private Boolean equalsImpl(Object o) { if (o == this) return true; if (!type.isInstance(o)) return false; for (Method memberMethod : getMemberMethods()) { String member = memberMethod.getName(); Object ourValue = memberValues.get(member); Object hisValue = null; AnnotationInvocationHandler hisHandler = asOneOfUs(o); if (hisHandler != null) { hisValue = hisHandler.memberValues.get(member); } else { try { hisValue = memberMethod.invoke(o); } catch (InvocationTargetException e) { return false; } catch (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.
/** * 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(Object o) { if (Proxy.isProxyClass(o.getClass())) { 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.
/** * 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(Object v1, Object v2) { 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).
/** * 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) { memberMethods = AccessController.doPrivileged( new PrivilegedAction<Method[]>() { public Method[] run() { final Method[] mm = type.getDeclaredMethods(); validateAnnotationMethods(mm); AccessibleObject.setAccessible(mm, true); return mm; } }); } return memberMethods; } private transient volatile Method[] memberMethods = null;
Validates that a method is structurally appropriate for an annotation type. As of Java SE 6, annotation types cannot contain static methods and the declared methods of an annotation type must take zero arguments and there are restrictions on the return type.
/** * Validates that a method is structurally appropriate for an * annotation type. As of Java SE 6, annotation types cannot * contain static methods and the declared methods of an * annotation type must take zero arguments and there are * restrictions on the return type. */
private void validateAnnotationMethods(Method[] memberMethods) { /* * Specification citations below are from JLS * 9.6. Annotation Types */ boolean valid = true; for(Method method : memberMethods) { /* * "By virtue of the AnnotationTypeElementDeclaration * production, a method declaration in an annotation type * declaration cannot have formal parameters, type * parameters, or a throws clause. * * "By virtue of the AbstractMethodModifier * production, a method declaration in an annotation type * declaration cannot be default or static." */ if (method.getModifiers() != (Modifier.PUBLIC | Modifier.ABSTRACT) || method.getParameterTypes().length != 0 || method.getExceptionTypes().length != 0) { valid = false; break; } /* * "It is a compile-time error if the return type of a * method declared in an annotation type is not one of the * following: a primitive type, String, Class, any * parameterized invocation of Class, an enum type * (section 8.9), an annotation type, or an array type * (chapter 10) whose element type is one of the preceding * types." */ Class<?> returnType = method.getReturnType(); if (returnType.isArray()) { returnType = returnType.getComponentType(); if (returnType.isArray()) { // Only single dimensional arrays valid = false; break; } } if (!((returnType.isPrimitive() && returnType != void.class) || returnType == java.lang.String.class || returnType == java.lang.Class.class || returnType.isEnum() || returnType.isAnnotation())) { valid = false; break; } /* * "It is a compile-time error if any method declared in an * annotation type has a signature that is * override-equivalent to that of any public or protected * method declared in class Object or in the interface * java.lang.annotation.Annotation." * * The methods in Object or Annotation meeting the other * criteria (no arguments, constrained return type, etc.) * above are: * * String toString() * int hashCode() * Class<? extends Annotation> annotationType() */ String methodName = method.getName(); if ((methodName.equals("toString") && returnType == java.lang.String.class) || (methodName.equals("hashCode") && returnType == int.class) || (methodName.equals("annotationType") && returnType == java.lang.Class.class)) { valid = false; break; } } if (valid) return; else throw new AnnotationFormatError("Malformed method on an annotation type"); }
Implementation of dynamicProxy.hashCode()
/** * Implementation of dynamicProxy.hashCode() */
private int hashCodeImpl() { int result = 0; for (Map.Entry<String, Object> e : memberValues.entrySet()) { result += (127 * e.getKey().hashCode()) ^ memberValueHashCode(e.getValue()); } return result; }
Computes hashCode of a member value (in "dynamic proxy return form")
/** * Computes hashCode of a member value (in "dynamic proxy return form") */
private static int memberValueHashCode(Object value) { 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(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { ObjectInputStream.GetField fields = s.readFields(); @SuppressWarnings("unchecked") Class<? extends Annotation> t = (Class<? extends Annotation>)fields.get("type", null); @SuppressWarnings("unchecked") Map<String, Object> streamVals = (Map<String, Object>)fields.get("memberValues", null); // Check to make sure that types have not evolved incompatibly AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(t); } catch(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"); } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); // consistent with runtime Map type Map<String, Object> mv = new LinkedHashMap<String, Object>(); // If there are annotation members without values, that // situation is handled by the invoke method. for (Map.Entry<String, Object> memberValue : streamVals.entrySet()) { String name = memberValue.getKey(); Object value = null; Class<?> memberType = memberTypes.get(name); if (memberType != null) { // i.e. member still exists value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { value = new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name)); } } mv.put(name, value); } UnsafeAccessor.setType(this, t); UnsafeAccessor.setMemberValues(this, mv); } private static class UnsafeAccessor { private static final sun.misc.Unsafe unsafe; private static final long typeOffset; private static final long memberValuesOffset; static { try { unsafe = sun.misc.Unsafe.getUnsafe(); typeOffset = unsafe.objectFieldOffset (AnnotationInvocationHandler.class.getDeclaredField("type")); memberValuesOffset = unsafe.objectFieldOffset (AnnotationInvocationHandler.class.getDeclaredField("memberValues")); } catch (Exception ex) { throw new ExceptionInInitializerError(ex); } } static void setType(AnnotationInvocationHandler o, Class<? extends Annotation> type) { unsafe.putObject(o, typeOffset, type); } static void setMemberValues(AnnotationInvocationHandler o, Map<String, Object> memberValues) { unsafe.putObject(o, memberValuesOffset, memberValues); } } }