Index: src/test/gls/annotations/AnnotationTest.groovy =================================================================== --- src/test/gls/annotations/AnnotationTest.groovy (revision 16713) +++ src/test/gls/annotations/AnnotationTest.groovy Tue Jun 16 22:27:16 EST 2009 @@ -362,6 +362,40 @@ println "testGetterCallWithSingletonAnnotation Done" } + void testAttributePropertyConstants() { + assertScript """ + import java.lang.annotation.* + import static Constants.* + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface Anno { + double value() default 0.0d + String[] array() default [] + } + + class Constants { + static final String FOO = "foo" + static final String BAR = "bar" + static final APPROX_PI = 3.14d + } + + class ClassWithAnnotationUsingConstant { + @Anno(array = [Constants.FOO, BAR, groovy.inspect.Inspector.GROOVY]) + public annotatedStrings + + @Anno(Math.PI) + public annotatedMath1 + @Anno(APPROX_PI) + public annotatedMath2 + } + + assert ClassWithAnnotationUsingConstant.getDeclaredField('annotatedStrings').annotations[0].array() == ['foo', 'bar', "GROOVY"] + assert ClassWithAnnotationUsingConstant.getDeclaredField('annotatedMath1').annotations[0].value() == Math.PI + assert ClassWithAnnotationUsingConstant.getDeclaredField('annotatedMath2').annotations[0].value() == Constants.APPROX_PI + """ + } + void testRuntimeRetentionAtAllLevels() { assertScript """ import java.lang.annotation.* Index: src/main/org/codehaus/groovy/control/StaticImportVisitor.java =================================================================== --- src/main/org/codehaus/groovy/control/StaticImportVisitor.java (revision 16715) +++ src/main/org/codehaus/groovy/control/StaticImportVisitor.java Tue Jun 16 22:12:02 EST 2009 @@ -21,6 +21,7 @@ import org.objectweb.asm.Opcodes; import java.util.*; +import java.lang.reflect.Field; /** * Visitor to resolve constants and method calls from static Imports @@ -39,6 +40,7 @@ private boolean inPropertyExpression; private Expression foundConstant; private Expression foundArgs; + private boolean inAnnotation; public StaticImportVisitor(CompilationUnit cu) { compilationUnit = cu; @@ -56,6 +58,13 @@ this.currentMethod = null; } + @Override + public void visitAnnotations(AnnotatedNode node) { + inAnnotation = true; + super.visitAnnotations(node); + inAnnotation = false; + } + public Expression transform(Expression exp) { if (exp == null) return null; if (exp.getClass() == VariableExpression.class) { @@ -96,6 +105,9 @@ Expression result = findStaticFieldImportFromModule(v.getName()); if (result != null) { result.setSourcePosition(ve); + if (inAnnotation) { + result = transformConstantAttributeExpression(result); + } return result; } if (!inPropertyExpression || inSpecialConstructorCall) addStaticVariableError(ve); @@ -103,6 +115,41 @@ return ve; } + // resolve constant-looking expressions statically (do here as gets transformed away later) + private Expression transformConstantAttributeExpression(Expression exp) { + if (exp instanceof PropertyExpression) { + PropertyExpression pe = (PropertyExpression) exp; + if (pe.getObjectExpression() instanceof ClassExpression) { + ClassExpression ce = (ClassExpression) pe.getObjectExpression(); + ClassNode type = ce.getType(); + if (type.isEnum()) return exp; + FieldNode fn = type.getField(pe.getPropertyAsString()); + if (fn != null && !fn.isEnum() && fn.isStatic() && fn.isFinal()) { + if (fn.getInitialValueExpression() instanceof ConstantExpression) { + return fn.getInitialValueExpression(); + } + } + try { + if (type.isResolved()) { + Field field = type.getTypeClass().getField(pe.getPropertyAsString()); + if (field != null) { + return new ConstantExpression(field.get(null)); + } + } + } catch(Exception e) {/*ignore*/} + } + } else if (exp instanceof ListExpression) { + ListExpression le = (ListExpression) exp; + ListExpression result = new ListExpression(); + for (Expression e : le.getExpressions()) { + result.addExpression(transformConstantAttributeExpression(e)); + } + return result; + } + + return exp; + } + protected Expression transformMethodCallExpression(MethodCallExpression mce) { Expression args = transform(mce.getArguments()); Expression method = transform(mce.getMethod()); Index: src/main/org/codehaus/groovy/ast/FieldNode.java =================================================================== --- src/main/org/codehaus/groovy/ast/FieldNode.java (revision 13600) +++ src/main/org/codehaus/groovy/ast/FieldNode.java Tue Jun 16 17:54:35 EST 2009 @@ -102,6 +102,20 @@ } /** + * @return true if the field is an enum + */ + public boolean isEnum() { + return (modifiers & ACC_ENUM) != 0; + } + + /** + * @return true if the field is final + */ + public boolean isFinal() { + return (modifiers & ACC_FINAL) != 0; + } + + /** * @return true if the field is public */ public boolean isPublic() { Index: src/main/org/codehaus/groovy/control/ResolveVisitor.java =================================================================== --- src/main/org/codehaus/groovy/control/ResolveVisitor.java (revision 16708) +++ src/main/org/codehaus/groovy/control/ResolveVisitor.java Tue Jun 16 22:34:57 EST 2009 @@ -34,6 +34,7 @@ import java.net.URL; import java.net.URLConnection; import java.util.*; +import java.lang.reflect.Field; /** * Visitor to resolve Types and convert VariableExpression to @@ -935,8 +936,7 @@ AnnotationNode an = (AnnotationNode) ace.getValue(); ClassNode type = an.getClassNode(); resolveOrFail(type, ", unable to find class for annotation", an); - for (Iterator iter = an.getMembers().entrySet().iterator(); iter.hasNext();) { - Map.Entry member = (Map.Entry) iter.next(); + for (Map.Entry member : an.getMembers().entrySet()) { Expression memberValue = (Expression) member.getValue(); member.setValue(transform(memberValue)); } @@ -954,20 +954,67 @@ if (an.isBuiltIn()) continue; ClassNode type = an.getClassNode(); resolveOrFail(type, ", unable to find class for annotation", an); - for (Iterator iter = an.getMembers().entrySet().iterator(); iter.hasNext();) { - Map.Entry member = (Map.Entry) iter.next(); + for (Map.Entry member : an.getMembers().entrySet()) { Expression memberValue = (Expression) member.getValue(); Expression newValue = transform(memberValue); + newValue = transformConstantAttributeExpression(newValue); member.setValue(newValue); + checkAnnotationMemberValue(newValue); + } + } + } + + // resolve constant-looking expressions statically (do here as gets transformed away later) + private Expression transformConstantAttributeExpression(Expression exp) { + if (exp instanceof PropertyExpression) { + PropertyExpression pe = (PropertyExpression) exp; + if (pe.getObjectExpression() instanceof ClassExpression) { + ClassExpression ce = (ClassExpression) pe.getObjectExpression(); + ClassNode type = ce.getType(); + if (type.isEnum()) + return exp; + + FieldNode fn = type.getField(pe.getPropertyAsString()); + if (fn != null && !fn.isEnum() && fn.isStatic() && fn.isFinal()) { + if (fn.getInitialValueExpression() instanceof ConstantExpression) { + return fn.getInitialValueExpression(); + } + } + + try { + if (type.isResolved()) { + Field field = type.getTypeClass().getField(pe.getPropertyAsString()); + if (field != null) { + return new ConstantExpression(field.get(null)); + } + } + } catch(Exception e) {/*ignore*/} + } + } else if (exp instanceof ListExpression) { + ListExpression le = (ListExpression) exp; + ListExpression result = new ListExpression(); + for (Expression e : le.getExpressions()) { + result.addExpression(transformConstantAttributeExpression(e)); + } + return result; + } + + return exp; + } + + private void checkAnnotationMemberValue(Expression newValue) { - if (newValue instanceof PropertyExpression) { - PropertyExpression pe = (PropertyExpression) newValue; - if (!(pe.getObjectExpression() instanceof ClassExpression)) { + if (newValue instanceof PropertyExpression) { + PropertyExpression pe = (PropertyExpression) newValue; + if (!(pe.getObjectExpression() instanceof ClassExpression)) { - addError("unable to find class for enum",pe.getObjectExpression()); + addError("unable to find class '" + pe.getText() + "' for annotation attribute constant", pe.getObjectExpression()); - } + } + } else if (newValue instanceof ListExpression) { + ListExpression le = (ListExpression) newValue; + for (Expression e : le.getExpressions()) { + checkAnnotationMemberValue(e); - } - } - } + } + } + } - } public void visitClass(ClassNode node) { ClassNode oldNode = currentClass;