/* * Copyright 2008-2017 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 org.codehaus.griffon.compile.core.ast.transform; import griffon.transform.PropertyListener; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.AnnotationNode; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.expr.ArgumentListExpression; import org.codehaus.groovy.ast.expr.CastExpression; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.Expression; 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.ExpressionStatement; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.transform.GroovyASTTransformation; import java.beans.PropertyChangeListener; import java.util.Map; /** * Handles generation of code for the {@code @PropertyListener} annotation. * <p/> * Any closures found as the annotation's value will be either transformed * into inner classes that implement PropertyChangeListener (when the value * is a closure defined in place) or be casted as a proxy of PropertyChangeListener * (when the value is a property reference found in the same class).<p> * List of closures are also supported. * * @author Andres Almiray */ @GroovyASTTransformation(phase= CompilePhase.CANONICALIZATION) public class PropertyListenerASTTransformation extends AbstractASTTransformation { private static final ClassNode PROPERTY_LISTENER_CLASS = makeClassSafe(PropertyListener.class); private static final ClassNode PROPERTY_CHANGE_LISTENER_CLASS = makeClassSafe(PropertyChangeListener.class); private static final String EMPTY_STRING = ""; /** * Convenience method to see if an annotated node is {@code @PropertyListener}. * * @param node the node to check * @return true if the node is an event publisher */ public static boolean hasListenerAnnotation(AnnotatedNode node) { for (AnnotationNode annotation : node.getAnnotations()) { if (PROPERTY_LISTENER_CLASS.equals(annotation.getClassNode())) { return true; } } return false; } public static void addListenerToProperty(SourceUnit source, AnnotationNode annotation, ClassNode declaringClass, FieldNode field) { for (Map.Entry<String, Expression> member : annotation.getMembers().entrySet()) { Expression value = member.getValue(); if ((value instanceof ListExpression)) { for (Expression expr : ((ListExpression) value).getExpressions()) { processExpression(declaringClass, field.getName(), expr); } member.setValue(new ConstantExpression(EMPTY_STRING)); } else { processExpression(declaringClass, field.getName(), value); member.setValue(new ConstantExpression(EMPTY_STRING)); } } } public static void addListenerToClass(SourceUnit source, AnnotationNode annotation, ClassNode classNode) { for (Map.Entry<String, Expression> member : annotation.getMembers().entrySet()) { Expression value = member.getValue(); if ((value instanceof ListExpression)) { for (Expression expr : ((ListExpression) value).getExpressions()) { processExpression(classNode, null, expr); } member.setValue(new ConstantExpression(EMPTY_STRING)); } else { processExpression(classNode, null, value); member.setValue(new ConstantExpression(EMPTY_STRING)); } } } /** * 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: " + nodes[0].getClass() + " / " + nodes[1].getClass()); } AnnotationNode annotation = (AnnotationNode) nodes[0]; AnnotatedNode parent = (AnnotatedNode) nodes[1]; ClassNode declaringClass = parent.getDeclaringClass(); if (parent instanceof FieldNode) { addListenerToProperty(source, annotation, declaringClass, (FieldNode) parent); } else if (parent instanceof ClassNode) { addListenerToClass(source, annotation, (ClassNode) parent); } } private static void processExpression(ClassNode classNode, String propertyName, Expression expression) { if (expression instanceof ClosureExpression) { addPropertyChangeListener(classNode, propertyName, (ClosureExpression) expression); } else if (expression instanceof VariableExpression) { addPropertyChangeListener(classNode, propertyName, (VariableExpression) expression); } else if (expression instanceof ConstantExpression) { addPropertyChangeListener(classNode, propertyName, (ConstantExpression) expression); } else { throw new RuntimeException("Internal error: wrong expression type. " + expression); } } private static void addPropertyChangeListener(ClassNode classNode, String propertyName, ClosureExpression closure) { ArgumentListExpression args = new ArgumentListExpression(); if (propertyName != null) args.addExpression(new ConstantExpression(propertyName)); args.addExpression(CastExpression.asExpression(PROPERTY_CHANGE_LISTENER_CLASS, closure)); addListenerStatement(classNode, args); } private static void addPropertyChangeListener(ClassNode classNode, String propertyName, VariableExpression variable) { ArgumentListExpression args = new ArgumentListExpression(); if (propertyName != null) args.addExpression(new ConstantExpression(propertyName)); args.addExpression(CastExpression.asExpression(PROPERTY_CHANGE_LISTENER_CLASS, variable)); addListenerStatement(classNode, args); } private static void addPropertyChangeListener(ClassNode classNode, String propertyName, ConstantExpression reference) { ArgumentListExpression args = new ArgumentListExpression(); if (propertyName != null) args.addExpression(new ConstantExpression(propertyName)); VariableExpression variable = new VariableExpression(reference.getText()); args.addExpression(CastExpression.asExpression(PROPERTY_CHANGE_LISTENER_CLASS, variable)); addListenerStatement(classNode, args); } private static void addListenerStatement(ClassNode classNode, ArgumentListExpression args) { BlockStatement body = new BlockStatement(); body.addStatement(new ExpressionStatement( new MethodCallExpression( VariableExpression.THIS_EXPRESSION, "addPropertyChangeListener", args ) )); classNode.addObjectInitializerStatements(body); } }