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 && ((ConstantExpression) weak).isTrueExpression()) {
088 useWeakListener = true;
089 }
090
091 if ((value instanceof ListExpression)) {
092 for (Expression expr : ((ListExpression) value).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, (ClosureExpression) expression, useWeakListener);
105 } else if (expression instanceof VariableExpression) {
106 addListChangeListener(classNode, propertyName, (VariableExpression) expression, useWeakListener);
107 } else if (expression instanceof ConstantExpression) {
108 addListChangeListener(classNode, propertyName, (ConstantExpression) expression, 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[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) {
171 throw new RuntimeException("Internal error: wrong types: " + nodes[0].getClass() + " / " + nodes[1].getClass());
172 }
173
174 AnnotationNode annotation = (AnnotationNode) nodes[0];
175 AnnotatedNode parent = (AnnotatedNode) nodes[1];
176
177 ClassNode declaringClass = parent.getDeclaringClass();
178 if (parent instanceof FieldNode) {
179 addListenerToProperty(source, annotation, declaringClass, (FieldNode) parent);
180 }
181 }
182 }
|