/*
* Copyright 2008-2017 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.griffon.compile.core.ast;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.BooleanExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.FieldExpression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.MapExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.NotExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.EmptyStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.IfStatement;
import org.codehaus.groovy.ast.stmt.ReturnStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.runtime.MetaClassHelper;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static griffon.util.GriffonNameUtils.getGetterName;
import static griffon.util.GriffonNameUtils.isBlank;
/**
* Helper methods for working with Groovy AST trees.
*
* @author Andres Almiray
* @since 2.0.0
*/
public class GriffonASTUtils {
private static final String JAVA_LANG_OBJECT = Object.class.getName();
/**
* Returns whether a classNode has the specified property or not
*
* @param classNode The ClassNode
* @param propertyName The name of the property
* @return True if the property exists in the ClassNode
*/
public static boolean hasProperty(ClassNode classNode, String propertyName) {
if (classNode == null || isBlank(propertyName)) {
return false;
}
final MethodNode method = classNode.getMethod(getGetterName(propertyName), new Parameter[0]);
if (method != null) return true;
for (PropertyNode pn : classNode.getProperties()) {
if (pn.getName().equals(propertyName) && !pn.isPrivate()) {
return true;
}
}
return false;
}
public static boolean hasOrInheritsProperty(ClassNode classNode, String propertyName) {
if (hasProperty(classNode, propertyName)) {
return true;
}
ClassNode parent = classNode.getSuperClass();
while (parent != null && !getFullName(parent).equals(JAVA_LANG_OBJECT)) {
if (hasProperty(parent, propertyName)) {
return true;
}
parent = parent.getSuperClass();
}
return false;
}
/**
* Tests whether the ClasNode implements the specified method name.
*
* @param classNode The ClassNode
* @param methodName The method name
* @return True if it does implement the method
*/
public static boolean implementsZeroArgMethod(ClassNode classNode, String methodName) {
MethodNode method = classNode.getDeclaredMethod(methodName, new Parameter[]{});
return method != null && (method.isPublic() || method.isProtected()) && !method.isAbstract();
}
@SuppressWarnings("unchecked")
public static boolean implementsOrInheritsZeroArgMethod(ClassNode classNode, String methodName, List<?> ignoreClasses) {
if (implementsZeroArgMethod(classNode, methodName)) {
return true;
}
ClassNode parent = classNode.getSuperClass();
while (parent != null && !getFullName(parent).equals(JAVA_LANG_OBJECT)) {
if (!ignoreClasses.contains(parent) && implementsZeroArgMethod(parent, methodName)) {
return true;
}
parent = parent.getSuperClass();
}
return false;
}
public static boolean implementsMethod(ClassNode classNode, MethodNode methodNode) {
MethodNode method = classNode.getDeclaredMethod(methodNode.getName(), methodNode.getParameters());
return method != null && method.getModifiers() == methodNode.getModifiers();
}
public static boolean implementsOrInheritsMethod(ClassNode classNode, MethodNode methodNode) {
if (implementsMethod(classNode, methodNode)) {
return true;
}
ClassNode parent = classNode.getSuperClass();
while (parent != null && !getFullName(parent).equals(JAVA_LANG_OBJECT)) {
if (implementsMethod(parent, methodNode)) {
return true;
}
parent = parent.getSuperClass();
}
return false;
}
public static void injectMethod(ClassNode classNode, MethodNode methodNode) {
injectMethod(classNode, methodNode, true);
}
public static void injectMethod(ClassNode classNode, MethodNode methodNode, boolean deep) {
if (deep) {
if (!implementsOrInheritsMethod(classNode, methodNode)) {
getFurthestParent(classNode).addMethod(methodNode);
}
} else {
if (!implementsMethod(classNode, methodNode)) {
classNode.addMethod(methodNode);
}
}
}
public static boolean hasField(ClassNode classNode, String name, int modifiers, ClassNode type) {
FieldNode fieldNode = classNode.getDeclaredField(name);
return fieldNode != null && fieldNode.getModifiers() == modifiers &&
fieldNode.getType().equals(type);
}
public static boolean hasOrInheritsField(ClassNode classNode, String name, int modifiers, ClassNode type) {
if (hasField(classNode, name, modifiers, type)) {
return true;
}
ClassNode parent = classNode.getSuperClass();
while (parent != null && !getFullName(parent).equals(JAVA_LANG_OBJECT)) {
if (hasField(parent, name, modifiers, type)) {
return true;
}
parent = parent.getSuperClass();
}
return false;
}
public static FieldNode injectField(ClassNode classNode, String name, int modifiers, ClassNode type, Object value) {
return injectField(classNode, name, modifiers, type, value, true);
}
public static FieldNode injectField(ClassNode classNode, String name, int modifiers, ClassNode type, Object value, boolean deep) {
Expression initialExpression = null;
if (value != null) initialExpression = new ConstantExpression(value);
return injectField(classNode, name, modifiers, type, initialExpression, deep);
}
public static FieldNode injectField(ClassNode classNode, String name, int modifiers, ClassNode type, Expression initialExpression) {
return injectField(classNode, name, modifiers, type, initialExpression, true);
}
public static FieldNode injectField(ClassNode classNode, String name, int modifiers, ClassNode type, Expression initialExpression, boolean deep) {
if (deep) {
if (!hasOrInheritsField(classNode, name, modifiers, type)) {
return getFurthestParent(classNode).addField(name, modifiers, type, initialExpression);
} else {
getFieldDeep(classNode, name, modifiers, type);
}
} else {
if (!hasField(classNode, name, modifiers, type)) {
return classNode.addField(name, modifiers, type, initialExpression);
} else {
return getField(classNode, name, modifiers, type);
}
}
return null;
}
public static FieldNode getField(ClassNode classNode, String name, int modifiers, ClassNode type) {
FieldNode fieldNode = classNode.getDeclaredField(name);
return fieldNode != null && fieldNode.getModifiers() == modifiers &&
fieldNode.getType().equals(type) ? fieldNode : null;
}
public static FieldNode getFieldDeep(ClassNode classNode, String name, int modifiers, ClassNode type) {
FieldNode fieldNode = getField(classNode, name, modifiers, type);
if (fieldNode != null) {
return fieldNode;
}
ClassNode parent = classNode.getSuperClass();
while (parent != null && !getFullName(parent).equals(JAVA_LANG_OBJECT)) {
fieldNode = getField(parent, name, modifiers, type);
if (fieldNode != null) {
return fieldNode;
}
parent = parent.getSuperClass();
}
return null;
}
public static void injectInterface(ClassNode classNode, ClassNode type) {
injectInterface(classNode, type, true);
}
public static void injectInterface(ClassNode classNode, ClassNode type, boolean deep) {
if (classNode.implementsInterface(type)) return;
if (deep) {
getFurthestParent(classNode).addInterface(type);
} else {
classNode.addInterface(type);
}
}
/**
* Gets the full name of a ClassNode.
*
* @param classNode The class node
* @return The full name
*/
public static String getFullName(ClassNode classNode) {
return classNode.getName();
}
public static ClassNode getFurthestParent(ClassNode classNode) {
ClassNode parent = classNode.getSuperClass();
while (parent != null && !getFullName(parent).equals(JAVA_LANG_OBJECT)) {
if (SourceUnitCollector.getInstance().getSourceUnit(parent) == null)
break;
classNode = parent;
parent = parent.getSuperClass();
}
return classNode;
}
public static boolean isEnum(ClassNode classNode) {
ClassNode parent = classNode.getSuperClass();
while (parent != null) {
if (parent.getName().equals("java.lang.Enum")) return true;
parent = parent.getSuperClass();
}
return false;
}
public static boolean addMethod(ClassNode classNode, MethodNode methodNode) {
return addMethod(classNode, methodNode, false);
}
public static boolean addMethod(ClassNode classNode, MethodNode methodNode, boolean replace) {
MethodNode oldMethod = classNode.getMethod(methodNode.getName(), methodNode.getParameters());
if (oldMethod == null) {
classNode.addMethod(methodNode);
return true;
} else if (replace) {
classNode.getMethods().remove(oldMethod);
classNode.addMethod(methodNode);
return true;
}
return false;
}
/**
* @return true if the two arrays are of the same size and have the same contents
*/
public static boolean parametersEqual(Parameter[] a, Parameter[] b) {
if (a.length == b.length) {
boolean answer = true;
for (int i = 0; i < a.length; i++) {
if (!a[i].getType().equals(b[i].getType())) {
answer = false;
break;
}
}
return answer;
}
return false;
}
public static void injectProperty(ClassNode classNode, String propertyName, Class<?> propertyClass) {
injectProperty(classNode, propertyName, Modifier.PUBLIC, new ClassNode(propertyClass), null);
}
public static void injectProperty(ClassNode classNode, String propertyName, Class<?> propertyClass, Object value) {
injectProperty(classNode, propertyName, Modifier.PUBLIC, new ClassNode(propertyClass), value);
}
public static void injectProperty(ClassNode classNode, String propertyName, int modifiers, Class<?> propertyClass) {
injectProperty(classNode, propertyName, modifiers, new ClassNode(propertyClass), null);
}
public static void injectProperty(ClassNode classNode, String propertyName, int modifiers, Class<?> propertyClass, Object value) {
injectProperty(classNode, propertyName, modifiers, new ClassNode(propertyClass), value);
}
public static void injectProperty(ClassNode classNode, String propertyName, ClassNode propertyClass) {
injectProperty(classNode, propertyName, Modifier.PUBLIC, propertyClass, null);
}
public static void injectProperty(ClassNode classNode, String propertyName, int modifiers, ClassNode propertyClass) {
injectProperty(classNode, propertyName, modifiers, propertyClass, null);
}
public static void injectProperty(ClassNode classNode, String propertyName, int modifiers, ClassNode propertyClass, Object value) {
if (!hasOrInheritsProperty(classNode, propertyName)) {
// inject into furthest relative
ClassNode parent = getFurthestParent(classNode);
Expression initialExpression = value instanceof Expression ? (Expression) value : null;
if (value != null && initialExpression == null)
initialExpression = new ConstantExpression(value);
parent.addProperty(propertyName, modifiers, propertyClass, initialExpression, null, null);
}
}
public static void injectConstant(ClassNode classNode, String propertyName, Class<?> propertyClass, Object value) {
final boolean hasProperty = hasOrInheritsProperty(classNode, propertyName);
if (!hasProperty) {
// inject into furthest relative
// ClassNode parent = getFurthestParent(classNode);
Expression initialExpression = new ConstantExpression(value);
classNode.addProperty(propertyName, Modifier.PUBLIC | Modifier.FINAL, new ClassNode(propertyClass), initialExpression, null, null);
}
}
public static void addReadOnlyProperty(ClassNode classNode, String propertyName, ClassNode propertyClass, Object value) {
final boolean hasProperty = hasOrInheritsProperty(classNode, propertyName);
if (!hasProperty) {
int visibility = Modifier.PRIVATE | Modifier.FINAL;
Expression initialValue = value != null && !(value instanceof Expression) ? new ConstantExpression(value) : (Expression) value;
classNode.addField(propertyName, visibility, propertyClass, initialValue);
addMethod(classNode, new MethodNode(
"get" + MetaClassHelper.capitalize(propertyName),
Modifier.PUBLIC,
propertyClass,
Parameter.EMPTY_ARRAY,
ClassNode.EMPTY_ARRAY,
new ReturnStatement(
new ExpressionStatement(
new FieldExpression(classNode.getField(propertyName))))));
}
}
public static final ClassNode[] NO_EXCEPTIONS = ClassNode.EMPTY_ARRAY;
public static final Parameter[] NO_PARAMS = Parameter.EMPTY_ARRAY;
public static final Expression THIS = VariableExpression.THIS_EXPRESSION;
public static final Expression SUPER = VariableExpression.SUPER_EXPRESSION;
public static final ArgumentListExpression NO_ARGS = ArgumentListExpression.EMPTY_ARGUMENTS;
public static final Token ASSIGN = Token.newSymbol(Types.ASSIGN, -1, -1);
public static final Token EQ = Token.newSymbol(Types.COMPARE_EQUAL, -1, -1);
public static final Token NE = Token.newSymbol(Types.COMPARE_NOT_EQUAL, -1, -1);
public static final Token AND = Token.newSymbol(Types.LOGICAL_AND, -1, -1);
public static final Token OR = Token.newSymbol(Types.LOGICAL_OR, -1, -1);
public static final Token CMP = Token.newSymbol(Types.COMPARE_TO, -1, -1);
public static final Token INSTANCEOF = Token.newSymbol(Types.KEYWORD_INSTANCEOF, -1, -1);
public static Statement returns(Expression expr) {
return new ReturnStatement(new ExpressionStatement(expr));
}
public static ArgumentListExpression vars(String... names) {
List<Expression> vars = new ArrayList<>();
for (String name : names) {
vars.add(var(name));
}
return new ArgumentListExpression(vars);
}
public static ArgumentListExpression args(Expression... expressions) {
List<Expression> args = new ArrayList<>();
Collections.addAll(args, expressions);
return new ArgumentListExpression(args);
}
public static ArgumentListExpression args(List<Expression> expressions) {
return new ArgumentListExpression(expressions);
}
public static VariableExpression var(String name) {
return new VariableExpression(name);
}
public static VariableExpression var(String name, ClassNode type) {
return new VariableExpression(name, type);
}
public static Parameter param(ClassNode type, String name) {
return param(type, name, null);
}
public static Parameter param(ClassNode type, String name, Expression initialExpression) {
Parameter param = new Parameter(type, name);
if (initialExpression != null) {
param.setInitialExpression(initialExpression);
}
return param;
}
public static ClassNode[] throwing(ClassNode... exceptions) {
return exceptions;
}
public static Parameter[] params(Parameter... params) {
return params != null ? params : Parameter.EMPTY_ARRAY;
}
public static NotExpression not(Expression expr) {
return new NotExpression(expr);
}
public static ConstantExpression constx(Object val) {
return new ConstantExpression(val);
}
public static ClassExpression classx(ClassNode clazz) {
return new ClassExpression(clazz);
}
public static ClassExpression classx(Class<?> clazz) {
return classx(ClassHelper.make(clazz).getPlainNodeReference());
}
public static BlockStatement block(Statement... stms) {
BlockStatement block = new BlockStatement();
for (Statement stm : stms) block.addStatement(stm);
return block;
}
public static Statement ifs(Expression cond, Expression trueExpr) {
return new IfStatement(
cond instanceof BooleanExpression ? (BooleanExpression) cond : new BooleanExpression(cond),
new ReturnStatement(trueExpr),
new EmptyStatement()
);
}
public static Statement ifs(Expression cond, Expression trueExpr, Expression falseExpr) {
return new IfStatement(
cond instanceof BooleanExpression ? (BooleanExpression) cond : new BooleanExpression(cond),
new ReturnStatement(trueExpr),
new ReturnStatement(falseExpr)
);
}
public static Statement ifs_no_return(Expression cond, Expression trueExpr) {
return new IfStatement(
cond instanceof BooleanExpression ? (BooleanExpression) cond : new BooleanExpression(cond),
new ExpressionStatement(trueExpr),
new EmptyStatement()
);
}
public static Statement ifs_no_return(Expression cond, Expression trueExpr, Expression falseExpr) {
return new IfStatement(
cond instanceof BooleanExpression ? (BooleanExpression) cond : new BooleanExpression(cond),
new ExpressionStatement(trueExpr),
new ExpressionStatement(falseExpr)
);
}
public static Statement ifs_no_return(Expression cond, Statement trueStmnt) {
return new IfStatement(
cond instanceof BooleanExpression ? (BooleanExpression) cond : new BooleanExpression(cond),
trueStmnt,
new EmptyStatement()
);
}
public static Statement ifs_no_return(Expression cond, Statement trueStmnt, Statement falseStmnt) {
return new IfStatement(
cond instanceof BooleanExpression ? (BooleanExpression) cond : new BooleanExpression(cond),
trueStmnt,
falseStmnt
);
}
public static Statement decls(Expression lhv, Expression rhv) {
return new ExpressionStatement(new DeclarationExpression(lhv, ASSIGN, rhv));
}
public static Statement assigns(Expression expression, Expression value) {
return new ExpressionStatement(assign(expression, value));
}
public static BinaryExpression assign(Expression lhv, Expression rhv) {
return new BinaryExpression(lhv, ASSIGN, rhv);
}
public static BinaryExpression eq(Expression lhv, Expression rhv) {
return new BinaryExpression(lhv, EQ, rhv);
}
public static BinaryExpression ne(Expression lhv, Expression rhv) {
return new BinaryExpression(lhv, NE, rhv);
}
public static BinaryExpression and(Expression lhv, Expression rhv) {
return new BinaryExpression(lhv, AND, rhv);
}
public static BinaryExpression or(Expression lhv, Expression rhv) {
return new BinaryExpression(lhv, OR, rhv);
}
public static BinaryExpression cmp(Expression lhv, Expression rhv) {
return new BinaryExpression(lhv, CMP, rhv);
}
public static BinaryExpression iof(Expression lhv, Expression rhv) {
return new BinaryExpression(lhv, INSTANCEOF, rhv);
}
public static BinaryExpression iof(Expression lhv, ClassNode rhv) {
return new BinaryExpression(lhv, INSTANCEOF, new ClassExpression(rhv));
}
public static Expression prop(Expression owner, String property) {
return new PropertyExpression(owner, property);
}
public static Expression prop(Expression owner, Expression property) {
return new PropertyExpression(owner, property);
}
public static MethodCallExpression call(Expression receiver, String methodName, ArgumentListExpression args) {
return new MethodCallExpression(receiver, methodName, args);
}
public static StaticMethodCallExpression call(ClassNode receiver, String methodName, ArgumentListExpression args) {
return new StaticMethodCallExpression(receiver, methodName, args);
}
public static ExpressionStatement stmnt(Expression expression) {
return new ExpressionStatement(expression);
}
public static FieldExpression field(FieldNode fieldNode) {
return new FieldExpression(fieldNode);
}
public static FieldExpression field(ClassNode owner, String fieldName) {
return new FieldExpression(owner.getField(fieldName));
}
public static ConstructorCallExpression ctor(ClassNode type) {
return ctor(type, NO_ARGS);
}
public static ConstructorCallExpression ctor(ClassNode type, Expression args) {
return new ConstructorCallExpression(type, args);
}
public static ListExpression listx(Expression... expressions) {
ListExpression list = new ListExpression();
for (Expression expression : expressions) {
list.addExpression(expression);
}
return list;
}
public static MapEntryExpression mapEntryx(Expression key, Expression value) {
return new MapEntryExpression(key, value);
}
public static MapExpression mapx(MapEntryExpression... entries) {
MapExpression map = new MapExpression();
for (MapEntryExpression entry : entries) {
map.addMapEntryExpression(entry);
}
return map;
}
}