org.spockframework.compiler.AstUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.spockframework.compiler.AstUtil.java

Source

/*
 * Copyright 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.spockframework.compiler;

import java.util.*;

import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.*;
import org.codehaus.groovy.runtime.dgmimpl.arrays.*;

import org.objectweb.asm.Opcodes;

import org.spockframework.lang.Wildcard;
import org.spockframework.runtime.SpockRuntime;
import org.spockframework.util.Nullable;

import org.spockframework.util.ObjectUtil;
import spock.lang.Specification;

/**
 * Utility methods for AST processing.
 * 
 * @author Peter Niederwieser
 */
public abstract class AstUtil {
    private static String GET_AT_METHOD_NAME = new IntegerArrayGetAtMetaMethod().getName();

    /**
     * Tells whether the given node has an annotation of the given type.
     *
     * @param node an AST node
     * @param annotationType an annotation type
     * @return <tt>true</tt> iff the given node has an annotation of the given type
     */
    public static boolean hasAnnotation(ASTNode node, Class<?> annotationType) {
        return getAnnotation(node, annotationType) != null;
    }

    public static AnnotationNode getAnnotation(ASTNode node, Class<?> annotationType) {
        if (!(node instanceof AnnotatedNode))
            return null;

        AnnotatedNode annotated = (AnnotatedNode) node;
        @SuppressWarnings("unchecked")
        List<AnnotationNode> annotations = annotated.getAnnotations();
        for (AnnotationNode a : annotations)
            if (a.getClassNode().getName().equals(annotationType.getName()))
                return a;
        return null;
    }

    /**
     * Returns a list of statements of the given method. Modifications to the returned list
     * will affect the method's statements.
     *
     * @param method a method (node)
     * @return a list of statements of the given method
     */
    @SuppressWarnings("unchecked")
    public static List<Statement> getStatements(MethodNode method) {
        Statement code = method.getCode();
        if (!(code instanceof BlockStatement)) { // null or single statement
            BlockStatement block = new BlockStatement();
            if (code != null)
                block.addStatement(code);
            method.setCode(block);
        }
        return ((BlockStatement) method.getCode()).getStatements();
    }

    @SuppressWarnings("unchecked")
    public static List<Statement> getStatements(ClosureExpression closure) {
        BlockStatement blockStat = (BlockStatement) closure.getCode();
        return blockStat == null ? Collections.<Statement>emptyList() : // it's not possible to add any statements to such a ClosureExpression, so immutable list is OK
                blockStat.getStatements();
    }

    public static boolean isInvocationWithImplicitThis(Expression invocation) {
        if (invocation instanceof PropertyExpression) {
            return ((PropertyExpression) invocation).isImplicitThis();
        }
        if (invocation instanceof MethodCallExpression) {
            return ((MethodCallExpression) invocation).isImplicitThis();
        }
        return false;
    }

    public static boolean hasImplicitParameter(ClosureExpression expr) {
        return expr.getParameters() != null && expr.getParameters().length == 0;
    }

    public static Expression getImplicitParameterRef(ClosureExpression expr) {
        assert hasImplicitParameter(expr);

        DynamicVariable var = new DynamicVariable("it", false);
        return new VariableExpression(var);
    }

    public static Expression getInvocationTarget(Expression expr) {
        if (expr instanceof PropertyExpression)
            return ((PropertyExpression) expr).getObjectExpression();
        if (expr instanceof MethodCallExpression)
            return ((MethodCallExpression) expr).getObjectExpression();
        if (expr instanceof StaticMethodCallExpression)
            return new ClassExpression(((StaticMethodCallExpression) expr).getOwnerType());
        if (expr instanceof ConstructorCallExpression)
            return new ClassExpression(((ConstructorCallExpression) expr).getType());
        return null;
    }

    public static boolean isWildcardRef(Expression expr) {
        VariableExpression varExpr = ObjectUtil.asInstance(expr, VariableExpression.class);
        if (varExpr == null || !varExpr.getName().equals(Wildcard.INSTANCE.toString()))
            return false;

        Variable accessedVar = varExpr.getAccessedVariable();
        if (!(accessedVar instanceof FieldNode))
            return false;

        return ((FieldNode) accessedVar).getOwner().getName().equals(Specification.class.getName());
    }

    public static boolean isJavaIdentifier(String id) {
        if (id == null || id.length() == 0 || !Character.isJavaIdentifierStart(id.charAt(0)))
            return false;

        for (int i = 1; i < id.length(); i++)
            if (!Character.isJavaIdentifierPart(id.charAt(i)))
                return false;

        return true;
    }

    public static <T extends Expression> T getExpression(Statement stat, Class<T> type) {
        ExpressionStatement exprStat = ObjectUtil.asInstance(stat, ExpressionStatement.class);
        if (exprStat == null)
            return null;
        return ObjectUtil.asInstance(exprStat.getExpression(), type);
    }

    public static boolean isSynthetic(MethodNode method) {
        return method.isSynthetic() || (method.getModifiers() & Opcodes.ACC_SYNTHETIC) != 0;
    }

    /**
     * Tells if the source position for the given AST node is plausible.
     * Does not imply that the source position is correct.
     *
     * @param node an AST node
     * @return <tt>true</tt> iff the AST node has a plausible source position
     */
    public static boolean hasPlausibleSourcePosition(ASTNode node) {
        return node.getLineNumber() > 0 && node.getLastLineNumber() >= node.getLineNumber()
                && node.getColumnNumber() > 0 && node.getLastColumnNumber() > node.getColumnNumber();
    }

    public static String getMethodName(Expression invocation) {
        if (invocation instanceof MethodCallExpression) {
            return ((MethodCallExpression) invocation).getMethodAsString();
        }
        if (invocation instanceof StaticMethodCallExpression) {
            return ((StaticMethodCallExpression) invocation).getMethod();
        }
        return null;
    }

    public static Expression getArguments(Expression invocation) {
        if (invocation instanceof MethodCallExpression)
            return ((MethodCallExpression) invocation).getArguments();
        if (invocation instanceof StaticMethodCallExpression)
            return ((StaticMethodCallExpression) invocation).getArguments();
        if (invocation instanceof ConstructorCallExpression)
            return ((ConstructorCallExpression) invocation).getArguments();
        return null;
    }

    // Note: The returned list may contain SpreadExpression's,
    // which can't be used outside an ArgumentListExpression.
    // To pass an argument list to the Groovy or Spock runtime,
    // use this method together with AstUtil.toArgumentArray().
    public static List<Expression> getArgumentList(Expression invocation) {
        return asArgumentList(getArguments(invocation));
    }

    private static List<Expression> asArgumentList(Expression arguments) {
        // MethodCallExpression.getArguments() may return an ArgumentListExpression,
        // a TupleExpression that wraps a NamedArgumentListExpression,
        // or (at least in 1.6) a NamedArgumentListExpression

        if (arguments instanceof NamedArgumentListExpression)
            // return same result as for NamedArgumentListExpression wrapped in TupleExpression
            return Collections.singletonList(arguments);

        return ((TupleExpression) arguments).getExpressions();
    }

    /**
     * Turns an argument list obtained from AstUtil.getArguments() into an Object[] array
     * suitable to be passed to InvokerHelper or SpockRuntime. The main challenge is
     * to handle SpreadExpression's correctly.
     */
    public static Expression toArgumentArray(List<Expression> argList, IRewriteResources resources) {
        List<Expression> normalArgs = new ArrayList<Expression>();
        List<Expression> spreadArgs = new ArrayList<Expression>();
        List<Expression> spreadPositions = new ArrayList<Expression>();

        for (int i = 0; i < argList.size(); i++) {
            Expression arg = argList.get(i);
            if (arg instanceof SpreadExpression) {
                spreadArgs.add(((SpreadExpression) arg).getExpression());
                spreadPositions.add(new ConstantExpression(i));
            } else {
                normalArgs.add(arg);
            }
        }

        if (spreadArgs.isEmpty())
            return new ArrayExpression(ClassHelper.OBJECT_TYPE, argList);

        return new MethodCallExpression(new ClassExpression(resources.getAstNodeCache().SpockRuntime),
                new ConstantExpression(SpockRuntime.DESPREAD_LIST),
                new ArgumentListExpression(new ArrayExpression(ClassHelper.OBJECT_TYPE, normalArgs),
                        new ArrayExpression(ClassHelper.OBJECT_TYPE, spreadArgs),
                        new ArrayExpression(ClassHelper.int_TYPE, spreadPositions)));
    }

    public static void copySourcePosition(ASTNode from, ASTNode to) {
        to.setLineNumber(from.getLineNumber());
        to.setLastLineNumber(from.getLastLineNumber());
        to.setColumnNumber(from.getColumnNumber());
        to.setLastColumnNumber(from.getLastColumnNumber());
    }

    @Nullable
    public static Expression getAssertionMessage(AssertStatement stat) {
        Expression msg = stat.getMessageExpression();
        if (msg == null)
            return null; // should not happen
        if (!(msg instanceof ConstantExpression))
            return msg;
        return ((ConstantExpression) msg).isNullExpression() ? null : msg;
    }

    public static boolean isThisExpression(Expression expr) {
        return expr instanceof VariableExpression && ((VariableExpression) expr).isThisExpression();
    }

    public static boolean isSuperExpression(Expression expr) {
        return expr instanceof VariableExpression && ((VariableExpression) expr).isSuperExpression();
    }

    public static boolean isThisOrSuperExpression(Expression expr) {
        return isThisExpression(expr) || isSuperExpression(expr);
    }

    public static void setVisibility(MethodNode method, int visibility) {
        int modifiers = method.getModifiers();
        modifiers &= ~(Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
        method.setModifiers(modifiers | visibility);
    }

    public static int getVisibility(FieldNode field) {
        return field.getModifiers() & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
    }

    public static void setVisibility(FieldNode field, int visibility) {
        int modifiers = field.getModifiers();
        modifiers &= ~(Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
        field.setModifiers(modifiers | visibility);
    }

    public static boolean isJointCompiled(ClassNode clazz) {
        return clazz.getModule().getUnit().getConfig().getJointCompilationOptions() != null;
    }

    public static MethodCallExpression createDirectMethodCall(Expression target, MethodNode method,
            Expression arguments) {
        MethodCallExpression result = new MethodCallExpression(target, method.getName(), arguments);
        result.setMethodTarget(method);
        result.setImplicitThis(false); // see http://groovy.329449.n5.nabble.com/Problem-with-latest-2-0-beta-3-snapshot-and-Spock-td5496353.html
        return result;
    }

    public static void deleteMethod(ClassNode clazz, MethodNode method) {
        // as of 1.8.6, this is the best possible implementation
        clazz.getMethods().remove(method);
        clazz.getDeclaredMethods(method.getName()).remove(method);
    }

    public static Expression getVariableName(BinaryExpression assignment) {
        Expression left = assignment.getLeftExpression();
        if (left instanceof Variable)
            return new ConstantExpression(((Variable) left).getName());
        if (left instanceof FieldExpression)
            return new ConstantExpression(((FieldExpression) left).getFieldName());
        return ConstantExpression.NULL;
    }

    public static Expression getVariableType(BinaryExpression assignment) {
        ClassNode type = assignment.getLeftExpression().getType();
        return type == null || type == ClassHelper.DYNAMIC_TYPE ? ConstantExpression.NULL
                : new ClassExpression(type);
    }

    public static void fixUpLocalVariables(Variable[] localVariables, VariableScope scope, boolean isClosureScope) {
        fixUpLocalVariables(Arrays.asList(localVariables), scope, isClosureScope);
    }

    public static MethodCallExpression createGetAtMethod(Expression expression, int index) {
        return new MethodCallExpression(expression, GET_AT_METHOD_NAME, new ConstantExpression(index));
    }

    /**
     * Fixes up scope references to variables that used to be free or class variables,
     * and have been changed to local variables.
     */
    public static void fixUpLocalVariables(List<? extends Variable> localVariables, VariableScope scope,
            boolean isClosureScope) {
        for (Variable localVar : localVariables) {
            Variable scopeVar = scope.getReferencedClassVariable(localVar.getName());
            if (scopeVar instanceof DynamicVariable) {
                scope.removeReferencedClassVariable(localVar.getName());
                scope.putReferencedLocalVariable(localVar);
                if (isClosureScope)
                    localVar.setClosureSharedVariable(true);
            }
        }
    }
}