< prev index next >

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

Print this page

        

*** 23,87 **** * 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) --- 23,140 ---- * questions. */ package sun.reflect.annotation; + import jdk.internal.vm.annotation.Stable; + import java.io.ObjectInputStream; ! import java.io.ObjectOutputStream; ! import java.io.ObjectStreamField; import java.io.Serializable; ! 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; ! /** ! * 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.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 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); } ! /** ! * 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, internedName); if (result instanceof ExceptionProxy) throw ((ExceptionProxy) result).generateException(); if (result.getClass().isArray() && Array.getLength(result) != 0)
*** 142,160 **** 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(); } --- 195,213 ---- StringBuilder result = new StringBuilder(128); result.append('@'); result.append(type.getName()); result.append('('); boolean firstMember = true; ! for (int i : keysIndex) { if (firstMember) firstMember = false; else result.append(", "); ! result.append(valuesTable[i]); result.append('='); ! result.append(memberValueToString(valuesTable[i + 1])); } result.append(')'); return result.toString(); }
*** 330,367 **** 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; } --- 383,440 ---- if (o == proxy) return true; if (!type.isInstance(o)) return false; ! AnnotationInvocationHandler hisHandler = asOneOfUs(o); ! ! 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 = accMember.invoke(o); ! } catch (InvocationTargetException ex) { ! if (ex.getTargetException() instanceof IncompleteAnnotationException) { ! hisValue = null; ! } else { return false; } + } catch (IllegalAccessException ex) { + throw new AssertionError(ex); } ! 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. */ ! static AnnotationInvocationHandler asOneOfUs(Object o) { if (Proxy.isProxyClass(o.getClass())) { InvocationHandler handler = Proxy.getInvocationHandler(o); if (handler instanceof AnnotationInvocationHandler) return (AnnotationInvocationHandler) handler; }
*** 375,384 **** --- 448,464 ---- * 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,543 **** 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; } /** --- 491,510 ---- assert type == boolean[].class; return Arrays.equals((boolean[]) v1, (boolean[]) v2); } /** * Implementation of dynamicProxy.hashCode() */ private int hashCodeImpl() { int result = 0; ! 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,601 **** 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(); --- 533,676 ---- 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> memberValues = (Map<String, Object>)fields.get("memberValues", null); // Check to make sure that types have not evolved incompatibly ! AnnotationType annotationType; try { annotationType = AnnotationType.getInstance(t); ! } 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(); ! ! // 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 : 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,641 **** 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); ! } } } --- 679,691 ---- value = new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name)); } } ! size = putValue(table, keysIndex, size, name, value); } ! this.type = t; ! this.valuesTable = table; ! this.keysIndex = keysIndex; } }
< prev index next >