/* * 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.ListChangeListener; import javafx.collections.WeakListChangeListener; 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.VariableExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.transform.GroovyASTTransformation; import static java.lang.reflect.Modifier.FINAL; import static java.lang.reflect.Modifier.PRIVATE; import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.NO_ARGS; import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.THIS; import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.call; import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.ctor; import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.field; import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.stmnt; /** * Handles generation of code for the {@code @ListChangeListener} annotation. * <p> * Any closures found as the annotation's value will be either transformed * into inner classes that implement ListChangeListener (when the value * is a closure defined in place) or be casted as a proxy of ListChangeListener * (when the value is a property reference found in the same class).<p> * List of closures are also supported. * * @author Andres Almiray * @since 2.4.0 */ @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) public class ListChangeListenerASTTransformation extends AbstractASTTransformation { private static final ClassNode LIST_CHANGE_LISTENER_CLASS = makeClassSafe(ListChangeListener.class); private static final ClassNode JAVAFX_LIST_CHANGE_LISTENER_CLASS = makeClassSafe(javafx.collections.ListChangeListener.class); private static final ClassNode JAVAFX_WEAK_LIST_CHANGE_LISTENER_CLASS = makeClassSafe(WeakListChangeListener.class); private static final String EMPTY_STRING = ""; private static final String VALUE = "value"; /** * Convenience method to see if an annotated node is {@code @ListChangeListener}. * * @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 (LIST_CHANGE_LISTENER_CLASS.equals(annotation.getClassNode())) { return true; } } return false; } public static void addListenerToProperty(SourceUnit source, AnnotationNode annotation, ClassNode declaringClass, FieldNode field) { Expression value = annotation.getMember(VALUE); Expression weak = annotation.getMember("weak"); boolean useWeakListener = false; if (weak != null && weak instanceof ConstantExpression && ((ConstantExpression) weak).isTrueExpression()) { useWeakListener = true; } if ((value instanceof ListExpression)) { for (Expression expr : ((ListExpression) value).getExpressions()) { processExpression(declaringClass, field.getName(), expr, useWeakListener); } annotation.setMember(VALUE, new ConstantExpression(EMPTY_STRING)); } else { processExpression(declaringClass, field.getName(), value, useWeakListener); annotation.setMember(VALUE, new ConstantExpression(EMPTY_STRING)); } } private static void processExpression(ClassNode classNode, String propertyName, Expression expression, boolean useWeakListener) { if (expression instanceof ClosureExpression) { addListChangeListener(classNode, propertyName, (ClosureExpression) expression, useWeakListener); } else if (expression instanceof VariableExpression) { addListChangeListener(classNode, propertyName, (VariableExpression) expression, useWeakListener); } else if (expression instanceof ConstantExpression) { addListChangeListener(classNode, propertyName, (ConstantExpression) expression, useWeakListener); } else { throw new RuntimeException("Internal error: wrong expression type. " + expression); } } private static void addListChangeListener(ClassNode classNode, String propertyName, ClosureExpression closure, boolean useWeakListener) { ArgumentListExpression args = createListenerExpression(classNode, propertyName, CastExpression.asExpression(JAVAFX_LIST_CHANGE_LISTENER_CLASS, closure), useWeakListener); addListenerStatement(classNode, propertyName, args); } private static void addListChangeListener(ClassNode classNode, String propertyName, VariableExpression variable, boolean useWeakListener) { ArgumentListExpression args = createListenerExpression(classNode, propertyName, CastExpression.asExpression(JAVAFX_LIST_CHANGE_LISTENER_CLASS, variable), useWeakListener); addListenerStatement(classNode, propertyName, args); } private static ArgumentListExpression createListenerExpression(ClassNode classNode, String propertyName, Expression expression, boolean useWeakListener) { ArgumentListExpression args = new ArgumentListExpression(); if (useWeakListener) { String fieldName = "$" + propertyName + "ListChangeListener__" + System.nanoTime(); FieldNode listenerField = new FieldNode( fieldName, PRIVATE | FINAL, JAVAFX_LIST_CHANGE_LISTENER_CLASS, classNode, expression); classNode.addField(listenerField); ArgumentListExpression params = new ArgumentListExpression(); params.addExpression(field(listenerField)); args.addExpression( ctor(JAVAFX_WEAK_LIST_CHANGE_LISTENER_CLASS, params) ); } else { args.addExpression(CastExpression.asExpression(JAVAFX_LIST_CHANGE_LISTENER_CLASS, expression)); } return args; } private static void addListChangeListener(ClassNode classNode, String propertyName, ConstantExpression reference, boolean useWeakListener) { addListChangeListener(classNode, propertyName, new VariableExpression(reference.getText()), useWeakListener); } private static void addListenerStatement(ClassNode classNode, String propertyName, ArgumentListExpression args) { BlockStatement body = new BlockStatement(); body.addStatement(stmnt( call( call(THIS, propertyName + "Property", NO_ARGS), "addListener", args ) )); classNode.addObjectInitializerStatements(body); } /** * 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); } } }