blob: 47f3563d23c0400ca5a2aeff78b3c6894e284b0a [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.util;
import com.sun.mirror.declaration.AnnotationMirror;
import com.sun.mirror.declaration.AnnotationValue;
import com.sun.mirror.declaration.ParameterDeclaration;
import com.sun.mirror.type.AnnotationType;
import com.sun.mirror.type.ArrayType;
import com.sun.mirror.type.ClassType;
import com.sun.mirror.type.InterfaceType;
import com.sun.mirror.type.PrimitiveType;
import com.sun.mirror.type.TypeMirror;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.jdt.apt.core.internal.declaration.*;
import org.eclipse.jdt.apt.core.internal.env.BaseProcessorEnv;
import org.eclipse.jdt.apt.core.internal.type.ArrayTypeImpl;
import org.eclipse.jdt.apt.core.internal.type.ErrorType;
import org.eclipse.jdt.apt.core.internal.type.WildcardTypeImpl;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
public class Factory
{
private static final String NULL_BINDING_NAME = "[NullBinding]"; //$NON-NLS-1$
// using auto-boxing to take advantage of caching, if any.
// the dummy value picked here falls within the caching range.
public static final Byte DUMMY_BYTE = 0;
public static final Character DUMMY_CHAR = '0';
public static final Double DUMMY_DOUBLE = 0d;
public static final Float DUMMY_FLOAT = 0f;
public static final Integer DUMMY_INTEGER = 0;
public static final Long DUMMY_LONG = 0l;
public static final Short DUMMY_SHORT = 0;
public static TypeDeclarationImpl createReferenceType(ITypeBinding binding, BaseProcessorEnv env)
{
if(binding == null || binding.isNullType()) return null;
TypeDeclarationImpl mirror = null;
// must test for annotation type before interface since annotation
// is an interface
if( binding.isAnnotation() )
mirror = new AnnotationDeclarationImpl(binding, env);
else if (binding.isInterface() )
mirror = new InterfaceDeclarationImpl(binding, env);
// must test for enum first since enum is also a class.
else if( binding.isEnum() )
mirror = new EnumDeclarationImpl(binding, env);
else if( binding.isClass() )
mirror = new ClassDeclarationImpl(binding, env);
else
throw new IllegalStateException("cannot create type declaration from " + binding); //$NON-NLS-1$
return mirror;
}
public static EclipseDeclarationImpl createDeclaration(IBinding binding, BaseProcessorEnv env)
{
if(binding == null) return null;
switch(binding.getKind())
{
case IBinding.TYPE:
final ITypeBinding typeBinding = (ITypeBinding)binding;
if( typeBinding.isAnonymous() || typeBinding.isArray() ||
typeBinding.isWildcardType() || typeBinding.isPrimitive() )
throw new IllegalStateException("failed to create declaration from " + binding); //$NON-NLS-1$
if( typeBinding.isTypeVariable() )
return new TypeParameterDeclarationImpl(typeBinding, env);
else
return createReferenceType(typeBinding, env);
case IBinding.VARIABLE:
final IVariableBinding varBinding = (IVariableBinding)binding;
if(varBinding.isEnumConstant())
return new EnumConstantDeclarationImpl(varBinding, env);
else
return new FieldDeclarationImpl(varBinding, env);
case IBinding.METHOD:
final IMethodBinding method = (IMethodBinding)binding;
if( method.isConstructor() )
return new ConstructorDeclarationImpl(method, env);
final ITypeBinding declaringType = method.getDeclaringClass();
if( declaringType != null && declaringType.isAnnotation() )
return new AnnotationElementDeclarationImpl(method, env);
else
return new MethodDeclarationImpl(method, env);
default:
throw new IllegalStateException("failed to create declaration from " + binding); //$NON-NLS-1$
}
}
public static EclipseDeclarationImpl createDeclaration(
ASTNode node,
IFile file,
BaseProcessorEnv env)
{
if( node == null )
return null;
switch( node.getNodeType() )
{
case ASTNode.SINGLE_VARIABLE_DECLARATION:
return new SourceParameterDeclarationImpl((SingleVariableDeclaration)node, file, env);
case ASTNode.VARIABLE_DECLARATION_FRAGMENT:
return new ASTBasedFieldDeclarationImpl( (VariableDeclarationFragment)node, file, env );
case ASTNode.METHOD_DECLARATION :
final org.eclipse.jdt.core.dom.MethodDeclaration methodDecl =
(org.eclipse.jdt.core.dom.MethodDeclaration)node;
if( methodDecl.isConstructor() )
return new ASTBasedConstructorDeclarationImpl(methodDecl, file, env);
else
return new ASTBasedMethodDeclarationImpl(methodDecl, file, env );
case ASTNode.ANNOTATION_TYPE_MEMBER_DECLARATION:
return new ASTBasedMethodDeclarationImpl((AnnotationTypeMemberDeclaration)node, file, env);
default :
throw new UnsupportedOperationException(
"cannot create mirror type from " + //$NON-NLS-1$
node.getClass().getName() );
}
}
public static EclipseMirrorType createTypeMirror(ITypeBinding binding, BaseProcessorEnv env)
{
if( binding == null ) return null;
if( binding.isPrimitive() ){
if( "int".equals(binding.getName()) ) //$NON-NLS-1$
return env.getIntType();
else if( "byte".equals(binding.getName()) ) //$NON-NLS-1$
return env.getByteType();
else if( "short".equals(binding.getName()) ) //$NON-NLS-1$
return env.getShortType();
else if( "char".equals(binding.getName()) ) //$NON-NLS-1$
return env.getCharType();
else if( "long".equals(binding.getName()) ) //$NON-NLS-1$
return env.getLongType();
else if( "float".equals(binding.getName()) ) //$NON-NLS-1$
return env.getFloatType();
else if( "double".equals(binding.getName()) ) //$NON-NLS-1$
return env.getDoubleType();
else if( "boolean".equals(binding.getName())) //$NON-NLS-1$
return env.getBooleanType();
else if( "void".equals(binding.getName()) ) //$NON-NLS-1$
return env.getVoidType();
else
throw new IllegalStateException("unrecognized primitive type: " + binding); //$NON-NLS-1$
}
else if( binding.isArray() )
return new ArrayTypeImpl(binding, env);
else if( binding.isWildcardType() ){
return new WildcardTypeImpl(binding, env);
}
else if( binding.isTypeVariable() )
return new TypeParameterDeclarationImpl(binding, env);
else
return createReferenceType(binding, env);
}
public static ParameterDeclaration createParameterDeclaration(
final SingleVariableDeclaration param,
final IFile file,
final BaseProcessorEnv env)
{
return new SourceParameterDeclarationImpl(param, file, env);
}
public static ParameterDeclaration createParameterDeclaration(
final ExecutableDeclarationImpl exec,
final int paramIndex,
final ITypeBinding type,
final BaseProcessorEnv env )
{
return new BinaryParameterDeclarationImpl(exec, type, paramIndex, env);
}
/**
* @param annotation the ast node.
* @param annotated the declaration that <code>annotation</code> annotated
* @param env
* @return a newly created {@link AnnotationMirror} object
*/
public static AnnotationMirror createAnnotationMirror(final IAnnotationBinding annotation,
final EclipseDeclarationImpl annotated,
final BaseProcessorEnv env)
{
return new AnnotationMirrorImpl(annotation, annotated, env);
}
public static AnnotationValue createDefaultValue(
Object domValue,
AnnotationElementDeclarationImpl decl,
BaseProcessorEnv env)
{
if( domValue == null ) return null;
final Object converted = convertDOMValueToMirrorValue(
domValue, null, decl, decl, env, decl.getReturnType());
return createAnnotationValueFromDOMValue(converted, null, -1, decl, env);
}
/**
* Build an {@link AnnotationValue} object based on the given dom value.
* @param domValue default value according to the DOM API.
* @param decl the element declaration whose default value is <code>domValue</code>
* if domValue is an annotation, then this is the declaration it annotated.
* In all other case, this parameter is ignored.
* @param env
* @return an annotation value
*/
public static AnnotationValue createDefaultValue(Object domValue,
ASTBasedAnnotationElementDeclarationImpl decl,
BaseProcessorEnv env)
{
if( domValue == null ) return null;
final Object converted = convertDOMValueToMirrorValue(
domValue, null, decl, decl, env, decl.getReturnType());
return createAnnotationValueFromDOMValue(converted, null, -1, decl, env);
}
/**
* Build an {@link AnnotationValue} object based on the given dom value.
* @param domValue annotation member value according to the DOM API.
* @param elementName the name of the member value
* @param anno the annotation that directly contains <code>domValue</code>
* @param env
* @return an annotation value
*/
public static AnnotationValue createAnnotationMemberValue(Object domValue,
String elementName,
AnnotationMirrorImpl anno,
BaseProcessorEnv env,
TypeMirror expectedType)
{
if( domValue == null ) return null;
final Object converted = convertDOMValueToMirrorValue(
domValue, elementName, anno,
anno.getAnnotatedDeclaration(), env, expectedType);
return createAnnotationValueFromDOMValue(converted, elementName, -1, anno, env);
}
/**
* @param convertedValue value in mirror form.
* @param name the name of the annotation member or null for default value
* @param index the number indicate the source order of the annotation member value
* in the annotation instance.
* @param mirror either {@link AnnotationMirrorImpl } or {@link AnnotationElementDeclarationImpl}
* @param env
*/
public static AnnotationValue createAnnotationValueFromDOMValue(Object convertedValue,
String name,
int index,
EclipseMirrorObject mirror,
BaseProcessorEnv env)
{
if( convertedValue == null ) return null;
if( mirror instanceof AnnotationMirrorImpl )
return new AnnotationValueImpl(convertedValue, name, index, (AnnotationMirrorImpl)mirror, env);
else
return new AnnotationValueImpl(convertedValue, index, (AnnotationElementDeclarationImpl)mirror, env);
}
/**
* Building an annotation value object based on the dom value.
*
* @param dom the dom value to convert to the mirror specification.
* @see com.sun.mirror.declaration.AnnotationValue#getObject()
* @param name the name of the element if <code>domValue</code> is an
* element member value of an annotation
* @param parent the parent of this annotation value.
* @param decl if <code>domValue</code> is a default value, then this is the
* annotation element declaration where the default value originates
* if <code>domValue</code> is an annotation, then <code>decl</code>
* is the declaration that it annotates.
* @param expectedType the declared type of the member value.
* @param needBoxing <code>true</code> indicate an array should be returned.
* @return the converted annotation value or null if the conversion failed
*/
private static Object convertDOMValueToMirrorValue(Object domValue,
String name,
EclipseMirrorObject parent,
EclipseDeclarationImpl decl,
BaseProcessorEnv env,
TypeMirror expectedType)
{
if( domValue == null ) return null;
final Object returnValue;
if( domValue instanceof Boolean ||
domValue instanceof Byte ||
domValue instanceof Character ||
domValue instanceof Double ||
domValue instanceof Float ||
domValue instanceof Integer ||
domValue instanceof Long ||
domValue instanceof Short ||
domValue instanceof String )
returnValue = domValue;
else if( domValue instanceof IVariableBinding )
{
returnValue = Factory.createDeclaration((IVariableBinding)domValue, env);
}
else if (domValue instanceof Object[])
{
final Object[] elements = (Object[])domValue;
final int len = elements.length;
final List<AnnotationValue> annoValues = new ArrayList<AnnotationValue>(len);
final TypeMirror leaf;
if( expectedType instanceof ArrayType )
leaf = ((ArrayType)expectedType).getComponentType();
else
leaf = expectedType; // doing our best here.
for( int i=0; i<len; i++ ){
if( elements[i] == null ) continue;
// can't have multi-dimensional array.
// there should be already a java compile time error
else if( elements[i] instanceof Object[] )
return null;
Object o = convertDOMValueToMirrorValue( elements[i], name, parent, decl, env, leaf );
if( o == null )
return null;
assert( !( o instanceof IAnnotationBinding ) ) :
"Unexpected return value from convertDomValueToMirrorValue! o.getClass().getName() = " //$NON-NLS-1$
+ o.getClass().getName();
final AnnotationValue annoValue = createAnnotationValueFromDOMValue(o, name, i, parent, env);
if( annoValue != null )
annoValues.add(annoValue);
}
return annoValues;
}
// caller should have caught this case.
else if( domValue instanceof ITypeBinding )
returnValue = Factory.createTypeMirror((ITypeBinding)domValue, env);
else if( domValue instanceof IAnnotationBinding )
{
returnValue = Factory.createAnnotationMirror((IAnnotationBinding)domValue, decl, env);
}
else
// should never reach this point
throw new IllegalStateException("cannot build annotation value object from " + domValue); //$NON-NLS-1$
return performNecessaryTypeConversion(expectedType, returnValue, name, parent, env);
}
public static Object getMatchingDummyValue(final Class<?> expectedType){
if( expectedType.isPrimitive() ){
if(expectedType == boolean.class)
return Boolean.FALSE;
else if( expectedType == byte.class )
return DUMMY_BYTE;
else if( expectedType == char.class )
return DUMMY_CHAR;
else if( expectedType == double.class)
return DUMMY_DOUBLE;
else if( expectedType == float.class )
return DUMMY_FLOAT;
else if( expectedType == int.class )
return DUMMY_INTEGER;
else if( expectedType == long.class )
return DUMMY_LONG;
else if(expectedType == short.class)
return DUMMY_SHORT;
else // expectedType == void.class. can this happen?
return DUMMY_INTEGER; // anything would work
}
else
return null;
}
/**
* This method is designed to be invoke by the invocation handler and anywhere that requires
* a AnnotationValue (AnnotationMirror member values and default values from anonotation member).
*
* Regardless of the path, there are common primitive type conversion that needs to take place.
* The type conversions are respects the type widening and narrowing rules from JLS 5.1.2 and 5.1.2.
*
* The only question remains is what is the type of the return value when the type conversion fails? *
* When <code>avoidReflectException</code> is set to <code>true</code>
* Return <code>false</code> if the expected type is <code>boolean</code>
* Return numeric 0 for all numeric primitive types and '0' for <code>char</code>
*
* Otherwise:
* Return the value unchanged.
*
* In the invocation handler case:
* The value returned by {@link java.lang.reflect.InvocationHandler#invoke}
* will be converted into the expected type by the {@link java.lang.reflect.Proxy}.
* If the value and the expected type does not agree, and the value is not null,
* a ClassCastException will be thrown. A NullPointerException will be resulted if the
* expected type is a primitive type and the value is null.
* This behavior is currently causing annotation processor a lot of pain and the decision is
* to not throw such unchecked exception. In the case where a ClassCastException or
* NullPointerException will be thrown return some dummy value. Otherwise, return
* the original value.
* Chosen dummy values:
* Return <code>false</code> if the expected type is <code>boolean</code>
* Return numeric 0 for all numeric primitive types and '0' for <code>char</code>
*
* This behavior is triggered by setting <code>avoidReflectException</code> to <code>true</code>
*
* Note: the new behavior deviates from what's documented in
* {@link java.lang.reflect.InvocationHandler#invoke} and also deviates from
* Sun's implementation.
*
* see CR260743 and 260563.
* @param value the current value from the annotation instance.
* @param expectedType the expected type of the value.
*
*/
public static Object performNecessaryPrimitiveTypeConversion(
final Class<?> expectedType,
final Object value,
final boolean avoidReflectException)
{
assert expectedType.isPrimitive() : "expectedType is not a primitive type: " + expectedType.getName(); //$NON-NLS-1$
if( value == null)
return avoidReflectException ? getMatchingDummyValue(expectedType) : null;
// apply widening conversion based on JLS 5.1.2 and 5.1.3
final String typeName = expectedType.getName();
final char expectedTypeChar = typeName.charAt(0);
final int nameLen = typeName.length();
// widening byte -> short, int, long, float or double
// narrowing byte -> char
if( value instanceof Byte )
{
final byte b = ((Byte)value).byteValue();
switch( expectedTypeChar )
{
case 'b':
if(nameLen == 4) // byte
return value; // exact match.
else
return avoidReflectException ? Boolean.FALSE : value;
case 'c':
return new Character((char)b); // narrowing.
case 'd':
return new Double(b); // widening.
case 'f':
return new Float(b); // widening.
case 'i':
return new Integer(b); // widening.
case 'l':
return new Long(b); // widening.
case 's':
return new Short(b); // widening.
default:
throw new IllegalStateException("unknown type " + expectedTypeChar); //$NON-NLS-1$
}
}
// widening short -> int, long, float, or double
// narrowing short -> byte or char
else if( value instanceof Short )
{
final short s = ((Short)value).shortValue();
switch( expectedTypeChar )
{
case 'b':
if(nameLen == 4) // byte
return new Byte((byte)s); // narrowing.
else
return avoidReflectException ? Boolean.FALSE : value; // completely wrong.
case 'c':
return new Character((char)s); // narrowing.
case 'd':
return new Double(s); // widening.
case 'f':
return new Float(s); // widening.
case 'i':
return new Integer(s); // widening.
case 'l':
return new Long(s); // widening.
case 's':
return value; // exact match
default:
throw new IllegalStateException("unknown type " + expectedTypeChar); //$NON-NLS-1$
}
}
// widening char -> int, long, float, or double
// narrowing char -> byte or short
else if( value instanceof Character )
{
final char c = ((Character)value).charValue();
switch( expectedTypeChar )
{
case 'b':
if(nameLen == 4) // byte
return new Byte((byte)c); // narrowing.
else
return avoidReflectException ? Boolean.FALSE : value; // completely wrong.
case 'c':
return value; // exact match
case 'd':
return new Double(c); // widening.
case 'f':
return new Float(c); // widening.
case 'i':
return new Integer(c); // widening.
case 'l':
return new Long(c); // widening.
case 's':
return new Short((short)c); // narrowing.
default:
throw new IllegalStateException("unknown type " + expectedTypeChar); //$NON-NLS-1$
}
}
// widening int -> long, float, or double
// narrowing int -> byte, short, or char
else if( value instanceof Integer )
{
final int i = ((Integer)value).intValue();
switch( expectedTypeChar )
{
case 'b':
if(nameLen == 4) // byte
return new Byte((byte)i); // narrowing.
else
return avoidReflectException ? Boolean.FALSE : value; // completely wrong.
case 'c':
return new Character((char)i); // narrowing
case 'd':
return new Double(i); // widening.
case 'f':
return new Float(i); // widening.
case 'i':
return value; // exact match
case 'l':
return new Long(i); // widening.
case 's':
return new Short((short)i); // narrowing.
default:
throw new IllegalStateException("unknown type " + expectedTypeChar); //$NON-NLS-1$
}
}
// widening long -> float or double
else if( value instanceof Long )
{
final long l = ((Long)value).longValue();
switch( expectedTypeChar )
{
case 'b': // both byte and boolean
case 'c':
case 'i':
case 's':
// completely wrong.
return avoidReflectException ? getMatchingDummyValue(expectedType) : value;
case 'd':
return new Double(l); // widening.
case 'f':
return new Float(l); // widening.
case 'l':
return value; // exact match.
default:
throw new IllegalStateException("unknown type " + expectedTypeChar); //$NON-NLS-1$
}
}
// widening float -> double
else if( value instanceof Float )
{
final float f = ((Float)value).floatValue();
switch( expectedTypeChar )
{
case 'b': // both byte and boolean
case 'c':
case 'i':
case 's':
case 'l':
// completely wrong.
return avoidReflectException ? getMatchingDummyValue(expectedType) : value;
case 'd':
return new Double(f); // widening.
case 'f':
return value; // exact match.
default:
throw new IllegalStateException("unknown type " + expectedTypeChar); //$NON-NLS-1$
}
}
else if( value instanceof Double ){
if(expectedTypeChar == 'd' )
return value; // exact match
else{
return avoidReflectException ? getMatchingDummyValue(expectedType) : value; // completely wrong.
}
}
else if( value instanceof Boolean ){
if( expectedTypeChar == 'b' && nameLen == 7) // "boolean".length() == 7
return value;
else
return avoidReflectException ? getMatchingDummyValue(expectedType) : value; // completely wrong.
}
else // can't convert
return avoidReflectException ? getMatchingDummyValue(expectedType) : value;
}
private static Class<?> getJavaLangClass_Primitive(final PrimitiveType primitiveType){
switch( primitiveType.getKind() ){
case BOOLEAN: return boolean.class;
case BYTE: return byte.class;
case CHAR: return char.class;
case DOUBLE: return double.class;
case FLOAT: return float.class;
case INT: return int.class;
case LONG: return long.class;
case SHORT: return short.class;
default:
throw new IllegalStateException("unknow primitive type " + primitiveType ); //$NON-NLS-1$
}
}
/**
* Apply type conversion according to JLS 5.1.2 and 5.1.3 and / or auto-boxing.
* @param expectedType the expected type
* @param value the value where conversion may be applied to
* @param name name of the member value
* @param parent the of the annotation of the member value
* @param env
* @return the value matching the expected type or itself if no conversion can be applied.
*/
private static Object performNecessaryTypeConversion(final TypeMirror expectedType,
final Object value,
final String name,
final EclipseMirrorObject parent,
final BaseProcessorEnv env)
{
if( expectedType == null )return value;
if( expectedType instanceof PrimitiveType )
{
final Class<?> primitiveClass = getJavaLangClass_Primitive( (PrimitiveType)expectedType );
return performNecessaryPrimitiveTypeConversion(primitiveClass, value, false);
}
// handle auto-boxing
else if( expectedType instanceof ArrayType)
{
final TypeMirror componentType = ((ArrayType)expectedType).getComponentType();
Object converted = value;
// if it is an error case, will just leave it as is.
if( !(componentType instanceof ArrayType ) )
converted = performNecessaryTypeConversion(componentType, value, name, parent, env);
final AnnotationValue annoValue = createAnnotationValueFromDOMValue(converted, name, 0, parent, env);
return Collections.singletonList(annoValue);
}
else // no change
return value;
}
public static InterfaceType createErrorInterfaceType(final ITypeBinding binding)
{
String name = null == binding ? NULL_BINDING_NAME : binding.getName();
return new ErrorType.ErrorInterface(name);
}
public static ClassType createErrorClassType(final ITypeBinding binding)
{
String name = null == binding ? NULL_BINDING_NAME : binding.getName();
return createErrorClassType(name);
}
public static ClassType createErrorClassType(final String name)
{
return new ErrorType.ErrorClass(name);
}
public static AnnotationType createErrorAnnotationType(final ITypeBinding binding)
{
String name = null == binding ? NULL_BINDING_NAME : binding.getName();
return createErrorAnnotationType(name);
}
public static AnnotationType createErrorAnnotationType(String name)
{
return new ErrorType.ErrorAnnotation(name);
}
public static ArrayType createErrorArrayType(final String name, final int dimension)
{
return new ErrorType.ErrorArrayType(name, dimension);
}
}