/*
 * Copyright (c) 2003, 2016, 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.util.stream.*;
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(proxy, 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.
/** * 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 value, string, class, enum const, or annotation if (type == Class.class) return toSourceString((Class<?>) value); else if (type == String.class) return toSourceString((String) value); if (type == Character.class) return toSourceString((char) value); else if (type == Double.class) return toSourceString((double) value); else if (type == Float.class) return toSourceString((float) value); else if (type == Long.class) return toSourceString((long) value); else return value.toString(); } else { Stream<String> stringStream; if (type == byte[].class) stringStream = convert((byte[]) value); else if (type == char[].class) stringStream = convert((char[]) value); else if (type == double[].class) stringStream = DoubleStream.of((double[]) value) .mapToObj(AnnotationInvocationHandler::toSourceString); else if (type == float[].class) stringStream = convert((float[]) value); else if (type == int[].class) stringStream = IntStream.of((int[]) value).mapToObj(String::valueOf); else if (type == long[].class) { stringStream = LongStream.of((long[]) value) .mapToObj(AnnotationInvocationHandler::toSourceString); } else if (type == short[].class) stringStream = convert((short[]) value); else if (type == boolean[].class) stringStream = convert((boolean[]) value); else if (type == Class[].class) stringStream = Arrays.stream((Class<?>[]) value). map(AnnotationInvocationHandler::toSourceString); else if (type == String[].class) stringStream = Arrays.stream((String[])value). map(AnnotationInvocationHandler::toSourceString); else stringStream = Arrays.stream((Object[])value).map(Objects::toString); return stringStreamToString(stringStream); } }
Translates a Class value to a form suitable for use in the string representation of an annotation.
/** * Translates a Class value to a form suitable for use in the * string representation of an annotation. */
private static String toSourceString(Class<?> clazz) { Class<?> finalComponent = clazz; StringBuilder arrayBackets = new StringBuilder(); while(finalComponent.isArray()) { finalComponent = finalComponent.getComponentType(); arrayBackets.append("[]"); } return finalComponent.getName() + arrayBackets.toString() + ".class" ; } private static String toSourceString(float f) { if (Float.isFinite(f)) return Float.toString(f) + "f" ; else { if (Float.isInfinite(f)) { return (f < 0.0f) ? "-1.0f/0.0f": "1.0f/0.0f"; } else return "0.0f/0.0f"; } } private static String toSourceString(double d) { if (Double.isFinite(d)) return Double.toString(d); else { if (Double.isInfinite(d)) { return (d < 0.0f) ? "-1.0/0.0": "1.0/0.0"; } else return "0.0/0.0"; } } private static String toSourceString(char c) { StringBuilder sb = new StringBuilder(4); sb.append('\''); if (c == '\'') sb.append("\\'"); else sb.append(c); return sb.append('\'') .toString(); } private static String toSourceString(long ell) { String str = String.valueOf(ell); return (ell < Integer.MIN_VALUE || ell > Integer.MAX_VALUE) ? (str + 'L') : str; }
Return a string suitable for use in the string representation of an annotation.
/** * Return a string suitable for use in the string representation * of an annotation. */
private static String toSourceString(String s) { StringBuilder sb = new StringBuilder(); sb.append('"'); // Escape embedded quote characters, if present, but don't do // anything more heroic. sb.append(s.replace("\"", "\\\"")); sb.append('"'); return sb.toString(); } private static Stream<String> convert(byte[] values) { List<String> list = new ArrayList<>(values.length); for (byte b : values) list.add(Byte.toString(b)); return list.stream(); } private static Stream<String> convert(char[] values) { List<String> list = new ArrayList<>(values.length); for (char c : values) list.add(toSourceString(c)); return list.stream(); } private static Stream<String> convert(float[] values) { List<String> list = new ArrayList<>(values.length); for (float f : values) { list.add(toSourceString(f)); } return list.stream(); } private static Stream<String> convert(short[] values) { List<String> list = new ArrayList<>(values.length); for (short s : values) list.add(Short.toString(s)); return list.stream(); } private static Stream<String> convert(boolean[] values) { List<String> list = new ArrayList<>(values.length); for (boolean b : values) list.add(Boolean.toString(b)); return list.stream(); } private static String stringStreamToString(Stream<String> stream) { return stream.collect(Collectors.joining(", ", "{", "}")); }
Implementation of dynamicProxy.equals(Object o)
/** * Implementation of dynamicProxy.equals(Object o) */
private Boolean equalsImpl(Object proxy, Object o) { if (o == proxy) 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() { Method[] value = memberMethods; if (value == null) { value = computeMemberMethods(); memberMethods = value; } return value; } private Method[] computeMemberMethods() { return AccessController.doPrivileged( new PrivilegedAction<Method[]>() { 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.
/** * 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<? 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<>(); // 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 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<? extends Annotation> type) { unsafe.putObject(o, typeOffset, type); } static void setMemberValues(AnnotationInvocationHandler o, Map<String, Object> memberValues) { unsafe.putObject(o, memberValuesOffset, memberValues); } } }