< prev index next >

src/java.base/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java

Print this page

        

@@ -23,65 +23,118 @@
  * questions.
  */
 
 package sun.reflect.annotation;
 
+import jdk.internal.vm.annotation.Stable;
+
 import java.io.ObjectInputStream;
-import java.lang.annotation.*;
-import java.lang.reflect.*;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamField;
 import java.io.Serializable;
-import java.util.*;
-import java.util.stream.*;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.AnnotationFormatError;
+import java.lang.annotation.IncompleteAnnotationException;
+import java.lang.invoke.VarHandle;
+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.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
 
 /**
  * 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.");
+    /**
+     * Serialized pseudo-fields, provided for serialization compatibility.
+     * @serialField type The annotation type.
+     * @serialField memberValues The member values as LinkedHashMap.
+     */
+    private static final ObjectStreamField[] serialPersistentFields = {
+        new ObjectStreamField("type", Class.class),
+        new ObjectStreamField("memberValues", Map.class)
+    };
+
+    @Stable
+    private transient Class<? extends Annotation> type;
+    @Stable
+    private transient Object[] valuesTable;
+    @Stable
+    private transient int[] keysIndex;
+
+    AnnotationInvocationHandler(Class<? extends Annotation> type,
+                                Map<String, Object> memberValues) {
+
+        // there will never be an extraneous value in the given map since
+        // parser already skips values that are not members of the annotation type
+        // and so does deserialization (see readObject)
+        assert AnnotationType.getInstance(type).members().keySet()
+                             .containsAll(memberValues.keySet());
+
         this.type = type;
-        this.memberValues = memberValues;
+        this.valuesTable = toLinearProbeHashTable(
+            memberValues,
+            this.keysIndex = new int[memberValues.size()]
+        );
+
+        // analogue to "freeze" action for final fields
+        VarHandle.releaseFence();
     }
 
     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;
+        String memberName = method.getName(); // guaranteed interned String
+        Class<?> dtype = method.getDeclaringClass();
+
+        if (dtype == Object.class ||  // equals/hashCode/toString
+            dtype == Annotation.class // annotationType
+            ) {
+            if (memberName == "equals") return equalsImpl(proxy, args[0]);
+            if (memberName == "hashCode") return hashCodeImpl();
+            if (memberName == "annotationType") return type;
+            if (memberName == "toString") return toStringImpl();
+            throw new AssertionError("Invalid method: " + method);
+        }
+
+        assert dtype == type;
+        assert method.getParameterCount() == 0;
+
+        return getValue(memberName);
         }
 
-        // Handle annotation member accessors
-        Object result = memberValues.get(member);
+    /**
+     * Returns a value for given interned member name.
+     *
+     * @param internedName an interned String containing the name of the member
+     *                     to retrieve.
+     * @throws IncompleteAnnotationException if invoked for annotation not
+     *                                       containing given name.
+     */
+    Object getValue(String internedName) {
+        assert internedName.intern() == internedName;
+
+        Object result = getValue(valuesTable, internedName);
 
         if (result == null)
-            throw new IncompleteAnnotationException(type, member);
+            throw new IncompleteAnnotationException(type, internedName);
 
         if (result instanceof ExceptionProxy)
             throw ((ExceptionProxy) result).generateException();
 
         if (result.getClass().isArray() && Array.getLength(result) != 0)

@@ -142,19 +195,19 @@
         StringBuilder result = new StringBuilder(128);
         result.append('@');
         result.append(type.getName());
         result.append('(');
         boolean firstMember = true;
-        for (Map.Entry<String, Object> e : memberValues.entrySet()) {
+        for (int i : keysIndex) {
             if (firstMember)
                 firstMember = false;
             else
                 result.append(", ");
 
-            result.append(e.getKey());
+            result.append(valuesTable[i]);
             result.append('=');
-            result.append(memberValueToString(e.getValue()));
+            result.append(memberValueToString(valuesTable[i + 1]));
         }
         result.append(')');
         return result.toString();
     }
 

@@ -330,38 +383,58 @@
         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);
+
+        if (hisHandler != null) { // One Of Us
+            if (valuesTable.length != hisHandler.valuesTable.length) {
+                return false;
+            }
+            for (int i = 0; i < valuesTable.length; i+=2) {
+                String k = (String) valuesTable[i];
+                if (k != null) {
+                    Object value = valuesTable[i+1];
+                    Object hisValue = getValue(hisHandler.valuesTable, k);
+                    if (!memberValueEquals(value, hisValue)) {
+                        return false;
+                    }
+                }
+            }
             } else {
+            for (Method accMember : AnnotationType.getInstance(type)
+                                                  .accessibleMembers().values()) {
+                String k = accMember.getName(); // guaranteed interned string
+                Object value = getValue(valuesTable, k);
+                Object hisValue;
                 try {
-                    hisValue = memberMethod.invoke(o);
-                } catch (InvocationTargetException e) {
+                    hisValue = accMember.invoke(o);
+                } catch (InvocationTargetException ex) {
+                    if (ex.getTargetException() instanceof IncompleteAnnotationException) {
+                        hisValue = null;
+                    } else {
                     return false;
-                } catch (IllegalAccessException e) {
-                    throw new AssertionError(e);
                 }
+                } catch (IllegalAccessException ex) {
+                    throw new AssertionError(ex);
             }
-            if (!memberValueEquals(ourValue, hisValue))
+                if (!memberValueEquals(value, 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) {
+    static AnnotationInvocationHandler asOneOfUs(Object o) {
         if (Proxy.isProxyClass(o.getClass())) {
             InvocationHandler handler = Proxy.getInvocationHandler(o);
             if (handler instanceof AnnotationInvocationHandler)
                 return (AnnotationInvocationHandler) handler;
         }

@@ -375,10 +448,17 @@
      * 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) {
+        if (v1 == v2) {
+            return true;
+        }
+        if (v1 == null || v2 == null) {
+            return false;
+        }
+
         Class<?> type = v1.getClass();
 
         // Check for primitive, string, class, enum const, annotation,
         // or ExceptionProxy
         if (!type.isArray())

@@ -411,133 +491,20 @@
         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());
+        for (int i = 0; i < valuesTable.length; i+=2) {
+            Object k = valuesTable[i];
+            if (k != null) {
+                result += (127 * k.hashCode()) ^
+                          memberValueHashCode(valuesTable[i+1]);
+            }
         }
         return result;
     }
 
     /**

@@ -566,36 +533,144 @@
         if (type == boolean[].class)
             return Arrays.hashCode((boolean[]) value);
         return Arrays.hashCode((Object[]) value);
     }
 
+    /**
+     * Creates an open-addressing liner-probe hash table to hold given size
+     * entries.
+     */
+    private static Object[] newLinearProbeHashTable(int size) {
+        // capacity = smallest power of 2 greater than or equal to size * 3 / 2
+        int cap = Integer.highestOneBit(3 * Math.max(1, size));
+        // table length = 2 * capacity (2 slots per (key, value) entry)
+        return new Object[2 * cap];
+    }
+
+     /**
+     * Creates an open-addressing liner-probe hash table from given valuesMap
+     * and fills the given keysIndex array with a sequence of indices into the
+     * created table that point to keys in valuesMap iteration order.
+     */
+    private static Object[] toLinearProbeHashTable(Map<String, Object> valuesMap,
+                                                   int[] keysIndex) {
+        assert valuesMap.size() == keysIndex.length;
+        Object[] table = newLinearProbeHashTable(valuesMap.size());
+        int size = 0;
+        for (Map.Entry<String, Object> e : valuesMap.entrySet()) {
+            size = putValue(table, keysIndex, size, e.getKey(), e.getValue());
+        }
+        return table;
+    }
+
+    /**
+     * Inserts new (key, val) entry into given open-addressing liner-probe hash
+     * table, and adds new key index into given keysIndex at given size position.
+     * The assumption is that the table does not already contain given key.
+     * @return incremented size.
+     */
+    private static int putValue(Object[] table, int[] keysIndex, int size,
+                                String key, Object val) {
+        // keys are interned strings to speed-up lookup since we already
+        // have an interned string to match in lookup (from Method.getName())
+        key = key.intern();
+        int i = firstKeyIndex(key, table.length);
+        while (table[i] != null) {
+            assert table[i] != key;
+            i = nextKeyIndex(i, table.length);
+        }
+        keysIndex[size] = i;
+        table[i] = key;
+        table[i + 1] = val;
+        return size + 1;
+    }
+
+    /**
+     * Creates a LinkedHashMap from given open-addressing liner-probe hash table
+     * and given keysIndex array holding a sequence of indices into table
+     * in order of insertion into returned LinkedHashMap.
+     */
+    private static LinkedHashMap<String, Object> toLinkedHashMap(Object[] table,
+                                                                 int[] keysIndex) {
+        LinkedHashMap<String, Object> map = new LinkedHashMap<>(keysIndex.length + 1, 1.0f);
+        for (int i : keysIndex) {
+            map.put((String) table[i], table[i + 1]);
+        }
+        return map;
+    }
+
+    /**
+     * Looks-up a value in an open-addressing liner-probe hash table by given
+     * interned String key.
+     */
+    private static Object getValue(Object[] table, String internedKey) {
+        int i = firstKeyIndex(internedKey, table.length);
+        Object k;
+        while ((k = table[i]) != null) {
+            if (k == internedKey) {
+                return table[i+1];
+            } else {
+                i = nextKeyIndex(i, table.length);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns index for Object x.
+     */
+    private static int firstKeyIndex(Object x, int length) {
+        int h = System.identityHashCode(x);
+        // Multiply by -127, and left-shift to use least bit as part of hash
+        return ((h << 1) - (h << 8)) & (length - 1);
+    }
+
+    /**
+     * Circularly traverses table of power of 2 size length.
+     */
+    private static int nextKeyIndex(int i, int length) {
+        return (i + 2) & (length - 1);
+    }
+
+    private void writeObject(java.io.ObjectOutputStream s)
+        throws java.io.IOException {
+        ObjectOutputStream.PutField fields = s.putFields();
+        fields.put("type", type);
+        fields.put("memberValues", toLinkedHashMap(valuesTable, keysIndex));
+        s.writeFields();
+    }
+
     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);
+        Map<String, Object> memberValues = (Map<String, Object>)fields.get("memberValues", null);
 
         // Check to make sure that types have not evolved incompatibly
-
-        AnnotationType annotationType = null;
+        AnnotationType annotationType;
         try {
             annotationType = AnnotationType.getInstance(t);
-        } catch(IllegalArgumentException e) {
+        } catch(AnnotationFormatError 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<>();
+
+        // just keep values that have a corresponding member, remove the rest...
+        memberValues.keySet().retainAll(memberTypes.keySet());
+
+        Object[] table = newLinearProbeHashTable(memberValues.size());
+        int[] keysIndex = new int[memberValues.size()];
+        int size = 0;
 
         // If there are annotation members without values, that
         // situation is handled by the invoke method.
-        for (Map.Entry<String, Object> memberValue : streamVals.entrySet()) {
+        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
             String name = memberValue.getKey();
             Object value = null;
             Class<?> memberType = memberTypes.get(name);
             if (memberType != null) {  // i.e. member still exists
                 value = memberValue.getValue();

@@ -604,38 +679,13 @@
                     value = new AnnotationTypeMismatchExceptionProxy(
                             value.getClass() + "[" + value + "]").setMember(
                                 annotationType.members().get(name));
                 }
             }
-            mv.put(name, value);
-        }
-
-        UnsafeAccessor.setType(this, t);
-        UnsafeAccessor.setMemberValues(this, mv);
+            size = putValue(table, keysIndex, size, name, value);
     }
 
-    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);
-        }
+        this.type = t;
+        this.valuesTable = table;
+        this.keysIndex = keysIndex;
     }
 }
< prev index next >