| /* |
| * 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); |
| } |
| } |
| } |