| /* |
| * Copyright (c) 2003, 2013, 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.lang.annotation.*; |
| import java.lang.reflect.*; |
| import java.io.Serializable; |
| import java.util.*; |
| import java.lang.annotation.*; |
| 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) { |
| 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(args[0]); |
| assert paramTypes.length == 0; |
| if (member.equals("toString")) |
| return toStringImpl(); |
| if (member.equals("hashCode")) |
| return hashCodeImpl(); |
| if (member.equals("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() { |
| StringBuffer result = new StringBuffer(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, string, class, enum const, |
| // or annotation |
| return value.toString(); |
| |
| if (type == byte[].class) |
| return Arrays.toString((byte[]) value); |
| if (type == char[].class) |
| return Arrays.toString((char[]) value); |
| if (type == double[].class) |
| return Arrays.toString((double[]) value); |
| if (type == float[].class) |
| return Arrays.toString((float[]) value); |
| if (type == int[].class) |
| return Arrays.toString((int[]) value); |
| if (type == long[].class) |
| return Arrays.toString((long[]) value); |
| if (type == short[].class) |
| return Arrays.toString((short[]) value); |
| if (type == boolean[].class) |
| return Arrays.toString((boolean[]) value); |
| return Arrays.toString((Object[]) value); |
| } |
| |
| /** |
| * Implementation of dynamicProxy.equals(Object o) |
| */ |
| private Boolean equalsImpl(Object o) { |
| if (o == this) |
| 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() { |
| if (memberMethods == null) { |
| memberMethods = AccessController.doPrivileged( |
| new PrivilegedAction<Method[]>() { |
| public Method[] run() { |
| final Method[] mm = type.getDeclaredMethods(); |
| AccessibleObject.setAccessible(mm, true); |
| return mm; |
| } |
| }); |
| } |
| return memberMethods; |
| } |
| private transient volatile Method[] memberMethods = null; |
| |
| /** |
| * 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 { |
| s.defaultReadObject(); |
| |
| |
| // Check to make sure that types have not evolved incompatibly |
| |
| AnnotationType annotationType = null; |
| try { |
| annotationType = AnnotationType.getInstance(type); |
| } 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(); |
| |
| |
| // 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(); |
| Class<?> memberType = memberTypes.get(name); |
| if (memberType != null) { // i.e. member still exists |
| Object value = memberValue.getValue(); |
| if (!(memberType.isInstance(value) || |
| value instanceof ExceptionProxy)) { |
| memberValue.setValue( |
| new AnnotationTypeMismatchExceptionProxy( |
| value.getClass() + "[" + value + "]").setMember( |
| annotationType.members().get(name))); |
| } |
| } |
| } |
| } |
| } |