Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * Copyright 2008-2009 the original author or authors.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 package groovy.beans;
 
 import  org.objectweb.asm.Opcodes;
 
Handles generation of code for the @Bindable annotation when @Vetoable is not present.

Generally, it adds (if needed) a PropertyChangeSupport field and the needed add/removePropertyChangeListener methods to support the listeners.

It also generates the setter and wires the setter through the PropertyChangeSupport.

If a Vetoable annotaton is detected it does nothing and lets the VetoableASTTransformation handle all the changes.

Author(s):
Danno Ferrin (shemnon)
Chris Reeves
 
 public class BindableASTTransformation implements ASTTransformation, Opcodes {
 
     protected static ClassNode boundClassNode = new ClassNode(Bindable.class);
     protected ClassNode pcsClassNode = new ClassNode(PropertyChangeSupport.class);

    
Convenience method to see if an annotated node is @Bindable.

Parameters:
node the node to check
Returns:
true if the node is bindable
 
     public static boolean hasBindableAnnotation(AnnotatedNode node) {
         for (AnnotationNode annotation : (Collection<AnnotationNode>) node.getAnnotations()) {
             if (.equals(annotation.getClassNode())) {
                 return true;
             }
         }
         return false;
     }

    
Handles the bulk of the processing, mostly delegating to other methods.

Parameters:
nodes the ast nodes
source the source unit for the nodes
 
     public void visit(ASTNode[] nodesSourceUnit source) {
         if (!(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) {
             throw new RuntimeException("Internal error: wrong types: $node.class / $parent.class");
         }
         AnnotationNode node = (AnnotationNodenodes[0];
         AnnotatedNode parent = (AnnotatedNodenodes[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()),
                                source));
            }
            if (VetoableASTTransformation.hasVetoableAnnotation(parent.getDeclaringClass())) {
                // VetoableASTTransformation will handle both @Bindable and @Vetoable
                return;
            }
            addListenerToProperty(sourcenodedeclaringClass, (FieldNodeparent);
        } else if (parent instanceof ClassNode) {
            addListenerToClass(sourcenode, (ClassNodeparent);
        }
    }
    private void addListenerToProperty(SourceUnit sourceAnnotationNode nodeClassNode declaringClassFieldNode field) {
        String fieldName = field.getName();
        for (PropertyNode propertyNode : (Collection<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()),
                                    source));
                } else {
                    if (needsPropertyChangeSupport(declaringClasssource)) {
                        addPropertyChangeSupport(declaringClass);
                    }
                    createListenerSetter(sourcenodedeclaringClasspropertyNode);
                }
                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()),
                        source));
    }
    private void addListenerToClass(SourceUnit sourceAnnotationNode nodeClassNode classNode) {
        if (needsPropertyChangeSupport(classNodesource)) {
            addPropertyChangeSupport(classNode);
        }
        for (PropertyNode propertyNode : (Collection<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(sourcenodeclassNodepropertyNode);
        }
    }
    /*
     * Wrap an existing setter.
     */
    private void wrapSetterMethod(ClassNode classNodeString 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();
            VariableExpression oldValue = new VariableExpression("$oldValue");
            VariableExpression newValue = new VariableExpression("$newValue");
            BlockStatement block = new BlockStatement();
            // create a local variable to hold the old value from the getter
            block.addStatement(new ExpressionStatement(
                new DeclarationExpression(oldValue,
                    Token.newSymbol(., 0, 0),
                    new MethodCallExpression(.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(new ExpressionStatement(
                new DeclarationExpression(newValue,
                    Token.newSymbol(., 0, 0),
                    new MethodCallExpression(.getterName.))));
            // add the firePropertyChange method call
            block.addStatement(new ExpressionStatement(new MethodCallExpression(
                    .,
                    "firePropertyChange",
                    new ArgumentListExpression(
                            new Expression[]{
                                    new ConstantExpression(propertyName),
                                    oldValue,
                                    newValue}))));
            // replace the existing code block with our new one
            setter.setCode(block);
        }
    }
    private void createListenerSetter(SourceUnit sourceAnnotationNode nodeClassNode classNodePropertyNode propertyNode) {
        String setterName = "set" + MetaClassHelper.capitalize(propertyNode.getName());
        if (classNode.getMethods(setterName).isEmpty()) {
            Expression fieldExpression = new FieldExpression(propertyNode.getField());
            Statement setterBlock = createBindableStatement(propertyNodefieldExpression);
            // create method void <setter>(<type> fieldName)
            createSetterMethod(classNodepropertyNodesetterNamesetterBlock);
        } else {
            wrapSetterMethod(classNodepropertyNode.getName());
        }
    }

    
Creates a statement body similar to: this.firePropertyChange("field", field, field = value)

Parameters:
propertyNode the field node for the property
fieldExpression a field expression for setting the property value
Returns:
the created statement
    protected Statement createBindableStatement(PropertyNode propertyNodeExpression fieldExpression) {
        // create statementBody
        return new ExpressionStatement(
                new MethodCallExpression(
                        .,
                        "firePropertyChange",
                        new ArgumentListExpression(
                                new Expression[]{
                                        new ConstantExpression(propertyNode.getName()),
                                        fieldExpression,
                                        new BinaryExpression(
                                                fieldExpression,
                                                Token.newSymbol(., 0, 0),
                                                new VariableExpression("value"))})));
    }

    
Creates a setter method with the given body.

Parameters:
declaringClass the class to which we will add the setter
propertyNode the field to back the setter
setterName the name of the setter
setterBlock the statement representing the setter block
    protected void createSetterMethod(ClassNode declaringClassPropertyNode propertyNodeString setterNameStatement setterBlock) {
        Parameter[] setterParameterTypes = {new Parameter(propertyNode.getType(), "value")};
        MethodNode setter =
                new MethodNode(setterNamepropertyNode.getModifiers(), .setterParameterTypes.setterBlock);
        setter.setSynthetic(true);
        // add it to the class
        declaringClass.addMethod(setter);
    }

    
Snoops through the declaring class and all parents looking for methods void addPropertyChangeListener(PropertyChangeListener), void removePropertyChangeListener(PropertyChangeListener), and void firePropertyChange(String, Object, Object). If any are defined all must be defined or a compilation error results.

Parameters:
declaringClass the class to search
sourceUnit the source unit, for error reporting. @NotNull.
Returns:
true if property change support should be added
    protected boolean needsPropertyChangeSupport(ClassNode declaringClassSourceUnit sourceUnit) {
        boolean foundAdd = falsefoundRemove = falsefoundFire = 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();
        }
        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.

Adds a new field: protected final java.beans.PropertyChangeSupport this$PropertyChangeSupport = new java.beans.PropertyChangeSupport(this)"

Also adds support methods: public void addPropertyChangeListener(java.beans.PropertyChangeListener) public void addPropertyChangeListener(String, java.beans.PropertyChangeListener) public void removePropertyChangeListener(java.beans.PropertyChangeListener) public void removePropertyChangeListener(String, java.beans.PropertyChangeListener) public java.beans.PropertyChangeListener[] getPropertyChangeListeners()

Parameters:
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,
                new ConstructorCallExpression(pcsClassNode,
                        new ArgumentListExpression(new Expression[]{new VariableExpression("this")})));
        // add method:
        // void addPropertyChangeListener(listener) {
        //     this$propertyChangeSupport.addPropertyChangeListener(listener)
        //  }
        declaringClass.addMethod(
                new MethodNode(
                        "addPropertyChangeListener",
                        ACC_PUBLIC | ACC_SYNTHETIC,
                        .,
                        new Parameter[]{new Parameter(pclClassNode"listener")},
                        .,
                        new ExpressionStatement(
                                new MethodCallExpression(
                                        new FieldExpression(pcsField),
                                        "addPropertyChangeListener",
                                        new ArgumentListExpression(
                                                new Expression[]{new VariableExpression("listener")})))));
        // add method:
        // void addPropertyChangeListener(name, listener) {
        //     this$propertyChangeSupport.addPropertyChangeListener(name, listener)
        //  }
        declaringClass.addMethod(
                new MethodNode(
                        "addPropertyChangeListener",
                        ACC_PUBLIC | ACC_SYNTHETIC,
                        .,
                        new Parameter[]{new Parameter(."name"), new Parameter(pclClassNode"listener")},
                        .,
                        new ExpressionStatement(
                                new MethodCallExpression(
                                        new FieldExpression(pcsField),
                                        "addPropertyChangeListener",
                                        new ArgumentListExpression(
                                                new Expression[]{new VariableExpression("name"), new VariableExpression("listener")})))));
        // add method:
        // boolean removePropertyChangeListener(listener) {
        //    return this$propertyChangeSupport.removePropertyChangeListener(listener);
        // }
        declaringClass.addMethod(
                new MethodNode(
                        "removePropertyChangeListener",
                        ACC_PUBLIC | ACC_SYNTHETIC,
                        .,
                        new Parameter[]{new Parameter(pclClassNode"listener")},
                        .,
                        new ExpressionStatement(
                                new MethodCallExpression(
                                        new FieldExpression(pcsField),
                                        "removePropertyChangeListener",
                                        new ArgumentListExpression(
                                                new Expression[]{new VariableExpression("listener")})))));
        // add method: void removePropertyChangeListener(name, listener)
        declaringClass.addMethod(
                new MethodNode(
                        "removePropertyChangeListener",
                        ACC_PUBLIC | ACC_SYNTHETIC,
                        .,
                        new Parameter[]{new Parameter(."name"), new Parameter(pclClassNode"listener")},
                        .,
                        new ExpressionStatement(
                                new MethodCallExpression(
                                        new FieldExpression(pcsField),
                                        "removePropertyChangeListener",
                                        new ArgumentListExpression(
                                                new Expression[]{new VariableExpression("name"), new VariableExpression("listener")})))));
        // add method:
        // void firePropertyChange(String name, Object oldValue, Object newValue) {
        //     this$propertyChangeSupport.firePropertyChange(name, oldValue, newValue)
        //  }
        declaringClass.addMethod(
                new MethodNode(
                        "firePropertyChange",
                        ACC_PUBLIC | ACC_SYNTHETIC,
                        .,
                        new Parameter[]{new Parameter(."name"), new Parameter(."oldValue"), new Parameter(."newValue")},
                        .,
                        new ExpressionStatement(
                                new MethodCallExpression(
                                        new FieldExpression(pcsField),
                                        "firePropertyChange",
                                        new ArgumentListExpression(
                                                new Expression[]{
                                                        new VariableExpression("name"),
                                                        new VariableExpression("oldValue"),
                                                        new VariableExpression("newValue")})))));
        // add method:
        // PropertyChangeListener[] getPropertyChangeListeners() {
        //   return this$propertyChangeSupport.getPropertyChangeListeners
        // }
        declaringClass.addMethod(
                new MethodNode(
                        "getPropertyChangeListeners",
                        ACC_PUBLIC | ACC_SYNTHETIC,
                        pclClassNode.makeArray(),
                        .,
                        .,
                        new ReturnStatement(
                                new ExpressionStatement(
                                        new MethodCallExpression(
                                                new FieldExpression(pcsField),
                                                "getPropertyChangeListeners",
                                                .)))));
        // add method:
        // PropertyChangeListener[] getPropertyChangeListeners(String name) {
        //   return this$propertyChangeSupport.getPropertyChangeListeners(name)
        // }
        declaringClass.addMethod(
                new MethodNode(
                        "getPropertyChangeListeners",
                        ACC_PUBLIC | ACC_SYNTHETIC,
                        pclClassNode.makeArray(),
                        new Parameter[]{new Parameter(."name")},
                        .,
                        new ReturnStatement(
                                new ExpressionStatement(
                                        new MethodCallExpression(
                                                new FieldExpression(pcsField),
                                                "getPropertyChangeListeners",
                                                new ArgumentListExpression(
                                                new Expression[]{new VariableExpression("name")}))))));
    }
New to GrepCode? Check out our FAQ X