< 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 >