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 @Vetoable annotation, and @Bindable if also present.

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

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

If a Bindable annotaton is detected it also adds support similar to what BindableASTTransformation would do.

Author(s):
Danno Ferrin (shemnon)
Chris Reeves
 
 
     protected static ClassNode constrainedClassNode = new ClassNode(Vetoable.class);
     protected ClassNode vcsClassNode = new ClassNode(VetoableChangeSupport.class);

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

Parameters:
node the node to check
Returns:
true if the node is constrained
 
     public static boolean hasVetoableAnnotation(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];
 
         if (nodes[1] instanceof ClassNode) {
             addListenerToClass(sourcenode, (ClassNodenodes[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()),
                                source));
            }
            addListenerToProperty(sourcenode, (AnnotatedNodenodes[1]);
        }
    }
    private void addListenerToProperty(SourceUnit sourceAnnotationNode nodeAnnotatedNode parent) {
        ClassNode declaringClass = parent.getDeclaringClass();
        FieldNode field = ((FieldNodeparent);
        String fieldName = field.getName();
        for (PropertyNode propertyNode : (Collection<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()),
                                    source));
                } else {
                    createListenerSetter(sourcenodebindabledeclaringClass,  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()),
                        source));
    }
    private void addListenerToClass(SourceUnit sourceAnnotationNode nodeClassNode classNode) {
        boolean bindable = BindableASTTransformation.hasBindableAnnotation(classNode);
        for (PropertyNode propertyNode : (Collection<PropertyNode>) classNode.getProperties()) {
            if (!hasVetoableAnnotation(propertyNode.getField())
                && !((propertyNode.getField().getModifiers() & Opcodes.ACC_FINAL) != 0)
                && !propertyNode.getField().isStatic())
            {
                createListenerSetter(sourcenode,
                    bindable || BindableASTTransformation.hasBindableAnnotation(propertyNode.getField()),
                    classNodepropertyNode);
            }
        }
    }

    
