Fixes issue 2033. Leveraged JDT's computed constant value for an expression instead of the actual written expression. Also dealt with: * Implicit conversion of primitives in the annotation value expressions. * Implicit array initializers in default annotation value expressions. * Erroneous assertions in the AnnotationProxyInvocationHandler when the return type was a primitive. Found by: tobyr Patch by: mmendez Review by: jat git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1796 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jdt/AnnotationProxyFactory.java b/dev/core/src/com/google/gwt/dev/jdt/AnnotationProxyFactory.java index c93536a..c7a9e03 100644 --- a/dev/core/src/com/google/gwt/dev/jdt/AnnotationProxyFactory.java +++ b/dev/core/src/com/google/gwt/dev/jdt/AnnotationProxyFactory.java
@@ -1,5 +1,5 @@ /* - * Copyright 2007 Google Inc. + * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -40,6 +40,40 @@ private static class AnnotationProxyInvocationHandler implements InvocationHandler { /** + * Returns <code>true</code> if the expected return type is assignable from + * the actual return type or if the expected return type is a primitive and + * the actual return type is the corresponding wrapper type. + */ + private static boolean isValidReturnType(Class<?> expectedReturnType, + Class<? extends Object> actualReturnType) { + if (expectedReturnType.isAssignableFrom(actualReturnType)) { + return true; + } + + if (expectedReturnType.isPrimitive()) { + if (expectedReturnType == boolean.class) { + return actualReturnType == Boolean.class; + } else if (expectedReturnType == byte.class) { + return actualReturnType == Byte.class; + } else if (expectedReturnType == char.class) { + return actualReturnType == Character.class; + } else if (expectedReturnType == double.class) { + return actualReturnType == Double.class; + } else if (expectedReturnType == float.class) { + return actualReturnType == Float.class; + } else if (expectedReturnType == int.class) { + return actualReturnType == Integer.class; + } else if (expectedReturnType == long.class) { + return actualReturnType == Long.class; + } else if (expectedReturnType == short.class) { + return actualReturnType == Short.class; + } + } + + return false; + } + + /** * The resolved class of this annotation. */ private Class<? extends Annotation> annotationClass; @@ -183,26 +217,30 @@ String name = method.getName(); - // See if the value was explicitly declared - Object value = identifierToValue.get(name); + Object value = null; + if (identifierToValue.containsKey(name)) { + // The value was explicitly provided + value = identifierToValue.get(name); + assert (value != null); + } else { + JMethod jMethod = annotationType.findMethod(name, new JType[0]); + if (jMethod != null) { + // The value will be the default value. + JAnnotationMethod annotationMethod = jMethod.isAnnotationMethod(); + assert (annotationMethod != null); + value = annotationMethod.getDefaultValue(); + assert (value != null); + } else if (method.getDeclaringClass() == Annotation.class + && "annotationType".equals(method.getName())) { + value = annotationClass; + } + } + if (value != null) { - assert (method.getReturnType().isAssignableFrom(value.getClass())); + assert (isValidReturnType(method.getReturnType(), value.getClass())); return value; } - // Try to find a method on the interface. - JMethod jMethod = annotationType.findMethod(name, new JType[0]); - if (jMethod != null) { - JAnnotationMethod annotationMethod = jMethod.isAnnotationMethod(); - assert (annotationMethod != null); - return annotationMethod.getDefaultValue(); - } - - if (method.getDeclaringClass() == Annotation.class - && "annotationType".equals(method.getName())) { - return annotationClass; - } - /* * Maybe it's an Object method, just delegate to myself. */
diff --git a/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java b/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java index 12056ea..2c7f5ec 100644 --- a/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java +++ b/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java
@@ -44,8 +44,8 @@ import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.dev.jdt.CacheManager.Mapper; import com.google.gwt.dev.util.Empty; -import com.google.gwt.dev.util.Util; import com.google.gwt.dev.util.PerfLogger; +import com.google.gwt.dev.util.Util; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.compiler.IProblem; @@ -60,21 +60,13 @@ import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess; import org.eclipse.jdt.internal.compiler.ast.Clinit; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; -import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; import org.eclipse.jdt.internal.compiler.ast.Expression; -import org.eclipse.jdt.internal.compiler.ast.FalseLiteral; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.Initializer; import org.eclipse.jdt.internal.compiler.ast.Javadoc; -import org.eclipse.jdt.internal.compiler.ast.MagicLiteral; import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.NameReference; -import org.eclipse.jdt.internal.compiler.ast.NullLiteral; -import org.eclipse.jdt.internal.compiler.ast.NumberLiteral; -import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation; -import org.eclipse.jdt.internal.compiler.ast.StringLiteral; -import org.eclipse.jdt.internal.compiler.ast.TrueLiteral; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeParameter; import org.eclipse.jdt.internal.compiler.ast.TypeReference; @@ -104,7 +96,6 @@ import java.io.File; import java.io.IOException; import java.lang.reflect.Array; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -301,13 +292,6 @@ } } - /** - * Returns the number associated with a JDT numeric literal. - */ - private static Object getNumericLiteralValue(NumberLiteral expression) { - return getConstantValue(expression.constant); - } - private static boolean isAnnotation(TypeDeclaration typeDecl) { if (typeDecl.kind() == IGenericType.ANNOTATION_TYPE_DECL) { return true; @@ -660,32 +644,21 @@ // Value Expression expressionValue = mvp.value; - Object value = evaluateConstantExpression(logger, expressionValue); - if (value == null) { + TypeBinding expectedElementValueType = mvp.binding.returnType; + Object elementValue = getAnnotationElementValue(logger, + expectedElementValueType, expressionValue); + if (elementValue == null) { return null; } - try { - Method method = clazz.getMethod(identifier, new Class[0]); - Class<?> expectedClass = method.getReturnType(); - Class<? extends Object> actualClass = value.getClass(); - if (expectedClass.isArray() && !actualClass.isArray()) { - /* - * JSL3 Section 9.7 single element annotations can skip the curly - * braces; means we do not get an array - */ - assert (expression instanceof SingleMemberAnnotation); - Object array = Array.newInstance(expectedClass.getComponentType(), 1); - Array.set(array, 0, value); - value = array; - } - } catch (SecurityException e) { - return null; - } catch (NoSuchMethodException e) { - return null; - } + /* + * If the expected value is supposed to be an array then the element value + * had better be an array. + */ + assert (expectedElementValueType.isArrayType() == false || expectedElementValueType.isArrayType() + && elementValue.getClass().isArray()); - identifierToValue.put(identifier, value); + identifierToValue.put(identifier, elementValue); } // Create the Annotation proxy @@ -698,43 +671,6 @@ identifierToValue); } - private Object createArrayConstant(TreeLogger logger, - ArrayInitializer arrayInitializer) { - Class<?> leafComponentClass = getClassLiteral(logger, - arrayInitializer.binding.leafComponentType); - if (leafComponentClass == null) { - return null; - } - - int[] dimensions = new int[arrayInitializer.binding.dimensions]; - - Expression[] initExpressions = arrayInitializer.expressions; - if (initExpressions != null) { - dimensions[0] = initExpressions.length; - } - - Object array = Array.newInstance(leafComponentClass, dimensions); - boolean failed = false; - if (initExpressions != null) { - for (int i = 0; i < initExpressions.length; ++i) { - Expression arrayInitExp = initExpressions[i]; - Object value = evaluateConstantExpression(logger, arrayInitExp); - if (value != null) { - Array.set(array, i, value); - } else { - failed = true; - break; - } - } - } - - if (!failed) { - return array; - } - - return null; - } - private JUpperBound createTypeParameterBounds(TreeLogger logger, TypeVariableBinding tvBinding) { TypeBinding firstBound = tvBinding.firstBound; @@ -813,69 +749,136 @@ return jtypeParamArray; } - private Object evaluateConstantExpression(TreeLogger logger, - Expression expression) { - if (expression instanceof MagicLiteral) { - if (expression instanceof FalseLiteral) { - return Boolean.FALSE; - } else if (expression instanceof NullLiteral) { - // null is not a valid annotation value; JLS Third Ed. Section 9.7 - } else if (expression instanceof TrueLiteral) { - return Boolean.TRUE; - } - } else if (expression instanceof NumberLiteral) { - Object value = getNumericLiteralValue((NumberLiteral) expression); - if (value != null) { - return value; - } - } else if (expression instanceof StringLiteral) { - StringLiteral stringLiteral = (StringLiteral) expression; - return stringLiteral.constant.stringValue(); - } else if (expression instanceof ClassLiteralAccess) { - ClassLiteralAccess classLiteral = (ClassLiteralAccess) expression; - Class<?> clazz = getClassLiteral(logger, classLiteral.targetType); - if (clazz != null) { - return clazz; - } - } else if (expression instanceof ArrayInitializer) { - Object value = createArrayConstant(logger, (ArrayInitializer) expression); - if (value != null) { - return value; - } - } else if (expression instanceof NameReference) { + /** + * Returns an annotation element value as defined in JLS 3.0 section 9.7. + * + * @param logger + * @param expectedElementValueType the expected element value type + * @param elementValueExpression the expression that defines the element value + * + * @return annotation element value as defined in JLS 3.0 section 9.7 + */ + private Object getAnnotationElementValue(TreeLogger logger, + TypeBinding expectedElementValueType, Expression elementValueExpression) { + + Object elementValue = null; + + if (elementValueExpression.constant != null + && elementValueExpression.constant != Constant.NotAConstant) { /* - * This name reference can only be to something that is a constant value, - * or an enumerated type since annotation values must be constants. + * Rely on JDT's computed constant value to deal with an + * AnnotationElementValue expression whose resolved type is a primitive or + * a string. */ - NameReference nameRef = (NameReference) expression; + Constant constant = elementValueExpression.constant; + int expectedTypeId = expectedElementValueType.id; - if (nameRef.constant != Constant.NotAConstant) { - return getConstantValue(nameRef.constant); - } else { - Class clazz = getClassLiteral(logger, nameRef.actualReceiverType); - if (clazz.isEnum()) { - String enumName = String.valueOf(nameRef.fieldBinding().name); - return Enum.valueOf(clazz, enumName); - } else { - /* - * If this is not an enumeration, then fall through to failure case - * below. - */ - } - } - } else if (expression instanceof Annotation) { - Object annotationInstance = createAnnotationInstance(logger, expression); - if (annotationInstance != null) { - return annotationInstance; - } - } else if (expression instanceof ConditionalExpression) { - ConditionalExpression condExpression = (ConditionalExpression) expression; - assert (condExpression.constant != Constant.NotAConstant); + if (expectedElementValueType.isArrayType()) { + /* + * This can happen when an element value is an array with a single + * element. In this case JLS 3.0 section 9.7 allows for the + * ArrayInitializer expression to be implicit. Since, element values can + * only be single dimensional arrays, we take the leaf type of the + * expected array type as our resultant element value type. + */ + assert (!elementValueExpression.resolvedType.isArrayType() && expectedElementValueType.dimensions() == 1); - return getConstantValue(condExpression.constant); + expectedTypeId = expectedElementValueType.leafComponentType().id; + } + + if (elementValueExpression.resolvedType.id != expectedTypeId) { + /* + * Narrowing and widening conversions are handled by the following + * Constant.castTo call. JDT wants the upper four bits of this mask to + * be the target type id and the lower four bits to be the source type + * id. See Constant.castTo for more details. + */ + constant = constant.castTo((expectedTypeId << 4) + + elementValueExpression.resolvedType.id); + } + + elementValue = getConstantValue(constant); + } else if (elementValueExpression instanceof ClassLiteralAccess) { + ClassLiteralAccess classLiteral = (ClassLiteralAccess) elementValueExpression; + elementValue = getClassLiteral(logger, classLiteral.targetType); + } else if (elementValueExpression instanceof ArrayInitializer) { + elementValue = getAnnotationElementValueArray(logger, + (ArrayInitializer) elementValueExpression); + } else if (elementValueExpression instanceof NameReference) { + /* + * Any primitive types, conditionals, strings, arrays and name references + * to constant fields will have all been handled by the constant + * expression block above. This name reference can only be for an + * enumerated type. + */ + NameReference nameRef = (NameReference) elementValueExpression; + + assert (nameRef.constant == null || nameRef.constant == Constant.NotAConstant); + assert (nameRef.actualReceiverType.isEnum()); + + Class<?> clazz = getClassLiteral(logger, nameRef.actualReceiverType); + Class<? extends Enum> enumClass = clazz.asSubclass(Enum.class); + + String enumName = String.valueOf(nameRef.fieldBinding().name); + elementValue = Enum.valueOf(enumClass, enumName); + } else if (elementValueExpression instanceof Annotation) { + elementValue = createAnnotationInstance(logger, elementValueExpression); + } else { + assert (false); + return null; } - assert (false); + assert (elementValue != null); + + if (expectedElementValueType.isArrayType() + && !elementValue.getClass().isArray()) { + /* + * Handle single element arrays where no explicit array initializer was + * given. + */ + Object array = Array.newInstance(elementValue.getClass(), 1); + Array.set(array, 0, elementValue); + elementValue = array; + } + + return elementValue; + } + + /** + * Returns an annotation element value array. These arrays can only have a + * single dimension. + */ + private Object getAnnotationElementValueArray(TreeLogger logger, + ArrayInitializer arrayInitializer) { + assert (arrayInitializer.binding.dimensions == 1); + + Class<?> leafComponentClass = getClassLiteral(logger, + arrayInitializer.binding.leafComponentType); + if (leafComponentClass == null) { + return null; + } + + Expression[] initExpressions = arrayInitializer.expressions; + int arrayLength = initExpressions != null ? initExpressions.length : 0; + + Object array = Array.newInstance(leafComponentClass, arrayLength); + boolean failed = false; + for (int i = 0; i < arrayLength; ++i) { + Expression arrayInitExp = initExpressions[i]; + Object value = getAnnotationElementValue(logger, + arrayInitializer.binding.leafComponentType, arrayInitExp); + if (value != null) { + Array.set(array, i, value); + } else { + failed = true; + break; + } + } + + if (!failed) { + return array; + } + return null; } @@ -1209,7 +1212,8 @@ AnnotationMethodDeclaration annotationMethod = (AnnotationMethodDeclaration) jmethod; Object defaultValue = null; if (annotationMethod.defaultValue != null) { - defaultValue = evaluateConstantExpression(logger, + defaultValue = getAnnotationElementValue(logger, + annotationMethod.returnType.resolvedType, annotationMethod.defaultValue); } method = new JAnnotationMethod(enclosingType, name, declStart, declEnd,
diff --git a/dev/core/test/com/google/gwt/core/ext/typeinfo/TypeOracleAnnotationSupportTest.java b/dev/core/test/com/google/gwt/core/ext/typeinfo/TypeOracleAnnotationSupportTest.java index 3158eb9..6da80ab 100644 --- a/dev/core/test/com/google/gwt/core/ext/typeinfo/TypeOracleAnnotationSupportTest.java +++ b/dev/core/test/com/google/gwt/core/ext/typeinfo/TypeOracleAnnotationSupportTest.java
@@ -20,6 +20,8 @@ import com.google.gwt.core.ext.typeinfo.test.AnnotatedClass; import com.google.gwt.core.ext.typeinfo.test.ClassAnnotatedWithBinaryOnlyAnnotation; import com.google.gwt.core.ext.typeinfo.test.ClassLiteralReferenceAnnotation; +import com.google.gwt.core.ext.typeinfo.test.PrimitiveValuesAnnotation; +import com.google.gwt.core.ext.typeinfo.test.PrimitivesAnnotatedClass; import com.google.gwt.core.ext.typeinfo.test.TestAnnotation; import com.google.gwt.dev.cfg.ModuleDef; import com.google.gwt.dev.cfg.ModuleDefLoader; @@ -73,6 +75,17 @@ // Tests default value that is a class literal. assertEquals(realAnnotation.classLiteral(), testAnnotation.classLiteral()); + + // Tests implicit array initializers + Class<?>[] expectedArrayValue = realAnnotation.arrayWithImplicitArrayInitializer(); + Class<?>[] actualArrayValue = testAnnotation.arrayWithImplicitArrayInitializer(); + assertTrue(expectedArrayValue.length > 0); + assertEquals(expectedArrayValue.length, actualArrayValue.length); + assertEquals(expectedArrayValue[0], actualArrayValue[0]); + + // Tests that empty array initializers are also handled correctly. + assertEquals(realAnnotation.emptyArray().length, + testAnnotation.emptyArray().length); } private final TreeLogger logger = TreeLogger.NULL; @@ -191,4 +204,37 @@ assertNull(classType.getAnnotation(BinaryOnlyAnnotation.class)); } + + /** + * Ensures that we properly handle primitive value attributes on annotations. + * (Special-cased, because of how JDT handles char/numeric literals) + */ + public void testPrimitiveValuesAnnotations() throws NotFoundException { + JClassType primitivesAnnotatedClass = typeOracle.getType(PrimitivesAnnotatedClass.class.getName()); + PrimitiveValuesAnnotation annotation = primitivesAnnotatedClass.getAnnotation(PrimitiveValuesAnnotation.class); + + boolean bool = annotation.bool(); + assertTrue(bool); + + byte b = annotation.b(); + assertTrue(b > 0); + + char c = annotation.c(); + assertTrue(c > 0); + + double d = annotation.d(); + assertTrue(d > 0); + + float f = annotation.f(); + assertTrue(f > 0); + + int i = annotation.i(); + assertTrue(i > 0); + + long l = annotation.l(); + assertTrue(l > 0); + + short s = annotation.s(); + assertTrue(s > 0); + } }
diff --git a/dev/core/test/com/google/gwt/core/ext/typeinfo/test/PrimitiveValuesAnnotation.java b/dev/core/test/com/google/gwt/core/ext/typeinfo/test/PrimitiveValuesAnnotation.java new file mode 100644 index 0000000..22fd71f --- /dev/null +++ b/dev/core/test/com/google/gwt/core/ext/typeinfo/test/PrimitiveValuesAnnotation.java
@@ -0,0 +1,48 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.core.ext.typeinfo.test; + +import java.io.Serializable; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Test annotation containing primitive values. + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface PrimitiveValuesAnnotation { + boolean bool() default true; + + byte b() default 0; + + char c() default '\0'; + + short s() default 0; + + int i() default 0; + + long l() default 0; + + float f() default 0; + + double d() default 0; + + int[] ia() default {}; +}
diff --git a/dev/core/test/com/google/gwt/core/ext/typeinfo/test/PrimitivesAnnotatedClass.java b/dev/core/test/com/google/gwt/core/ext/typeinfo/test/PrimitivesAnnotatedClass.java new file mode 100644 index 0000000..bd4b377 --- /dev/null +++ b/dev/core/test/com/google/gwt/core/ext/typeinfo/test/PrimitivesAnnotatedClass.java
@@ -0,0 +1,28 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.core.ext.typeinfo.test; + +/** + * Test class for annotations with primitive values. + * + */ +@PrimitiveValuesAnnotation(b = PrimitivesAnnotatedClass.byteAsInt, + c = (byte) 12, s = 'a', i = (short) 1452, + l = 12345, f = (byte) 15, d = 123412312L) +public class PrimitivesAnnotatedClass { + static final int byteAsInt = 123; +} +
diff --git a/dev/core/test/com/google/gwt/core/ext/typeinfo/test/TestAnnotation.java b/dev/core/test/com/google/gwt/core/ext/typeinfo/test/TestAnnotation.java index bb88bfc..e776051 100644 --- a/dev/core/test/com/google/gwt/core/ext/typeinfo/test/TestAnnotation.java +++ b/dev/core/test/com/google/gwt/core/ext/typeinfo/test/TestAnnotation.java
@@ -1,5 +1,5 @@ /* - * Copyright 2007 Google Inc. + * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -33,16 +33,6 @@ ElementType.TYPE}) public @interface TestAnnotation { /** - * Default value for the annotation. - */ - String value(); - - /** - * Tests element default values that are themselves annotations. - */ - NestedAnnotation nestedAnnotation() default @NestedAnnotation("Not assigned"); - - /** * Used to test initialization using conditional statements. */ boolean useMinLong = true; @@ -51,11 +41,21 @@ * Used to test initialization using SingleNameReferences. */ String defaultStringValue = "Hello There"; + + /** + * Tests that implicit array initializers are handled correctly. + */ + Class<?>[] arrayWithImplicitArrayInitializer() default Object.class; + + /** + * Tests default value initialization of class literals. + */ + Class<?> classLiteral() default Object.class; /** - * Tests default values using conditional statements. + * Tests that an empty array initializer is handled correctly. */ - long longValue() default useMinLong ? Long.MIN_VALUE : Long.MAX_VALUE; + int[] emptyArray() default {}; /** * Tests array default values. @@ -63,13 +63,23 @@ int[] intArrayValue() default {1,2,3}; /** + * Tests default values using conditional statements. + */ + long longValue() default useMinLong ? Long.MIN_VALUE : Long.MAX_VALUE; + + /** + * Tests element default values that are themselves annotations. + */ + NestedAnnotation nestedAnnotation() default @NestedAnnotation("Not assigned"); + + /** * Tests default value initialization via a QualifiedNameReference. */ String stringValue() default TestAnnotation.defaultStringValue; - + /** - * Tests default value initialization of class literals. + * Default value for the annotation. */ - Class<?> classLiteral() default Object.class; + String value(); }