/******************************************************************************* * Copyright (c) 2005, 2016 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 Integer.valueOf( _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 Boolean.valueOf(_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<>(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(); } }