![JAR search and dependency download from the Maven repository](/logo.png)
org.codenarc.util.AstUtil Maven / Gradle / Ivy
/*
* 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.codenarc.util;
import static java.util.Arrays.*;
import groovy.lang.Closure;
import groovy.lang.MetaClass;
import groovy.lang.Range;
import org.codehaus.groovy.ast.*;
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.IfStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codenarc.source.SourceCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Contains static utility methods and constants related to Groovy AST.
*
* This is an internal class and its API is subject to change.
*
* @author Chris Mair
* @author Hamlet D'Arcy
*/
@SuppressWarnings("PMD.CollapsibleIfStatements")
public class AstUtil {
private static final Logger LOG = LoggerFactory.getLogger(AstUtil.class);
private static final char NEWLINE = '\n';
public static final List AUTO_IMPORTED_PACKAGES = asList("java.lang", "java.io", "java.net", "java.util", "groovy.lang", "groovy.util");
public static final List AUTO_IMPORTED_CLASSES = asList("java.math.BigDecimal", "java.math.BigInteger");
public static final List COMPARISON_OPERATORS = asList("==", "!=", "<", "<=", ">", ">=", "<=>");
private static final Map> PREDEFINED_CONSTANTS = new HashMap>();
static {
PREDEFINED_CONSTANTS.put("Boolean", asList("FALSE", "TRUE"));
}
/**
* Private constructor. All methods are static.
*/
private AstUtil() { }
/**
* Tells you if an expression is a constant or literal. Basically, is it a map, list, constant, or a predefined
* constant like true/false.
* @param expression
* any expression
* @return
* as described
*/
public static boolean isConstantOrLiteral(Expression expression) {
if (expression instanceof ConstantExpression) return true;
if (expression instanceof ListExpression) return true;
if (expression instanceof MapExpression) return true;
return isPredefinedConstant(expression);
}
/**
* Tells you if the expression is a predefined constant like TRUE or FALSE.
* @param expression
* any expression
* @return
* as described
*/
private static boolean isPredefinedConstant(Expression expression) {
if (expression instanceof PropertyExpression) {
Expression object = ((PropertyExpression) expression).getObjectExpression();
Expression property = ((PropertyExpression) expression).getProperty();
if (object instanceof VariableExpression) {
List predefinedConstantNames = PREDEFINED_CONSTANTS.get(((VariableExpression) object).getName());
if (predefinedConstantNames != null && predefinedConstantNames.contains(property.getText())) {
return true;
}
}
}
return false;
}
/**
* Returns true if an expression is a constant or else a literal that contains only constant values.
* Basically, is it a constant, or else a map like [a:1, b:99, c:true], or a list like ['abc', 99.0, false]
* @param expression - any expression
*/
public static boolean isConstantOrConstantLiteral(Expression expression) {
return expression instanceof ConstantExpression ||
isPredefinedConstant(expression) ||
isMapLiteralWithOnlyConstantValues(expression) ||
isListLiteralWithOnlyConstantValues(expression);
}
/**
* Returns true if a Map literal that contains only entries where both key and value are constants.
* @param expression - any expression
*/
public static boolean isMapLiteralWithOnlyConstantValues(Expression expression) {
if (expression instanceof MapExpression) {
List entries = ((MapExpression) expression).getMapEntryExpressions();
for (MapEntryExpression entry : entries) {
if (!isConstantOrConstantLiteral(entry.getKeyExpression()) ||
!isConstantOrConstantLiteral(entry.getValueExpression())) {
return false;
}
}
return true;
}
return false;
}
/**
* Returns true if a List literal that contains only entries that are constants.
* @param expression - any expression
*/
public static boolean isListLiteralWithOnlyConstantValues(Expression expression) {
if (expression instanceof ListExpression) {
List expressions = ((ListExpression) expression).getExpressions();
for (Expression e : expressions) {
if (!isConstantOrConstantLiteral(e)) {
return false;
}
}
return true;
}
return false;
}
/**
* Tells you if an expression is the expected constant.
* @param expression
* any expression
* @param expected
* the expected int or String
* @return
* as described
*/
public static boolean isConstant(Expression expression, Object expected) {
return expression instanceof ConstantExpression && expected.equals(((ConstantExpression) expression).getValue());
}
public static boolean isPropertyNamed(Expression property, Object expectedName) {
return (property instanceof PropertyExpression && isConstant(((PropertyExpression) property).getProperty(), expectedName));
}
/**
* Return true if the Statement is a block
* @param statement - the Statement to check
* @return true if the Statement is a block
*/
public static boolean isBlock(Statement statement) {
return statement instanceof BlockStatement;
}
/**
* Return true if the Statement is a block and it is empty (contains no "meaningful" statements).
* This implementation also addresses some "weirdness" around some statement types (specifically finally)
* where the BlockStatement answered false to isEmpty() even if it was.
* @param origStatement - the Statement to check
* @return true if the BlockStatement is empty
*/
public static boolean isEmptyBlock(Statement origStatement) {
Stack stack = new Stack();
stack.push(origStatement);
while (!stack.isEmpty()) {
ASTNode statement = stack.pop();
if (!(statement instanceof BlockStatement)) {
return false;
}
if (((BlockStatement) statement).isEmpty()) {
return true;
}
if (((BlockStatement) statement).getStatements().size() != 1) {
return false;
}
stack.push(((BlockStatement) statement).getStatements().get(0));
}
return false;
}
public static ASTNode getEmptyBlock(Statement origStatement) {
Stack stack = new Stack();
stack.push(origStatement);
while (!stack.isEmpty()) {
ASTNode statement = stack.pop();
if (!(statement instanceof BlockStatement)) {
return null;
}
if (((BlockStatement) statement).isEmpty()) {
return statement;
}
if (((BlockStatement) statement).getStatements().size() != 1) {
return null;
}
stack.push(((BlockStatement) statement).getStatements().get(0));
}
return null;
}
/**
* Return the List of Arguments for the specified MethodCallExpression or a ConstructorCallExpression.
* The returned List contains either ConstantExpression or MapEntryExpression objects.
* @param methodCall - the AST MethodCallExpression or ConstructorCalLExpression
* @return the List of argument objects
*/
public static List extends Expression> getMethodArguments(ASTNode methodCall) {
if (methodCall instanceof ConstructorCallExpression) {
return extractExpressions(((ConstructorCallExpression) methodCall).getArguments());
} else if (methodCall instanceof MethodCallExpression) {
return extractExpressions(((MethodCallExpression) methodCall).getArguments());
} else if (methodCall instanceof StaticMethodCallExpression) {
return extractExpressions(((StaticMethodCallExpression) methodCall).getArguments());
} else if (respondsTo(methodCall, "getArguments")) {
throw new RuntimeException(); // TODO: remove, should never happen
}
return new ArrayList();
}
private static List extends Expression> extractExpressions(Expression argumentsExpression ) {
if (argumentsExpression instanceof ArrayExpression) {
return ((ArrayExpression) argumentsExpression).getExpressions();
} else if (argumentsExpression instanceof ListExpression) {
return ((ListExpression) argumentsExpression).getExpressions();
} else if (argumentsExpression instanceof TupleExpression) {
return ((TupleExpression) argumentsExpression).getExpressions();
} else if (argumentsExpression instanceof MapExpression) {
return ((MapExpression) argumentsExpression).getMapEntryExpressions();
} else if (respondsTo(argumentsExpression, "getExpressions")) {
throw new RuntimeException(); // TODO: add warning
} else if (respondsTo(argumentsExpression, "getMapEntryExpressions")) {
throw new RuntimeException(); // TODO: add warning
}
return new ArrayList();
}
/**
* Tells you if the expression is a method call on a particular object (which is represented as a String).
* For instance, you may ask isMethodCallOnObject(e, 'this') to find a this reference.
* @param expression - the expression
* @param methodObjectPattern - the name of the method object (receiver) such as 'this'
* @return
* as described
*/
public static boolean isMethodCallOnObject(Expression expression, String methodObjectPattern) {
if (expression instanceof MethodCallExpression) {
Expression objectExpression = ((MethodCallExpression) expression).getObjectExpression();
if (objectExpression instanceof VariableExpression) {
String name = ((VariableExpression) objectExpression).getName();
if (name != null && name.matches(methodObjectPattern)) {
return true;
}
}
if (objectExpression instanceof PropertyExpression && objectExpression.getText() != null && objectExpression.getText().matches(methodObjectPattern)) {
return true;
}
if (objectExpression instanceof MethodCallExpression && isMethodNamed((MethodCallExpression) objectExpression, methodObjectPattern)) {
return true;
}
}
return false;
}
/**
* Return true only if the Statement represents a method call for the specified method object (receiver),
* method name, and with the specified number of arguments.
* @param stmt - the AST Statement
* @param methodObject - the name of the method object (receiver)
* @param methodName - the name of the method being called
* @param numArguments - the number of arguments passed into the method
* @return true only if the Statement is a method call matching the specified criteria
*/
public static boolean isMethodCall(Statement stmt, String methodObject, String methodName, int numArguments) {
if (stmt instanceof ExpressionStatement) {
Expression expression = ((ExpressionStatement) stmt).getExpression();
if (expression instanceof MethodCallExpression) {
return isMethodCall(expression, methodObject, methodName, numArguments);
}
}
return false;
}
/**
* Return true only if the MethodCallExpression represents a method call for the specified method object (receiver),
* method name, and with the specified number of arguments.
* @param methodCall - the AST MethodCallExpression
* @param methodObjectPattern - the name of the method object (receiver)
* @param methodPattern - the name of the method being called
* @param numArguments - the number of arguments passed into the method
* @return true only if the method call matches the specified criteria
*/
public static boolean isMethodCall(MethodCallExpression methodCall, String methodObjectPattern, String methodPattern, int numArguments) {
return (isMethodCall(methodCall, methodObjectPattern, methodPattern) && AstUtil.getMethodArguments(methodCall).size() == numArguments);
}
/**
* Return true only if the expression is a MethodCallExpression representing a method call for the specified
* method object (receiver), method name, and with the specified number of arguments.
* @param expression - the AST expression
* @param methodObject - the name of the method object (receiver)
* @param methodName - the name of the method being called
* @param numArguments - the number of arguments passed into the method
* @return true only if the method call matches the specified criteria
*/
public static boolean isMethodCall(Expression expression, String methodObject, String methodName, int numArguments) {
return expression instanceof MethodCallExpression && isMethodCall((MethodCallExpression) expression, methodObject, methodName, numArguments);
}
/**
* Return true only if the expression represents a method call (MethodCallExpression) for the specified
* method object (receiver) and method name.
*
* @param expression - the AST expression to be checked
* @param methodObjectPattern - the name of the method object (receiver)
* @param methodNamePattern - the name of the method being called
* @return true only if the expression is a method call that matches the specified criteria
*/
public static boolean isMethodCall(Expression expression, String methodObjectPattern, String methodNamePattern) {
return isMethodCallOnObject(expression, methodObjectPattern) && isMethodNamed((MethodCallExpression) expression, methodNamePattern);
}
/**
* Return true only if the MethodCallExpression represents a method call for any one of the specified method
* objects (receivers) and any one of the method names. Optionally, you can restrict it to a method call with
* a certain number of arguments.
* @param methodCall
* the method call object
* @param methodObjects
* a list of receivers, such as ['this', 'super']
* @param methodNames
* a list of method names
* @param numArguments
* optionally, require a certain number of arguments
* @return
* as described
*/
public static boolean isMethodCall(MethodCallExpression methodCall, List methodObjects, List methodNames, Integer numArguments) {
if (methodNames != null) {
for (String name: methodNames) {
if (methodObjects != null) {
for (String objectName: methodObjects) {
boolean match = isMethodCallOnObject(methodCall, objectName) && isMethodNamed(methodCall, name);
if (match && numArguments == null) {
return true;
} else if (match && getMethodArguments(methodCall).size() == numArguments) {
return true;
}
}
}
}
}
return false;
}
public static boolean isMethodCall(MethodCallExpression methodCall, List methodObjects, List methodNames) {
return isMethodCall(methodCall, methodObjects, methodNames, null);
}
/**
* Tells you if the expression is a method call for a certain method name with a certain
* number of arguments.
* @param expression
* the (potentially) method call
* @param methodName
* the name of the method expected
* @param numArguments
* number of expected arguments
* @return
* as described
*/
public static boolean isMethodCall(Expression expression, String methodName, int numArguments) {
return expression instanceof MethodCallExpression
&& isMethodNamed((MethodCallExpression) expression, methodName)
&& getMethodArguments(expression).size() == numArguments;
}
/**
* Tells you if the expression is a method call for a certain method name with a certain
* number of arguments.
* @param expression
* the (potentially) method call
* @param methodName
* the name of the method expected
* @param numArguments
* number of expected arguments
* @return
* as described
*/
public static boolean isMethodCall(Expression expression, String methodName, Range numArguments) {
if (expression instanceof MethodCallExpression && AstUtil.isMethodNamed((MethodCallExpression) expression, methodName)) {
int arity = AstUtil.getMethodArguments(expression).size();
if (arity >= (Integer)numArguments.getFrom() && arity <= (Integer)numArguments.getTo()) {
return true;
}
}
return false;
}
/**
* Return true only if the MethodCallExpression represents a method call for the specified method name
* @param methodCall - the AST MethodCallExpression
* @param methodNamePattern - the expected name of the method being called
* @param numArguments - The number of expected arguments
* @return true only if the method call name matches
*/
public static boolean isMethodNamed(MethodCallExpression methodCall, String methodNamePattern, Integer numArguments) {
Expression method = methodCall.getMethod();
// !important: performance enhancement
boolean IS_NAME_MATCH = false;
if (method instanceof ConstantExpression) {
if (((ConstantExpression) method).getValue() instanceof String) {
IS_NAME_MATCH = ((String)((ConstantExpression) method).getValue()).matches(methodNamePattern);
}
}
if (IS_NAME_MATCH && numArguments != null) {
return AstUtil.getMethodArguments(methodCall).size() == numArguments;
}
return IS_NAME_MATCH;
}
public static boolean isMethodNamed(MethodCallExpression methodCall, String methodNamePattern) {
return isMethodNamed(methodCall, methodNamePattern, null);
}
/**
* Return true if the expression is a constructor call on any of the named classes, with any number of parameters.
* @param expression - the expression
* @param classNames - the possible List of class names
* @return as described
*/
public static boolean isConstructorCall(Expression expression, List classNames) {
return expression instanceof ConstructorCallExpression && classNames.contains(expression.getType().getName());
}
/**
* Return true if the expression is a constructor call on a class that matches the supplied.
* @param expression - the expression
* @param classNamePattern - the possible List of class names
* @return as described
*/
public static boolean isConstructorCall(Expression expression, String classNamePattern) {
return expression instanceof ConstructorCallExpression && expression.getType().getName().matches(classNamePattern);
}
/**
* Return the AnnotationNode for the named annotation, or else null.
* @param node - the AnnotatedNode
* @param name - the name of the annotation
* @return the AnnotationNode or else null
*/
public static AnnotationNode getAnnotation(AnnotatedNode node, String name) {
return node.getAnnotations().stream()
.filter(a -> a.getClassNode().getName().equals(name))
.findFirst()
.orElse(null);
}
/**
* Return true only if the node has the named annotation
* @param node - the AST Node to check
* @param name - the name of the annotation
* @return true only if the node has the named annotation
*/
public static boolean hasAnnotation(AnnotatedNode node, String name) {
return AstUtil.getAnnotation(node, name) != null;
}
/**
* Return true only if the node has any of the named annotations
* @param node - the AST Node to check
* @param names - the names of the annotations
* @return true only if the node has any of the named annotations
*/
public static boolean hasAnyAnnotation(AnnotatedNode node, String... names) {
for (String name : names) {
if (hasAnnotation(node, name)) {
return true;
}
}
return false;
}
/**
* Return the List of VariableExpression objects referenced by the specified DeclarationExpression.
* @param declarationExpression - the DeclarationExpression
* @return the List of VariableExpression objects
*/
public static List getVariableExpressions(DeclarationExpression declarationExpression) {
Expression leftExpression = declarationExpression.getLeftExpression();
// !important: performance enhancement
if (leftExpression instanceof ArrayExpression) {
List expressions = ((ArrayExpression) leftExpression).getExpressions();
return expressions.isEmpty() ? Arrays.asList(leftExpression) : expressions;
} else if (leftExpression instanceof ListExpression) {
List expressions = ((ListExpression) leftExpression).getExpressions();
return expressions.isEmpty() ? Arrays.asList(leftExpression) : expressions;
} else if (leftExpression instanceof TupleExpression) {
List expressions = ((TupleExpression) leftExpression).getExpressions();
return expressions.isEmpty() ? Arrays.asList(leftExpression) : expressions;
} else if (leftExpression instanceof VariableExpression) {
return Arrays.asList(leftExpression);
}
// todo: write warning
return Collections.emptyList();
}
/**
* Return true if the DeclarationExpression represents a 'final' variable declaration.
*
* NOTE: THIS IS A WORKAROUND.
*
* There does not seem to be an easy way to determine whether the 'final' modifier has been
* specified for a variable declaration. Return true if the 'final' is present before the variable name.
*/
public static boolean isFinalVariable(DeclarationExpression declarationExpression, SourceCode sourceCode) {
if (isFromGeneratedSourceCode(declarationExpression)) {
return false;
}
List variableExpressions = getVariableExpressions(declarationExpression);
if (!variableExpressions.isEmpty()) {
Expression variableExpression = variableExpressions.get(0);
int startOfDeclaration = declarationExpression.getColumnNumber();
int startOfVariableName = variableExpression.getColumnNumber();
int sourceLineNumber = findFirstNonAnnotationLine(declarationExpression, sourceCode);
String sourceLine = sourceCode.getLines().get(sourceLineNumber-1);
String modifiers = (startOfDeclaration >= 0 && startOfVariableName >= 0 && sourceLine.length() >= startOfVariableName) ?
sourceLine.substring(startOfDeclaration - 1, startOfVariableName - 1) : "";
return modifiers.contains("final");
}
return false;
}
/**
* @return true if the ASTNode was generated (synthetic) rather than from the "real" input source code.
*/
public static boolean isFromGeneratedSourceCode(ASTNode node) {
return node.getLineNumber() < 0 || (node instanceof ClassNode && ((ClassNode)node).isScript());
}
/**
* Tells you if the expression is true, which can be true or Boolean.TRUE.
* @param expression
* expression
* @return
* as described
*/
public static boolean isTrue(Expression expression) {
if (expression == null) {
return false;
}
if (expression instanceof PropertyExpression
&& classNodeImplementsType(((PropertyExpression) expression).getObjectExpression().getType(), Boolean.class)) {
if (((PropertyExpression) expression).getProperty() instanceof ConstantExpression
&& "TRUE".equals(((ConstantExpression) ((PropertyExpression) expression).getProperty()).getValue())) {
return true;
}
}
return ((expression instanceof ConstantExpression) && ((ConstantExpression) expression).isTrueExpression()) ||
"Boolean.TRUE".equals(expression.getText());
}
/**
* Tells you if the expression is either the true or false literal.
* @param expression
* expression
* @return
* as described
*/
public static boolean isBoolean(Expression expression) {
return isTrue(expression) || isFalse(expression);
}
/**
* Tells you if the expression is the null literal.
* @param expression
* expression.
* @return
* as described
*/
public static boolean isNull(ASTNode expression) {
return expression instanceof ConstantExpression && ((ConstantExpression)expression).isNullExpression();
}
/**
* Tells you if the expression is the false expression, either literal or constant.
* @param expression
* expression
* @return
* as described
*/
public static boolean isFalse(Expression expression) {
if (expression == null) {
return false;
}
if (expression instanceof PropertyExpression && classNodeImplementsType(((PropertyExpression) expression).getObjectExpression().getType(), Boolean.class)) {
if (((PropertyExpression) expression).getProperty() instanceof ConstantExpression
&& "FALSE".equals(((ConstantExpression) ((PropertyExpression) expression).getProperty()).getValue())) {
return true;
}
}
return ((expression instanceof ConstantExpression) && ((ConstantExpression) expression).isFalseExpression())
|| "Boolean.FALSE".equals(expression.getText());
}
/**
* Return true only if the specified object responds to the named method
* @param object - the object to check
* @param methodName - the name of the method
* @return true if the object responds to the named method
*/
public static boolean respondsTo(Object object, String methodName) {
MetaClass metaClass = DefaultGroovyMethods.getMetaClass(object);
if (!metaClass.respondsTo(object, methodName).isEmpty()) {
return true;
}
Map properties = DefaultGroovyMethods.getProperties(object);
return properties.containsKey(methodName);
}
/**
* This method tells you if a ClassNode implements or extends a certain class.
* @param node
* the node
* @param target
* the class
* @return
* true if the class node 'is a' target
*/
public static boolean classNodeImplementsType(ClassNode node, Class target) {
ClassNode targetNode = ClassHelper.make(target);
if (node.implementsInterface(targetNode)) {
return true;
}
if (node.isDerivedFrom(targetNode)) {
return true;
}
if (node.getName().equals(target.getName())) {
return true;
}
if (node.getName().equals(target.getSimpleName())) {
return true;
}
if (node.getSuperClass() != null && node.getSuperClass().getName().equals(target.getName())) {
return true;
}
if (node.getSuperClass() != null && node.getSuperClass().getName().equals(target.getSimpleName())) {
return true;
}
if (node.getInterfaces() != null) {
for (ClassNode declaredInterface : node.getInterfaces()) {
if (classNodeImplementsType(declaredInterface, target)) {
return true;
}
}
}
return false;
}
/**
* Returns true if the ASTNode is a declaration of a closure, either as a declaration
* or a field.
* @param expression
* the target expression
* @return
* as described
*/
public static boolean isClosureDeclaration(ASTNode expression) {
if (expression instanceof DeclarationExpression) {
if (((DeclarationExpression) expression).getRightExpression() instanceof ClosureExpression) {
return true;
}
}
if (expression instanceof FieldNode) {
ClassNode type = ((FieldNode) expression).getType();
if (AstUtil.classNodeImplementsType(type, Closure.class)) {
return true;
} else if (((FieldNode) expression).getInitialValueExpression() instanceof ClosureExpression) {
return true;
}
}
return false;
}
/**
* Gets the parameter names of a method node.
* @param node
* the node to search parameter names on
* @return
* argument names, never null
*/
public static List getParameterNames(MethodNode node) {
ArrayList result = new ArrayList();
if (node.getParameters() != null) {
for (Parameter parameter : node.getParameters()) {
result.add(parameter.getName());
}
}
return result;
}
/**
* Gets the argument names of a method call. If the arguments are not VariableExpressions then a null
* will be returned.
* @param methodCall
* the method call to search
* @return
* a list of strings, never null, but some elements may be null
*/
public static List getArgumentNames(MethodCallExpression methodCall) {
ArrayList result = new ArrayList();
Expression arguments = methodCall.getArguments();
List argExpressions = null;
if (arguments instanceof ArrayExpression) {
argExpressions = ((ArrayExpression) arguments).getExpressions();
} else if (arguments instanceof ListExpression) {
argExpressions = ((ListExpression) arguments).getExpressions();
} else if (arguments instanceof TupleExpression) {
argExpressions = ((TupleExpression) arguments).getExpressions();
} else {
LOG.warn("getArgumentNames arguments is not an expected type");
}
if (argExpressions != null) {
for (Expression exp : argExpressions) {
if (exp instanceof VariableExpression) {
result.add(((VariableExpression) exp).getName());
}
}
}
return result;
}
/**
* Returns true if the expression is a binary expression with the specified token.
* @param expression
* expression
* @param token
* token
* @return
* as described
*/
public static boolean isBinaryExpressionType(Expression expression, String token) {
if (expression instanceof BinaryExpression) {
if (token.equals(((BinaryExpression) expression).getOperation().getText())) {
return true;
}
}
return false;
}
/**
* Returns true if the expression is a binary expression with the specified token.
* @param expression - the expression node
* @param tokens - the List of allowable (operator) tokens
* @return as described
*/
public static boolean isBinaryExpressionType(Expression expression, List tokens) {
if (expression instanceof BinaryExpression) {
if (tokens.contains(((BinaryExpression) expression).getOperation().getText())) {
return true;
}
}
return false;
}
/**
* Tells you if the expression is a null safe dereference.
* @param expression
* expression
* @return
* true if is null safe dereference.
*/
public static boolean isSafe(Expression expression) {
if (expression instanceof MethodCallExpression) {
return ((MethodCallExpression) expression).isSafe();
}
if (expression instanceof PropertyExpression) {
return ((PropertyExpression) expression).isSafe();
}
return false;
}
/**
* Tells you if the expression is a spread operator call
* @param expression
* expression
* @return
* true if is spread expression
*/
public static boolean isSpreadSafe(Expression expression) {
if (expression instanceof MethodCallExpression) {
return ((MethodCallExpression) expression).isSpreadSafe();
}
if (expression instanceof PropertyExpression) {
return ((PropertyExpression) expression).isSpreadSafe();
}
return false;
}
/**
* Tells you if the ASTNode is a method node for the given name, arity, and return type.
* @param node
* the node to inspect
* @param methodNamePattern
* the expected name of the method
* @param numArguments
* the expected number of arguments, optional
* @param returnType
* the expected return type, optional
* @return
* true if this node is a MethodNode meeting the parameters. false otherwise
*/
public static boolean isMethodNode(ASTNode node, String methodNamePattern, Integer numArguments, Class returnType) {
if (!(node instanceof MethodNode)) {
return false;
}
if (!(((MethodNode) node).getName().matches(methodNamePattern))) {
return false;
}
if (numArguments != null && ((MethodNode)node).getParameters() != null && ((MethodNode)node).getParameters().length != numArguments) {
return false;
}
if (returnType != null && !AstUtil.classNodeImplementsType(((MethodNode) node).getReturnType(), returnType)) {
return false;
}
return true;
}
public static boolean isMethodNode(ASTNode node, String methodNamePattern, Integer numArguments) {
return isMethodNode(node, methodNamePattern, numArguments, null);
}
public static boolean isMethodNode(ASTNode node, String methodNamePattern) {
return isMethodNode(node, methodNamePattern, null, null);
}
/**
* Tells you if the given ASTNode is a VariableExpression with the given name.
* @param expression
* any AST Node
* @param pattern
* a string pattern to match
* @return
* true if the node is a variable with the specified name
*/
public static boolean isVariable(ASTNode expression, String pattern) {
return (expression instanceof VariableExpression && ((VariableExpression) expression).getName().matches(pattern));
}
/**
* Tells you if the ASTNode has a public modifier on it. If the node does not have modifiers at all (like
* a variable expression) then false is returned.
* @param node
* node to query
* @return
* true if definitely public, false if not public or unknown
*/
public static boolean isPublic(ASTNode node) {
Integer modifiers = null;
// !important - Performance improvement
if (node instanceof ClassNode) {
modifiers = ((ClassNode) node).getModifiers();
} else if (node instanceof FieldNode) {
modifiers = ((FieldNode) node).getModifiers();
}else if (node instanceof MethodNode) {
modifiers = ((MethodNode) node).getModifiers();
}else if (node instanceof PropertyNode) {
modifiers = ((PropertyNode) node).getModifiers();
} else {
LOG.warn("isPublic node is not an expected type");
}
if (modifiers != null) {
return Modifier.isPublic(modifiers);
}
return false;
}
public static boolean isNotNullCheck(Object expression) {
if (expression instanceof BinaryExpression) {
if ("!=".equals(((BinaryExpression) expression).getOperation().getText())) {
if (isNull(((BinaryExpression) expression).getLeftExpression())
|| isNull(((BinaryExpression) expression).getRightExpression())) {
return true;
}
}
}
return false;
}
public static boolean isNullCheck(Object expression) {
if (expression instanceof BinaryExpression) {
if ("==".equals(((BinaryExpression) expression).getOperation().getText())) {
if (isNull(((BinaryExpression) expression).getLeftExpression()) || isNull(((BinaryExpression) expression).getRightExpression())) {
return true;
}
}
}
return false;
}
public static String getNullComparisonTarget(Object expression) {
if (expression instanceof BinaryExpression && "!=".equals(((BinaryExpression) expression).getOperation().getText())) {
if (isNull(((BinaryExpression) expression).getLeftExpression())) {
return ((BinaryExpression) expression).getRightExpression().getText();
} else if (isNull(((BinaryExpression) expression).getRightExpression())) {
return ((BinaryExpression) expression).getLeftExpression().getText();
}
}
return null;
}
public static boolean isInstanceOfCheck(Object expression) {
return (expression instanceof BinaryExpression && "instanceof".equals(((BinaryExpression) expression).getOperation().getText()));
}
public static String getInstanceOfTarget(Object expression) {
if (isInstanceOfCheck(expression)) {
return ((BinaryExpression)expression).getLeftExpression().getText();
}
return null;
}
/**
* Supports discovering many common JDK types, but not all.
*/
public static Class getFieldType(ClassNode node, String fieldName) {
while (node != null) {
for (FieldNode field: node.getFields()) {
if (field.getName().equals(fieldName)) {
return getFieldType(field);
}
}
node = node.getOuterClass();
}
return null;
}
/**
* Supports discovering many common JDK types, but not all.
*/
public static Class getFieldType(FieldNode field) {
// Step 1: Analyze the field's declared type
Class declaredType = getClassForClassNode(field.getType());
if (declaredType != null) {
return declaredType;
}
// Step 2: Analyze the cast type of the initial expression
if (field.getInitialExpression() != null) {
Class castType = getClassForClassNode(field.getInitialExpression().getType());
if (castType != null) {
return castType;
}
}
// Step 3: Look at the literal within the constant
if (field.getInitialExpression() instanceof ConstantExpression) {
Object constantValue = ((ConstantExpression) field.getInitialExpression()).getValue();
if (constantValue == null) {
return null;
} else if (constantValue instanceof String) {
return String.class;
} else if (isBoolean(field.getInitialExpression())) {
return Boolean.class;
} else if (constantValue.getClass() == Integer.class || constantValue.getClass() == Integer.TYPE) {
return Integer.class;
} else if (constantValue.getClass() == Long.class || constantValue.getClass() == Long.TYPE) {
return Long.class;
} else if (constantValue.getClass() == Double.class || constantValue.getClass() == Double.TYPE) {
return Double.class;
} else if (constantValue.getClass() == Float.class || constantValue.getClass() == Float.TYPE) {
return Float.class;
}
}
return null;
}
/**
* This is private. It is a helper function for the utils.
*/
private static Class getClassForClassNode(ClassNode type) {
// todo hamlet - move to a different "InferenceUtil" object
Class primitiveType = getPrimitiveType(type);
if (primitiveType != null) {
return primitiveType;
} else if (classNodeImplementsType(type, String.class)) {
return String.class;
} else if (classNodeImplementsType(type, ReentrantLock.class)) {
return ReentrantLock.class;
} else if (type.getName() != null && type.getName().endsWith("[]")) {
return Object[].class; // better type inference could be done, but oh well
}
return null;
}
private static Class getPrimitiveType(ClassNode type) {
if (classNodeImplementsType(type, Boolean.class) || classNodeImplementsType(type, Boolean.TYPE)) {
return Boolean.class;
} else if (classNodeImplementsType(type, Long.class) || classNodeImplementsType(type, Long.TYPE)) {
return Long.class;
} else if (classNodeImplementsType(type, Short.class) || classNodeImplementsType(type, Short.TYPE)) {
return Short.class;
} else if (classNodeImplementsType(type, Double.class) || classNodeImplementsType(type, Double.TYPE)) {
return Double.class;
} else if (classNodeImplementsType(type, Float.class) || classNodeImplementsType(type, Float.TYPE)) {
return Float.class;
} else if (classNodeImplementsType(type, Character.class) || classNodeImplementsType(type, Character.TYPE)) {
return Character.class;
} else if (classNodeImplementsType(type, Integer.class) || classNodeImplementsType(type, Integer.TYPE)) {
return Integer.class;
} else if (classNodeImplementsType(type, Long.class) || classNodeImplementsType(type, Long.TYPE)) {
return Long.class;
} else if (classNodeImplementsType(type, Byte.class) || classNodeImplementsType(type, Byte.TYPE)) {
return Byte.class;
}
return null;
}
public static boolean isThisReference(Expression expression) {
return expression instanceof VariableExpression && "this".equals(((VariableExpression) expression).getName());
}
public static boolean isSuperReference(Expression expression) {
return expression instanceof VariableExpression && "super".equals(((VariableExpression) expression).getName());
}
public static boolean classNodeHasProperty(ClassNode classNode, String propertyName) {
if (classNode.getFields() != null) {
for (FieldNode field : classNode.getFields()) {
if (propertyName.equals(field.getName())) {
return true;
}
}
}
if (classNode.getProperties() != null) {
for (PropertyNode property : classNode.getProperties()) {
if (propertyName.equals(property.getName())) {
return true;
}
}
}
return false;
}
public static int findClassDeclarationLineNumber(ClassNode node, SourceCode sourceCode) {
int lineNumber = node.getLineNumber();
if (!node.getAnnotations().isEmpty()) {
AnnotationNode lastAnnotation = node.getAnnotations().get(node.getAnnotations().size() - 1);
if (lastAnnotation.getLastLineNumber() != -1) {
Pattern classDeclarationPattern = Pattern.compile("class\\s+" + node.getNameWithoutPackage());
for (int i = lastAnnotation.getLastLineNumber(); i <= sourceCode.getLines().size(); i++) {
String line = sourceCode.line(i - 1);
Matcher matcher = classDeclarationPattern.matcher(line);
if (matcher.find()) {
return i;
}
}
}
}
return lineNumber;
}
/**
* Gets the first non annotation line number of a node, taking into account annotations.
*/
public static int findFirstNonAnnotationLine(ASTNode node, SourceCode sourceCode) {
if (node instanceof AnnotatedNode && !((AnnotatedNode) node).getAnnotations().isEmpty()) {
List annotations = ((AnnotatedNode) node).getAnnotations();
AnnotationNode lastAnnotation = annotations.get(annotations.size() - 1);
String rawLine = getRawLine(sourceCode, lastAnnotation.getLastLineNumber()-1);
if(rawLine == null) {
return node.getLineNumber();
}
// is the annotation the last thing on the line?
if (rawLine.length() > lastAnnotation.getLastColumnNumber()) {
// no it is not
boolean doesItEndsWithLineComment = rawLine.substring(lastAnnotation.getLastColumnNumber() - 1).trim().startsWith("//");
if (doesItEndsWithLineComment) {
return lastAnnotation.getLastLineNumber() + 1;
}
if (node.getClass() == ClassNode.class) {
if (rawLine.contains("class")) {
return lastAnnotation.getLastLineNumber();
}
// Otherwise, fall through to use the next line
} else if (node.getClass() == MethodNode.class) { // methods, but not constructors (since their name is different)
if (rawLine.contains(((MethodNode) node).getName())) {
return lastAnnotation.getLastLineNumber();
}
// Otherwise, fall through to use the next line
} else if (node instanceof FieldNode) {
if (rawLine.contains(((FieldNode) node).getName())) {
return lastAnnotation.getLastLineNumber();
}
// Otherwise, fall through to use the next line
} else {
return lastAnnotation.getLastLineNumber();
}
}
// yes it is the last thing on the line; return the next line
return lastAnnotation.getLastLineNumber() + 1;
}
return node.getLineNumber();
}
public static String getRawLine(SourceCode sourceCode, int lineNumber) {
List allLines = sourceCode.getLines();
return (lineNumber >= 0) && lineNumber < allLines.size() ? allLines.get(lineNumber) : null;
}
public static boolean isOneLiner(Object statement) {
if (statement instanceof BlockStatement && ((BlockStatement) statement).getStatements() != null) {
if (((BlockStatement) statement).getStatements().size() == 1) {
return true;
}
}
return false;
}
public static boolean expressionIsNullCheck(ASTNode node) {
if (!(node instanceof IfStatement)) {
return false;
}
if ((((IfStatement) node).getBooleanExpression() == null)) {
return false;
}
BooleanExpression booleanExp = ((IfStatement) node).getBooleanExpression();
if (isBinaryExpressionType(booleanExp.getExpression(), "==")) {
if (isNull(((BinaryExpression)booleanExp.getExpression()).getLeftExpression())
&& ((BinaryExpression) booleanExp.getExpression()).getRightExpression() instanceof VariableExpression) {
return true;
} else if (isNull(((BinaryExpression) booleanExp.getExpression()).getRightExpression())
&& ((BinaryExpression) booleanExp.getExpression()).getLeftExpression() instanceof VariableExpression) {
return true;
}
} else if (booleanExp.getExpression() instanceof NotExpression && ((NotExpression) booleanExp.getExpression()).getExpression() instanceof VariableExpression) {
return true;
}
return false;
}
public static boolean expressionIsAssignment(ASTNode node, String variableName) {
if (node instanceof Expression && isBinaryExpressionType((Expression) node, "=")) {
if (isVariable(((BinaryExpression) node).getLeftExpression(), variableName)) {
return true;
}
} else if (node instanceof ExpressionStatement && isBinaryExpressionType(((ExpressionStatement) node).getExpression(), "=")) {
if (AstUtil.isVariable(((BinaryExpression)((ExpressionStatement) node).getExpression()).getLeftExpression(), variableName)) {
return true;
}
}
return false;
}
private static String repeat(char c, int count) {
String result = "";
for (int x = 0; x <= count; x++) {
result = result + c;
}
return result;
}
public static String getNodeText(ASTNode expression, SourceCode sourceCode) {
String line = sourceCode.getLines().get(expression.getLineNumber() - 1);
// If multi-line, only include rest of first line
int endColumn = expression.getLineNumber() == expression.getLastLineNumber()
? expression.getLastColumnNumber() - 1
: line.length();
return line.substring(expression.getColumnNumber() - 1, endColumn);
}
public static String getLastLineOfNodeText(ASTNode expression, SourceCode sourceCode) {
String line = sourceCode.getLines().get(expression.getLastLineNumber() - 1);
// If multi-line, only include rest of last line
int startColumn = expression.getLineNumber() == expression.getLastLineNumber()
? expression.getColumnNumber() - 1
: 0;
return line.substring(startColumn, expression.getLastColumnNumber() - 1);
}
public static List getSourceLinesForNode(ASTNode node, SourceCode sourceCode) {
if (node.getLineNumber() < 1 || node.getLastLineNumber() < 1) {
return Collections.emptyList();
}
List lines = new ArrayList<>();
for (int lineIndex = node.getLineNumber() - 1; lineIndex <= node.getLastLineNumber() -1; lineIndex++) {
// the raw line is required to apply columnNumber and lastColumnNumber
String line = getRawLine(sourceCode, lineIndex);
// extract the relevant part of the first line
if (lineIndex == node.getLineNumber() - 1) {
line = line.substring(node.getColumnNumber() - 1);
}
lines.add(line.trim());
}
return lines;
}
public static String getDeclaration(ASTNode node, SourceCode sourceCode) {
if (node.getLineNumber() < 1) return "";
if (node.getLastLineNumber() < 1) return "";
if (node.getColumnNumber() < 1) return "";
if (node.getLastColumnNumber() < 1) return "";
String acc = "";
for (int lineIndex = node.getLineNumber() - 1; lineIndex <= node.getLastLineNumber() -1; lineIndex++) {
// the raw line is required to apply columnNumber and lastColumnNumber
String line = getRawLine(sourceCode, lineIndex);
// extract the relevant part of the first line
if (lineIndex == node.getLineNumber() - 1) {
int nonRelevantColumns = node.getColumnNumber() - 1;
line = line.replaceFirst(".{" + nonRelevantColumns + "}", repeat(' ', nonRelevantColumns)); // retain the line length as it's important when using lastColumnNumber
}
// extract the relevant part of the last line
if (lineIndex == node.getLastLineNumber() - 1) {
// Groovy 3.0 lastColumnNumber is incorrect up for some fields
}
if (line.contains("{")) {
acc += line.substring(0, line.indexOf("{"));
break;
} else {
acc += line + " ";
}
}
return acc;
}
public static String createPrettyExpression(ASTNode expression) {
if (expression instanceof ConstantExpression && ((ConstantExpression) expression).getValue() instanceof String) {
return "'" + expression.getText() + "'";
}
if (expression instanceof GStringExpression) {
return "\"" + expression.getText() + "\"";
}
return expression.getText();
}
public static String getSourceBetweenNodes(ASTNode beforeNode, ASTNode afterNode, SourceCode sourceCode) {
if (AstUtil.isFromGeneratedSourceCode(beforeNode) || AstUtil.isFromGeneratedSourceCode(afterNode)) {
return "";
}
StringBuilder str = new StringBuilder();
String beforeLastLine = lastSourceLine(beforeNode, sourceCode);
int firstColumnAfterBeforeNode = beforeNode.getLastColumnNumber() - 1; // That lastColumnNumber is the column after the end, and is 1-based
if (beforeNode.getLastLineNumber() == afterNode.getLineNumber()) {
str.append(beforeLastLine, firstColumnAfterBeforeNode, afterNode.getColumnNumber() - 1);
} else {
str.append(beforeLastLine.substring(firstColumnAfterBeforeNode));
str.append(NEWLINE);
for (int i = beforeNode.getLastLineNumber(); i < afterNode.getLineNumber() - 1; i++) {
str.append(sourceCode.line(i));
str.append(NEWLINE);
}
String afterFirstLine = sourceLine(afterNode, sourceCode);
str.append(afterFirstLine, 0, afterNode.getColumnNumber() - 1);
}
return str.toString();
}
private static String lastSourceLine(ASTNode node, SourceCode sourceCode) {
return sourceCode.getLines().get(node.getLastLineNumber() - 1);
}
private static String sourceLine(ASTNode node, SourceCode sourceCode) {
return sourceCode.getLines().get(AstUtil.findFirstNonAnnotationLine(node, sourceCode) - 1);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy