/*
* Copyright 2004-2005 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.grails.compiler.injection;
import grails.artefact.Enhanced;
import grails.build.logging.GrailsConsole;
import grails.compiler.ast.GrailsArtefactClassInjector;
import grails.util.GrailsNameUtils;
import grails.util.GrailsUtil;
import groovy.lang.Closure;
import groovy.lang.MissingMethodException;
import groovy.transform.CompileStatic;
import groovy.transform.TypeChecked;
import groovy.transform.TypeCheckingMode;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.InnerClassNode;
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.ClosureExpression;
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.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.NamedArgumentListExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.CatchStatement;
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.ast.stmt.ThrowStatement;
import org.codehaus.groovy.ast.stmt.TryCatchStatement;
import org.codehaus.groovy.classgen.VariableScopeVisitor;
import org.codehaus.groovy.control.Janitor;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import grails.util.GrailsClassUtils;
import grails.core.GrailsDomainClassProperty;
import org.codehaus.groovy.runtime.MetaClassHelper;
import org.codehaus.groovy.syntax.SyntaxException;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
import org.codehaus.groovy.transform.sc.StaticCompileTransformation;
import org.springframework.util.StringUtils;
/**
* Helper methods for working with Groovy AST trees.
*
* @author Graeme Rocher
* @since 0.3
*/
public class GrailsASTUtils {
public static final String DOMAIN_DIR = "domain";
public static final String GRAILS_APP_DIR = "grails-app";
public static final String METHOD_MISSING_METHOD_NAME = "methodMissing";
public static final String STATIC_METHOD_MISSING_METHOD_NAME = "$static_methodMissing";
public static final Token EQUALS_OPERATOR = Token.newSymbol("==", 0, 0);
public static final Token LOGICAL_AND_OPERATOR = Token.newSymbol("&&", 0, 0);
public static final Token NOT_EQUALS_OPERATOR = Token.newSymbol("!=", 0, 0);
public static final String OBJECT_CLASS = "java.lang.Object";
private static final ClassNode ENHANCED_CLASS_NODE = new ClassNode(Enhanced.class);
public static final ClassNode MISSING_METHOD_EXCEPTION = new ClassNode(MissingMethodException.class);
public static final ConstantExpression NULL_EXPRESSION = new ConstantExpression(null);
public static final Token ASSIGNMENT_OPERATOR = Token.newSymbol(Types.ASSIGNMENT_OPERATOR, 0, 0);
public static final ClassNode OBJECT_CLASS_NODE = new ClassNode(Object.class).getPlainNodeReference();
public static final ClassNode VOID_CLASS_NODE = ClassHelper.VOID_TYPE;
public static final ClassNode INTEGER_CLASS_NODE = new ClassNode(Integer.class).getPlainNodeReference();
private static final ClassNode COMPILESTATIC_CLASS_NODE = ClassHelper.make(CompileStatic.class);
private static final ClassNode TYPECHECKINGMODE_CLASS_NODE = ClassHelper.make(TypeCheckingMode.class);
public static final Parameter[] ZERO_PARAMETERS = new Parameter[0];
public static final ArgumentListExpression ZERO_ARGUMENTS = new ArgumentListExpression();
/**
* @deprecated Sharing copies of VariableExpression which refer to "this" is unsafe
*/
@Deprecated
public static final VariableExpression THIS_EXPR = new VariableExpression("this");
public static void warning(final SourceUnit sourceUnit, final ASTNode node, final String warningMessage) {
final String sample = sourceUnit.getSample(node.getLineNumber(), node.getColumnNumber(), new Janitor());
GrailsConsole.getInstance().warning(warningMessage + "\n\n" + sample);
}
/**
* Generates a fatal compilation error.
*
* @param sourceUnit the SourceUnit
* @param astNode the ASTNode which caused the error
* @param message The error message
*/
public static void error(final SourceUnit sourceUnit, final ASTNode astNode, final String message) {
error(sourceUnit, astNode, message, true);
}
/**
* Generates a fatal compilation error.
*
* @param sourceUnit the SourceUnit
* @param astNode the ASTNode which caused the error
* @param message The error message
* @param fatal indicates if this is a fatal error
*/
public static void error(final SourceUnit sourceUnit, final ASTNode astNode, final String message, final boolean fatal) {
final SyntaxException syntaxException = new SyntaxException(message, astNode.getLineNumber(), astNode.getColumnNumber());
final SyntaxErrorMessage syntaxErrorMessage = new SyntaxErrorMessage(syntaxException, sourceUnit);
sourceUnit.getErrorCollector().addError(syntaxErrorMessage, fatal);
}
/**
* 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 || !StringUtils.hasText(propertyName)) {
return false;
}
final MethodNode method = classNode.getMethod(GrailsNameUtils.getGetterName(propertyName), Parameter.EMPTY_ARRAY);
if (method != null) return true;
// check read-only field with setter
if( classNode.getField(propertyName) != null && !classNode.getMethods(GrailsNameUtils.getSetterName(propertyName)).isEmpty()) {
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, Parameter.EMPTY_ARRAY);
return method != null && (method.isPublic() || method.isProtected()) && !method.isAbstract();
}
@SuppressWarnings("rawtypes")
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;
}
/**
* 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")) {
classNode = parent;
parent = parent.getSuperClass();
}
return classNode;
}
public static ClassNode getFurthestUnresolvedParent(ClassNode classNode) {
ClassNode parent = classNode.getSuperClass();
while (parent != null && !getFullName(parent).equals("java.lang.Object") &&
!parent.isResolved() && !Modifier.isAbstract(parent.getModifiers())) {
classNode = parent;
parent = parent.getSuperClass();
}
return classNode;
}
/**
* Adds a delegate method to the target class node where the first argument
* is to the delegate method is 'this'. In other words a method such as
* foo(Object instance, String bar) would be added with a signature of foo(String)
* and 'this' is passed to the delegate instance
*
* @param classNode The class node
* @param delegate The expression that looks up the delegate
* @param declaredMethod The declared method
* @return The added method node or null if it couldn't be added
*/
public static MethodNode addDelegateInstanceMethod(ClassNode classNode, Expression delegate, MethodNode declaredMethod) {
return addDelegateInstanceMethod(classNode, delegate, declaredMethod, null, true);
}
public static MethodNode addDelegateInstanceMethod(ClassNode classNode, Expression delegate, MethodNode declaredMethod, AnnotationNode markerAnnotation) {
return addDelegateInstanceMethod(classNode, delegate, declaredMethod, markerAnnotation, true);
}
public static MethodNode addDelegateInstanceMethod(ClassNode classNode, Expression delegate, MethodNode declaredMethod, boolean thisAsFirstArgument) {
return addDelegateInstanceMethod(classNode,delegate,declaredMethod, null, thisAsFirstArgument);
}
public static MethodNode addDelegateInstanceMethod(ClassNode classNode, Expression delegate, MethodNode declaredMethod, AnnotationNode markerAnnotation, boolean thisAsFirstArgument) {
return addDelegateInstanceMethod(classNode,delegate,declaredMethod, markerAnnotation, thisAsFirstArgument, null, false);
}
public static MethodNode addDelegateInstanceMethod(ClassNode classNode, Expression delegate, MethodNode declaredMethod, AnnotationNode markerAnnotation, boolean thisAsFirstArgument, Map<String, ClassNode> genericsPlaceholders) {
return addDelegateInstanceMethod(classNode, delegate, declaredMethod, markerAnnotation, thisAsFirstArgument, genericsPlaceholders, false);
}
/**
* Adds a delegate method to the target class node where the first argument
* is to the delegate method is 'this'. In other words a method such as
* foo(Object instance, String bar) would be added with a signature of foo(String)
* and 'this' is passed to the delegate instance
*
* @param classNode The class node
* @param delegate The expression that looks up the delegate
* @param declaredMethod The declared method
* @param thisAsFirstArgument Whether 'this' should be passed as the first argument to the method
* @return The added method node or null if it couldn't be added
*/
public static MethodNode addDelegateInstanceMethod(ClassNode classNode, Expression delegate, MethodNode declaredMethod, AnnotationNode markerAnnotation, boolean thisAsFirstArgument, Map<String, ClassNode> genericsPlaceholders, boolean noNullCheck) {
Parameter[] parameterTypes = thisAsFirstArgument ? getRemainingParameterTypes(declaredMethod.getParameters()) : declaredMethod.getParameters();
String methodName = declaredMethod.getName();
if (classNode.hasDeclaredMethod(methodName, copyParameters(parameterTypes, genericsPlaceholders))) {
return null;
}
String propertyName = GrailsClassUtils.getPropertyForGetter(methodName);
if (propertyName != null && parameterTypes.length == 0 && classNode.hasProperty(propertyName)) {
return null;
}
propertyName = GrailsClassUtils.getPropertyForSetter(methodName);
if (propertyName != null && parameterTypes.length == 1 && classNode.hasProperty(propertyName)) {
return null;
}
BlockStatement methodBody = new BlockStatement();
ArgumentListExpression arguments = createArgumentListFromParameters(parameterTypes, thisAsFirstArgument, genericsPlaceholders);
ClassNode returnType = replaceGenericsPlaceholders(declaredMethod.getReturnType(), genericsPlaceholders);
MethodCallExpression methodCallExpression = new MethodCallExpression(delegate, methodName, arguments);
methodCallExpression.setMethodTarget(declaredMethod);
if(!noNullCheck) {
ThrowStatement missingMethodException = createMissingMethodThrowable(classNode, declaredMethod);
VariableExpression apiVar = addApiVariableDeclaration(delegate, declaredMethod, methodBody);
IfStatement ifStatement = createIfElseStatementForApiMethodCall(methodCallExpression, apiVar, missingMethodException);
methodBody.addStatement(ifStatement);
} else {
methodBody.addStatement(new ExpressionStatement(methodCallExpression));
}
MethodNode methodNode = new MethodNode(methodName,
Modifier.PUBLIC, returnType, copyParameters(parameterTypes, genericsPlaceholders),
GrailsArtefactClassInjector.EMPTY_CLASS_ARRAY, methodBody);
copyAnnotations(declaredMethod, methodNode);
if(shouldAddMarkerAnnotation(markerAnnotation, methodNode)) {
methodNode.addAnnotation(markerAnnotation);
}
classNode.addMethod(methodNode);
return methodNode;
}
private static boolean shouldAddMarkerAnnotation(AnnotationNode markerAnnotation, MethodNode methodNode) {
return markerAnnotation != null && methodNode.getAnnotations(markerAnnotation.getClassNode()).isEmpty();
}
private static IfStatement createIfElseStatementForApiMethodCall(MethodCallExpression methodCallExpression, VariableExpression apiVar, ThrowStatement missingMethodException) {
BlockStatement ifBlock = new BlockStatement();
ifBlock.addStatement(missingMethodException);
BlockStatement elseBlock = new BlockStatement();
elseBlock.addStatement(new ExpressionStatement(methodCallExpression));
return new IfStatement(new BooleanExpression(new BinaryExpression(apiVar, EQUALS_OPERATOR, NULL_EXPRESSION)),ifBlock,elseBlock);
}
private static VariableExpression addApiVariableDeclaration(Expression delegate, MethodNode declaredMethod, BlockStatement methodBody) {
VariableExpression apiVar = new VariableExpression("$api_" + declaredMethod.getName(), delegate.getType());
DeclarationExpression de = new DeclarationExpression(apiVar, ASSIGNMENT_OPERATOR, delegate);
methodBody.addStatement(new ExpressionStatement(de));
return apiVar;
}
private static ThrowStatement createMissingMethodThrowable(ClassNode classNode, MethodNode declaredMethodNode) {
ArgumentListExpression exceptionArgs = new ArgumentListExpression();
exceptionArgs.addExpression(new ConstantExpression(declaredMethodNode.getName()));
exceptionArgs.addExpression(new ClassExpression(classNode));
return new ThrowStatement(new ConstructorCallExpression(MISSING_METHOD_EXCEPTION, exceptionArgs));
}
/**
* Creates an argument list from the given parameter types.
*
* @param parameterTypes The parameter types
* @param thisAsFirstArgument Whether to include a reference to 'this' as the first argument
* @param genericsPlaceholders
*
* @return the arguments
*/
public static ArgumentListExpression createArgumentListFromParameters(Parameter[] parameterTypes, boolean thisAsFirstArgument, Map<String, ClassNode> genericsPlaceholders) {
ArgumentListExpression arguments = new ArgumentListExpression();
if (thisAsFirstArgument) {
arguments.addExpression(new VariableExpression("this"));
}
for (Parameter parameterType : parameterTypes) {
arguments.addExpression(new VariableExpression(parameterType.getName(), replaceGenericsPlaceholders(parameterType.getType(), genericsPlaceholders)));
}
return arguments;
}
/**
* Gets the remaining parameters excluding the first parameter in the given list
*
* @param parameters The parameters
* @return A new array with the first parameter removed
*/
public static Parameter[] getRemainingParameterTypes(Parameter[] parameters) {
if (parameters.length == 0) {
return GrailsArtefactClassInjector.ZERO_PARAMETERS;
}
Parameter[] newParameters = new Parameter[parameters.length - 1];
System.arraycopy(parameters, 1, newParameters, 0, parameters.length - 1);
return newParameters;
}
/**
* Adds a static method call to given class node that delegates to the given method
*
* @param classNode The class node
* @param delegateMethod The delegate method
* @return The added method node or null if it couldn't be added
*/
public static MethodNode addDelegateStaticMethod(ClassNode classNode, MethodNode delegateMethod) {
ClassExpression classExpression = new ClassExpression(delegateMethod.getDeclaringClass());
return addDelegateStaticMethod(classExpression, classNode, delegateMethod);
}
/**
* Adds a static method to the given class node that delegates to the given method
* and resolves the object to invoke the method on from the given expression.
*
* @param expression The expression
* @param classNode The class node
* @param delegateMethod The delegate method
* @return The added method node or null if it couldn't be added
*/
public static MethodNode addDelegateStaticMethod(Expression expression, ClassNode classNode, MethodNode delegateMethod) {
return addDelegateStaticMethod(expression, classNode, delegateMethod, null, null, true);
}
/**
* Adds a static method to the given class node that delegates to the given method
* and resolves the object to invoke the method on from the given expression.
*
* @param delegate The expression
* @param classNode The class node
* @param delegateMethod The delegate method
* @param markerAnnotation A marker annotation to be added to all methods
* @return The added method node or null if it couldn't be added
*/
public static MethodNode addDelegateStaticMethod(Expression delegate, ClassNode classNode, MethodNode delegateMethod, AnnotationNode markerAnnotation, Map<String, ClassNode> genericsPlaceholders, boolean noNullCheck) {
Parameter[] parameterTypes = delegateMethod.getParameters();
String declaredMethodName = delegateMethod.getName();
if (METHOD_MISSING_METHOD_NAME.equals(declaredMethodName)) {
declaredMethodName = STATIC_METHOD_MISSING_METHOD_NAME;
}
if (classNode.hasDeclaredMethod(declaredMethodName, copyParameters(parameterTypes, genericsPlaceholders))) {
return null;
}
BlockStatement methodBody = new BlockStatement();
ArgumentListExpression arguments = createArgumentListFromParameters(parameterTypes, false, genericsPlaceholders);
MethodCallExpression methodCallExpression = new MethodCallExpression(
delegate, delegateMethod.getName(), arguments);
methodCallExpression.setMethodTarget(delegateMethod);
if(!noNullCheck && !(delegate instanceof ClassExpression)) {
ThrowStatement missingMethodException = createMissingMethodThrowable(classNode, delegateMethod);
VariableExpression apiVar = addApiVariableDeclaration(delegate, delegateMethod, methodBody);
IfStatement ifStatement = createIfElseStatementForApiMethodCall(methodCallExpression, apiVar, missingMethodException);
methodBody.addStatement(ifStatement);
} else {
methodBody.addStatement(new ExpressionStatement(methodCallExpression));
}
ClassNode returnType = replaceGenericsPlaceholders(delegateMethod.getReturnType(), genericsPlaceholders);
MethodNode methodNode = new MethodNode(declaredMethodName, Modifier.PUBLIC | Modifier.STATIC, returnType,
copyParameters(parameterTypes, genericsPlaceholders), GrailsArtefactClassInjector.EMPTY_CLASS_ARRAY,
methodBody);
copyAnnotations(delegateMethod, methodNode);
if (shouldAddMarkerAnnotation(markerAnnotation, methodNode)) {
methodNode.addAnnotation(markerAnnotation);
}
classNode.addMethod(methodNode);
return methodNode;
}
/**
* Adds or modifies an existing constructor to delegate to the
* given static constructor method for initialization logic.
*
* @param classNode The class node
* @param constructorMethod The constructor static method
*/
public static ConstructorNode addDelegateConstructor(ClassNode classNode, MethodNode constructorMethod, Map<String, ClassNode> genericsPlaceholders) {
BlockStatement constructorBody = new BlockStatement();
Parameter[] constructorParams = getRemainingParameterTypes(constructorMethod.getParameters());
ArgumentListExpression arguments = createArgumentListFromParameters(constructorParams, true, genericsPlaceholders);
MethodCallExpression constructCallExpression = new MethodCallExpression(
new ClassExpression(constructorMethod.getDeclaringClass()), "initialize", arguments);
constructCallExpression.setMethodTarget(constructorMethod);
ExpressionStatement constructorInitExpression = new ExpressionStatement(constructCallExpression);
if (constructorParams.length > 0) {
constructorBody.addStatement(new ExpressionStatement(new ConstructorCallExpression(ClassNode.THIS, GrailsArtefactClassInjector.ZERO_ARGS)));
}
constructorBody.addStatement(constructorInitExpression);
if (constructorParams.length == 0) {
// handle default constructor
ConstructorNode constructorNode = getDefaultConstructor(classNode);
if (constructorNode != null) {
List<AnnotationNode> annotations = constructorNode.getAnnotations(new ClassNode(GrailsDelegatingConstructor.class));
if (annotations.size() == 0) {
Statement existingBodyCode = constructorNode.getCode();
if (existingBodyCode instanceof BlockStatement) {
((BlockStatement) existingBodyCode).addStatement(constructorInitExpression);
}
else {
constructorNode.setCode(constructorBody);
}
}
} else {
constructorNode = new ConstructorNode(Modifier.PUBLIC, constructorBody);
classNode.addConstructor(constructorNode);
}
constructorNode.addAnnotation(new AnnotationNode(new ClassNode(GrailsDelegatingConstructor.class)));
return constructorNode;
}
else {
// create new constructor, restoring default constructor if there is none
ConstructorNode cn = findConstructor(classNode, constructorParams);
if (cn == null) {
cn = new ConstructorNode(Modifier.PUBLIC, copyParameters(constructorParams, genericsPlaceholders), null, constructorBody);
classNode.addConstructor(cn);
}
else {
List<AnnotationNode> annotations = cn.getAnnotations(new ClassNode(GrailsDelegatingConstructor.class));
if (annotations.size() == 0) {
Statement code = cn.getCode();
constructorBody.addStatement(code);
cn.setCode(constructorBody);
}
}
ConstructorNode defaultConstructor = getDefaultConstructor(classNode);
if (defaultConstructor == null) {
// add empty
classNode.addConstructor(new ConstructorNode(Modifier.PUBLIC, new BlockStatement()));
}
cn.addAnnotation(new AnnotationNode(new ClassNode(GrailsDelegatingConstructor.class)));
return cn;
}
}
/**
* Finds a constructor for the given class node and parameter types
*
* @param classNode The class node
* @param constructorParams The parameter types
* @return The located constructor or null
*/
public static ConstructorNode findConstructor(ClassNode classNode,Parameter[] constructorParams) {
List<ConstructorNode> declaredConstructors = classNode.getDeclaredConstructors();
for (ConstructorNode declaredConstructor : declaredConstructors) {
if (parametersEqual(constructorParams, declaredConstructor.getParameters())) {
return declaredConstructor;
}
}
return null;
}
/**
* @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) {
return false;
}
for (int i = 0; i < a.length; i++) {
if (!a[i].getType().equals(b[i].getType())) {
return false;
}
}
return true;
}
/**
* Obtains the default constructor for the given class node.
*
* @param classNode The class node
* @return The default constructor or null if there isn't one
*/
public static ConstructorNode getDefaultConstructor(ClassNode classNode) {
for (ConstructorNode cons : classNode.getDeclaredConstructors()) {
if (cons.getParameters().length == 0) {
return cons;
}
}
return null;
}
public static Parameter[] copyParameters(Parameter[] parameterTypes) {
return copyParameters(parameterTypes, null);
}
public static Parameter[] copyParameters(Parameter[] parameterTypes, Map<String, ClassNode> genericsPlaceholders) {
Parameter[] newParameterTypes = new Parameter[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
Parameter parameterType = parameterTypes[i];
Parameter newParameter = new Parameter(replaceGenericsPlaceholders(parameterType.getType(), genericsPlaceholders), parameterType.getName(), parameterType.getInitialExpression());
copyAnnotations(parameterType, newParameter);
newParameterTypes[i] = newParameter;
}
return newParameterTypes;
}
private static final Map<String, ClassNode> emptyGenericsPlaceHoldersMap = Collections.emptyMap();
public static ClassNode nonGeneric(ClassNode type) {
return replaceGenericsPlaceholders(type, emptyGenericsPlaceHoldersMap);
}
@SuppressWarnings("unchecked")
public static ClassNode nonGeneric(ClassNode type, final ClassNode wildcardReplacement) {
return replaceGenericsPlaceholders(type, emptyGenericsPlaceHoldersMap, wildcardReplacement);
}
public static ClassNode replaceGenericsPlaceholders(ClassNode type, Map<String, ClassNode> genericsPlaceholders) {
return replaceGenericsPlaceholders(type, genericsPlaceholders, null);
}
public static ClassNode replaceGenericsPlaceholders(ClassNode type, Map<String, ClassNode> genericsPlaceholders, ClassNode defaultPlaceholder) {
if (type.isArray()) {
return replaceGenericsPlaceholders(type.getComponentType(), genericsPlaceholders).makeArray();
}
if (!type.isUsingGenerics() && !type.isRedirectNode()) {
return type.getPlainNodeReference();
}
if(type.isGenericsPlaceHolder() && genericsPlaceholders != null) {
final ClassNode placeHolderType;
if(genericsPlaceholders.containsKey(type.getUnresolvedName())) {
placeHolderType = genericsPlaceholders.get(type.getUnresolvedName());
} else {
placeHolderType = defaultPlaceholder;
}
if(placeHolderType != null) {
return placeHolderType.getPlainNodeReference();
} else {
return ClassHelper.make(Object.class).getPlainNodeReference();
}
}
final ClassNode nonGen = type.getPlainNodeReference();
if("java.lang.Object".equals(type.getName())) {
nonGen.setGenericsPlaceHolder(false);
nonGen.setGenericsTypes(null);
nonGen.setUsingGenerics(false);
} else {
if(type.isUsingGenerics()) {
GenericsType[] parameterized = type.getGenericsTypes();
if (parameterized != null && parameterized.length > 0) {
GenericsType[] copiedGenericsTypes = new GenericsType[parameterized.length];
for (int i = 0; i < parameterized.length; i++) {
GenericsType parameterizedType = parameterized[i];
GenericsType copiedGenericsType = null;
if (parameterizedType.isPlaceholder() && genericsPlaceholders != null) {
ClassNode placeHolderType = genericsPlaceholders.get(parameterizedType.getName());
if(placeHolderType != null) {
copiedGenericsType = new GenericsType(placeHolderType.getPlainNodeReference());
} else {
copiedGenericsType = new GenericsType(ClassHelper.make(Object.class).getPlainNodeReference());
}
} else {
copiedGenericsType = new GenericsType(replaceGenericsPlaceholders(parameterizedType.getType(), genericsPlaceholders));
}
copiedGenericsTypes[i] = copiedGenericsType;
}
nonGen.setGenericsTypes(copiedGenericsTypes);
}
}
}
return nonGen;
}
public static boolean isCandidateInstanceMethod(ClassNode classNode, MethodNode declaredMethod) {
Parameter[] parameterTypes = declaredMethod.getParameters();
return isCandidateMethod(declaredMethod) && parameterTypes != null &&
parameterTypes.length > 0 && isAssignableFrom(parameterTypes[0].getType(), classNode);
}
/**
* Determines if the class or interface represented by the superClass
* argument is either the same as, or is a superclass or superinterface of,
* the class or interface represented by the specified subClass parameter.
*
* @param superClass The super class to check
* @param childClass The sub class the check
* @return true if the childClass is either equal to or a sub class of the specified superClass
*/
public static boolean isAssignableFrom(ClassNode superClass, ClassNode childClass) {
ClassNode currentSuper = childClass;
while (currentSuper != null) {
if (currentSuper.equals(superClass)) {
return true;
}
currentSuper = currentSuper.getSuperClass();
}
return false;
}
public static boolean isCandidateMethod(MethodNode declaredMethod) {
return !declaredMethod.isSynthetic() &&
!declaredMethod.getName().contains("$")
&& Modifier.isPublic(declaredMethod.getModifiers()) &&
!Modifier.isAbstract(declaredMethod.getModifiers());
}
public static boolean isConstructorMethod(MethodNode declaredMethod) {
return declaredMethod.isStatic() && declaredMethod.isPublic() &&
declaredMethod.getName().equals("initialize") &&
declaredMethod.getParameters().length >= 1 &&
declaredMethod.getParameters()[0].getType().equals(AbstractGrailsArtefactTransformer.OBJECT_CLASS);
}
public static boolean isDomainClass(final ClassNode classNode, final SourceUnit sourceNode) {
@SuppressWarnings("unchecked")
boolean isDomainClass = GrailsASTUtils.hasAnyAnnotations(classNode,
grails.persistence.Entity.class,
javax.persistence.Entity.class);
if (!isDomainClass && sourceNode != null) {
final String sourcePath = sourceNode.getName();
final File sourceFile = new File(sourcePath);
File parent = sourceFile.getParentFile();
while (parent != null && !isDomainClass) {
final File parentParent = parent.getParentFile();
if (parent.getName().equals(DOMAIN_DIR) &&
parentParent != null &&
parentParent.getName().equals(GRAILS_APP_DIR)) {
isDomainClass = true;
}
parent = parentParent;
}
}
return isDomainClass;
}
public static void addDelegateInstanceMethods(ClassNode classNode, ClassNode delegateNode, Expression delegateInstance) {
addDelegateInstanceMethods(classNode, delegateNode, delegateInstance, null);
}
public static void addDelegateInstanceMethods(ClassNode classNode, ClassNode delegateNode, Expression delegateInstance, Map<String, ClassNode> genericsPlaceholders) {
addDelegateInstanceMethods(classNode, classNode, delegateNode, delegateInstance, genericsPlaceholders, false, false);
}
public static void addDelegateInstanceMethods(ClassNode supportedSuperType, ClassNode classNode, ClassNode delegateNode, Expression delegateInstance) {
addDelegateInstanceMethods(supportedSuperType, classNode, delegateNode, delegateInstance, null, false, false);
}
public static void addDelegateInstanceMethods(ClassNode supportedSuperType, ClassNode classNode, ClassNode delegateNode, Expression delegateInstance, Map<String, ClassNode> genericsPlaceholders, boolean noNullCheck, boolean addCompileStatic) {
while (!delegateNode.equals(AbstractGrailsArtefactTransformer.OBJECT_CLASS)) {
List<MethodNode> declaredMethods = delegateNode.getMethods();
for (MethodNode declaredMethod : declaredMethods) {
if (isConstructorMethod(declaredMethod)) {
addDelegateConstructor(classNode, declaredMethod, genericsPlaceholders);
}
else if (isCandidateInstanceMethod(supportedSuperType, declaredMethod)) {
MethodNode methodNode = addDelegateInstanceMethod(classNode, delegateInstance, declaredMethod, null, true, genericsPlaceholders, noNullCheck);
if(addCompileStatic) {
addCompileStaticAnnotation(methodNode);
}
}
}
delegateNode = delegateNode.getSuperClass();
}
}
public static FieldNode addFieldIfNonExistent(ClassNode classNode, ClassNode fieldType, String fieldName) {
if (classNode != null && classNode.getField(fieldName) == null) {
return classNode.addField(fieldName, Modifier.PRIVATE, fieldType,
new ConstructorCallExpression(fieldType, new ArgumentListExpression()));
}
return null;
}
/**
* Adds the given expression as a member of the given annotation
*
* @param annotationNode The annotation node
* @param memberName The name of the member
* @param expression The expression
*/
public static void addExpressionToAnnotationMember(AnnotationNode annotationNode, String memberName, Expression expression) {
Expression exclude = annotationNode.getMember(memberName);
if(exclude instanceof ListExpression) {
((ListExpression)exclude).addExpression(expression);
}
else if(exclude != null) {
ListExpression list = new ListExpression();
list.addExpression(list);
list.addExpression(expression);
annotationNode.setMember(memberName, list);
}
else {
annotationNode.setMember(memberName, expression);
}
}
/**
* Adds an annotation to the give nclass node if it doesn't already exist
*
* @param classNode The class node
* @param annotationClass The annotation class
*/
public static void addAnnotationIfNecessary(ClassNode classNode, Class<? extends Annotation> annotationClass) {
addAnnotationOrGetExisting(classNode, annotationClass);
}
/**
* Adds an annotation to the given class node or returns the existing annotation
*
* @param classNode The class node
* @param annotationClass The annotation class
*/
public static AnnotationNode addAnnotationOrGetExisting(ClassNode classNode, Class<? extends Annotation> annotationClass) {
return addAnnotationOrGetExisting(classNode, annotationClass, Collections.<String, Object>emptyMap());
}
/**
* Adds an annotation to the given class node or returns the existing annotation
*
* @param classNode The class node
* @param annotationClass The annotation class
*/
public static AnnotationNode addAnnotationOrGetExisting(ClassNode classNode, Class<? extends Annotation> annotationClass, Map<String, Object> members) {
List<AnnotationNode> annotations = classNode.getAnnotations();
ClassNode annotationClassNode = ClassHelper.make(annotationClass);
AnnotationNode annotationToAdd = new AnnotationNode(annotationClassNode);
if (annotations.isEmpty()) {
classNode.addAnnotation(annotationToAdd);
}
else {
AnnotationNode existing = findAnnotation(annotationClassNode, annotations);
if (existing != null){
annotationToAdd = existing;
}
else {
classNode.addAnnotation(annotationToAdd);
}
}
if(members != null && !members.isEmpty()) {
for (Map.Entry<String, Object> memberEntry : members.entrySet()) {
Object value = memberEntry.getValue();
annotationToAdd.setMember( memberEntry.getKey(), value instanceof Expression ? (Expression)value : new ConstantExpression(value));
}
}
return annotationToAdd;
}
/**
* Add the grails.artefact.Enhanced annotation to classNode if it does not already exist and ensure that
* all of the features in the enhancedFor array are represented in the enhancedFor attribute of the
* Enhanced annnotation
* @param classNode the class to add the Enhanced annotation to
* @param enhancedFor an array of feature names to include in the enhancedFor attribute of the annotation
* @return the AnnotationNode associated with the Enhanced annotation for classNode
* @see Enhanced
*/
public static AnnotationNode addEnhancedAnnotation(final ClassNode classNode, final String... enhancedFor) {
final AnnotationNode enhancedAnnotationNode;
final List<AnnotationNode> annotations = classNode.getAnnotations(ENHANCED_CLASS_NODE);
if (annotations.isEmpty()) {
enhancedAnnotationNode = new AnnotationNode(ENHANCED_CLASS_NODE);
enhancedAnnotationNode.setMember("version", new ConstantExpression(GrailsUtil.getGrailsVersion()));
classNode.addAnnotation(enhancedAnnotationNode);
} else {
enhancedAnnotationNode = annotations.get(0);
}
if(enhancedFor != null && enhancedFor.length > 0) {
ListExpression enhancedForArray = (ListExpression) enhancedAnnotationNode.getMember("enhancedFor");
if(enhancedForArray == null) {
enhancedForArray = new ListExpression();
enhancedAnnotationNode.setMember("enhancedFor", enhancedForArray);
}
final List<Expression> featureNameExpressions = enhancedForArray.getExpressions();
for(final String feature : enhancedFor) {
boolean exists = false;
for(Expression expression : featureNameExpressions) {
if(expression instanceof ConstantExpression && feature.equals(((ConstantExpression)expression).getValue())) {
exists = true;
break;
}
}
if(!exists) {
featureNameExpressions.add(new ConstantExpression(feature));
}
}
}
return enhancedAnnotationNode;
}
public static AnnotationNode findAnnotation(ClassNode annotationClassNode, List<AnnotationNode> annotations) {
for (AnnotationNode annotation : annotations) {
if (annotation.getClassNode().equals(annotationClassNode)) {
return annotation;
}
}
return null;
}
public static AnnotationNode findAnnotation(ClassNode classNode, Class<?> type) {
List<AnnotationNode> annotations = classNode.getAnnotations();
return annotations == null ? null : findAnnotation(new ClassNode(type),annotations);
}
/**
* Returns true if classNode is marked with annotationClass
* @param classNode A ClassNode to inspect
* @param annotationClass an annotation to look for
* @return true if classNode is marked with annotationClass, otherwise false
*/
public static boolean hasAnnotation(final ClassNode classNode, final Class<? extends Annotation> annotationClass) {
return !classNode.getAnnotations(new ClassNode(annotationClass)).isEmpty();
}
/**
* @param classNode a ClassNode to search
* @param annotationsToLookFor Annotations to look for
* @return true if classNode is marked with any of the annotations in annotationsToLookFor
*/
public static boolean hasAnyAnnotations(final ClassNode classNode, final Class<? extends Annotation>... annotationsToLookFor) {
for (Class<? extends Annotation> annotationClass : annotationsToLookFor) {
if(hasAnnotation(classNode, annotationClass)) {
return true;
}
}
return false;
}
public static void addMethodIfNotPresent(ClassNode controllerClassNode, MethodNode methodNode) {
MethodNode existing = controllerClassNode.getMethod(methodNode.getName(), methodNode.getParameters());
if (existing == null) {
controllerClassNode.addMethod(methodNode);
}
}
public static ExpressionStatement createPrintlnStatement(String message) {
return new ExpressionStatement(new MethodCallExpression(new VariableExpression("this"),"println", new ArgumentListExpression(new ConstantExpression(message))));
}
public static ExpressionStatement createPrintlnStatement(String message, String variable) {
return new ExpressionStatement(new MethodCallExpression(new VariableExpression("this"),"println", new ArgumentListExpression(new BinaryExpression(new ConstantExpression(message),Token.newSymbol(Types.PLUS, 0, 0),new VariableExpression(variable)))));
}
/**
* Wraps a method body in try / catch logic that catches any errors and logs an error, but does not rethrow!
*
* @param methodNode The method node
*/
public static void wrapMethodBodyInTryCatchDebugStatements(MethodNode methodNode) {
BlockStatement code = (BlockStatement) methodNode.getCode();
BlockStatement newCode = new BlockStatement();
TryCatchStatement tryCatchStatement = new TryCatchStatement(code, new BlockStatement());
newCode.addStatement(tryCatchStatement);
methodNode.setCode(newCode);
BlockStatement catchBlock = new BlockStatement();
ArgumentListExpression logArguments = new ArgumentListExpression();
logArguments.addExpression(new BinaryExpression(new ConstantExpression("Error initializing class: "),Token.newSymbol(Types.PLUS, 0, 0),new VariableExpression("e")));
logArguments.addExpression(new VariableExpression("e"));
catchBlock.addStatement(new ExpressionStatement(new MethodCallExpression(new VariableExpression("log"), "error", logArguments)));
tryCatchStatement.addCatch(new CatchStatement(new Parameter(new ClassNode(Throwable.class), "e"),catchBlock));
}
/**
* Evaluates a constraints closure and returns metadata about the constraints configured in the closure. The
* Map returned has property names as keys and the value associated with each of those property names is
* a Map<String, Expression> which has constraint names as keys and the Expression associated with that constraint
* as values.
*
* @param closureExpression the closure expression to evaluate
* @return the Map as described above
*/
public static Map<String, Map<String, Expression>> getConstraintMetadata(final ClosureExpression closureExpression) {
final List<MethodCallExpression> methodExpressions = new ArrayList<MethodCallExpression>();
final Map<String, Map<String, Expression>> results = new LinkedHashMap<String, Map<String, Expression>>();
final Statement closureCode = closureExpression.getCode();
if (closureCode instanceof BlockStatement) {
final List<Statement> closureStatements = ((BlockStatement) closureCode).getStatements();
for (final Statement closureStatement : closureStatements) {
if (closureStatement instanceof ExpressionStatement) {
final Expression expression = ((ExpressionStatement) closureStatement).getExpression();
if (expression instanceof MethodCallExpression) {
methodExpressions.add((MethodCallExpression) expression);
}
} else if (closureStatement instanceof ReturnStatement) {
final ReturnStatement returnStatement = (ReturnStatement) closureStatement;
Expression expression = returnStatement.getExpression();
if (expression instanceof MethodCallExpression) {
methodExpressions.add((MethodCallExpression) expression);
}
}
for (final MethodCallExpression methodCallExpression : methodExpressions) {
final Expression objectExpression = methodCallExpression.getObjectExpression();
if (objectExpression instanceof VariableExpression && "this".equals(((VariableExpression)objectExpression).getName())) {
final Expression methodCallArguments = methodCallExpression.getArguments();
if (methodCallArguments instanceof TupleExpression) {
final List<Expression> methodCallArgumentExpressions = ((TupleExpression) methodCallArguments).getExpressions();
if (methodCallArgumentExpressions != null && methodCallArgumentExpressions.size() == 1 && methodCallArgumentExpressions.get(0) instanceof NamedArgumentListExpression) {
final Map<String, Expression> constraintNameToExpression = new LinkedHashMap<String, Expression>();
final List<MapEntryExpression> mapEntryExpressions = ((NamedArgumentListExpression) methodCallArgumentExpressions.get(0)).getMapEntryExpressions();
for (final MapEntryExpression mapEntryExpression : mapEntryExpressions) {
final Expression keyExpression = mapEntryExpression.getKeyExpression();
if (keyExpression instanceof ConstantExpression) {
final Object value = ((ConstantExpression) keyExpression).getValue();
if (value instanceof String) {
constraintNameToExpression.put((String)value, mapEntryExpression.getValueExpression());
}
}
}
results.put(methodCallExpression.getMethodAsString(), constraintNameToExpression);
}
}
}
}
}
}
return results;
}
/**
* Returns a map containing the names and types of the given association type. eg. GrailsDomainClassProperty.HAS_MANY
* @param classNode The target class ndoe
* @param associationType The associationType
* @return A map
*/
public static Map<String, ClassNode> getAssocationMap(ClassNode classNode, String associationType) {
PropertyNode property = classNode.getProperty(associationType);
Map<String, ClassNode> associationMap = new HashMap<String, ClassNode>();
if (property != null && property.isStatic()) {
Expression e = property.getInitialExpression();
if (e instanceof MapExpression) {
MapExpression me = (MapExpression) e;
for (MapEntryExpression mee : me.getMapEntryExpressions()) {
String key = mee.getKeyExpression().getText();
Expression valueExpression = mee.getValueExpression();
if (valueExpression instanceof ClassExpression) {
associationMap.put(key, valueExpression.getType());
}
}
}
}
return associationMap;
}
public static Map<String,ClassNode> getAllAssociationMap(ClassNode classNode) {
Map<String, ClassNode> associationMap = new HashMap<String, ClassNode>();
associationMap.putAll( getAssocationMap(classNode, GrailsDomainClassProperty.HAS_MANY));
associationMap.putAll( getAssocationMap(classNode, GrailsDomainClassProperty.HAS_ONE));
associationMap.putAll( getAssocationMap(classNode, GrailsDomainClassProperty.BELONGS_TO));
return associationMap;
}
public static ClassNode findInterface(ClassNode classNode, ClassNode interfaceNode) {
while(!classNode.equals(OBJECT_CLASS_NODE)) {
Set<ClassNode> interfaces = classNode.getAllInterfaces();
for (ClassNode anInterface : interfaces) {
if(anInterface.equals(interfaceNode)) return anInterface;
}
classNode = classNode.getSuperClass();
}
return null;
}
public static boolean hasZeroArgsConstructor(ClassNode implementationNode) {
List<ConstructorNode> constructors = implementationNode.getDeclaredConstructors();
if(constructors.isEmpty()) return true;
for (ConstructorNode constructor : constructors) {
if(constructor.getParameters().length == 0 ) return true;
}
return false;
}
/**
* Whether the given class node is an inner class
*
* @param classNode The class node
* @return True if it is
*/
public static boolean isInnerClassNode(ClassNode classNode) {
return (classNode instanceof InnerClassNode) || classNode.getName().contains("$");
}
/**
* Returns true if the given class name is a parent class of the given class
*
* @param classNode The class node
* @param parentClassName the parent class name
* @return True if it is a subclass
*/
public static boolean isSubclassOf(ClassNode classNode, String parentClassName) {
ClassNode currentSuper = classNode.getSuperClass();
while (currentSuper != null && !currentSuper.getName().equals(OBJECT_CLASS)) {
if (currentSuper.getName().equals(parentClassName)) return true;
currentSuper = currentSuper.getSuperClass();
}
return false;
}
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.SOURCE)
private static @interface GrailsDelegatingConstructor {}
/**
* Marks a method to be staticly compiled
*
* @param annotatedNode
* @return The annotated method
*/
public static AnnotatedNode addCompileStaticAnnotation(AnnotatedNode annotatedNode) {
return addCompileStaticAnnotation(annotatedNode, false);
}
/**
* Adds @CompileStatic annotation to method
*
* @param annotatedNode
* @param skip
* @return The annotated method
*/
public static AnnotatedNode addCompileStaticAnnotation(AnnotatedNode annotatedNode, boolean skip) {
if(annotatedNode != null) {
AnnotationNode an = new AnnotationNode(COMPILESTATIC_CLASS_NODE);
if(skip) {
an.addMember("value", new PropertyExpression(new ClassExpression(TYPECHECKINGMODE_CLASS_NODE), "SKIP"));
}
annotatedNode.addAnnotation(an);
if(!skip) {
annotatedNode.getDeclaringClass().addTransform(StaticCompileTransformation.class, an);
}
}
return annotatedNode;
}
/**
* Set the method target of a MethodCallExpression to the first matching method with same number of arguments.
* This doesn't check argument types.
*
* @param methodCallExpression
* @param targetClassNode
*/
public static MethodCallExpression applyDefaultMethodTarget(final MethodCallExpression methodCallExpression, final ClassNode targetClassNode) {
return applyMethodTarget(methodCallExpression, targetClassNode, (ClassNode[])null);
}
/**
* Set the method target of a MethodCallExpression to the first matching method with same number of arguments.
* This doesn't check argument types.
*
* @param methodCallExpression
* @param targetClass
* @return The method call expression
*/
public static MethodCallExpression applyDefaultMethodTarget(final MethodCallExpression methodCallExpression, final Class<?> targetClass) {
return applyDefaultMethodTarget(methodCallExpression, ClassHelper.make(targetClass).getPlainNodeReference());
}
/**
* Set the method target of a MethodCallExpression to the first matching method with same number and type of arguments.
*
* A null parameter type will match any type
*
* @param methodCallExpression
* @param targetClassNode
* @param targetParameterTypes
* @return The method call expression
*/
public static MethodCallExpression applyMethodTarget(final MethodCallExpression methodCallExpression, final ClassNode targetClassNode, final ClassNode... targetParameterTypes) {
String methodName = methodCallExpression.getMethodAsString();
if(methodName==null) return methodCallExpression;
int argumentCount = methodCallExpression.getArguments() != null ? ((TupleExpression)methodCallExpression.getArguments()).getExpressions().size() : 0;
String methodFoundInClass = null;
for (MethodNode method : targetClassNode.getMethods(methodName)) {
int methodParameterCount = method.getParameters() != null ? method.getParameters().length : 0;
if (methodParameterCount == argumentCount && (targetParameterTypes == null || (parameterTypesMatch(method.getParameters(), targetParameterTypes)))) {
String methodFromClass = method.getDeclaringClass().getName();
if(methodFoundInClass == null) {
methodCallExpression.setMethodTarget(method);
methodFoundInClass = methodFromClass;
} else if (methodFromClass.equals(methodFoundInClass)) {
throw new RuntimeException("Multiple methods with same name '" + methodName + "' and argument count (" + argumentCount + ") in " + targetClassNode.getName() + ". Cannot apply default method target.");
}
}
}
return methodCallExpression;
}
/**
* Set the method target of a MethodCallExpression to the first matching method with same number and type of arguments.
*
* @param methodCallExpression
* @param targetClass
* @param targetParameterClassTypes
* @return The method call expression
*/
public static MethodCallExpression applyMethodTarget(final MethodCallExpression methodCallExpression, final Class<?> targetClass, final Class<?>... targetParameterClassTypes) {
return applyMethodTarget(methodCallExpression, ClassHelper.make(targetClass).getPlainNodeReference(), convertTargetParameterTypes(targetParameterClassTypes));
}
/**
* Set the method target of a MethodCallExpression to the first matching method with same number and type of arguments.
*
* @param methodCallExpression
* @param targetClassNode
* @param targetParameterClassTypes
* @return The method call expression
*/
public static MethodCallExpression applyMethodTarget(final MethodCallExpression methodCallExpression, final ClassNode targetClassNode, final Class<?>... targetParameterClassTypes) {
return applyMethodTarget(methodCallExpression, targetClassNode, convertTargetParameterTypes(targetParameterClassTypes));
}
private static ClassNode[] convertTargetParameterTypes(final Class<?>[] targetParameterClassTypes) {
ClassNode[] targetParameterTypes = null;
if(targetParameterClassTypes != null) {
targetParameterTypes = new ClassNode[targetParameterClassTypes.length];
for(int i=0;i < targetParameterClassTypes.length;i++) {
targetParameterTypes[i] = targetParameterClassTypes[i] != null ? ClassHelper.make(targetParameterClassTypes[i]).getPlainNodeReference() : null;
}
}
return targetParameterTypes;
}
private static boolean parameterTypesMatch(Parameter[] parameters, ClassNode[] targetParameterTypes) {
if(targetParameterTypes==null || targetParameterTypes.length==0) return true;
for(int i=0;i < parameters.length;i++) {
if(targetParameterTypes.length > i && targetParameterTypes[i] != null && !parameters[i].getType().getName().equals(targetParameterTypes[i].getName())) {
return false;
}
}
return true;
}
/**
* Build static direct call to getter of a property
*
* @param objectExpression
* @param propertyName
* @param targetClassNode
* @return The method call expression
*/
public static MethodCallExpression buildGetPropertyExpression(final Expression objectExpression, final String propertyName, final ClassNode targetClassNode) {
return buildGetPropertyExpression(objectExpression, propertyName, targetClassNode, false);
}
/**
* Build static direct call to getter of a property
*
* @param objectExpression
* @param propertyName
* @param targetClassNode
* @param useBooleanGetter
* @return The method call expression
*/
public static MethodCallExpression buildGetPropertyExpression(final Expression objectExpression, final String propertyName, final ClassNode targetClassNode, final boolean useBooleanGetter) {
String methodName = (useBooleanGetter ? "is" : "get") + MetaClassHelper.capitalize(propertyName);
MethodCallExpression methodCallExpression = new MethodCallExpression(objectExpression, methodName, MethodCallExpression.NO_ARGUMENTS);
MethodNode getterMethod = targetClassNode.getGetterMethod(methodName);
if(getterMethod != null) {
methodCallExpression.setMethodTarget(getterMethod);
}
return methodCallExpression;
}
/**
* Build static direct call to setter of a property
*
* @param objectExpression
* @param propertyName
* @param targetClassNode
* @param valueExpression
* @return The method call expression
*/
public static MethodCallExpression buildSetPropertyExpression(final Expression objectExpression, final String propertyName, final ClassNode targetClassNode, final Expression valueExpression) {
String methodName = "set" + MetaClassHelper.capitalize(propertyName);
MethodCallExpression methodCallExpression = new MethodCallExpression(objectExpression, methodName, new ArgumentListExpression(valueExpression));
MethodNode setterMethod = targetClassNode.getSetterMethod(methodName);
if(setterMethod != null) {
methodCallExpression.setMethodTarget(setterMethod);
}
return methodCallExpression;
}
/**
* Build static direct call to put entry in Map
*
* @param objectExpression
* @param keyName
* @param valueExpression
* @return The method call expression
*/
public static MethodCallExpression buildPutMapExpression(final Expression objectExpression, final String keyName, final Expression valueExpression) {
return applyDefaultMethodTarget(new MethodCallExpression(objectExpression, "put", new ArgumentListExpression(new ConstantExpression(keyName), valueExpression)), Map.class);
}
/**
* Build static direct call to get entry from Map
*
* @param objectExpression
* @param keyName
* @return The method call expression
*/
public static MethodCallExpression buildGetMapExpression(final Expression objectExpression, final String keyName) {
return applyDefaultMethodTarget(new MethodCallExpression(objectExpression, "get", new ArgumentListExpression(new ConstantExpression(keyName))), Map.class);
}
public static Expression buildGetThisObjectExpression(boolean inClosureBlock) {
if (!inClosureBlock) {
return buildThisExpression();
} else {
return buildGetPropertyExpression(buildThisExpression(), "thisObject", ClassHelper.make(Closure.class).getPlainNodeReference());
}
}
public static Expression buildThisExpression() {
return new VariableExpression("this");
}
public static MethodCallExpression noImplicitThis(MethodCallExpression methodCallExpression) {
return applyImplicitThis(methodCallExpression, false);
}
public static MethodCallExpression applyImplicitThis(MethodCallExpression methodCallExpression, boolean useImplicitThis) {
methodCallExpression.setImplicitThis(useImplicitThis);
return methodCallExpression;
}
public static void copyAnnotations(final AnnotatedNode from, final AnnotatedNode to) {
copyAnnotations(from, to, null, null);
}
public static void copyAnnotations(final AnnotatedNode from, final AnnotatedNode to, final Set<String> included, final Set<String> excluded) {
final List<AnnotationNode> annotationsToCopy = from.getAnnotations();
for(final AnnotationNode node : annotationsToCopy) {
String annotationClassName = node.getClassNode().getName();
if((excluded==null || !excluded.contains(annotationClassName)) &&
(included==null || included.contains(annotationClassName))) {
final AnnotationNode copyOfAnnotationNode = cloneAnnotation(node);
to.addAnnotation(copyOfAnnotationNode);
}
}
}
public static AnnotationNode cloneAnnotation(final AnnotationNode node) {
final AnnotationNode copyOfAnnotationNode = new AnnotationNode(node.getClassNode());
final Map<String, Expression> members = node.getMembers();
for(final Map.Entry<String, Expression> entry : members.entrySet()) {
copyOfAnnotationNode.addMember(entry.getKey(), entry.getValue());
}
return copyOfAnnotationNode;
}
public static void filterAnnotations(final AnnotatedNode annotatedNode, final Set<String> classNamesToRetain, final Set<String> classNamesToRemove) {
for(Iterator<AnnotationNode> iterator = annotatedNode.getAnnotations().iterator(); iterator.hasNext(); ) {
final AnnotationNode node = iterator.next();
String annotationClassName = node.getClassNode().getName();
if((classNamesToRemove==null || classNamesToRemove.contains(annotationClassName)) &&
(classNamesToRetain==null || !classNamesToRetain.contains(annotationClassName))) {
iterator.remove();
}
}
}
public static void removeCompileStaticAnnotations(final AnnotatedNode annotatedNode) {
filterAnnotations(annotatedNode, null, new HashSet<String>(Arrays.asList(new String[]{CompileStatic.class.getName(), TypeChecked.class.getName()})));
}
public static void markApplied(ASTNode astNode, Class<?> transformationClass) {
resolveRedirect(astNode).setNodeMetaData(appliedTransformationKey(transformationClass), Boolean.TRUE);
}
private static ASTNode resolveRedirect(ASTNode astNode) {
if(astNode instanceof ClassNode) {
astNode = ((ClassNode)astNode).redirect();
}
return astNode;
}
private static String appliedTransformationKey(Class<?> transformationClass) {
return "APPLIED_" + transformationClass.getName();
}
public static boolean isApplied(ASTNode astNode, Class<?> transformationClass) {
return resolveRedirect(astNode).getNodeMetaData(appliedTransformationKey(transformationClass)) == Boolean.TRUE;
}
public static void processVariableScopes(SourceUnit source, ClassNode classNode) {
processVariableScopes(source, classNode, null);
}
public static void processVariableScopes(SourceUnit source, ClassNode classNode, MethodNode methodNode) {
VariableScopeVisitor scopeVisitor = new VariableScopeVisitor(source);
if(methodNode == null) {
scopeVisitor.visitClass(classNode);
} else {
scopeVisitor.prepareVisit(classNode);
scopeVisitor.visitMethod(methodNode);
}
}
public static boolean isSetterOrGetterMethod(MethodNode md) {
String methodName = md.getName();
if((methodName.startsWith("set") && methodName.length() > 3) && md.getParameters() != null && md.getParameters().length == 1) {
return true;
}
if(((methodName.startsWith("get") && methodName.length() > 3) || (methodName.startsWith("is") && methodName.length() > 2)) && (md.getParameters()==null || md.getParameters().length == 0)) {
return true;
}
return false;
}
}