org.eclipse.jdt.internal.compiler.apt.model.AnnotationMirrorImpl Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2005, 2011 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.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 false;
}
private static boolean equals(AnnotationBinding annotationBinding, AnnotationBinding annotationBinding2) {
if (annotationBinding.getAnnotationType() != annotationBinding2.getAnnotationType()) return false;
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
|| !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 getElementValues() {
if (this._binding == null) {
return Collections.emptyMap();
}
ElementValuePair[] pairs = _binding.getElementValuePairs();
Map valueMap =
new LinkedHashMap(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 getElementValuesWithDefaults() {
if (this._binding == null) {
return Collections.emptyMap();
}
ElementValuePair[] pairs = _binding.getElementValuePairs();
ReferenceBinding annoType = _binding.getAnnotationType();
Map valueMap =
new LinkedHashMap();
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.
*
* 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.
*
* @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 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.
*
* 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 mirrors = new ArrayList(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[]).
*
* 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);
}
}
}