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