/* * 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 */ class AnnotationInvocationHandler implements InvocationHandler, Serializable { private static final long serialVersionUID = 6182022883658399397L; private final Class type; private final Map memberValues; AnnotationInvocationHandler(Class type, Map 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"); switch(member) { case "toString": return toStringImpl(); case "hashCode": return hashCodeImpl(); case "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 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() */ private String toStringImpl() { StringBuilder result = new StringBuilder(128); result.append('@'); result.append(type.getName()); result.append('('); boolean firstMember = true; for (Map.Entry 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 */ private static String memberValueToString(Object value) { if (value instanceof ExceptionProxy) { return ((ExceptionProxy)value).memberToString(); } 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(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. */ 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. */ 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). */ private Method[] getMemberMethods() { Method[] value = memberMethods; if (value == null) { value = computeMemberMethods(); memberMethods = value; } return value; } private Method[] computeMemberMethods() { return AccessController.doPrivileged( new PrivilegedAction() { public Method[] run() { final Method[] methods = type.getDeclaredMethods(); validateAnnotationMethods(methods); AccessibleObject.setAccessible(methods, true); return methods; }}); } private transient volatile Method[] memberMethods; /** * Validates that a method is structurally appropriate for an * annotation type. As of Java SE 8, 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.1. Annotation Type Elements */ 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 AnnotationTypeElementModifier * production, a method declaration in an annotation type * declaration cannot be default or static." */ if (method.getModifiers() != (Modifier.PUBLIC | Modifier.ABSTRACT) || method.isDefault() || method.getParameterCount() != 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, contrained return type, etc.) * above are: * * String toString() * int hashCode() * Class 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() */ private int hashCodeImpl() { int result = 0; for (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(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 t = (Class)fields.get("type", null); @SuppressWarnings("unchecked") Map streamVals = (Map)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> memberTypes = annotationType.memberTypes(); // consistent with runtime Map type Map mv = new LinkedHashMap<>(); // If there are annotation members without values, that // situation is handled by the invoke method. for (Map.Entry 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 jdk.internal.misc.Unsafe unsafe; private static final long typeOffset; private static final long memberValuesOffset; static { try { unsafe = jdk.internal.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 type) { unsafe.putObject(o, typeOffset, type); } static void setMemberValues(AnnotationInvocationHandler o, Map memberValues) { unsafe.putObject(o, memberValuesOffset, memberValues); } } }