All Downloads are FREE. Search and download functionalities are using the official Maven repository.

groovy.beans.ListenerListASTTransformation.groovy Maven / Gradle / Ivy

There is a newer version: 3.0.21
Show newest version
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.control.messages.SyntaxErrorMessage
import org.codehaus.groovy.syntax.SyntaxException
import org.codehaus.groovy.syntax.Token
import org.codehaus.groovy.syntax.Types
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.objectweb.asm.Opcodes
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.ast.expr.*
import org.codehaus.groovy.ast.stmt.*

/**
 * Handles generation of code for the {@code @ListenerList} annotation.
 * 

* Generally, it adds the needed add<Listener>, remove<Listener> and * get<Listener>s methods to support the Java Beans API. *

* Additionally it adds corresponding fire<Event> methods. *

* * @author Alexander Klein * @author Hamlet D'Arcy */ @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) class ListenerListASTTransformation implements ASTTransformation, Opcodes { private static final Class MY_CLASS = groovy.beans.ListenerList.class private static final ClassNode COLLECTION_TYPE = ClassHelper.make(Collection) public void visit(ASTNode[] nodes, SourceUnit source) { if (!(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) { throw new RuntimeException("Internal error: wrong types: ${node.class} / ${parent.class}") } AnnotationNode node = nodes[0] FieldNode field = nodes[1] ClassNode declaringClass = nodes[1].declaringClass ClassNode parentClass = field.type boolean isCollection = parentClass.isDerivedFrom(COLLECTION_TYPE) || parentClass.implementsInterface(COLLECTION_TYPE) if (!isCollection) { addError(node, source, '@' + MY_CLASS.name + ' can only annotate collection properties.') return } def types = field.type.genericsTypes if (!types) { addError(node, source, '@' + MY_CLASS.name + ' fields must have a generic type.') return } if (types[0].wildcard) { addError(node, source, '@' + MY_CLASS.name + ' fields with generic wildcards not yet supported.') return } def listener = types[0].type if (!field.initialValueExpression) { field.initialValueExpression = new ListExpression() } def name = node.getMember('name')?.value ?: listener.nameWithoutPackage def fireList = listener.methods.findAll { MethodNode m -> m.isPublic() && !m.isSynthetic() && !m.isStatic() } def synchronize = node.getMember('synchronize')?.value ?: false addAddListener(source, node, declaringClass, field, listener, name, synchronize) addRemoveListener(source, node, declaringClass, field, listener, name, synchronize) addGetListeners(source, node, declaringClass, field, listener, name, synchronize) fireList.each { MethodNode method -> addFireMethods(source, node, declaringClass, field, types, synchronize, method) } } private static def addError(AnnotationNode node, SourceUnit source, String message) { source.errorCollector.addError( new SyntaxErrorMessage(new SyntaxException( message, node.lineNumber, node.columnNumber), source)) } /** * Adds the add<Listener> method like: *

     * synchronized void add${name.capitalize}(${listener.name} listener) {
     *     if (listener == null)
     *         return
     *     if (${field.name} == null)
     *        ${field.name} = []
     *     ${field.name}.add(listener)
     * }
     * 
*/ void addAddListener(SourceUnit source, AnnotationNode node, ClassNode declaringClass, FieldNode field, ClassNode listener, String name, synchronize) { def methodModifiers = synchronize ? ACC_PUBLIC | ACC_SYNCHRONIZED : ACC_PUBLIC def methodReturnType = ClassHelper.make(Void.TYPE) def methodName = "add${name.capitalize()}" def cn = ClassHelper.makeWithoutCaching(listener.name) cn.redirect = listener def methodParameter = [new Parameter(cn,'listener')] as Parameter[] if (declaringClass.hasMethod(methodName, methodParameter)) { addError node, source, "Conflict using @${MY_CLASS.name}. Class $declaringClass.name already has method $methodName" return } BlockStatement block = new BlockStatement() block.addStatements([ new IfStatement( new BooleanExpression( new BinaryExpression( new VariableExpression('listener'), Token.newSymbol(Types.COMPARE_EQUAL, 0, 0), ConstantExpression.NULL ) ), new ReturnStatement(ConstantExpression.NULL), EmptyStatement.INSTANCE ), new IfStatement( new BooleanExpression( new BinaryExpression( new VariableExpression(field.name), Token.newSymbol(Types.COMPARE_EQUAL, 0, 0), ConstantExpression.NULL ) ), new ExpressionStatement( new BinaryExpression( new VariableExpression(field.name), Token.newSymbol(Types.EQUAL, 0, 0), new ListExpression() ) ), EmptyStatement.INSTANCE ), new ExpressionStatement( new MethodCallExpression(new VariableExpression(field.name), new ConstantExpression('add'), new ArgumentListExpression(new VariableExpression('listener'))) ) ]) declaringClass.addMethod(new MethodNode(methodName, methodModifiers, methodReturnType, methodParameter, [] as ClassNode[], block)) } /** * Adds the remove method like: *
     * synchronized void remove${name.capitalize}(${listener.name} listener) {
     *     if (listener == null)
     *         return
     *     if (${field.name} == null)
     *         ${field.name} = []
     *     ${field.name}.remove(listener)
     * }
     * 
*/ void addRemoveListener(SourceUnit source, AnnotationNode node, ClassNode declaringClass, FieldNode field, ClassNode listener, String name, synchronize) { def methodModifiers = synchronize ? ACC_PUBLIC | ACC_SYNCHRONIZED : ACC_PUBLIC def methodReturnType = ClassHelper.make(Void.TYPE) def methodName = "remove${name.capitalize()}" def cn = ClassHelper.makeWithoutCaching(listener.name) cn.redirect = listener def methodParameter = [new Parameter(cn,'listener')] as Parameter[] if (declaringClass.hasMethod(methodName, methodParameter)) { addError node, source, "Conflict using @${MY_CLASS.name}. Class $declaringClass.name already has method $methodName" return } BlockStatement block = new BlockStatement() block.addStatements([ new IfStatement( new BooleanExpression( new BinaryExpression( new VariableExpression('listener'), Token.newSymbol(Types.COMPARE_EQUAL, 0, 0), ConstantExpression.NULL ) ), new ReturnStatement(ConstantExpression.NULL), EmptyStatement.INSTANCE ), new IfStatement( new BooleanExpression( new BinaryExpression( new VariableExpression(field.name), Token.newSymbol(Types.COMPARE_EQUAL, 0, 0), ConstantExpression.NULL ) ), new ExpressionStatement( new BinaryExpression( new VariableExpression(field.name), Token.newSymbol(Types.EQUAL, 0, 0), new ListExpression() ) ), EmptyStatement.INSTANCE ), new ExpressionStatement( new MethodCallExpression(new VariableExpression(field.name), new ConstantExpression('remove'), new ArgumentListExpression(new VariableExpression("listener"))) ) ]) declaringClass.addMethod(new MethodNode(methodName, methodModifiers, methodReturnType, methodParameter, [] as ClassNode[], block)) } /** * Adds the get<Listener>s method like: *
     * synchronized ${name.capitalize}[] get${name.capitalize}s() {
     *     def __result = []
     *     if (${field.name} != null)
     *         __result.addAll(${field.name})
     *     return __result as ${name.capitalize}[]
     * }
     * 
*/ void addGetListeners(SourceUnit source, AnnotationNode node, ClassNode declaringClass, FieldNode field, ClassNode listener, String name, synchronize) { def methodModifiers = synchronize ? ACC_PUBLIC | ACC_SYNCHRONIZED : ACC_PUBLIC def methodReturnType = listener.makeArray() def methodName = "get${name.capitalize()}s" def methodParameter = [] as Parameter[] if (declaringClass.hasMethod(methodName, methodParameter)) { addError node, source, "Conflict using @${MY_CLASS.name}. Class $declaringClass.name already has method $methodName" return } BlockStatement block = new BlockStatement() block.addStatements([ new ExpressionStatement( new DeclarationExpression( new VariableExpression("__result", ClassHelper.DYNAMIC_TYPE), Token.newSymbol(Types.EQUALS, 0, 0), new ListExpression() )), new IfStatement( new BooleanExpression( new BinaryExpression( new VariableExpression(field.name), Token.newSymbol(Types.COMPARE_NOT_EQUAL, 0, 0), ConstantExpression.NULL ) ), new ExpressionStatement( new MethodCallExpression(new VariableExpression('__result'), new ConstantExpression('addAll'), new ArgumentListExpression(new VariableExpression(field.name))) ), EmptyStatement.INSTANCE ), new ReturnStatement( new CastExpression( methodReturnType, new VariableExpression('__result') ) ) ]) declaringClass.addMethod(new MethodNode(methodName, methodModifiers, methodReturnType, methodParameter, [] as ClassNode[], block)) } /** * Adds the fire<Event> methods like: *
     * void fire${fireMethod.capitalize()}(${parameterList.join(', ')}) {
     *     if (${field.name} != null) {
     *         def __list = new ArrayList(${field.name})
     *         __list.each { listener ->
     *             listener.$eventMethod(${evt})
     *         }
     *     }
     * }
     * 
*/ void addFireMethods(SourceUnit source, AnnotationNode node, ClassNode declaringClass, FieldNode field, GenericsType[] types, boolean synchronize, MethodNode method) { def methodReturnType = ClassHelper.make(Void.TYPE) def methodName = "fire${method.name.capitalize()}" def methodModifiers = synchronize ? ACC_PUBLIC | ACC_SYNCHRONIZED : ACC_PUBLIC if (declaringClass.hasMethod(methodName, method.parameters)) { addError node, source, "Conflict using @${MY_CLASS.name}. Class $declaringClass.name already has method $methodName" return } def args = new ArgumentListExpression(method.parameters) BlockStatement block = new BlockStatement() def listenerListType = ClassHelper.make(ArrayList).plainNodeReference listenerListType.setGenericsTypes(types) block.addStatements([ new IfStatement( new BooleanExpression( new BinaryExpression( new VariableExpression(field.name), Token.newSymbol(Types.COMPARE_NOT_EQUAL, 0, 0), ConstantExpression.NULL ) ), new BlockStatement([ new ExpressionStatement( new DeclarationExpression( new VariableExpression('__list', listenerListType), Token.newSymbol(Types.EQUALS, 0, 0), new ConstructorCallExpression(listenerListType, new ArgumentListExpression( new VariableExpression(field.name) )) ) ), new ForStatement( new Parameter(ClassHelper.DYNAMIC_TYPE, 'listener'), new VariableExpression('__list'), new BlockStatement([ new ExpressionStatement( new MethodCallExpression( new VariableExpression('listener'), method.name, args ) ) ], new VariableScope()) ) ], new VariableScope()), EmptyStatement.INSTANCE ) ]) def params = method.parameters.collect { def paramType = ClassHelper.getWrapper(it.type) def cn = ClassHelper.makeWithoutCaching(paramType.name) cn.setRedirect(paramType) new Parameter(cn, it.name) } declaringClass.addMethod(methodName, methodModifiers, methodReturnType, params as Parameter[], [] as ClassNode[], block) } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy