ListChangeListenerASTTransformation.java
001 /*
002  * SPDX-License-Identifier: Apache-2.0
003  *
004  * Copyright 2008-2021 the original author or authors.
005  *
006  * Licensed under the Apache License, Version 2.0 (the "License");
007  * you may not use this file except in compliance with the License.
008  * You may obtain a copy of the License at
009  *
010  *     http://www.apache.org/licenses/LICENSE-2.0
011  *
012  * Unless required by applicable law or agreed to in writing, software
013  * distributed under the License is distributed on an "AS IS" BASIS,
014  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015  * See the License for the specific language governing permissions and
016  * limitations under the License.
017  */
018 package org.codehaus.griffon.compile.core.ast.transform;
019 
020 import griffon.transform.ListChangeListener;
021 import javafx.collections.WeakListChangeListener;
022 import org.codehaus.groovy.ast.ASTNode;
023 import org.codehaus.groovy.ast.AnnotatedNode;
024 import org.codehaus.groovy.ast.AnnotationNode;
025 import org.codehaus.groovy.ast.ClassNode;
026 import org.codehaus.groovy.ast.FieldNode;
027 import org.codehaus.groovy.ast.expr.ArgumentListExpression;
028 import org.codehaus.groovy.ast.expr.CastExpression;
029 import org.codehaus.groovy.ast.expr.ClosureExpression;
030 import org.codehaus.groovy.ast.expr.ConstantExpression;
031 import org.codehaus.groovy.ast.expr.Expression;
032 import org.codehaus.groovy.ast.expr.ListExpression;
033 import org.codehaus.groovy.ast.expr.VariableExpression;
034 import org.codehaus.groovy.ast.stmt.BlockStatement;
035 import org.codehaus.groovy.control.CompilePhase;
036 import org.codehaus.groovy.control.SourceUnit;
037 import org.codehaus.groovy.transform.GroovyASTTransformation;
038 
039 import static java.lang.reflect.Modifier.FINAL;
040 import static java.lang.reflect.Modifier.PRIVATE;
041 import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.NO_ARGS;
042 import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.THIS;
043 import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.call;
044 import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.ctor;
045 import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.field;
046 import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.stmnt;
047 
048 /**
049  * Handles generation of code for the {@code @ListChangeListener} annotation.
050  <p>
051  * Any closures found as the annotation's value will be either transformed
052  * into inner classes that implement ListChangeListener (when the value
053  * is a closure defined in place) or be casted as a proxy of ListChangeListener
054  * (when the value is a property reference found in the same class).<p>
055  * List of closures are also supported.
056  *
057  @author Andres Almiray
058  @since 2.4.0
059  */
060 @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
061 public class ListChangeListenerASTTransformation extends AbstractASTTransformation {
062     private static final ClassNode LIST_CHANGE_LISTENER_CLASS = makeClassSafe(ListChangeListener.class);
063     private static final ClassNode JAVAFX_LIST_CHANGE_LISTENER_CLASS = makeClassSafe(javafx.collections.ListChangeListener.class);
064     private static final ClassNode JAVAFX_WEAK_LIST_CHANGE_LISTENER_CLASS = makeClassSafe(WeakListChangeListener.class);
065     private static final String EMPTY_STRING = "";
066     private static final String VALUE = "value";
067 
068     /**
069      * Convenience method to see if an annotated node is {@code @ListChangeListener}.
070      *
071      @param node the node to check
072      @return true if the node is an event publisher
073      */
074     public static boolean hasListenerAnnotation(AnnotatedNode node) {
075         for (AnnotationNode annotation : node.getAnnotations()) {
076             if (LIST_CHANGE_LISTENER_CLASS.equals(annotation.getClassNode())) {
077                 return true;
078             }
079         }
080         return false;
081     }
082 
083     public static void addListenerToProperty(SourceUnit source, AnnotationNode annotation, ClassNode declaringClass, FieldNode field) {
084         Expression value = annotation.getMember(VALUE);
085         Expression weak = annotation.getMember("weak");
086         boolean useWeakListener = false;
087         if (weak != null && weak instanceof ConstantExpression && ((ConstantExpressionweak).isTrueExpression()) {
088             useWeakListener = true;
089         }
090 
091         if ((value instanceof ListExpression)) {
092             for (Expression expr : ((ListExpressionvalue).getExpressions()) {
093                 processExpression(declaringClass, field.getName(), expr, useWeakListener);
094             }
095             annotation.setMember(VALUE, new ConstantExpression(EMPTY_STRING));
096         else {
097             processExpression(declaringClass, field.getName(), value, useWeakListener);
098             annotation.setMember(VALUE, new ConstantExpression(EMPTY_STRING));
099         }
100     }
101 
102     private static void processExpression(ClassNode classNode, String propertyName, Expression expression, boolean useWeakListener) {
103         if (expression instanceof ClosureExpression) {
104             addListChangeListener(classNode, propertyName, (ClosureExpressionexpression, useWeakListener);
105         else if (expression instanceof VariableExpression) {
106             addListChangeListener(classNode, propertyName, (VariableExpressionexpression, useWeakListener);
107         else if (expression instanceof ConstantExpression) {
108             addListChangeListener(classNode, propertyName, (ConstantExpressionexpression, useWeakListener);
109         else {
110             throw new RuntimeException("Internal error: wrong expression type. " + expression);
111         }
112     }
113 
114     private static void addListChangeListener(ClassNode classNode, String propertyName, ClosureExpression closure, boolean useWeakListener) {
115         ArgumentListExpression args = createListenerExpression(classNode, propertyName, CastExpression.asExpression(JAVAFX_LIST_CHANGE_LISTENER_CLASS, closure), useWeakListener);
116         addListenerStatement(classNode, propertyName, args);
117     }
118 
119     private static void addListChangeListener(ClassNode classNode, String propertyName, VariableExpression variable, boolean useWeakListener) {
120         ArgumentListExpression args = createListenerExpression(classNode, propertyName, CastExpression.asExpression(JAVAFX_LIST_CHANGE_LISTENER_CLASS, variable), useWeakListener);
121         addListenerStatement(classNode, propertyName, args);
122     }
123 
124     private static ArgumentListExpression createListenerExpression(ClassNode classNode, String propertyName, Expression expression, boolean useWeakListener) {
125         ArgumentListExpression args = new ArgumentListExpression();
126         if (useWeakListener) {
127             String fieldName = "$" + propertyName + "ListChangeListener__" + System.nanoTime();
128             FieldNode listenerField = new FieldNode(
129                 fieldName,
130                 PRIVATE | FINAL,
131                 JAVAFX_LIST_CHANGE_LISTENER_CLASS,
132                 classNode,
133                 expression);
134             classNode.addField(listenerField);
135             ArgumentListExpression params = new ArgumentListExpression();
136             params.addExpression(field(listenerField));
137             args.addExpression(
138                 ctor(JAVAFX_WEAK_LIST_CHANGE_LISTENER_CLASS, params)
139             );
140         else {
141             args.addExpression(CastExpression.asExpression(JAVAFX_LIST_CHANGE_LISTENER_CLASS, expression));
142         }
143         return args;
144     }
145 
146     private static void addListChangeListener(ClassNode classNode, String propertyName, ConstantExpression reference, boolean useWeakListener) {
147         addListChangeListener(classNode, propertyName, new VariableExpression(reference.getText()), useWeakListener);
148     }
149 
150     private static void addListenerStatement(ClassNode classNode, String propertyName, ArgumentListExpression args) {
151         BlockStatement body = new BlockStatement();
152         body.addStatement(stmnt(
153             call(
154                 call(THIS, propertyName + "Property", NO_ARGS),
155                 "addListener",
156                 args
157             )
158         ));
159 
160         classNode.addObjectInitializerStatements(body);
161     }
162 
163     /**
164      * Handles the bulk of the processing, mostly delegating to other methods.
165      *
166      @param nodes  the ast nodes
167      @param source the source unit for the nodes
168      */
169     public void visit(ASTNode[] nodes, SourceUnit source) {
170         if (!(nodes[0instanceof AnnotationNode|| !(nodes[1instanceof AnnotatedNode)) {
171             throw new RuntimeException("Internal error: wrong types: " + nodes[0].getClass() " / " + nodes[1].getClass());
172         }
173 
174         AnnotationNode annotation = (AnnotationNodenodes[0];
175         AnnotatedNode parent = (AnnotatedNodenodes[1];
176 
177         ClassNode declaringClass = parent.getDeclaringClass();
178         if (parent instanceof FieldNode) {
179             addListenerToProperty(source, annotation, declaringClass, (FieldNodeparent);
180         }
181     }
182 }