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

org.codehaus.groovy.transform.CategoryASTTransformation Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2008-2009 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.groovy.transform;

import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.ForStatement;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.syntax.SyntaxException;
import org.objectweb.asm.Opcodes;

import groovy.lang.Reference;

import java.util.Set;
import java.util.LinkedList;
import java.util.HashSet;
import java.util.Arrays;

/**
 * Handles generation of code for the @Category annotation
 * - all non-static methods converted to static ones with additional parameter 'self'
 *
 * @author Alex Tkachman
 */
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class CategoryASTTransformation implements ASTTransformation, Opcodes {
    private static final VariableExpression THIS_EXPRESSION;
    static {
        THIS_EXPRESSION = new VariableExpression("$this");
        THIS_EXPRESSION.setClosureSharedVariable(true);
    }

    /**
     * Property invocations done on 'this' reference are transformed so that the invocations at runtime are
     * done on the additional parameter 'self'
     */
    public void visit(ASTNode[] nodes, final SourceUnit source) {
        if (nodes.length != 2 || !(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof ClassNode)) {
            throw new RuntimeException("Internal error: expecting [AnnotationNode, ClassNode] but got: " + Arrays.asList(nodes));
        }

        AnnotationNode annotation = (AnnotationNode) nodes[0];
        ClassNode parent = (ClassNode) nodes[1];

        ClassNode targetClass = getTargetClass(source, annotation);

        final LinkedList> varStack = new LinkedList>();
        Set names = new HashSet();
        for (FieldNode field : parent.getFields()) {
            names.add(field.getName());
        }
        varStack.add(names);
        final Reference parameter = new Reference();
        final ClassCodeExpressionTransformer expressionTransformer = new ClassCodeExpressionTransformer() {
            protected SourceUnit getSourceUnit() {
                return source;
            }

            private void addVariablesToStack(Parameter[] params) {
                Set names = new HashSet();
                names.addAll(varStack.getLast());
                for (Parameter param : params) {
                    names.add(param.getName());
                }
                varStack.add(names);
            }
            
            public void visitMethod(MethodNode node) {
                addVariablesToStack(node.getParameters());
                super.visitMethod(node);
                varStack.removeLast();
            }

            public void visitBlockStatement(BlockStatement block) {
                Set names = new HashSet();
                names.addAll(varStack.getLast());
                varStack.add(names);
                super.visitBlockStatement(block);
                varStack.remove(names);
            }

            public void visitDeclarationExpression(DeclarationExpression expression) {
                varStack.getLast().add(expression.getVariableExpression().getName());
                super.visitDeclarationExpression(expression);
            }

            public void visitForLoop(ForStatement forLoop) {
                Expression exp = forLoop.getCollectionExpression();
                exp.visit(this);
                Parameter loopParam = forLoop.getVariable();
                if (loopParam != null) {
                    varStack.getLast().add(loopParam.getName());
                }
                super.visitForLoop(forLoop);
            }

            public void visitExpressionStatement(ExpressionStatement es) {
                // GROOVY-3543: visit the declaration expressions so that declaration variables get added on the varStack
                Expression exp = es.getExpression();
                if (exp instanceof DeclarationExpression) {
                    exp.visit(this);
                }
                super.visitExpressionStatement(es);
            }

            public Expression transform(Expression exp) {
                if (exp instanceof VariableExpression) {
                    VariableExpression ve = (VariableExpression) exp;
                    if (ve.getName().equals("this"))
                        return THIS_EXPRESSION;
                    else {
                        if (!varStack.getLast().contains(ve.getName())) {
                            return new PropertyExpression(THIS_EXPRESSION, ve.getName());
                        }
                    }
                } else if (exp instanceof PropertyExpression) {
                    PropertyExpression pe = (PropertyExpression) exp;
                    if (pe.getObjectExpression() instanceof VariableExpression) {
                        VariableExpression vex = (VariableExpression) pe.getObjectExpression();
                        if (vex.isThisExpression()) {
                            pe.setObjectExpression(THIS_EXPRESSION);
                            return pe;
                        }
                    }
                } else if (exp instanceof ClosureExpression) {
                    ClosureExpression ce = (ClosureExpression) exp;
                    ce.getVariableScope().putReferencedLocalVariable((Parameter) parameter.get());
                    Parameter[] params = ce.getParameters();
                    if (params==null){
                        params = new Parameter[0];
                    } else if (params.length==0) {
                        params = new Parameter[] {
                                new Parameter(ClassHelper.OBJECT_TYPE, "it")
                        };
                    }
                    addVariablesToStack(params);
                    ce.getCode().visit(this);
                }
                return super.transform(exp);
            }
        };

        for (MethodNode method : parent.getMethods()) {
            if (!method.isStatic()) {
                method.setModifiers(method.getModifiers() | Opcodes.ACC_STATIC);
                final Parameter[] origParams = method.getParameters();
                final Parameter[] newParams = new Parameter[origParams.length + 1];
                Parameter p = new Parameter(targetClass, "$this");
                p.setClosureSharedVariable(true);
                newParams[0] = p; 
                parameter.set(p);
                System.arraycopy(origParams, 0, newParams, 1, origParams.length);
                method.setParameters(newParams);

                expressionTransformer.visitMethod(method);
            }
        }
    }

    private ClassNode getTargetClass(SourceUnit source, AnnotationNode annotation) {
        final Expression value = annotation.getMember("value");
        if (value == null || !(value instanceof ClassExpression)) {
            //noinspection ThrowableInstanceNeverThrown
            source.getErrorCollector().addErrorAndContinue(
                    new SyntaxErrorMessage(new SyntaxException(
                            "@groovy.lang.Category must define 'value' which is the class to apply this category to",
                            annotation.getLineNumber(),
                            annotation.getColumnNumber()),
                            source));
        }

        return value != null ? value.getType() : null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy