All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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.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.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 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); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy