You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2017/12/20 00:56:41 UTC

[44/49] groovy git commit: Move source files to proper packages

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/beans/BindableASTTransformation.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/beans/BindableASTTransformation.java b/src/main/groovy/groovy/beans/BindableASTTransformation.java
new file mode 100644
index 0000000..e1bf2e5
--- /dev/null
+++ b/src/main/groovy/groovy/beans/BindableASTTransformation.java
@@ -0,0 +1,428 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 groovy.beans;
+
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.ast.tools.PropertyNodeUtils;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.messages.SimpleMessage;
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.runtime.MetaClassHelper;
+import org.codehaus.groovy.syntax.SyntaxException;
+import org.codehaus.groovy.transform.ASTTransformation;
+import org.codehaus.groovy.transform.GroovyASTTransformation;
+import org.objectweb.asm.Opcodes;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+
+/**
+ * Handles generation of code for the {@code @Bindable} annotation when {@code @Vetoable}
+ * is not present.
+ * <p>
+ * Generally, it adds (if needed) a PropertyChangeSupport field and
+ * the needed add/removePropertyChangeListener methods to support the
+ * listeners.
+ * <p>
+ * It also generates the setter and wires the setter through the
+ * PropertyChangeSupport.
+ * <p>
+ * If a {@link Vetoable} annotation is detected it does nothing and
+ * lets the {@link VetoableASTTransformation} handle all the changes.
+ *
+ * @author Danno Ferrin (shemnon)
+ * @author Chris Reeves
+ */
+@GroovyASTTransformation(phase= CompilePhase.CANONICALIZATION)
+public class BindableASTTransformation implements ASTTransformation, Opcodes {
+
+    protected static final ClassNode boundClassNode = ClassHelper.make(Bindable.class);
+
+    /**
+     * Convenience method to see if an annotated node is {@code @Bindable}.
+     *
+     * @param node the node to check
+     * @return true if the node is bindable
+     */
+    public static boolean hasBindableAnnotation(AnnotatedNode node) {
+        for (AnnotationNode annotation : node.getAnnotations()) {
+            if (boundClassNode.equals(annotation.getClassNode())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Handles the bulk of the processing, mostly delegating to other methods.
+     *
+     * @param nodes   the ast nodes
+     * @param source  the source unit for the nodes
+     */
+    public void visit(ASTNode[] nodes, SourceUnit source) {
+        if (!(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) {
+            throw new RuntimeException("Internal error: wrong types: $node.class / $parent.class");
+        }
+        AnnotationNode node = (AnnotationNode) nodes[0];
+        AnnotatedNode parent = (AnnotatedNode) nodes[1];
+
+        if (VetoableASTTransformation.hasVetoableAnnotation(parent)) {
+            // VetoableASTTransformation will handle both @Bindable and @Vetoable
+            return;
+        }
+
+        ClassNode declaringClass = parent.getDeclaringClass();
+        if (parent instanceof FieldNode) {
+            if ((((FieldNode) parent).getModifiers() & Opcodes.ACC_FINAL) != 0) {
+                source.getErrorCollector().addErrorAndContinue(new SyntaxErrorMessage(
+                        new SyntaxException("@groovy.beans.Bindable cannot annotate a final property.",
+                                node.getLineNumber(), node.getColumnNumber(), node.getLastLineNumber(), node.getLastColumnNumber()),
+                        source));
+            }
+
+            if (VetoableASTTransformation.hasVetoableAnnotation(parent.getDeclaringClass())) {
+                // VetoableASTTransformation will handle both @Bindable and @Vetoable
+                return;
+            }
+            addListenerToProperty(source, node, declaringClass, (FieldNode) parent);
+        } else if (parent instanceof ClassNode) {
+            addListenerToClass(source, (ClassNode) parent);
+        }
+    }
+
+    private void addListenerToProperty(SourceUnit source, AnnotationNode node, ClassNode declaringClass, FieldNode field) {
+        String fieldName = field.getName();
+        for (PropertyNode propertyNode : declaringClass.getProperties()) {
+            if (propertyNode.getName().equals(fieldName)) {
+                if (field.isStatic()) {
+                    //noinspection ThrowableInstanceNeverThrown
+                    source.getErrorCollector().addErrorAndContinue(new SyntaxErrorMessage(
+                            new SyntaxException("@groovy.beans.Bindable cannot annotate a static property.",
+                                    node.getLineNumber(), node.getColumnNumber(), node.getLastLineNumber(), node.getLastColumnNumber()),
+                            source));
+                } else {
+                    if (needsPropertyChangeSupport(declaringClass, source)) {
+                        addPropertyChangeSupport(declaringClass);
+                    }
+                    createListenerSetter(declaringClass, propertyNode);
+                }
+                return;
+            }
+        }
+        //noinspection ThrowableInstanceNeverThrown
+        source.getErrorCollector().addErrorAndContinue(new SyntaxErrorMessage(
+                new SyntaxException("@groovy.beans.Bindable must be on a property, not a field.  Try removing the private, protected, or public modifier.",
+                        node.getLineNumber(), node.getColumnNumber(), node.getLastLineNumber(), node.getLastColumnNumber()),
+                source));
+    }
+
+    private void addListenerToClass(SourceUnit source, ClassNode classNode) {
+        if (needsPropertyChangeSupport(classNode, source)) {
+            addPropertyChangeSupport(classNode);
+        }
+        for (PropertyNode propertyNode : classNode.getProperties()) {
+            FieldNode field = propertyNode.getField();
+            // look to see if per-field handlers will catch this one...
+            if (hasBindableAnnotation(field)
+                || ((field.getModifiers() & Opcodes.ACC_FINAL) != 0)
+                || field.isStatic()
+                || VetoableASTTransformation.hasVetoableAnnotation(field))
+            {
+                // explicitly labeled properties are already handled,
+                // don't transform final properties
+                // don't transform static properties
+                // VetoableASTTransformation will handle both @Bindable and @Vetoable
+                continue;
+            }
+            createListenerSetter(classNode, propertyNode);
+        }
+    }
+
+    /*
+     * Wrap an existing setter.
+     */
+    private static void wrapSetterMethod(ClassNode classNode, String propertyName) {
+        String getterName = "get" + MetaClassHelper.capitalize(propertyName);
+        MethodNode setter = classNode.getSetterMethod("set" + MetaClassHelper.capitalize(propertyName));
+
+        if (setter != null) {
+            // Get the existing code block
+            Statement code = setter.getCode();
+
+            Expression oldValue = varX("$oldValue");
+            Expression newValue = varX("$newValue");
+            BlockStatement block = new BlockStatement();
+
+            // create a local variable to hold the old value from the getter
+            block.addStatement(declS(oldValue, callThisX(getterName)));
+
+            // call the existing block, which will presumably set the value properly
+            block.addStatement(code);
+
+            // get the new value to emit in the event
+            block.addStatement(declS(newValue, callThisX(getterName)));
+
+            // add the firePropertyChange method call
+            block.addStatement(stmt(callThisX("firePropertyChange", args(constX(propertyName), oldValue, newValue))));
+
+            // replace the existing code block with our new one
+            setter.setCode(block);
+        }
+    }
+
+    private void createListenerSetter(ClassNode classNode, PropertyNode propertyNode) {
+        String setterName = "set" + MetaClassHelper.capitalize(propertyNode.getName());
+        if (classNode.getMethods(setterName).isEmpty()) {
+            Statement setterBlock = createBindableStatement(propertyNode, fieldX(propertyNode.getField()));
+
+            // create method void <setter>(<type> fieldName)
+            createSetterMethod(classNode, propertyNode, setterName, setterBlock);
+        } else {
+            wrapSetterMethod(classNode, propertyNode.getName());
+        }
+    }
+
+    /**
+     * Creates a statement body similar to:
+     * <code>this.firePropertyChange("field", field, field = value)</code>
+     *
+     * @param propertyNode           the field node for the property
+     * @param fieldExpression a field expression for setting the property value
+     * @return the created statement
+     */
+    protected Statement createBindableStatement(PropertyNode propertyNode, Expression fieldExpression) {
+        // create statementBody
+        return stmt(callThisX("firePropertyChange", args(constX(propertyNode.getName()), fieldExpression, assignX(fieldExpression, varX("value")))));
+    }
+
+    /**
+     * Creates a setter method with the given body.
+     *
+     * @param declaringClass the class to which we will add the setter
+     * @param propertyNode          the field to back the setter
+     * @param setterName     the name of the setter
+     * @param setterBlock    the statement representing the setter block
+     */
+    protected void createSetterMethod(ClassNode declaringClass, PropertyNode propertyNode, String setterName, Statement setterBlock) {
+        MethodNode setter = new MethodNode(
+                setterName,
+                PropertyNodeUtils.adjustPropertyModifiersForMethod(propertyNode),
+                ClassHelper.VOID_TYPE,
+                params(param(propertyNode.getType(), "value")),
+                ClassNode.EMPTY_ARRAY,
+                setterBlock);
+        setter.setSynthetic(true);
+        // add it to the class
+        declaringClass.addMethod(setter);
+    }
+
+    /**
+     * Snoops through the declaring class and all parents looking for methods
+     * <code>void addPropertyChangeListener(PropertyChangeListener)</code>,
+     * <code>void removePropertyChangeListener(PropertyChangeListener)</code>, and
+     * <code>void firePropertyChange(String, Object, Object)</code>. If any are defined all
+     * must be defined or a compilation error results.
+     *
+     * @param declaringClass the class to search
+     * @param sourceUnit the source unit, for error reporting. {@code @NotNull}.
+     * @return true if property change support should be added
+     */
+    protected boolean needsPropertyChangeSupport(ClassNode declaringClass, SourceUnit sourceUnit) {
+        boolean foundAdd = false, foundRemove = false, foundFire = false;
+        ClassNode consideredClass = declaringClass;
+        while (consideredClass!= null) {
+            for (MethodNode method : consideredClass.getMethods()) {
+                // just check length, MOP will match it up
+                foundAdd = foundAdd || method.getName().equals("addPropertyChangeListener") && method.getParameters().length == 1;
+                foundRemove = foundRemove || method.getName().equals("removePropertyChangeListener") && method.getParameters().length == 1;
+                foundFire = foundFire || method.getName().equals("firePropertyChange") && method.getParameters().length == 3;
+                if (foundAdd && foundRemove && foundFire) {
+                    return false;
+                }
+            }
+            consideredClass = consideredClass.getSuperClass();
+        }
+        // check if a super class has @Bindable annotations
+        consideredClass = declaringClass.getSuperClass();
+        while (consideredClass!=null) {
+            if (hasBindableAnnotation(consideredClass)) return false;
+            for (FieldNode field : consideredClass.getFields()) {
+                if (hasBindableAnnotation(field)) return false;
+            }
+            consideredClass = consideredClass.getSuperClass();
+        }
+        if (foundAdd || foundRemove || foundFire) {
+            sourceUnit.getErrorCollector().addErrorAndContinue(
+                new SimpleMessage("@Bindable cannot be processed on "
+                    + declaringClass.getName()
+                    + " because some but not all of addPropertyChangeListener, removePropertyChange, and firePropertyChange were declared in the current or super classes.",
+                sourceUnit)
+            );
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Adds the necessary field and methods to support property change support.
+     * <p>
+     * Adds a new field:
+     * <pre>
+     * <code>protected final java.beans.PropertyChangeSupport this$PropertyChangeSupport = new java.beans.PropertyChangeSupport(this)</code>"
+     * </pre>
+     * <p>
+     * Also adds support methods:
+     * <pre>
+     * <code>public void addPropertyChangeListener(java.beans.PropertyChangeListener)</code>
+     * <code>public void addPropertyChangeListener(String, java.beans.PropertyChangeListener)</code>
+     * <code>public void removePropertyChangeListener(java.beans.PropertyChangeListener)</code>
+     * <code>public void removePropertyChangeListener(String, java.beans.PropertyChangeListener)</code>
+     * <code>public java.beans.PropertyChangeListener[] getPropertyChangeListeners()</code>
+     * </pre>
+     *
+     * @param declaringClass the class to which we add the support field and methods
+     */
+    protected void addPropertyChangeSupport(ClassNode declaringClass) {
+        ClassNode pcsClassNode = ClassHelper.make(PropertyChangeSupport.class);
+        ClassNode pclClassNode = ClassHelper.make(PropertyChangeListener.class);
+        //String pcsFieldName = "this$propertyChangeSupport";
+
+        // add field:
+        // protected final PropertyChangeSupport this$propertyChangeSupport = new java.beans.PropertyChangeSupport(this)
+        FieldNode pcsField = declaringClass.addField(
+                "this$propertyChangeSupport",
+                ACC_FINAL | ACC_PRIVATE | ACC_SYNTHETIC,
+                pcsClassNode,
+                ctorX(pcsClassNode, args(varX("this"))));
+
+        // add method:
+        // void addPropertyChangeListener(listener) {
+        //     this$propertyChangeSupport.addPropertyChangeListener(listener)
+        //  }
+        declaringClass.addMethod(
+                new MethodNode(
+                        "addPropertyChangeListener",
+                        ACC_PUBLIC,
+                        ClassHelper.VOID_TYPE,
+                        params(param(pclClassNode, "listener")),
+                        ClassNode.EMPTY_ARRAY,
+                        stmt(callX(fieldX(pcsField), "addPropertyChangeListener", args(varX("listener", pclClassNode))))));
+
+        // add method:
+        // void addPropertyChangeListener(name, listener) {
+        //     this$propertyChangeSupport.addPropertyChangeListener(name, listener)
+        //  }
+        declaringClass.addMethod(
+                new MethodNode(
+                        "addPropertyChangeListener",
+                        ACC_PUBLIC,
+                        ClassHelper.VOID_TYPE,
+                        params(param(ClassHelper.STRING_TYPE, "name"), param(pclClassNode, "listener")),
+                        ClassNode.EMPTY_ARRAY,
+                        stmt(callX(fieldX(pcsField), "addPropertyChangeListener", args(varX("name", ClassHelper.STRING_TYPE), varX("listener", pclClassNode))))));
+
+        // add method:
+        // boolean removePropertyChangeListener(listener) {
+        //    return this$propertyChangeSupport.removePropertyChangeListener(listener);
+        // }
+        declaringClass.addMethod(
+                new MethodNode(
+                        "removePropertyChangeListener",
+                        ACC_PUBLIC,
+                        ClassHelper.VOID_TYPE,
+                        params(param(pclClassNode, "listener")),
+                        ClassNode.EMPTY_ARRAY,
+                        stmt(callX(fieldX(pcsField), "removePropertyChangeListener", args(varX("listener", pclClassNode))))));
+
+        // add method: void removePropertyChangeListener(name, listener)
+        declaringClass.addMethod(
+                new MethodNode(
+                        "removePropertyChangeListener",
+                        ACC_PUBLIC,
+                        ClassHelper.VOID_TYPE,
+                        params(param(ClassHelper.STRING_TYPE, "name"), param(pclClassNode, "listener")),
+                        ClassNode.EMPTY_ARRAY,
+                        stmt(callX(fieldX(pcsField), "removePropertyChangeListener", args(varX("name", ClassHelper.STRING_TYPE), varX("listener", pclClassNode))))));
+
+        // add method:
+        // void firePropertyChange(String name, Object oldValue, Object newValue) {
+        //     this$propertyChangeSupport.firePropertyChange(name, oldValue, newValue)
+        //  }
+        declaringClass.addMethod(
+                new MethodNode(
+                        "firePropertyChange",
+                        ACC_PUBLIC,
+                        ClassHelper.VOID_TYPE,
+                        params(param(ClassHelper.STRING_TYPE, "name"), param(ClassHelper.OBJECT_TYPE, "oldValue"), param(ClassHelper.OBJECT_TYPE, "newValue")),
+                        ClassNode.EMPTY_ARRAY,
+                        stmt(callX(fieldX(pcsField), "firePropertyChange", args(varX("name", ClassHelper.STRING_TYPE), varX("oldValue"), varX("newValue"))))));
+
+        // add method:
+        // PropertyChangeListener[] getPropertyChangeListeners() {
+        //   return this$propertyChangeSupport.getPropertyChangeListeners
+        // }
+        declaringClass.addMethod(
+                new MethodNode(
+                        "getPropertyChangeListeners",
+                        ACC_PUBLIC,
+                        pclClassNode.makeArray(),
+                        Parameter.EMPTY_ARRAY,
+                        ClassNode.EMPTY_ARRAY,
+                        returnS(callX(fieldX(pcsField), "getPropertyChangeListeners"))));
+
+        // add method:
+        // PropertyChangeListener[] getPropertyChangeListeners(String name) {
+        //   return this$propertyChangeSupport.getPropertyChangeListeners(name)
+        // }
+        declaringClass.addMethod(
+                new MethodNode(
+                        "getPropertyChangeListeners",
+                        ACC_PUBLIC,
+                        pclClassNode.makeArray(),
+                        params(param(ClassHelper.STRING_TYPE, "name")),
+                        ClassNode.EMPTY_ARRAY,
+                        returnS(callX(fieldX(pcsField), "getPropertyChangeListeners", args(varX("name", ClassHelper.STRING_TYPE))))));
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/beans/DefaultPropertyAccessor.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/beans/DefaultPropertyAccessor.java b/src/main/groovy/groovy/beans/DefaultPropertyAccessor.java
new file mode 100644
index 0000000..47dae41
--- /dev/null
+++ b/src/main/groovy/groovy/beans/DefaultPropertyAccessor.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 groovy.beans;
+
+/**
+ * @author Andres Almiray
+ */
+public class DefaultPropertyAccessor implements PropertyAccessor {
+    public static final PropertyAccessor INSTANCE = new DefaultPropertyAccessor();
+
+    public Object read(Object owner, String propertyName) {
+        return DefaultPropertyReader.INSTANCE.read(owner, propertyName);
+    }
+
+    public void write(Object owner, String propertyName, Object value) {
+        DefaultPropertyWriter.INSTANCE.write(owner, propertyName, value);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/beans/DefaultPropertyReader.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/beans/DefaultPropertyReader.java b/src/main/groovy/groovy/beans/DefaultPropertyReader.java
new file mode 100644
index 0000000..a03b9a3
--- /dev/null
+++ b/src/main/groovy/groovy/beans/DefaultPropertyReader.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 groovy.beans;
+
+import org.codehaus.groovy.runtime.InvokerHelper;
+
+/**
+ * @author Andres Almiray
+ */
+public class DefaultPropertyReader implements PropertyReader {
+    public static final PropertyReader INSTANCE = new DefaultPropertyReader();
+
+    public Object read(Object owner, String propertyName) {
+        return InvokerHelper.getPropertySafe(owner, propertyName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/beans/DefaultPropertyWriter.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/beans/DefaultPropertyWriter.java b/src/main/groovy/groovy/beans/DefaultPropertyWriter.java
new file mode 100644
index 0000000..12ac7db
--- /dev/null
+++ b/src/main/groovy/groovy/beans/DefaultPropertyWriter.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 groovy.beans;
+
+import org.codehaus.groovy.runtime.InvokerHelper;
+
+/**
+ * @author Andres Almiray
+ */
+public class DefaultPropertyWriter implements PropertyWriter {
+    public static final PropertyWriter INSTANCE = new DefaultPropertyWriter();
+
+    public void write(Object owner, String propertyName, Object value) {
+        InvokerHelper.setProperty(owner, propertyName, value);
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/beans/ListenerList.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/beans/ListenerList.groovy b/src/main/groovy/groovy/beans/ListenerList.groovy
new file mode 100644
index 0000000..b8119f1
--- /dev/null
+++ b/src/main/groovy/groovy/beans/ListenerList.groovy
@@ -0,0 +1,131 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 groovy.beans
+
+import org.codehaus.groovy.transform.GroovyASTTransformationClass
+
+import java.lang.annotation.Documented
+import java.lang.annotation.ElementType
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import java.lang.annotation.Target
+
+/**
+ * This annotation adds Java-style listener support to a class based on an annotated Collection-property.
+ * <p>
+ * For any given Collection property, several methods will be written into the enclosing class during the compile phase. These
+ * changes are visible from Java or other languages. The List is intended to hold listeners of some sort, and the methods
+ * addListener, removeListener, and getListeners are all added to the class. The actual methods names depend on the generic
+ * type of the collection.
+ * <p>
+ * Given the following example:<br>
+ * <pre>
+ * class MyClass {
+ *     &#064;groovy.beans.ListenerList
+ *     List&lt;java.awt.event.ActionListener&gt; listeners
+ * }
+ * </pre>
+ * The following code is generated:
+ * <pre>
+ * public class MyClass extends java.lang.Object {
+ *     &#064;groovy.beans.ListenerList
+ *     private java.util.List&lt;java.awt.event.ActionListener&gt; listeners
+ *
+ *     public void addActionListener(java.awt.event.ActionListener listener) {
+ *         if ( listener == null) {
+ *             return null
+ *         }
+ *         if ( listeners == null) {
+ *             listeners = []
+ *         }
+ *         listeners.add(listener)
+ *     }
+ *
+ *     public void removeActionListener(java.awt.event.ActionListener listener) {
+ *         if ( listener == null) {
+ *             return null
+ *         }
+ *         if ( listeners == null) {
+ *             listeners = []
+ *         }
+ *         listeners.remove(listener)
+ *     }
+ *
+ *     public java.awt.event.ActionListener[] getActionListeners() {
+ *         java.lang.Object __result = []
+ *         if ( listeners != null) {
+ *             __result.addAll(listeners)
+ *         }
+ *         return (( __result ) as java.awt.event.ActionListener[])
+ *     }
+ *
+ *     public void fireActionPerformed(java.awt.event.ActionEvent param0) {
+ *         if ( listeners != null) {
+ *             def __list = new java.util.ArrayList(listeners)
+ *             for (java.lang.Object listener : __list ) {
+ *                 listener.actionPerformed(param0)
+ *             }
+ *         }
+ *     }
+ * }
+ * </pre>
+ * A fire method is created for each public method in the target class. In this case, ActionListener only has one
+ * method. For a four method interface, four fire methods would be created.
+ * <p>
+ * The annotation can take the following parameters:
+ * <pre>
+ * name        = a suffix for creating the add, remove, and get methods.
+ *               Default: Name of the listener type
+ *               In the above example, if name is set to MyListener, then the class will have an addMyListener,
+ *               removeMyListener, and getMyListeners methods. 
+ *
+ * synchronize = Whether or not the methods created should be synchronized at the method level. 
+ *               Default: false
+ * </pre>
+ * <p>
+ * <strong>Compilation Errors</strong> - Using this annotation incorrectly results in compilation errors rather
+ * than runtime errors. A list of potential problems includes:
+ * <ul>
+ * <li>This annotation can only be applied to a field of type Collection</li>
+ * <li>The annotated Collection field must have a generic type</li>
+ * <li>The annotated Collection field must not have a generic wildcard declared</li>
+ * <li>The generated methods must not already exist</li>
+ * </ul>
+ *
+ * @see ListenerListASTTransformation
+ * @author Alexander Klein
+ * @author Hamlet D'Arcy
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.FIELD)
+@GroovyASTTransformationClass('groovy.beans.ListenerListASTTransformation')
+@interface ListenerList {
+    /**
+     * A suffix for creating the add, remove, and get methods
+     * defaulting to the name of the listener type, e.g. if name is set to MyListener,
+     * then the class will have addMyListener, removeMyListener, and getMyListeners methods.
+     */
+    String name() default ""
+
+    /**
+     * Whether or not the methods created should be synchronized at the method level.
+     */
+    boolean synchronize() default false
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/beans/ListenerListASTTransformation.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/beans/ListenerListASTTransformation.groovy b/src/main/groovy/groovy/beans/ListenerListASTTransformation.groovy
new file mode 100644
index 0000000..1d7fbf7
--- /dev/null
+++ b/src/main/groovy/groovy/beans/ListenerListASTTransformation.groovy
@@ -0,0 +1,384 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 groovy.beans
+
+import org.codehaus.groovy.ast.ASTNode
+import org.codehaus.groovy.ast.AnnotatedNode
+import org.codehaus.groovy.ast.AnnotationNode
+import org.codehaus.groovy.ast.ClassHelper
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.FieldNode
+import org.codehaus.groovy.ast.GenericsType
+import org.codehaus.groovy.ast.MethodNode
+import org.codehaus.groovy.ast.Parameter
+import org.codehaus.groovy.ast.VariableScope
+import org.codehaus.groovy.ast.expr.ArgumentListExpression
+import org.codehaus.groovy.ast.expr.BinaryExpression
+import org.codehaus.groovy.ast.expr.BooleanExpression
+import org.codehaus.groovy.ast.expr.CastExpression
+import org.codehaus.groovy.ast.expr.ConstantExpression
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression
+import org.codehaus.groovy.ast.expr.DeclarationExpression
+import org.codehaus.groovy.ast.expr.ListExpression
+import org.codehaus.groovy.ast.expr.MethodCallExpression
+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.ForStatement
+import org.codehaus.groovy.ast.stmt.IfStatement
+import org.codehaus.groovy.ast.stmt.ReturnStatement
+import org.codehaus.groovy.control.CompilePhase
+import org.codehaus.groovy.control.SourceUnit
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage
+import org.codehaus.groovy.syntax.SyntaxException
+import org.codehaus.groovy.syntax.Token
+import org.codehaus.groovy.syntax.Types
+import org.codehaus.groovy.transform.ASTTransformation
+import org.codehaus.groovy.transform.GroovyASTTransformation
+import org.objectweb.asm.Opcodes
+
+/**
+ * Handles generation of code for the {@code @ListenerList} annotation.
+ * <p>
+ * Generally, it adds the needed add&lt;Listener&gt;, remove&lt;Listener&gt; and
+ * get&lt;Listener&gt;s methods to support the Java Beans API.
+ * <p>
+ * Additionally it adds corresponding fire&lt;Event&gt; methods.
+ * <p>
+ *
+ * @author Alexander Klein
+ * @author Hamlet D'Arcy
+ */
+@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
+class ListenerListASTTransformation implements ASTTransformation, Opcodes {
+    private static final Class MY_CLASS = groovy.beans.ListenerList.class
+    private static final ClassNode COLLECTION_TYPE = ClassHelper.make(Collection)
+
+    public void visit(ASTNode[] nodes, SourceUnit source) {
+        if (!(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) {
+            throw new RuntimeException("Internal error: wrong types: ${node.class} / ${parent.class}")
+        }
+        AnnotationNode node = nodes[0]
+        FieldNode field = nodes[1]
+        ClassNode declaringClass = nodes[1].declaringClass
+        ClassNode parentClass = field.type
+
+        boolean isCollection = parentClass.isDerivedFrom(COLLECTION_TYPE) || parentClass.implementsInterface(COLLECTION_TYPE)
+
+        if (!isCollection) {
+            addError(node, source, '@' + MY_CLASS.name + ' can only annotate collection properties.')
+            return
+        }
+
+        def types = field.type.genericsTypes
+        if (!types) {
+            addError(node, source, '@' + MY_CLASS.name + ' fields must have a generic type.')
+            return
+        }
+
+        if (types[0].wildcard) {
+            addError(node, source, '@' + MY_CLASS.name + ' fields with generic wildcards not yet supported.')
+            return
+        }
+
+        def listener = types[0].type
+
+        if (!field.initialValueExpression) {
+            field.initialValueExpression = new ListExpression()
+        }
+
+        def name = node.getMember('name')?.value ?: listener.nameWithoutPackage
+
+        def fireList = listener.methods.findAll { MethodNode m ->
+            m.isPublic() && !m.isSynthetic() && !m.isStatic()
+        }
+
+        def synchronize = node.getMember('synchronize')?.value ?: false
+        addAddListener(source, node, declaringClass, field, listener, name, synchronize)
+        addRemoveListener(source, node, declaringClass, field, listener, name, synchronize)
+        addGetListeners(source, node, declaringClass, field, listener, name, synchronize)
+
+        fireList.each { MethodNode method ->
+            addFireMethods(source, node, declaringClass, field, types, synchronize, method)
+        }
+    }
+
+    private static def addError(AnnotationNode node, SourceUnit source, String message) {
+        source.errorCollector.addError(
+                new SyntaxErrorMessage(new SyntaxException(
+                        message,
+                        node.lineNumber,
+                        node.columnNumber),
+                        source))
+    }
+
+    /**
+     * Adds the add&lt;Listener&gt; method like:
+     * <pre>
+     * synchronized void add${name.capitalize}(${listener.name} listener) {
+     *     if (listener == null)
+     *         return
+     *     if (${field.name} == null)
+     *        ${field.name} = []
+     *     ${field.name}.add(listener)
+     * }
+     * </pre>
+     */
+    void addAddListener(SourceUnit source, AnnotationNode node, ClassNode declaringClass, FieldNode field, ClassNode listener, String name, synchronize) {
+
+        def methodModifiers = synchronize ? ACC_PUBLIC | ACC_SYNCHRONIZED : ACC_PUBLIC
+        def methodReturnType = ClassHelper.make(Void.TYPE)
+        def methodName = "add${name.capitalize()}"
+        def cn = ClassHelper.makeWithoutCaching(listener.name)
+        cn.redirect = listener
+        def methodParameter = [new Parameter(cn,'listener')] as Parameter[]
+
+        if (declaringClass.hasMethod(methodName, methodParameter)) {
+            addError node, source, "Conflict using @${MY_CLASS.name}. Class $declaringClass.name already has method $methodName"
+            return
+        }
+
+        BlockStatement block = new BlockStatement()
+        block.addStatements([
+                new IfStatement(
+                        new BooleanExpression(
+                                new BinaryExpression(
+                                        new VariableExpression('listener'),
+                                        Token.newSymbol(Types.COMPARE_EQUAL, 0, 0),
+                                        ConstantExpression.NULL
+                                )
+                        ),
+                        new ReturnStatement(ConstantExpression.NULL),
+                        EmptyStatement.INSTANCE
+                ),
+                new IfStatement(
+                        new BooleanExpression(
+                                new BinaryExpression(
+                                        new VariableExpression(field.name),
+                                        Token.newSymbol(Types.COMPARE_EQUAL, 0, 0),
+                                        ConstantExpression.NULL
+                                )
+                        ),
+                        new ExpressionStatement(
+                                new BinaryExpression(
+                                        new VariableExpression(field.name),
+                                        Token.newSymbol(Types.EQUAL, 0, 0),
+                                        new ListExpression()
+                                )
+                        ),
+                        EmptyStatement.INSTANCE
+                ),
+                new ExpressionStatement(
+                        new MethodCallExpression(new VariableExpression(field.name), new ConstantExpression('add'), new ArgumentListExpression(new VariableExpression('listener')))
+                )
+        ])
+        declaringClass.addMethod(new MethodNode(methodName, methodModifiers, methodReturnType, methodParameter, [] as ClassNode[], block))
+    }
+
+    /**
+     * Adds the remove<Listener> method like:
+     * <pre>
+     * synchronized void remove${name.capitalize}(${listener.name} listener) {
+     *     if (listener == null)
+     *         return
+     *     if (${field.name} == null)
+     *         ${field.name} = []
+     *     ${field.name}.remove(listener)
+     * }
+     * </pre>
+     */
+    void addRemoveListener(SourceUnit source, AnnotationNode node, ClassNode declaringClass, FieldNode field, ClassNode listener, String name, synchronize) {
+        def methodModifiers = synchronize ? ACC_PUBLIC | ACC_SYNCHRONIZED : ACC_PUBLIC
+        def methodReturnType = ClassHelper.make(Void.TYPE)
+        def methodName = "remove${name.capitalize()}"
+        def cn = ClassHelper.makeWithoutCaching(listener.name)
+        cn.redirect = listener
+        def methodParameter = [new Parameter(cn,'listener')] as Parameter[]
+
+        if (declaringClass.hasMethod(methodName, methodParameter)) {
+            addError node, source, "Conflict using @${MY_CLASS.name}. Class $declaringClass.name already has method $methodName"
+            return
+        }
+
+        BlockStatement block = new BlockStatement()
+        block.addStatements([
+                new IfStatement(
+                        new BooleanExpression(
+                                new BinaryExpression(
+                                        new VariableExpression('listener'),
+                                        Token.newSymbol(Types.COMPARE_EQUAL, 0, 0),
+                                        ConstantExpression.NULL
+                                )
+                        ),
+                        new ReturnStatement(ConstantExpression.NULL),
+                        EmptyStatement.INSTANCE
+                ),
+                new IfStatement(
+                        new BooleanExpression(
+                                new BinaryExpression(
+                                        new VariableExpression(field.name),
+                                        Token.newSymbol(Types.COMPARE_EQUAL, 0, 0),
+                                        ConstantExpression.NULL
+                                )
+                        ),
+                        new ExpressionStatement(
+                                new BinaryExpression(
+                                        new VariableExpression(field.name),
+                                        Token.newSymbol(Types.EQUAL, 0, 0),
+                                        new ListExpression()
+                                )
+                        ),
+                        EmptyStatement.INSTANCE
+                ),
+                new ExpressionStatement(
+                        new MethodCallExpression(new VariableExpression(field.name), new ConstantExpression('remove'), new ArgumentListExpression(new VariableExpression("listener")))
+                )
+        ])
+        declaringClass.addMethod(new MethodNode(methodName, methodModifiers, methodReturnType, methodParameter, [] as ClassNode[], block))
+    }
+
+    /**
+     * Adds the get&lt;Listener&gt;s method like:
+     * <pre>
+     * synchronized ${name.capitalize}[] get${name.capitalize}s() {
+     *     def __result = []
+     *     if (${field.name} != null)
+     *         __result.addAll(${field.name})
+     *     return __result as ${name.capitalize}[]
+     * }
+     * </pre>
+     */
+    void addGetListeners(SourceUnit source, AnnotationNode node, ClassNode declaringClass, FieldNode field, ClassNode listener, String name, synchronize) {
+        def methodModifiers = synchronize ? ACC_PUBLIC | ACC_SYNCHRONIZED : ACC_PUBLIC
+        def methodReturnType = listener.makeArray()
+        def methodName = "get${name.capitalize()}s"
+        def methodParameter = [] as Parameter[]
+
+        if (declaringClass.hasMethod(methodName, methodParameter)) {
+            addError node, source, "Conflict using @${MY_CLASS.name}. Class $declaringClass.name already has method $methodName"
+            return
+        }
+
+        BlockStatement block = new BlockStatement()
+        block.addStatements([
+                new ExpressionStatement(
+                        new DeclarationExpression(
+                                new VariableExpression("__result", ClassHelper.DYNAMIC_TYPE),
+                                Token.newSymbol(Types.EQUALS, 0, 0),
+                                new ListExpression()
+                        )),
+                new IfStatement(
+                        new BooleanExpression(
+                                new BinaryExpression(
+                                        new VariableExpression(field.name),
+                                        Token.newSymbol(Types.COMPARE_NOT_EQUAL, 0, 0),
+                                        ConstantExpression.NULL
+                                )
+                        ),
+                        new ExpressionStatement(
+                                new MethodCallExpression(new VariableExpression('__result'), new ConstantExpression('addAll'), new ArgumentListExpression(new VariableExpression(field.name)))
+                        ),
+                        EmptyStatement.INSTANCE
+                ),
+                new ReturnStatement(
+                        new CastExpression(
+                                methodReturnType,
+                                new VariableExpression('__result')
+                        )
+                )
+        ])
+        declaringClass.addMethod(new MethodNode(methodName, methodModifiers, methodReturnType, methodParameter, [] as ClassNode[], block))
+    }
+
+    /**
+     * Adds the fire&lt;Event&gt; methods like:
+     * <pre>
+     * void fire${fireMethod.capitalize()}(${parameterList.join(', ')}) {
+     *     if (${field.name} != null) {
+     *         def __list = new ArrayList(${field.name})
+     *         __list.each { listener ->
+     *             listener.$eventMethod(${evt})
+     *         }
+     *     }
+     * }
+     * </pre>
+     */
+    void addFireMethods(SourceUnit source, AnnotationNode node, ClassNode declaringClass, FieldNode field, GenericsType[] types, boolean synchronize, MethodNode method) {
+
+        def methodReturnType = ClassHelper.make(Void.TYPE)
+        def methodName = "fire${method.name.capitalize()}"
+        def methodModifiers = synchronize ? ACC_PUBLIC | ACC_SYNCHRONIZED : ACC_PUBLIC
+
+        if (declaringClass.hasMethod(methodName, method.parameters)) {
+            addError node, source, "Conflict using @${MY_CLASS.name}. Class $declaringClass.name already has method $methodName"
+            return
+        }
+
+        def args = new ArgumentListExpression(method.parameters)
+
+        BlockStatement block = new BlockStatement()
+        def listenerListType = ClassHelper.make(ArrayList).plainNodeReference
+        listenerListType.setGenericsTypes(types)
+        block.addStatements([
+                new IfStatement(
+                        new BooleanExpression(
+                                new BinaryExpression(
+                                        new VariableExpression(field.name),
+                                        Token.newSymbol(Types.COMPARE_NOT_EQUAL, 0, 0),
+                                        ConstantExpression.NULL
+                                )
+                        ),
+                        new BlockStatement([
+                                new ExpressionStatement(
+                                        new DeclarationExpression(
+                                                new VariableExpression('__list', listenerListType),
+                                                Token.newSymbol(Types.EQUALS, 0, 0),
+                                                new ConstructorCallExpression(listenerListType, new ArgumentListExpression(
+                                                        new VariableExpression(field.name)
+                                                ))
+                                        )
+                                ),
+                                new ForStatement(
+                                        new Parameter(ClassHelper.DYNAMIC_TYPE, 'listener'),
+                                        new VariableExpression('__list'),
+                                        new BlockStatement([
+                                                new ExpressionStatement(
+                                                        new MethodCallExpression(
+                                                                new VariableExpression('listener'),
+                                                                method.name,
+                                                                args
+                                                        )
+                                                )
+                                        ], new VariableScope())
+                                )
+                        ], new VariableScope()),
+                        EmptyStatement.INSTANCE
+                )
+        ])
+
+        def params = method.parameters.collect {
+            def paramType = ClassHelper.getWrapper(it.type)
+            def cn = paramType.plainNodeReference
+            cn.setRedirect(paramType)
+            new Parameter(cn, it.name)
+        }
+        declaringClass.addMethod(methodName, methodModifiers, methodReturnType, params as Parameter[], [] as ClassNode[], block)
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/beans/PropertyAccessor.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/beans/PropertyAccessor.java b/src/main/groovy/groovy/beans/PropertyAccessor.java
new file mode 100644
index 0000000..822ab10
--- /dev/null
+++ b/src/main/groovy/groovy/beans/PropertyAccessor.java
@@ -0,0 +1,25 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 groovy.beans;
+
+/**
+ * @author Andres Almiray
+ */
+public interface PropertyAccessor extends PropertyReader, PropertyWriter {
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/beans/PropertyReader.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/beans/PropertyReader.java b/src/main/groovy/groovy/beans/PropertyReader.java
new file mode 100644
index 0000000..4ef13af
--- /dev/null
+++ b/src/main/groovy/groovy/beans/PropertyReader.java
@@ -0,0 +1,26 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 groovy.beans;
+
+/**
+ * @author Andres Almiray
+ */
+public interface PropertyReader {
+    Object read(Object owner, String propertyName);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/beans/PropertyWriter.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/beans/PropertyWriter.java b/src/main/groovy/groovy/beans/PropertyWriter.java
new file mode 100644
index 0000000..5b9e1ce
--- /dev/null
+++ b/src/main/groovy/groovy/beans/PropertyWriter.java
@@ -0,0 +1,26 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 groovy.beans;
+
+/**
+ * @author Andres Almiray
+ */
+public interface PropertyWriter {
+    void write(Object owner, String propertyName, Object value);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/beans/Vetoable.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/beans/Vetoable.java b/src/main/groovy/groovy/beans/Vetoable.java
new file mode 100644
index 0000000..ff7d8de
--- /dev/null
+++ b/src/main/groovy/groovy/beans/Vetoable.java
@@ -0,0 +1,113 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 groovy.beans;
+
+import org.codehaus.groovy.transform.GroovyASTTransformationClass;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates a groovy property or a class.
+ * <p>
+ * When annotating a property it indicates that the property should be a
+ * constrained property according to the JavaBeans spec, subject to
+ * listeners vetoing the property change.
+ * <p>
+ * When annotating a class it indicates that all groovy properties in that
+ * class should be bound as though each property had the annotation (even
+ * if it already has it explicitly).
+ * <p>
+ * It is a compilation error to place this annotation on a field (that is
+ * not a property, i.e. has scope visibility modifiers).
+ * <p>
+ * If a property with a user defined setter method is annotated the code
+ * block is wrapped with the needed code to fire off the event.
+ * <p>
+ * Here is a simple example of how to annotate a class with Vetoable: 
+ * <pre>
+ * &#064;groovy.beans.Vetoable
+ * class Person {
+ *     String firstName
+ *     def zipCode
+ * }
+ * </pre>
+ * This code is transformed by the compiler into something resembling the following
+ * snippet. Notice the difference between a String and a def/Object property: 
+ * <pre>
+ * public class Person implements groovy.lang.GroovyObject { 
+ *     private java.lang.String firstName
+ *     private java.lang.Object zipCode 
+ *     final private java.beans.VetoableChangeSupport this$vetoableChangeSupport 
+ * 
+ *     public Person() {
+ *         this$vetoableChangeSupport = new java.beans.VetoableChangeSupport(this)
+ *     }
+ * 
+ *     public void addVetoableChangeListener(java.beans.VetoableChangeListener listener) {
+ *         this$vetoableChangeSupport.addVetoableChangeListener(listener)
+ *     }
+ * 
+ *     public void addVetoableChangeListener(java.lang.String name, java.beans.VetoableChangeListener listener) {
+ *         this$vetoableChangeSupport.addVetoableChangeListener(name, listener)
+ *     }
+ * 
+ *     public void removeVetoableChangeListener(java.beans.VetoableChangeListener listener) {
+ *         this$vetoableChangeSupport.removeVetoableChangeListener(listener)
+ *     }
+ * 
+ *     public void removeVetoableChangeListener(java.lang.String name, java.beans.VetoableChangeListener listener) {
+ *         this$vetoableChangeSupport.removeVetoableChangeListener(name, listener)
+ *     }
+ * 
+ *     public void fireVetoableChange(java.lang.String name, java.lang.Object oldValue, java.lang.Object newValue) throws java.beans.PropertyVetoException {
+ *         this$vetoableChangeSupport.fireVetoableChange(name, oldValue, newValue)
+ *     }
+ * 
+ *     public java.beans.VetoableChangeListener[] getVetoableChangeListeners() {
+ *         return this$vetoableChangeSupport.getVetoableChangeListeners()
+ *     }
+ * 
+ *     public java.beans.VetoableChangeListener[] getVetoableChangeListeners(java.lang.String name) {
+ *         return this$vetoableChangeSupport.getVetoableChangeListeners(name)
+ *     }
+ * 
+ *     public void setFirstName(java.lang.String value) throws java.beans.PropertyVetoException {
+ *         this.fireVetoableChange('firstName', firstName, value)
+ *         firstName = value 
+ *     }
+ * 
+ *     public void setZipCode(java.lang.Object value) throws java.beans.PropertyVetoException {
+ *         this.fireVetoableChange('zipCode', zipCode, value)
+ *         zipCode = value 
+ *     }
+ * }
+ * </pre>
+ *
+ * @see VetoableASTTransformation
+ * @author Danno Ferrin (shemnon)
+ */
+@java.lang.annotation.Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.FIELD, ElementType.TYPE})
+@GroovyASTTransformationClass("groovy.beans.VetoableASTTransformation")
+public @interface Vetoable {
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/beans/VetoableASTTransformation.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/beans/VetoableASTTransformation.java b/src/main/groovy/groovy/beans/VetoableASTTransformation.java
new file mode 100644
index 0000000..983e8da
--- /dev/null
+++ b/src/main/groovy/groovy/beans/VetoableASTTransformation.java
@@ -0,0 +1,444 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 groovy.beans;
+
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.ast.tools.PropertyNodeUtils;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.messages.SimpleMessage;
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.runtime.MetaClassHelper;
+import org.codehaus.groovy.syntax.SyntaxException;
+import org.codehaus.groovy.transform.GroovyASTTransformation;
+import org.objectweb.asm.Opcodes;
+
+import java.beans.PropertyVetoException;
+import java.beans.VetoableChangeListener;
+import java.beans.VetoableChangeSupport;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+
+/**
+ * Handles generation of code for the {@code @Vetoable} annotation, and {@code @Bindable}
+ * if also present.
+ * <p>
+ * Generally, it adds (if needed) a VetoableChangeSupport field and
+ * the needed add/removeVetoableChangeListener methods to support the
+ * listeners.
+ * <p>
+ * It also generates the setter and wires the setter through the
+ * VetoableChangeSupport.
+ * <p>
+ * If a {@link Bindable} annotation is detected it also adds support similar
+ * to what {@link BindableASTTransformation} would do.
+ *
+ * @author Danno Ferrin (shemnon)
+ * @author Chris Reeves
+ */
+@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
+public class VetoableASTTransformation extends BindableASTTransformation {
+
+    protected static final ClassNode constrainedClassNode = ClassHelper.make(Vetoable.class);
+
+    /**
+     * Convenience method to see if an annotated node is {@code @Vetoable}.
+     *
+     * @param node the node to check
+     * @return true if the node is constrained
+     */
+    public static boolean hasVetoableAnnotation(AnnotatedNode node) {
+        for (AnnotationNode annotation : node.getAnnotations()) {
+            if (constrainedClassNode.equals(annotation.getClassNode())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Handles the bulk of the processing, mostly delegating to other methods.
+     *
+     * @param nodes   the AST nodes
+     * @param source  the source unit for the nodes
+     */
+    public void visit(ASTNode[] nodes, SourceUnit source) {
+        if (!(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) {
+            throw new RuntimeException("Internal error: wrong types: $node.class / $parent.class");
+        }
+        AnnotationNode node = (AnnotationNode) nodes[0];
+
+        if (nodes[1] instanceof ClassNode) {
+            addListenerToClass(source, (ClassNode) nodes[1]);
+        } else {
+            if ((((FieldNode)nodes[1]).getModifiers() & Opcodes.ACC_FINAL) != 0) {
+                source.getErrorCollector().addErrorAndContinue(new SyntaxErrorMessage(
+                        new SyntaxException("@groovy.beans.Vetoable cannot annotate a final property.",
+                                node.getLineNumber(), node.getColumnNumber(), node.getLastLineNumber(), node.getLastColumnNumber()),
+                        source));
+            }
+
+            addListenerToProperty(source, node, (AnnotatedNode) nodes[1]);
+        }
+    }
+
+    private void addListenerToProperty(SourceUnit source, AnnotationNode node, AnnotatedNode parent) {
+        ClassNode declaringClass = parent.getDeclaringClass();
+        FieldNode field = ((FieldNode) parent);
+        String fieldName = field.getName();
+        for (PropertyNode propertyNode : declaringClass.getProperties()) {
+            boolean bindable = BindableASTTransformation.hasBindableAnnotation(parent)
+                    || BindableASTTransformation.hasBindableAnnotation(parent.getDeclaringClass());
+
+            if (propertyNode.getName().equals(fieldName)) {
+                if (field.isStatic()) {
+                    //noinspection ThrowableInstanceNeverThrown
+                    source.getErrorCollector().addErrorAndContinue(new SyntaxErrorMessage(
+                            new SyntaxException("@groovy.beans.Vetoable cannot annotate a static property.",
+                                    node.getLineNumber(), node.getColumnNumber(), node.getLastLineNumber(), node.getLastColumnNumber()),
+                            source));
+                } else {
+                    createListenerSetter(source, bindable, declaringClass, propertyNode);
+                }
+                return;
+            }
+        }
+        //noinspection ThrowableInstanceNeverThrown
+        source.getErrorCollector().addErrorAndContinue(new SyntaxErrorMessage(
+                new SyntaxException("@groovy.beans.Vetoable must be on a property, not a field.  Try removing the private, protected, or public modifier.",
+                        node.getLineNumber(), node.getColumnNumber(), node.getLastLineNumber(), node.getLastColumnNumber()),
+                source));
+    }
+
+
+    private void addListenerToClass(SourceUnit source, ClassNode classNode) {
+        boolean bindable = BindableASTTransformation.hasBindableAnnotation(classNode);
+        for (PropertyNode propertyNode : classNode.getProperties()) {
+            if (!hasVetoableAnnotation(propertyNode.getField())
+                && !propertyNode.getField().isFinal()
+                && !propertyNode.getField().isStatic())
+            {
+                createListenerSetter(source,
+                        bindable || BindableASTTransformation.hasBindableAnnotation(propertyNode.getField()),
+                    classNode, propertyNode);
+            }
+        }
+    }
+
+    /**
+     * Wrap an existing setter.
+     */
+    private static void wrapSetterMethod(ClassNode classNode, boolean bindable, String propertyName) {
+        String getterName = "get" + MetaClassHelper.capitalize(propertyName);
+        MethodNode setter = classNode.getSetterMethod("set" + MetaClassHelper.capitalize(propertyName));
+
+        if (setter != null) {
+            // Get the existing code block
+            Statement code = setter.getCode();
+
+            Expression oldValue = varX("$oldValue");
+            Expression newValue = varX("$newValue");
+            Expression proposedValue = varX(setter.getParameters()[0].getName());
+            BlockStatement block = new BlockStatement();
+
+            // create a local variable to hold the old value from the getter
+            block.addStatement(declS(oldValue, callThisX(getterName)));
+
+            // add the fireVetoableChange method call
+            block.addStatement(stmt(callThisX("fireVetoableChange", args(
+                    constX(propertyName), oldValue, proposedValue))));
+
+            // call the existing block, which will presumably set the value properly
+            block.addStatement(code);
+
+            if (bindable) {
+                // get the new value to emit in the event
+                block.addStatement(declS(newValue, callThisX(getterName)));
+
+                // add the firePropertyChange method call
+                block.addStatement(stmt(callThisX("firePropertyChange", args(constX(propertyName), oldValue, newValue))));
+            }
+
+            // replace the existing code block with our new one
+            setter.setCode(block);
+        }
+    }
+
+    private void createListenerSetter(SourceUnit source, boolean bindable, ClassNode declaringClass, PropertyNode propertyNode) {
+        if (bindable && needsPropertyChangeSupport(declaringClass, source)) {
+            addPropertyChangeSupport(declaringClass);
+        }
+        if (needsVetoableChangeSupport(declaringClass, source)) {
+            addVetoableChangeSupport(declaringClass);
+        }
+        String setterName = "set" + MetaClassHelper.capitalize(propertyNode.getName());
+        if (declaringClass.getMethods(setterName).isEmpty()) {
+            Expression fieldExpression = fieldX(propertyNode.getField());
+            BlockStatement setterBlock = new BlockStatement();
+            setterBlock.addStatement(createConstrainedStatement(propertyNode, fieldExpression));
+            if (bindable) {
+                setterBlock.addStatement(createBindableStatement(propertyNode, fieldExpression));
+            } else {
+                setterBlock.addStatement(createSetStatement(fieldExpression));
+            }
+
+            // create method void <setter>(<type> fieldName)
+            createSetterMethod(declaringClass, propertyNode, setterName, setterBlock);
+        } else {
+            wrapSetterMethod(declaringClass, bindable, propertyNode.getName());
+        }
+    }
+
+    /**
+     * Creates a statement body similar to:
+     * <code>this.fireVetoableChange("field", field, field = value)</code>
+     *
+     * @param propertyNode           the field node for the property
+     * @param fieldExpression a field expression for setting the property value
+     * @return the created statement
+     */
+    protected Statement createConstrainedStatement(PropertyNode propertyNode, Expression fieldExpression) {
+        return stmt(callThisX("fireVetoableChange", args(constX(propertyNode.getName()), fieldExpression, varX("value"))));
+    }
+
+    /**
+     * Creates a statement body similar to:
+     * <code>field = value</code>.
+     * <p>
+     * Used when the field is not also {@code @Bindable}.
+     *
+     * @param fieldExpression a field expression for setting the property value
+     * @return the created statement
+     */
+    protected Statement createSetStatement(Expression fieldExpression) {
+        return assignS(fieldExpression, varX("value"));
+    }
+
+    /**
+     * Snoops through the declaring class and all parents looking for a field
+     * of type VetoableChangeSupport.  Remembers the field and returns false
+     * if found otherwise returns true to indicate that such support should
+     * be added.
+     *
+     * @param declaringClass the class to search
+     * @return true if vetoable change support should be added
+     */
+    protected boolean needsVetoableChangeSupport(ClassNode declaringClass, SourceUnit sourceUnit) {
+        boolean foundAdd = false, foundRemove = false, foundFire = false;
+        ClassNode consideredClass = declaringClass;
+        while (consideredClass!= null) {
+            for (MethodNode method : consideredClass.getMethods()) {
+                // just check length, MOP will match it up
+                foundAdd = foundAdd || method.getName().equals("addVetoableChangeListener") && method.getParameters().length == 1;
+                foundRemove = foundRemove || method.getName().equals("removeVetoableChangeListener") && method.getParameters().length == 1;
+                foundFire = foundFire || method.getName().equals("fireVetoableChange") && method.getParameters().length == 3;
+                if (foundAdd && foundRemove && foundFire) {
+                    return false;
+                }
+            }
+            consideredClass = consideredClass.getSuperClass();
+        }
+        // check if a super class has @Vetoable annotations
+        consideredClass = declaringClass.getSuperClass();
+        while (consideredClass!=null) {
+            if (hasVetoableAnnotation(consideredClass)) return false;
+            for (FieldNode field : consideredClass.getFields()) {
+                if (hasVetoableAnnotation(field)) return false;
+            }
+            consideredClass = consideredClass.getSuperClass();
+        }
+        if (foundAdd || foundRemove || foundFire) {
+            sourceUnit.getErrorCollector().addErrorAndContinue(
+                new SimpleMessage("@Vetoable cannot be processed on "
+                    + declaringClass.getName()
+                    + " because some but not all of addVetoableChangeListener, removeVetoableChange, and fireVetoableChange were declared in the current or super classes.",
+                sourceUnit)
+            );
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Creates a setter method with the given body.
+     * <p>
+     * This differs from normal setters in that we need to add a declared
+     * exception java.beans.PropertyVetoException
+     *
+     * @param declaringClass the class to which we will add the setter
+     * @param propertyNode          the field to back the setter
+     * @param setterName     the name of the setter
+     * @param setterBlock    the statement representing the setter block
+     */
+    protected void createSetterMethod(ClassNode declaringClass, PropertyNode propertyNode, String setterName, Statement setterBlock) {
+        ClassNode[] exceptions = {ClassHelper.make(PropertyVetoException.class)};
+        MethodNode setter = new MethodNode(
+                setterName,
+                PropertyNodeUtils.adjustPropertyModifiersForMethod(propertyNode),
+                ClassHelper.VOID_TYPE,
+                params(param(propertyNode.getType(), "value")),
+                exceptions,
+                setterBlock);
+        setter.setSynthetic(true);
+        // add it to the class
+        declaringClass.addMethod(setter);
+    }
+
+    /**
+     * Adds the necessary field and methods to support vetoable change support.
+     * <p>
+     * Adds a new field:
+     * <code>"protected final java.beans.VetoableChangeSupport this$vetoableChangeSupport = new java.beans.VetoableChangeSupport(this)"</code>
+     * <p>
+     * Also adds support methods:
+     * <code>public void addVetoableChangeListener(java.beans.VetoableChangeListener)</code>
+     * <code>public void addVetoableChangeListener(String, java.beans.VetoableChangeListener)</code>
+     * <code>public void removeVetoableChangeListener(java.beans.VetoableChangeListener)</code>
+     * <code>public void removeVetoableChangeListener(String, java.beans.VetoableChangeListener)</code>
+     * <code>public java.beans.VetoableChangeListener[] getVetoableChangeListeners()</code>
+     *
+     * @param declaringClass the class to which we add the support field and methods
+     */
+    protected void addVetoableChangeSupport(ClassNode declaringClass) {
+        ClassNode vcsClassNode = ClassHelper.make(VetoableChangeSupport.class);
+        ClassNode vclClassNode = ClassHelper.make(VetoableChangeListener.class);
+
+        // add field:
+        // protected static VetoableChangeSupport this$vetoableChangeSupport = new java.beans.VetoableChangeSupport(this)
+        FieldNode vcsField = declaringClass.addField(
+                "this$vetoableChangeSupport",
+                ACC_FINAL | ACC_PRIVATE | ACC_SYNTHETIC,
+                vcsClassNode,
+                ctorX(vcsClassNode, args(varX("this"))));
+
+        // add method:
+        // void addVetoableChangeListener(listener) {
+        //     this$vetoableChangeSupport.addVetoableChangeListener(listener)
+        //  }
+        declaringClass.addMethod(
+                new MethodNode(
+                        "addVetoableChangeListener",
+                        ACC_PUBLIC,
+                        ClassHelper.VOID_TYPE,
+                        params(param(vclClassNode, "listener")),
+                        ClassNode.EMPTY_ARRAY,
+                        stmt(callX(fieldX(vcsField), "addVetoableChangeListener", args(varX("listener", vclClassNode))))));
+
+        // add method:
+        // void addVetoableChangeListener(name, listener) {
+        //     this$vetoableChangeSupport.addVetoableChangeListener(name, listener)
+        //  }
+        declaringClass.addMethod(
+                new MethodNode(
+                        "addVetoableChangeListener",
+                        ACC_PUBLIC,
+                        ClassHelper.VOID_TYPE,
+                        params(param(ClassHelper.STRING_TYPE, "name"), param(vclClassNode, "listener")),
+                        ClassNode.EMPTY_ARRAY,
+                        stmt(callX(fieldX(vcsField), "addVetoableChangeListener", args(varX("name", ClassHelper.STRING_TYPE), varX("listener", vclClassNode))))));
+
+        // add method:
+        // boolean removeVetoableChangeListener(listener) {
+        //    return this$vetoableChangeSupport.removeVetoableChangeListener(listener);
+        // }
+        declaringClass.addMethod(
+                new MethodNode(
+                        "removeVetoableChangeListener",
+                        ACC_PUBLIC,
+                        ClassHelper.VOID_TYPE,
+                        params(param(vclClassNode, "listener")),
+                        ClassNode.EMPTY_ARRAY,
+                        stmt(callX(fieldX(vcsField), "removeVetoableChangeListener", args(varX("listener", vclClassNode))))));
+
+        // add method: void removeVetoableChangeListener(name, listener)
+        declaringClass.addMethod(
+                new MethodNode(
+                        "removeVetoableChangeListener",
+                        ACC_PUBLIC,
+                        ClassHelper.VOID_TYPE,
+                        params(param(ClassHelper.STRING_TYPE, "name"), param(vclClassNode, "listener")),
+                        ClassNode.EMPTY_ARRAY,
+                        stmt(callX(fieldX(vcsField), "removeVetoableChangeListener", args(varX("name", ClassHelper.STRING_TYPE), varX("listener", vclClassNode))))));
+
+        // add method:
+        // void fireVetoableChange(String name, Object oldValue, Object newValue)
+        //    throws PropertyVetoException
+        // {
+        //     this$vetoableChangeSupport.fireVetoableChange(name, oldValue, newValue)
+        //  }
+        declaringClass.addMethod(
+                new MethodNode(
+                        "fireVetoableChange",
+                        ACC_PUBLIC,
+                        ClassHelper.VOID_TYPE,
+                        params(param(ClassHelper.STRING_TYPE, "name"), param(ClassHelper.OBJECT_TYPE, "oldValue"), param(ClassHelper.OBJECT_TYPE, "newValue")),
+                        new ClassNode[] {ClassHelper.make(PropertyVetoException.class)},
+                        stmt(callX(fieldX(vcsField), "fireVetoableChange", args(varX("name", ClassHelper.STRING_TYPE), varX("oldValue"), varX("newValue"))))));
+
+        // add method:
+        // VetoableChangeListener[] getVetoableChangeListeners() {
+        //   return this$vetoableChangeSupport.getVetoableChangeListeners
+        // }
+        declaringClass.addMethod(
+                new MethodNode(
+                        "getVetoableChangeListeners",
+                        ACC_PUBLIC,
+                        vclClassNode.makeArray(),
+                        Parameter.EMPTY_ARRAY,
+                        ClassNode.EMPTY_ARRAY,
+                        returnS(callX(fieldX(vcsField), "getVetoableChangeListeners"))));
+
+        // add method:
+        // VetoableChangeListener[] getVetoableChangeListeners(String name) {
+        //   return this$vetoableChangeSupport.getVetoableChangeListeners(name)
+        // }
+        declaringClass.addMethod(
+                new MethodNode(
+                        "getVetoableChangeListeners",
+                        ACC_PUBLIC,
+                        vclClassNode.makeArray(),
+                        params(param(ClassHelper.STRING_TYPE, "name")),
+                        ClassNode.EMPTY_ARRAY,
+                        returnS(callX(fieldX(vcsField), "getVetoableChangeListeners", args(varX("name", ClassHelper.STRING_TYPE))))));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/cli/CliBuilderException.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/cli/CliBuilderException.groovy b/src/main/groovy/groovy/cli/CliBuilderException.groovy
new file mode 100644
index 0000000..84a9438
--- /dev/null
+++ b/src/main/groovy/groovy/cli/CliBuilderException.groovy
@@ -0,0 +1,24 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 groovy.cli
+
+import groovy.transform.InheritConstructors
+
+@InheritConstructors
+class CliBuilderException extends RuntimeException { }

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/cli/Option.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/cli/Option.java b/src/main/groovy/groovy/cli/Option.java
new file mode 100644
index 0000000..9b48861
--- /dev/null
+++ b/src/main/groovy/groovy/cli/Option.java
@@ -0,0 +1,105 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 groovy.cli;
+
+import groovy.transform.Undefined;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a method or property can be used to set a CLI option.
+ */
+@java.lang.annotation.Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.FIELD})
+public @interface Option {
+    /**
+     * The description of this option
+     *
+     * @return the description of this option
+     */
+    String description() default "";
+
+    /**
+     * The short name of this option. Defaults to the name of member being annotated if the longName is empty.
+     *
+     * @return the short name of this option
+     */
+    String shortName() default "";
+
+    /**
+     * The long name of this option. Defaults to the name of member being annotated.
+     *
+     * @return the long name of this option
+     */
+    String longName() default "";
+
+    /**
+     * The value separator for this multi-valued option. Only allowed for array-typed arguments.
+     *
+     * @return the value separator for this multi-valued option
+     */
+    String valueSeparator() default "";
+
+    /**
+     * Whether this option can have an optional argument.
+     * Only supported for array-typed arguments to indicate that the array may be empty.
+     *
+     * @return true if this array-typed option can have an optional argument (i.e. could be empty)
+     */
+    boolean optionalArg() default false;
+
+    /**
+     * How many arguments this option has.
+     * A value greater than 1 is only allowed for array-typed arguments.
+     * Ignored for boolean options which are assumed to have a default of 0
+     * or if {@code numberOfArgumentsString} is set.
+     *
+     * @return the number of arguments
+     */
+    int numberOfArguments() default 1;
+
+    /**
+     * How many arguments this option has represented as a String.
+     * Only allowed for array-typed arguments.
+     * Overrides {@code numberOfArguments} if set.
+     * The special values of '+' means one or more and '*' as 0 or more.
+     *
+     * @return the number of arguments (as a String)
+     */
+    String numberOfArgumentsString() default "";
+
+    /**
+     * The default value for this option as a String; subject to type conversion and 'convert'.
+     * Ignored for Boolean options.
+     *
+     * @return the default value for this option
+     */
+    String defaultValue() default "";
+
+    /**
+     * A conversion closure to convert the incoming String into the desired object
+     *
+     * @return the closure to convert this option's argument(s)
+     */
+    Class convert() default Undefined.CLASS.class;
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/cli/OptionField.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/cli/OptionField.groovy b/src/main/groovy/groovy/cli/OptionField.groovy
new file mode 100644
index 0000000..69cc1f5
--- /dev/null
+++ b/src/main/groovy/groovy/cli/OptionField.groovy
@@ -0,0 +1,27 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 groovy.cli
+
+import groovy.transform.AnnotationCollector
+import groovy.transform.Field
+
+@Option
+@Field
+@AnnotationCollector
+@interface OptionField { }
\ No newline at end of file