blob: dff9c000a55304e6a0e0aa7b78d7ff3f66a3efcc [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2007 BEA Systems, Inc.
* 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:
* tyeung@bea.com - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.apt.core.internal.env;
import com.sun.mirror.type.MirroredTypeException;
import com.sun.mirror.type.MirroredTypesException;
import com.sun.mirror.type.TypeMirror;
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.Collection;
import java.util.Collections;
import org.eclipse.jdt.apt.core.internal.declaration.AnnotationMirrorImpl;
import org.eclipse.jdt.apt.core.internal.util.Factory;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
public class AnnotationInvocationHandler implements InvocationHandler
{
private static final String JAVA_LANG_CLASS = "java.lang.Class"; //$NON-NLS-1$
private final AnnotationMirrorImpl _instance;
private final Class<?> _clazz;
public AnnotationInvocationHandler(final AnnotationMirrorImpl annotation,
final Class<?> clazz)
{
_instance = annotation;
_clazz = clazz;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
final String methodName = method.getName();
if( args == null || args.length == 0 )
{
if( methodName.equals("hashCode") ) //$NON-NLS-1$
return new Integer( _instance.hashCode() );
if( methodName.equals("toString") ) //$NON-NLS-1$
return _instance.toString();
if( methodName.equals("annotationType")) //$NON-NLS-1$
return _clazz;
}
else if( args.length == 1 && methodName.equals("equals") ) //$NON-NLS-1$
{
return new Boolean( _instance.equals( args[0] ) );
}
if( args != null && args.length != 0 )
throw new NoSuchMethodException("method " + method.getName() + formatArgs(args) + " does not exists"); //$NON-NLS-1$ //$NON-NLS-2$
final String c_methodName = method.getName();
final IMethodBinding methodBinding = _instance.getMethodBinding(c_methodName);
if( methodBinding == null )
throw new NoSuchMethodException("method " + method.getName() + "() does not exists"); //$NON-NLS-1$ //$NON-NLS-2$
final ITypeBinding retType = methodBinding.getReturnType();
if( retType == null ) return null;
final String qName = retType.getTypeDeclaration().getQualifiedName();
// type of annotation member is java.lang.Class
if( retType.isClass() && JAVA_LANG_CLASS.equals(qName) ){
// need to figure out the class that's being accessed
final ITypeBinding[] classTypes = _instance.getMemberValueTypeBinding(c_methodName);
TypeMirror mirrorType = null;
if( classTypes != null && classTypes.length > 0 ){
mirrorType = Factory.createTypeMirror(classTypes[0], _instance.getEnvironment() );
}
if( mirrorType == null )
mirrorType = Factory.createErrorClassType(classTypes[0]);
throw new MirroredTypeException(mirrorType);
}
else if( retType.isArray() ){
final ITypeBinding leafType = retType.getElementType();
final String leafQName = leafType.getTypeDeclaration().getQualifiedName();
// type of annotation member is java.lang.Class[]
if( leafType.isClass() && JAVA_LANG_CLASS.equals(leafQName) ){
final ITypeBinding[] classTypes = _instance.getMemberValueTypeBinding(c_methodName);
final Collection<TypeMirror> mirrorTypes;
if( classTypes == null || classTypes.length == 0 )
mirrorTypes = Collections.emptyList();
else{
mirrorTypes = new ArrayList<TypeMirror>(classTypes.length);
for( ITypeBinding type : classTypes ){
TypeMirror mirror = Factory.createTypeMirror(type, _instance.getEnvironment() );
if( mirror == null )
mirrorTypes.add(Factory.createErrorClassType(type));
else
mirrorTypes.add(mirror);
}
}
throw new MirroredTypesException(mirrorTypes);
}
}
final Object sourceValue = _instance.getValue(c_methodName);
return getReflectionValueWithTypeConversion(sourceValue, method.getReturnType());
}
private Object getReflectionValueWithTypeConversion(
final Object domValue,
final Class<?> expectedType )
{
final Object actualValue = _getReflectionValue(domValue, expectedType);
return performNecessaryTypeConversion(expectedType, actualValue);
}
private Object _getReflectionValue(final Object domValue, final Class<?> expectedType)
{
if( expectedType == null || domValue == null )
return null;
if( domValue instanceof IVariableBinding )
{
final IVariableBinding varBinding = (IVariableBinding)domValue;
final ITypeBinding declaringClass = varBinding.getDeclaringClass();
if( declaringClass != null ){
try {
final Field returnedField = expectedType.getField( varBinding.getName() );
return returnedField == null ? null : returnedField.get(null);
}
catch (NoSuchFieldException nsfe) {
return null;
}
catch (IllegalAccessException iae) {
return null;
}
}
return null;
}
else if (domValue instanceof Object[])
{
final Object[] elements = (Object[])domValue;
if(!expectedType.isArray())
return null; // bad user source
final Class<?> componentType = expectedType.getComponentType();
final int length = elements.length;
final Object array = Array.newInstance(componentType, length);
for( int i=0; i<length; i++ ){
final Object returnObj =
getReflectionValueWithTypeConversion( elements[i], componentType );
// fill in the array.
// If it is an array of some primitive type, we will need to unwrap it.
if( componentType.isPrimitive() ){
if( componentType == boolean.class ){
final Boolean bool = (Boolean)returnObj;
Array.setBoolean( array, i, bool.booleanValue());
}
else if( componentType == byte.class ){
final Byte b = (Byte)returnObj;
Array.setByte( array, i, b.byteValue() );
}
else if( componentType == char.class ){
final Character c = (Character)returnObj;
Array.setChar( array, i, c.charValue() );
}
else if( componentType == double.class ){
final Double d = (Double)returnObj;
Array.setDouble( array, i, d.doubleValue() );
}
else if( componentType == float.class ){
final Float f = (Float)returnObj;
Array.setFloat( array, i, f.floatValue() );
}
else if( componentType == int.class ){
final Integer integer = (Integer)returnObj;
Array.setInt( array, i, integer.intValue() );
}
else if( componentType == long.class ){
final Long l = (Long)returnObj;
Array.setLong( array, i, l.longValue() );
}
else if( componentType == short.class ){
final Short s = (Short)returnObj;
Array.setShort( array, i, s.shortValue() );
}
else {
throw new IllegalStateException("unrecognized primitive type: " + componentType ); //$NON-NLS-1$
}
}
else{
Array.set( array, i, returnObj );
}
}
return array;
}
// caller should have caught this case.
else if( domValue instanceof ITypeBinding )
throw new IllegalStateException("sourceValue is a type binding."); //$NON-NLS-1$
else if( domValue instanceof IAnnotationBinding )
{
// We cannot convert an annotation into anything else
if (!expectedType.isAnnotation()) {
return null;
}
final AnnotationMirrorImpl annoMirror =
(AnnotationMirrorImpl)Factory.createAnnotationMirror(
(IAnnotationBinding)domValue,
_instance.getAnnotatedDeclaration(),
_instance.getEnvironment());
final AnnotationInvocationHandler handler = new AnnotationInvocationHandler(annoMirror, expectedType);
return Proxy.newProxyInstance(expectedType.getClassLoader(),
new Class[]{ expectedType }, handler );
}
// primitive wrapper or String.
else
return domValue;
}
private Object performNecessaryTypeConversion(Class<?> expectedType, Object actualValue){
if( actualValue == null )
return Factory.getMatchingDummyValue(expectedType);
else if( expectedType.isPrimitive() )
return Factory.performNecessaryPrimitiveTypeConversion( expectedType, actualValue, true);
else if( expectedType.isAssignableFrom(actualValue.getClass()))
return actualValue;
else if( expectedType.isArray() ){
// the above assignableFrom test failed which leave up with
// the array-ificiation problem.
// arrays are always type corrected.
actualValue = performNecessaryTypeConversion(expectedType.getComponentType(), actualValue);
return arrayify(expectedType, actualValue);
}
// type conversion cannot be performed and expected type is not a primitive
// Returning null so that we don't get a ClassCastException.
else return null;
}
private Object arrayify(final Class<?> expectedType, Object actualValue){
assert expectedType.isArray() : "expected type must be an array"; //$NON-NLS-1$
assert ( !(actualValue instanceof Object[]) ) :
"actual value cannot be of type Object[]"; //$NON-NLS-1$
final Class<?> componentType = expectedType.getComponentType();
final Object array = Array.newInstance(componentType, 1);
if( componentType.isPrimitive() ){
if( componentType == boolean.class ){
final Boolean bool = (Boolean)actualValue;
Array.setBoolean( array, 0, bool.booleanValue());
}
else if( componentType == byte.class ){
final Byte b = (Byte)actualValue;
Array.setByte( array, 0, b.byteValue() );
}
else if( componentType == char.class ){
final Character c = (Character)actualValue;
Array.setChar( array, 0, c.charValue() );
}
else if( componentType == double.class ){
final Double d = (Double)actualValue;
Array.setDouble( array, 0, d.doubleValue() );
}
else if( componentType == float.class ){
final Float f = (Float)actualValue;
Array.setFloat( array, 0, f.floatValue() );
}
else if( componentType == int.class ){
final Integer integer = (Integer)actualValue;
Array.setInt( array, 0, integer.intValue() );
}
else if( componentType == long.class ){
final Long l = (Long)actualValue;
Array.setLong( array, 0, l.longValue() );
}
else if( componentType == short.class ){
final Short s = (Short)actualValue;
Array.setShort( array, 0, s.shortValue() );
}
else {
throw new IllegalStateException("unrecognized primitive type: " + componentType ); //$NON-NLS-1$
}
}
else{
Array.set( array, 0, actualValue );
}
return array;
}
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();
}
}