Index: src/main/org/codehaus/groovy/control/ResolveVisitor.java =================================================================== --- src/main/org/codehaus/groovy/control/ResolveVisitor.java (revision 6351) +++ src/main/org/codehaus/groovy/control/ResolveVisitor.java (working copy) @@ -79,6 +79,7 @@ import org.codehaus.groovy.ast.stmt.SynchronizedStatement; import org.codehaus.groovy.ast.stmt.ThrowStatement; import org.codehaus.groovy.ast.stmt.WhileStatement; +import org.codehaus.groovy.classgen.BuiltinAnnotations; import org.codehaus.groovy.classgen.Verifier; import org.codehaus.groovy.control.messages.ExceptionMessage; import org.codehaus.groovy.syntax.Types; @@ -226,6 +227,7 @@ resolveFromDefaultImports(type,testDefaultImports) || resolveFromStaticInnerClasses(type,testStaticInnerClasses) || resolveFromClassCache(type) || + resolveFromBuiltinAnnotations(type) || resolveToClass(type) || resolveToScript(type); @@ -370,6 +372,16 @@ return false; } + private boolean resolveFromBuiltinAnnotations(ClassNode type) { + for (int i = 0; i < BuiltinAnnotations.ALL_BUILTIN_ANNOTATIONS.length; i++) { + AnnotationNode candidateBuiltin = BuiltinAnnotations.ALL_BUILTIN_ANNOTATIONS[i]; + if (candidateBuiltin.getClassNode().getName().equals(type.getName())) { + type.setRedirect(candidateBuiltin.getClassNode()); + return true; + } + } + return false; + } private void setClass(ClassNode n, Class cls) { ClassNode cn = ClassHelper.make(cls); Index: src/main/org/codehaus/groovy/ast/ClassNode.java =================================================================== --- src/main/org/codehaus/groovy/ast/ClassNode.java (revision 6351) +++ src/main/org/codehaus/groovy/ast/ClassNode.java (working copy) @@ -37,10 +37,17 @@ import groovy.lang.GroovyObject; import org.codehaus.groovy.GroovyBugError; +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.ConstructorCallExpression; import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.FieldExpression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.expr.TupleExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.EmptyStatement; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.ReturnStatement; import org.codehaus.groovy.ast.stmt.Statement; import org.objectweb.asm.Opcodes; @@ -49,6 +56,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -98,6 +106,7 @@ private List methods = new ArrayList(); private List fields = new ArrayList(); private List properties = new ArrayList(); + private boolean addedPropertyChangeSupport; private Map fieldIndex = new HashMap(); private ModuleNode module; private CompileUnit compileUnit; @@ -128,7 +137,7 @@ /** * Returns the ClassNode this ClassNode is redirecting to. */ - protected ClassNode redirect(){ + public ClassNode redirect(){ if (redirect==null) return this; return redirect.redirect(); } @@ -1023,4 +1032,107 @@ public void setGenericsTypes(GenericsType[] genericsTypes) { this.genericsTypes = genericsTypes; } + + public boolean addPropertyChangeSupport() { + //TODO look for this$propertyChangeSupport in parent and devline to add if present + if (!addedPropertyChangeSupport) { + ClassNode pcsClassNode = ClassHelper.make(java.beans.PropertyChangeSupport.class); + ClassNode pclClassNode = ClassHelper.make(java.beans.PropertyChangeListener.class); + + // add field protected static PropertyChangeSupport this$propertyChangeSupport = new java.beans.PropertyChangeSupport(this) + addField( + "this$propertyChangeSupport", + ACC_FINAL | ACC_PROTECTED | ACC_SYNTHETIC, + pcsClassNode, + new ConstructorCallExpression(pcsClassNode, + new ArgumentListExpression(new Expression[] {new VariableExpression("this")}))); + + + // add method void addPropertyChangeListener(listner) { + // this$propertyChangeSupport.addPropertyChangeListner(listener) + // } + addMethod( + new MethodNode( + "addPropertyChangeListener", + ACC_PUBLIC | ACC_SYNTHETIC, + ClassHelper.VOID_TYPE, + new Parameter[] { new Parameter(pclClassNode, "listener")}, + ClassNode.EMPTY_ARRAY, + new ExpressionStatement( + new MethodCallExpression( + new FieldExpression(getField("this$propertyChangeSupport")), + "addPropertyChangeListener", + new ArgumentListExpression( + new Expression[] {new VariableExpression("listener")}))))); + // add method void addPropertyChangeListener(name, listner) { + // this$propertyChangeSupport.addPropertyChangeListner(name, listener) + // } + addMethod( + new MethodNode( + "addPropertyChangeListener", + ACC_PUBLIC | ACC_SYNTHETIC, + ClassHelper.VOID_TYPE, + new Parameter[] { new Parameter(ClassHelper.STRING_TYPE, "name"), new Parameter(pclClassNode, "listener")}, + ClassNode.EMPTY_ARRAY, + new ExpressionStatement( + new MethodCallExpression( + new FieldExpression(getField("this$propertyChangeSupport")), + "addPropertyChangeListener", + new ArgumentListExpression( + new Expression[] {new VariableExpression("name"), new VariableExpression("listener")}))))); + + // add method boolean removePropertyChangeListener(listner) { + // return this$propertyChangeSupport.removePropertyChangeListener(listener); + // } + addMethod( + new MethodNode( + "removePropertyChangeListener", + ACC_PUBLIC | ACC_SYNTHETIC, + ClassHelper.VOID_TYPE, + new Parameter[] { new Parameter(pclClassNode, "listener")}, + ClassNode.EMPTY_ARRAY, + new ExpressionStatement( + new MethodCallExpression( + new FieldExpression(getField("this$propertyChangeSupport")), + "removePropertyChangeListener", + new ArgumentListExpression( + new Expression[] {new VariableExpression("listener")}))))); + // add method void removePropertyChangeListener(name, listner) + addMethod( + new MethodNode( + "removePropertyChangeListener", + ACC_PUBLIC | ACC_SYNTHETIC, + ClassHelper.VOID_TYPE, + new Parameter[] { new Parameter(ClassHelper.STRING_TYPE, "name"), new Parameter(pclClassNode, "listener")}, + ClassNode.EMPTY_ARRAY, + new ExpressionStatement( + new MethodCallExpression( + new FieldExpression(getField("this$propertyChangeSupport")), + "removePropertyChangeListener", + new ArgumentListExpression( + new Expression[] {new VariableExpression("name"), new VariableExpression("listener")}))))); + // add PropertyChangeSupport[] getPropertyChangeListeners() { + // return propertyChangeSupport.getPropertyChangeListeners + // } + addMethod( + new MethodNode( + "getPropertyChangeListeners", + ACC_PUBLIC | ACC_SYNTHETIC, + pclClassNode.makeArray(), + Parameter.EMPTY_ARRAY, + ClassNode.EMPTY_ARRAY, + new ReturnStatement( + new ExpressionStatement( + new MethodCallExpression( + new FieldExpression(getField("this$propertyChangeSupport")), + "getPropertyChangeListeners", + ArgumentListExpression.EMPTY_ARGUMENTS))))); + + + addedPropertyChangeSupport = true; + return true; + } else { + return false; + } + } } Index: src/main/org/codehaus/groovy/ast/AnnotationNode.java =================================================================== --- src/main/org/codehaus/groovy/ast/AnnotationNode.java (revision 6351) +++ src/main/org/codehaus/groovy/ast/AnnotationNode.java (working copy) @@ -51,6 +51,7 @@ import java.util.Map; import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.classgen.BuiltinAnnotations; /** @@ -118,11 +119,11 @@ } public boolean isBuiltIn(){ - return false; + return BuiltinAnnotations.ALL_BUILTIN_ANNOTATION_CLASS_NODES.contains(getClassNode().redirect()); } - + /** - * Flag corresponding to RetentionPolicy. + * Flag corresponding to RetentionPolicy.RUNTIME. * @return true if the annotation should be visible at runtime, * false otherwise */ @@ -142,7 +143,7 @@ public void setRuntimeRetention(boolean flag) { this.runtimeRetention = flag; } - + /** * Flag corresponding to RetentionPolicy.SOURCE. * @return true if the annotation is only allowed in sources @@ -154,11 +155,11 @@ /** Sets the internal flag if the current annotation has * RetentionPolicy.SOURCE. - */ + */ public void setSourceRetention(boolean flag) { this.sourceRetention = flag; } - + public void setAllowedTargets(int bitmap) { this.allowedTarges = bitmap; } Index: src/main/org/codehaus/groovy/classgen/Verifier.java =================================================================== --- src/main/org/codehaus/groovy/classgen/Verifier.java (revision 6351) +++ src/main/org/codehaus/groovy/classgen/Verifier.java (working copy) @@ -53,6 +53,7 @@ import java.util.Iterator; import java.util.List; +import org.codehaus.groovy.ast.AnnotationNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.CodeVisitorSupport; @@ -91,7 +92,7 @@ /** * Verifies the AST node and adds any defaulted AST code before * bytecode generation occurs. - * + * * @author James Strachan * @version $Revision$ */ @@ -115,16 +116,16 @@ */ public void visitClass(ClassNode node) { this.classNode = node; - + if ((classNode.getModifiers() & Opcodes.ACC_INTERFACE) >0) { - //interfaces have no construcotrs, but this code expects one, + //interfaces have no construcotrs, but this code expects one, //so create a dummy and don't add it to the class node ConstructorNode dummy = new ConstructorNode(0,null); addInitialization(node, dummy); node.visitContents(this); return; } - + addDefaultParameterMethods(node); addDefaultParameterConstructors(node); @@ -171,13 +172,13 @@ // don't do anything as the base class implements the invokeMethod if (!addDelegateObject) { - + VariableExpression vMethods = new VariableExpression("method"); VariableExpression vArguments = new VariableExpression("arguments"); VariableScope blockScope = new VariableScope(); blockScope.getReferencedLocalVariables().put("method",vMethods); blockScope.getReferencedLocalVariables().put("arguments",vArguments); - + node.addSyntheticMethod( "invokeMethod", ACC_PUBLIC, @@ -186,7 +187,7 @@ new Parameter(ClassHelper.STRING_TYPE, "method"), new Parameter(ClassHelper.OBJECT_TYPE, "arguments") }, - ClassNode.EMPTY_ARRAY, + ClassNode.EMPTY_ARRAY, new BlockStatement( new Statement[] { initMetaClassField, @@ -206,8 +207,8 @@ blockScope ) ); - + if (!node.isScript()) { node.addSyntheticMethod( "getProperty", @@ -234,7 +235,7 @@ blockScope = new VariableScope(); blockScope.getReferencedLocalVariables().put("property",vProp); blockScope.getReferencedLocalVariables().put("value",vValue); - + node.addSyntheticMethod( "setProperty", ACC_PUBLIC, @@ -268,7 +269,7 @@ constructor.setSynthetic(true); node.addConstructor(constructor); } - + if (!(node instanceof InnerClassNode)) {// add a static timestamp field to the class FieldNode timeTagField = new FieldNode( Verifier.__TIMESTAMP, @@ -281,11 +282,11 @@ timeTagField.setSynthetic(true); node.addField(timeTagField); } - - addInitialization(node); + checkReturnInObjectInitializer(node.getObjectInitializerStatements()); node.getObjectInitializerStatements().clear(); node.visitContents(this); + addInitialization(node); } private void checkReturnInObjectInitializer(List init) { CodeVisitorSupport cvs = new CodeVisitorSupport() { @@ -319,7 +320,7 @@ String name = expression.getName(); if (!name.equals("this") && !name.equals("super")) return; throw new RuntimeParserException("cannot reference "+name+" inside of "+type+"(....) before supertype constructor has been called",expression); - } + } }; Statement s = node.getCode(); //todo why can a statement can be null? @@ -433,13 +434,13 @@ // Implementation methods //------------------------------------------------------------------------- - + private interface DefaultArgsAction { public void call(ArgumentListExpression arguments, Parameter[] newParams, MethodNode method); } - + /** - * Creates a new helper method for each combination of default parameter expressions + * Creates a new helper method for each combination of default parameter expressions */ protected void addDefaultParameterMethods(final ClassNode node) { List methods = new ArrayList(node.getMethods()); @@ -457,7 +458,7 @@ } }); } - + protected void addDefaultParameterConstructors(final ClassNode node) { List methods = new ArrayList(node.getDeclaredConstructors()); addDefaultParameters(methods, new DefaultArgsAction(){ @@ -471,7 +472,7 @@ } /** - * Creates a new helper method for each combination of default parameter expressions + * Creates a new helper method for each combination of default parameter expressions */ protected void addDefaultParameters(List methods, DefaultArgsAction action) { for (Iterator iter = methods.iterator(); iter.hasNext();) { @@ -529,10 +530,10 @@ protected void addInitialization(ClassNode node, ConstructorNode constructorNode) { Statement firstStatement = constructorNode.getFirstStatement(); ConstructorCallExpression first = getFirstIfSpecialConstructorCall(firstStatement); - + // in case of this(...) let the other constructor do the intit if (first!=null && first.isThisCall()) return; - + List statements = new ArrayList(); List staticStatements = new ArrayList(); for (Iterator iter = node.getFields().iterator(); iter.hasNext();) { @@ -555,7 +556,7 @@ // it is super(..) since this(..) is already covered otherStatements.remove(0); statements.add(0, firstStatement); - } + } statements.addAll(otherStatements); } constructorNode.setCode(new BlockStatement(statements, block.getVariableScope())); @@ -611,6 +612,31 @@ protected Statement createSetterBlock(PropertyNode propertyNode, FieldNode field) { Expression expression = new FieldExpression(field); + + // getAnnotation(String) is indexed by the text in the code, not FQN, so we must iterate + // the problem is that the has table is created before names are fully resolved + for (Iterator iter = field.getAnnotations().values().iterator(); iter.hasNext(); ) { + AnnotationNode annotationNode = (AnnotationNode) iter.next(); + if ("groovy.lang.BoundProperty".equals(annotationNode.getClassNode().getName())) { + // found a bound expression, output bound block + field.getDeclaringClass().addPropertyChangeSupport(); + // add this$propertyChangeSupport.firePropertyChange("field", field, field = value); + return new ExpressionStatement( + new MethodCallExpression( + new FieldExpression(field.getDeclaringClass().getField("this$propertyChangeSupport")), + "firePropertyChange", + new ArgumentListExpression( + new Expression[] { + new ConstantExpression(field.getName()), + expression, + new BinaryExpression( + expression, + Token.newSymbol(Types.EQUAL, 0, 0), + new VariableExpression("value"))}))); + } + } + + // did not create bound block, create unbound block return new ExpressionStatement( new BinaryExpression(expression, Token.newSymbol(Types.EQUAL, 0, 0), new VariableExpression("value"))); } Index: src/main/org/codehaus/groovy/classgen/AnnotationVisitor.java =================================================================== --- src/main/org/codehaus/groovy/classgen/AnnotationVisitor.java (revision 6351) +++ src/main/org/codehaus/groovy/classgen/AnnotationVisitor.java (working copy) @@ -94,6 +94,14 @@ } public AnnotationNode visit(AnnotationNode node) { + // check for builtin replacements + for (int i = 0; i < BuiltinAnnotations.ALL_BUILTIN_ANNOTATIONS.length; i++) { + AnnotationNode candidateBuiltin = BuiltinAnnotations.ALL_BUILTIN_ANNOTATIONS[i]; + if (candidateBuiltin.getClassNode() == node.getClassNode()) { + return candidateBuiltin; + } + } + if(!isValidAnnotationClass(node)) { node.setValid(false); return node; @@ -271,7 +279,7 @@ } else if("SOURCE".equals(retentionPolicyEnum.toString())) { this.annotation.setSourceRetention(true); - } + } } private void initializeTarget(Class annotationClass, Class targetClass, Object targetAnnotation) { Index: src/main/org/codehaus/groovy/classgen/ExtendedVerifier.java =================================================================== --- src/main/org/codehaus/groovy/classgen/ExtendedVerifier.java (revision 6351) +++ src/main/org/codehaus/groovy/classgen/ExtendedVerifier.java (working copy) @@ -110,15 +110,21 @@ return; } + boolean builtinsOnly = true; + Collection annotations = node.getAnnotations().values(); + for(Iterator it = annotations.iterator(); builtinsOnly && it.hasNext(); ) { + AnnotationNode an = (AnnotationNode)it.next(); + builtinsOnly &= BuiltinAnnotations.ALL_BUILTIN_ANNOTATION_CLASS_NODES.contains(an.getClassNode().redirect()); + } + this.currentClass.setAnnotated(true); - if(!isAnnotationCompatible()) { + if(!isAnnotationCompatible() && !builtinsOnly) { addError("Annotations are not supported in the current runtime." + JVM_ERROR_MESSAGE, node); return; } - Collection annotations = node.getAnnotations().values(); for(Iterator it = annotations.iterator(); it.hasNext(); ) { AnnotationNode an = (AnnotationNode) it.next(); Index: build.xml =================================================================== --- build.xml (revision 6351) +++ build.xml (working copy) @@ -60,9 +60,19 @@ description="Compile the Java and Groovy code in the main source."> + + + debug="yes" source="5" target="5" fork="true" classpathref="compilePath"> + + + + + + +