| /******************************************************************************* |
| * Copyright (c) 2005, 2014 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.apt.model; |
| |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Proxy; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.element.AnnotationValue; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.MirroredTypeException; |
| import javax.lang.model.type.MirroredTypesException; |
| import javax.lang.model.type.TypeMirror; |
| |
| import org.eclipse.jdt.internal.compiler.apt.dispatch.BaseProcessingEnvImpl; |
| import org.eclipse.jdt.internal.compiler.impl.Constant; |
| import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ElementValuePair; |
| import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeIds; |
| |
| public class AnnotationMirrorImpl implements AnnotationMirror, InvocationHandler { |
| |
| public final BaseProcessingEnvImpl _env; |
| public final AnnotationBinding _binding; |
| |
| /* package */ AnnotationMirrorImpl(BaseProcessingEnvImpl env, AnnotationBinding binding) { |
| _env = env; |
| _binding = binding; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof AnnotationMirrorImpl) { |
| if (this._binding == null) { |
| return ((AnnotationMirrorImpl) obj)._binding == null; |
| } |
| return equals(this._binding, ((AnnotationMirrorImpl) obj)._binding); |
| } |
| return obj == null ? false : obj.equals(this); // obj could be wrapped by a proxy. |
| } |
| |
| private static boolean equals(AnnotationBinding annotationBinding, AnnotationBinding annotationBinding2) { |
| if (annotationBinding.getAnnotationType() != annotationBinding2.getAnnotationType()) return false; //$IDENTITY-COMPARISON$ |
| final ElementValuePair[] elementValuePairs = annotationBinding.getElementValuePairs(); |
| final ElementValuePair[] elementValuePairs2 = annotationBinding2.getElementValuePairs(); |
| final int length = elementValuePairs.length; |
| if (length != elementValuePairs2.length) return false; |
| loop: for (int i = 0; i < length; i++) { |
| ElementValuePair pair = elementValuePairs[i]; |
| // loop on the given pair to make sure one will match |
| for (int j = 0; j < length; j++) { |
| ElementValuePair pair2 = elementValuePairs2[j]; |
| if (pair.binding == pair2.binding) { |
| if (pair.value == null) { |
| if (pair2.value == null) { |
| continue loop; |
| } |
| return false; |
| } else { |
| if (pair2.value == null) return false; |
| if (pair2.value instanceof Object[] && pair.value instanceof Object[]) { |
| if (!Arrays.equals((Object[]) pair.value, (Object[]) pair2.value)) { |
| return false; |
| } |
| } else if (!pair2.value.equals(pair.value)){ |
| return false; |
| } |
| } |
| continue loop; |
| } |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| public DeclaredType getAnnotationType() { |
| return (DeclaredType) _env.getFactory().newTypeMirror(_binding.getAnnotationType()); |
| } |
| |
| /** |
| * @return all the members of this annotation mirror that have explicit values. |
| * Default values are not included. |
| */ |
| public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() { |
| if (this._binding == null) { |
| return Collections.emptyMap(); |
| } |
| ElementValuePair[] pairs = _binding.getElementValuePairs(); |
| Map<ExecutableElement, AnnotationValue> valueMap = |
| new LinkedHashMap<ExecutableElement, AnnotationValue>(pairs.length); |
| for (ElementValuePair pair : pairs) { |
| MethodBinding method = pair.getMethodBinding(); |
| if (method == null) { |
| // ideally we should be able to create a fake ExecutableElementImpl |
| continue; |
| } |
| ExecutableElement e = new ExecutableElementImpl(_env, method); |
| AnnotationValue v = new AnnotationMemberValue(_env, pair.getValue(), method); |
| valueMap.put(e, v); |
| } |
| return Collections.unmodifiableMap(valueMap); |
| } |
| |
| /** |
| * @see javax.lang.model.util.Elements#getElementValuesWithDefaults(AnnotationMirror) |
| * @return all the members of this annotation mirror that have explicit or default |
| * values. |
| */ |
| public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValuesWithDefaults() { |
| if (this._binding == null) { |
| return Collections.emptyMap(); |
| } |
| ElementValuePair[] pairs = _binding.getElementValuePairs(); |
| ReferenceBinding annoType = _binding.getAnnotationType(); |
| Map<ExecutableElement, AnnotationValue> valueMap = |
| new LinkedHashMap<ExecutableElement, AnnotationValue>(); |
| for (MethodBinding method : annoType.methods()) { |
| // if binding is in ElementValuePair list, then get value from there |
| boolean foundExplicitValue = false; |
| for (int i = 0; i < pairs.length; ++i) { |
| MethodBinding explicitBinding = pairs[i].getMethodBinding(); |
| if (method == explicitBinding) { |
| ExecutableElement e = new ExecutableElementImpl(_env, explicitBinding); |
| AnnotationValue v = new AnnotationMemberValue(_env, pairs[i].getValue(), explicitBinding); |
| valueMap.put(e, v); |
| foundExplicitValue = true; |
| break; |
| } |
| } |
| // else get default value if one exists |
| if (!foundExplicitValue) { |
| Object defaultVal = method.getDefaultValue(); |
| if (null != defaultVal) { |
| ExecutableElement e = new ExecutableElementImpl(_env, method); |
| AnnotationValue v = new AnnotationMemberValue(_env, defaultVal, method); |
| valueMap.put(e, v); |
| } |
| } |
| } |
| return Collections.unmodifiableMap(valueMap); |
| } |
| |
| public int hashCode() { |
| if (this._binding == null) return this._env.hashCode(); |
| return this._binding.hashCode(); |
| } |
| |
| /* |
| * Used by getAnnotation(), which returns a reflective proxy of the annotation class. When processors then |
| * invoke methods such as value() on the annotation proxy, this method is called. |
| * <p> |
| * A challenge here is that the processor was not necessarily compiled against the same annotation |
| * definition that the compiler is looking at right now, not to mention that the annotation itself |
| * may be defective in source. So the actual type of the value may be quite different than the |
| * type expected by the caller, which will result in a ClassCastException, which is ugly for the |
| * processor to try to catch. So we try to catch and correct this type mismatch where possible. |
| * <p> |
| * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) |
| */ |
| @Override |
| public Object invoke(Object proxy, Method method, Object[] args) throws Throwable |
| { |
| if (this._binding == null) return null; |
| final String methodName = method.getName(); |
| if ( args == null || args.length == 0 ) { |
| if( methodName.equals("hashCode") ) { //$NON-NLS-1$ |
| return new Integer( hashCode() ); |
| } |
| else if( methodName.equals("toString") ) { //$NON-NLS-1$ |
| return toString(); |
| } |
| else if( methodName.equals("annotationType")) { //$NON-NLS-1$ |
| return proxy.getClass().getInterfaces()[0]; |
| } |
| } |
| else if ( args.length == 1 && methodName.equals("equals") ) { //$NON-NLS-1$ |
| return new Boolean( equals( args[0] ) ); |
| } |
| |
| // If it's not one of the above methods, it must be an annotation member, so it cannot take any arguments |
| if ( args != null && args.length != 0 ) { |
| throw new NoSuchMethodException("method " + method.getName() + formatArgs(args) + " does not exist on annotation " + toString()); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| final MethodBinding methodBinding = getMethodBinding(methodName); |
| if ( methodBinding == null ) { |
| throw new NoSuchMethodException("method " + method.getName() + "() does not exist on annotation" + toString()); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| Object actualValue = null; |
| boolean foundMethod = false; |
| ElementValuePair[] pairs = _binding.getElementValuePairs(); |
| for (ElementValuePair pair : pairs) { |
| if (methodName.equals(new String(pair.getName()))) { |
| actualValue = pair.getValue(); |
| foundMethod = true; |
| break; |
| } |
| } |
| if (!foundMethod) { |
| // couldn't find explicit value; see if there's a default |
| actualValue = methodBinding.getDefaultValue(); |
| } |
| Class<?> expectedType = method.getReturnType(); |
| TypeBinding actualType = methodBinding.returnType; |
| return getReflectionValue(actualValue, actualType, expectedType); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * Sun implementation shows the values. We avoid that here, |
| * because getting the values is not idempotent. |
| */ |
| @Override |
| public String toString() { |
| if (this._binding == null) { |
| return "@any()"; //$NON-NLS-1$ |
| } |
| return "@" + _binding.getAnnotationType().debugName(); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Used for constructing exception message text. |
| * @return a string like "(a, b, c)". |
| */ |
| private String formatArgs(final Object[] args) |
| { |
| // estimate that each class name (plus the separators) is 10 characters long plus 2 for "()". |
| final StringBuilder builder = new StringBuilder(args.length * 8 + 2 ); |
| builder.append('('); |
| for( int i=0; i<args.length; i++ ) |
| { |
| if( i > 0 ) |
| builder.append(", "); //$NON-NLS-1$ |
| builder.append(args[i].getClass().getName()); |
| } |
| builder.append(')'); |
| return builder.toString(); |
| } |
| |
| /** |
| * Find a particular annotation member by name. |
| * @return a compiler method binding, or null if no member was found. |
| */ |
| private MethodBinding getMethodBinding(String name) { |
| ReferenceBinding annoType = _binding.getAnnotationType(); |
| MethodBinding[] methods = annoType.getMethods(name.toCharArray()); |
| for (MethodBinding method : methods) { |
| // annotation members have no parameters |
| if (method.parameters.length == 0) { |
| return method; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Convert an annotation member value from JDT into Reflection, and from whatever its actual type |
| * is into whatever type the reflective invoker of a method is expecting. |
| * <p> |
| * Only certain types are permitted as member values. Specifically, a member must be a constant, |
| * and must be either a primitive type, String, Class, an enum constant, an annotation, or an |
| * array of any of those. Multidimensional arrays are not permitted. |
| * |
| * @param actualValue the value as represented by {@link ElementValuePair#getValue()} |
| * @param actualType the return type of the corresponding {@link MethodBinding} |
| * @param expectedType the type that the reflective method invoker is expecting |
| * @return an object of the expected type representing the annotation member value, |
| * or an appropriate dummy value (such as null) if no value is available |
| */ |
| private Object getReflectionValue(Object actualValue, TypeBinding actualType, Class<?> expectedType) |
| { |
| if (null == expectedType) { |
| // With no expected type, we can't even guess at a conversion |
| return null; |
| } |
| if (null == actualValue) { |
| // Return a type-appropriate equivalent of null |
| return Factory.getMatchingDummyValue(expectedType); |
| } |
| if (expectedType.isArray()) { |
| if (Class.class.equals(expectedType.getComponentType())) { |
| // package Class[]-valued return as a MirroredTypesException |
| if (actualType.isArrayType() && actualValue instanceof Object[] && |
| ((ArrayBinding)actualType).leafComponentType.erasure().id == TypeIds.T_JavaLangClass) { |
| Object[] bindings = (Object[])actualValue; |
| List<TypeMirror> mirrors = new ArrayList<TypeMirror>(bindings.length); |
| for (int i = 0; i < bindings.length; ++i) { |
| if (bindings[i] instanceof TypeBinding) { |
| mirrors.add(_env.getFactory().newTypeMirror((TypeBinding)bindings[i])); |
| } |
| } |
| throw new MirroredTypesException(mirrors); |
| } |
| // TODO: actual value is not a TypeBinding[]. Should we return a TypeMirror[] around an ErrorType? |
| return null; |
| } |
| // Handle arrays of types other than Class, e.g., int[], MyEnum[], ... |
| return convertJDTArrayToReflectionArray(actualValue, actualType, expectedType); |
| } |
| else if (Class.class.equals(expectedType)) { |
| // package the Class-valued return as a MirroredTypeException |
| if (actualValue instanceof TypeBinding) { |
| TypeMirror mirror = _env.getFactory().newTypeMirror((TypeBinding)actualValue); |
| throw new MirroredTypeException(mirror); |
| } |
| else { |
| // TODO: actual value is not a TypeBinding. Should we return a TypeMirror around an ErrorType? |
| return null; |
| } |
| } |
| else { |
| // Handle unitary values of type other than Class, e.g., int, MyEnum, ... |
| return convertJDTValueToReflectionType(actualValue, actualType, expectedType); |
| } |
| } |
| |
| /** |
| * Convert an array of JDT types as obtained from ElementValuePair.getValue() |
| * (e.g., an Object[] containing IntConstant elements) to the type expected by |
| * a reflective method invocation (e.g., int[]). |
| * <p> |
| * This does not handle arrays of Class, but it does handle primitives, enum constants, |
| * and types such as String. |
| * @param jdtValue the actual value returned by ElementValuePair.getValue() or MethodBinding.getDefault() |
| * @param jdtType the return type of the annotation method binding |
| * @param expectedType the type that the invoker of the method is expecting; must be an array type |
| * @return an Object which is, e.g., an int[]; or null, if an array cannot be created. |
| */ |
| private Object convertJDTArrayToReflectionArray(Object jdtValue, TypeBinding jdtType, Class<?> expectedType) |
| { |
| assert null != expectedType && expectedType.isArray(); |
| if (!jdtType.isArrayType()) { |
| // the compiler says that the type binding isn't an array type; this probably means |
| // that there's some sort of syntax error. |
| return null; |
| } |
| Object[] jdtArray; |
| // See bug 261969: it's legal to pass a solo element for an array-typed value |
| if (jdtValue != null && !(jdtValue instanceof Object[])) { |
| // Create an array of the expected type |
| jdtArray = (Object[]) Array.newInstance(jdtValue.getClass(), 1); |
| jdtArray[0] = jdtValue; |
| } else { |
| jdtArray = (Object[])jdtValue; |
| } |
| TypeBinding jdtLeafType = jdtType.leafComponentType(); |
| Class<?> expectedLeafType = expectedType.getComponentType(); |
| final int length = jdtArray.length; |
| final Object returnArray = Array.newInstance(expectedLeafType, length); |
| for (int i = 0; i < length; ++i) { |
| Object jdtElementValue = jdtArray[i]; |
| if (expectedLeafType.isPrimitive() || String.class.equals(expectedLeafType)) { |
| if (jdtElementValue instanceof Constant) { |
| if (boolean.class.equals(expectedLeafType)) { |
| Array.setBoolean(returnArray, i, ((Constant)jdtElementValue).booleanValue()); |
| } |
| else if (byte.class.equals(expectedLeafType)) { |
| Array.setByte(returnArray, i, ((Constant)jdtElementValue).byteValue()); |
| } |
| else if (char.class.equals(expectedLeafType)) { |
| Array.setChar(returnArray, i, ((Constant)jdtElementValue).charValue()); |
| } |
| else if (double.class.equals(expectedLeafType)) { |
| Array.setDouble(returnArray, i, ((Constant)jdtElementValue).doubleValue()); |
| } |
| else if (float.class.equals(expectedLeafType)) { |
| Array.setFloat(returnArray, i, ((Constant)jdtElementValue).floatValue()); |
| } |
| else if (int.class.equals(expectedLeafType)) { |
| Array.setInt(returnArray, i, ((Constant)jdtElementValue).intValue()); |
| } |
| else if (long.class.equals(expectedLeafType)) { |
| Array.setLong(returnArray, i, ((Constant)jdtElementValue).longValue()); |
| } |
| else if (short.class.equals(expectedLeafType)) { |
| Array.setShort(returnArray, i, ((Constant)jdtElementValue).shortValue()); |
| } |
| else if (String.class.equals(expectedLeafType)) { |
| Array.set(returnArray, i, ((Constant)jdtElementValue).stringValue()); |
| } |
| } |
| else { |
| // Primitive or string is expected, but our actual value cannot be coerced into one. |
| // TODO: if the actual value is an array of primitives, should we unpack the first one? |
| Factory.setArrayMatchingDummyValue(returnArray, i, expectedLeafType); |
| } |
| } |
| else if (expectedLeafType.isEnum()) { |
| Object returnVal = null; |
| if (jdtLeafType != null && jdtLeafType.isEnum() && jdtElementValue instanceof FieldBinding) { |
| FieldBinding binding = (FieldBinding)jdtElementValue; |
| try { |
| Field returnedField = null; |
| returnedField = expectedLeafType.getField( new String(binding.name) ); |
| if (null != returnedField) { |
| returnVal = returnedField.get(null); |
| } |
| } |
| catch (NoSuchFieldException nsfe) { |
| // return null |
| } |
| catch (IllegalAccessException iae) { |
| // return null |
| } |
| } |
| Array.set(returnArray, i, returnVal); |
| } |
| else if (expectedLeafType.isAnnotation()) { |
| // member value is expected to be an annotation type. Wrap it in an Annotation proxy. |
| Object returnVal = null; |
| if (jdtLeafType.isAnnotationType() && jdtElementValue instanceof AnnotationBinding) { |
| AnnotationMirrorImpl annoMirror = |
| (AnnotationMirrorImpl)_env.getFactory().newAnnotationMirror((AnnotationBinding)jdtElementValue); |
| returnVal = Proxy.newProxyInstance(expectedLeafType.getClassLoader(), |
| new Class[]{ expectedLeafType }, annoMirror ); |
| } |
| Array.set(returnArray, i, returnVal); |
| } |
| else { |
| Array.set(returnArray, i, null); |
| } |
| } |
| return returnArray; |
| } |
| |
| /** |
| * Convert a JDT annotation value as obtained from ElementValuePair.getValue() |
| * (e.g., IntConstant, FieldBinding, etc.) to the type expected by a reflective |
| * method invocation (e.g., int, an enum constant, etc.). |
| * @return a value of type {@code expectedType}, or a dummy value of that type if |
| * the actual value cannot be converted. |
| */ |
| private Object convertJDTValueToReflectionType(Object jdtValue, TypeBinding actualType, Class<?> expectedType) { |
| if (expectedType.isPrimitive() || String.class.equals(expectedType)) { |
| if (jdtValue instanceof Constant) { |
| if (boolean.class.equals(expectedType)) { |
| return ((Constant)jdtValue).booleanValue(); |
| } |
| else if (byte.class.equals(expectedType)) { |
| return ((Constant)jdtValue).byteValue(); |
| } |
| else if (char.class.equals(expectedType)) { |
| return ((Constant)jdtValue).charValue(); |
| } |
| else if (double.class.equals(expectedType)) { |
| return ((Constant)jdtValue).doubleValue(); |
| } |
| else if (float.class.equals(expectedType)) { |
| return ((Constant)jdtValue).floatValue(); |
| } |
| else if (int.class.equals(expectedType)) { |
| return ((Constant)jdtValue).intValue(); |
| } |
| else if (long.class.equals(expectedType)) { |
| return ((Constant)jdtValue).longValue(); |
| } |
| else if (short.class.equals(expectedType)) { |
| return ((Constant)jdtValue).shortValue(); |
| } |
| else if (String.class.equals(expectedType)) { |
| return ((Constant)jdtValue).stringValue(); |
| } |
| } |
| // Primitive or string is expected, but our actual value cannot be coerced into one. |
| // TODO: if the actual value is an array of primitives, should we unpack the first one? |
| return Factory.getMatchingDummyValue(expectedType); |
| } |
| else if (expectedType.isEnum()) { |
| Object returnVal = null; |
| if (actualType != null && actualType.isEnum() && jdtValue instanceof FieldBinding) { |
| |
| FieldBinding binding = (FieldBinding)jdtValue; |
| try { |
| Field returnedField = null; |
| returnedField = expectedType.getField( new String(binding.name) ); |
| if (null != returnedField) { |
| returnVal = returnedField.get(null); |
| } |
| } |
| catch (NoSuchFieldException nsfe) { |
| // return null |
| } |
| catch (IllegalAccessException iae) { |
| // return null |
| } |
| } |
| return null == returnVal ? Factory.getMatchingDummyValue(expectedType) : returnVal; |
| } |
| else if (expectedType.isAnnotation()) { |
| // member value is expected to be an annotation type. Wrap it in an Annotation proxy. |
| if (actualType.isAnnotationType() && jdtValue instanceof AnnotationBinding) { |
| AnnotationMirrorImpl annoMirror = |
| (AnnotationMirrorImpl)_env.getFactory().newAnnotationMirror((AnnotationBinding)jdtValue); |
| return Proxy.newProxyInstance(expectedType.getClassLoader(), |
| new Class[]{ expectedType }, annoMirror ); |
| } |
| else { |
| // No way to cast a non-annotation value to an annotation type; return null to caller |
| return null; |
| } |
| } |
| else { |
| return Factory.getMatchingDummyValue(expectedType); |
| } |
| } |
| |
| } |