Wrap an existing setter.
    private void wrapSetterMethod(ClassNode classNodeboolean bindableString 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");
            VariableExpression proposedValue = new VariableExpression(setter.getParameters()[0].getName());
            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.))));
            // add the fireVetoableChange method call
            block.addStatement(new ExpressionStatement(new MethodCallExpression(
                    .,
                    "fireVetoableChange",
                    new ArgumentListExpression(
                            new Expression[]{
                                    new ConstantExpression(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(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 nodeboolean bindableClassNode declaringClassPropertyNode propertyNode) {
        if (bindable && needsPropertyChangeSupport(declaringClasssource)) {
            addPropertyChangeSupport(declaringClass);
        }
        if (needsVetoableChangeSupport(declaringClasssource)) {
            addVetoableChangeSupport(declaringClass);
        }
        String setterName = "set" + MetaClassHelper.capitalize(propertyNode.getName());
        if (declaringClass.getMethods(setterName).isEmpty()) {
            Expression fieldExpression = new FieldExpression(propertyNode.getField());
            BlockStatement setterBlock = new BlockStatement();
            setterBlock.addStatement(createConstrainedStatement(propertyNodefieldExpression));
            if (bindable) {
                setterBlock.addStatement(createBindableStatement(propertyNodefieldExpression));
            } else {
                setterBlock.addStatement(createSetStatement(fieldExpression));
            }
            // create method void <setter>(<type> fieldName)
            createSetterMethod(declaringClasspropertyNodesetterNamesetterBlock);
        } else {
            wrapSetterMethod(declaringClassbindablepropertyNode.getName());
        }
    }

    
Creates a statement body silimar to: this.fireVetoableChange("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 createConstrainedStatement(PropertyNode propertyNodeExpression fieldExpression) {
        return new ExpressionStatement(
                new MethodCallExpression(
                        .,
                        "fireVetoableChange",
                        new ArgumentListExpression(
                                new Expression[]{
                                        new ConstantExpression(propertyNode.getName()),
                                        fieldExpression,
                                        new VariableExpression("value")})));
    }

    
Creates a statement body similar to: field = value

Used when the field is not also

Parameters:
fieldExpression a field expression for setting the property value
Returns:
the created statement
Bindable:
    protected Statement createSetStatement(Expression fieldExpression) {
        return new ExpressionStatement(
                new BinaryExpression(
                        fieldExpression,
                        Token.newSymbol(., 0, 0),
                        new VariableExpression("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.

Parameters:
declaringClass the class to search
Returns:
true if vetoable change support should be added
    protected boolean needsVetoableChangeSupport(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("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();
        }
        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.

This differs from normal setters in that we need to add a declared exception java.beans.PropertyVetoException

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")};
        ClassNode[] exceptions = {new ClassNode(PropertyVetoException.class)};
        MethodNode setter =
                new MethodNode(setterNamepropertyNode.getModifiers(), .setterParameterTypesexceptionssetterBlock);
        setter.setSynthetic(true);
        // add it to the class
        declaringClass.addMethod(setter);
    }

    
Adds the necessary field and methods to support vetoable change support.

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

Also adds support methods: public void addVetoableChangeListener(java.beans.VetoableChangeListener) public void addVetoableChangeListener(String, java.beans.VetoableChangeListener) public void removeVetoableChangeListener(java.beans.VetoableChangeListener) public void removeVetoableChangeListener(String, java.beans.VetoableChangeListener) public java.beans.VetoableChangeListener[] getVetoableChangeListeners()

Parameters:
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,
                new ConstructorCallExpression(vcsClassNode,
                        new ArgumentListExpression(new Expression[]{new VariableExpression("this")})));
        // add method:
        // void addVetoableChangeListener(listener) {
        //     this$vetoableChangeSupport.addVetoableChangeListener(listener)
        //  }
        declaringClass.addMethod(
                new MethodNode(
                        "addVetoableChangeListener",
                        ACC_PUBLIC | ACC_SYNTHETIC,
                        .,
                        new Parameter[]{new Parameter(vclClassNode"listener")},
                        .,
                        new ExpressionStatement(
                                new MethodCallExpression(
                                        new FieldExpression(vcsField),
                                        "addVetoableChangeListener",
                                        new ArgumentListExpression(
                                                new Expression[]{new VariableExpression("listener")})))));
        // add method:
        // void addVetoableChangeListener(name, listener) {
        //     this$vetoableChangeSupport.addVetoableChangeListener(name, listener)
        //  }
        declaringClass.addMethod(
                new MethodNode(
                        "addVetoableChangeListener",
                        ACC_PUBLIC | ACC_SYNTHETIC,
                        .,
                        new Parameter[]{new Parameter(."name"), new Parameter(vclClassNode"listener")},
                        .,
                        new ExpressionStatement(
                                new MethodCallExpression(
                                        new FieldExpression(vcsField),
                                        "addVetoableChangeListener",
                                        new ArgumentListExpression(
                                                new Expression[]{new VariableExpression("name"), new VariableExpression("listener")})))));
        // add method:
        // boolean removeVetoableChangeListener(listener) {
        //    return this$vetoableChangeSupport.removeVetoableChangeListener(listener);
        // }
        declaringClass.addMethod(
                new MethodNode(
                        "removeVetoableChangeListener",
                        ACC_PUBLIC | ACC_SYNTHETIC,
                        .,
                        new Parameter[]{new Parameter(vclClassNode"listener")},
                        .,
                        new ExpressionStatement(
                                new MethodCallExpression(
                                        new FieldExpression(vcsField),
                                        "removeVetoableChangeListener",
                                        new ArgumentListExpression(
                                                new Expression[]{new VariableExpression("listener")})))));
        // add method: void removeVetoableChangeListener(name, listener)
        declaringClass.addMethod(
                new MethodNode(
                        "removeVetoableChangeListener",
                        ACC_PUBLIC | ACC_SYNTHETIC,
                        .,
                        new Parameter[]{new Parameter(."name"), new Parameter(vclClassNode"listener")},
                        .,
                        new ExpressionStatement(
                                new MethodCallExpression(
                                        new FieldExpression(vcsField),
                                        "removeVetoableChangeListener",
                                        new ArgumentListExpression(
                                                new Expression[]{new VariableExpression("name"), new VariableExpression("listener")})))));
        // 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 | ACC_SYNTHETIC,
                        .,
                        new Parameter[]{new Parameter(."name"), new Parameter(."oldValue"), new Parameter(."newValue")},
                        new ClassNode[] {new ClassNode(PropertyVetoException.class)},
                        new ExpressionStatement(
                                new MethodCallExpression(
                                        new FieldExpression(vcsField),
                                        "fireVetoableChange",
                                        new ArgumentListExpression(
                                                new Expression[]{
                                                        new VariableExpression("name"),
                                                        new VariableExpression("oldValue"),
                                                        new VariableExpression("newValue")})))));
        // add method:
        // VetoableChangeListener[] getVetoableChangeListeners() {
        //   return this$vetoableChangeSupport.getVetoableChangeListeners
        // }
        declaringClass.addMethod(
                new MethodNode(
                        "getVetoableChangeListeners",
                        ACC_PUBLIC | ACC_SYNTHETIC,
                        vclClassNode.makeArray(),
                        .,
                        .,
                        new ReturnStatement(
                                new ExpressionStatement(
                                        new MethodCallExpression(
                                                new FieldExpression(vcsField),
                                                "getVetoableChangeListeners",
                                                .)))));
        // add method:
        // VetoableChangeListener[] getVetoableChangeListeners(String name) {
        //   return this$vetoableChangeSupport.getVetoableChangeListeners(name)
        // }
        declaringClass.addMethod(
                new MethodNode(
                        "getVetoableChangeListeners",
                        ACC_PUBLIC | ACC_SYNTHETIC,
                        vclClassNode.makeArray(),
                        new Parameter[]{new Parameter(."name")},
                        .,
                        new ReturnStatement(
                                new ExpressionStatement(
                                        new MethodCallExpression(
                                                new FieldExpression(vcsField),
                                                "getVetoableChangeListeners",
                                                new ArgumentListExpression(
                                                new Expression[]{new VariableExpression("name")}))))));
    }
New to GrepCode? Check out our FAQ X