src/java.base/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java
changeset 47216 71c04702a3d5
parent 46873 7ac2f551b0d6
child 47436 389695e5e8db
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,634 @@
+/*
+ * 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
+ */
+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.
+     */
+    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<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.
+     */
+    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.
+     */
+    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.
+     */
+    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)
+     */
+    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.
+     */
+    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<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.
+     */
+    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()
+     */
+    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")
+     */
+    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
+                = jdk.internal.misc.Unsafe.getUnsafe();
+        private static final long typeOffset = unsafe.objectFieldOffset
+                (AnnotationInvocationHandler.class, "type");
+        private static final long memberValuesOffset = unsafe.objectFieldOffset
+                (AnnotationInvocationHandler.class, "memberValues");
+
+        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);
+        }
+    }
+}