Java tutorial
/* * 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.codehaus.groovy.grails.compiler.injection; import grails.build.logging.GrailsConsole; import grails.persistence.Entity; import grails.util.GrailsNameUtils; import groovy.lang.MissingMethodException; 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.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.Predicate; import org.apache.commons.lang.StringUtils; import org.codehaus.groovy.ast.ASTNode; 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.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.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.control.Janitor; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.control.messages.SyntaxErrorMessage; import org.codehaus.groovy.grails.commons.GrailsClassUtils; import org.codehaus.groovy.grails.commons.GrailsDomainClassProperty; import org.codehaus.groovy.syntax.SyntaxException; import org.codehaus.groovy.syntax.Token; import org.codehaus.groovy.syntax.Types; /** * 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 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(); public static final VariableExpression THIS_EXPR = new VariableExpression("this"); public static final Parameter[] ZERO_PARAMETERS = new Parameter[0]; public static final ArgumentListExpression ZERO_ARGUMENTS = new ArgumentListExpression(); 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.isBlank(propertyName)) { return false; } final MethodNode method = classNode.getMethod(GrailsNameUtils.getGetterName(propertyName), Parameter.EMPTY_ARRAY); 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, 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); } /** * 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) { 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); ThrowStatement missingMethodException = createMissingMethodThrowable(classNode, declaredMethod); VariableExpression apiVar = addApiVariableDeclaration(delegate, declaredMethod, methodBody); IfStatement ifStatement = createIfElseStatementForApiMethodCall(methodCallExpression, apiVar, missingMethodException); methodBody.addStatement(ifStatement); MethodNode methodNode = new MethodNode(methodName, Modifier.PUBLIC, returnType, copyParameters(parameterTypes, genericsPlaceholders), GrailsArtefactClassInjector.EMPTY_CLASS_ARRAY, methodBody); methodNode.addAnnotations(declaredMethod.getAnnotations()); 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()); 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(AbstractGrailsArtefactTransformer.THIS_EXPRESSION); } 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); } /** * 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 * @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 expression, ClassNode classNode, MethodNode delegateMethod, AnnotationNode markerAnnotation, Map<String, ClassNode> genericsPlaceholders) { Parameter[] parameterTypes = delegateMethod.getParameters(); String declaredMethodName = delegateMethod.getName(); if (classNode.hasDeclaredMethod(declaredMethodName, copyParameters(parameterTypes, genericsPlaceholders))) { return null; } BlockStatement methodBody = new BlockStatement(); ArgumentListExpression arguments = new ArgumentListExpression(); for (Parameter parameterType : parameterTypes) { arguments.addExpression(new VariableExpression(parameterType.getName())); } MethodCallExpression methodCallExpression = new MethodCallExpression(expression, declaredMethodName, arguments); methodCallExpression.setMethodTarget(delegateMethod); ThrowStatement missingMethodException = createMissingMethodThrowable(classNode, delegateMethod); VariableExpression apiVar = addApiVariableDeclaration(expression, delegateMethod, methodBody); IfStatement ifStatement = createIfElseStatementForApiMethodCall(methodCallExpression, apiVar, missingMethodException); methodBody.addStatement(ifStatement); ClassNode returnType = replaceGenericsPlaceholders(delegateMethod.getReturnType(), genericsPlaceholders); if (METHOD_MISSING_METHOD_NAME.equals(declaredMethodName)) { declaredMethodName = STATIC_METHOD_MISSING_METHOD_NAME; } MethodNode methodNode = classNode.getDeclaredMethod(declaredMethodName, parameterTypes); if (methodNode == null) { methodNode = new MethodNode(declaredMethodName, Modifier.PUBLIC | Modifier.STATIC, returnType, copyParameters(parameterTypes, genericsPlaceholders), GrailsArtefactClassInjector.EMPTY_CLASS_ARRAY, methodBody); methodNode.addAnnotations(delegateMethod.getAnnotations()); 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 void 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))); } 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))); } } /** * 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; } private 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()); newParameter.addAnnotations(parameterType.getAnnotations()); newParameterTypes[i] = newParameter; } return newParameterTypes; } public static ClassNode nonGeneric(ClassNode type) { return replaceGenericsPlaceholders(type, null); } public static ClassNode replaceGenericsPlaceholders(ClassNode type, Map<String, ClassNode> genericsPlaceholders) { if (type.isArray()) { return replaceGenericsPlaceholders(type.getComponentType(), genericsPlaceholders).makeArray(); } if (!type.isUsingGenerics() && !type.isRedirectNode()) { return type.getPlainNodeReference(); } if (type.isGenericsPlaceHolder()) { ClassNode placeHolderType = genericsPlaceholders != null ? genericsPlaceholders.get(type.getUnresolvedName()) : null; if (placeHolderType != null) { return placeHolderType.getPlainNodeReference(); } else { return ClassHelper.make(Object.class).getPlainNodeReference(); } } final ClassNode nonGen = type.getPlainNodeReference(); 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()) { ClassNode placeHolderType = genericsPlaceholders != null ? genericsPlaceholders.get(parameterizedType.getName()) : null; 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 */ private 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) { 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); } public static void addDelegateInstanceMethods(ClassNode supportedSuperType, ClassNode classNode, ClassNode delegateNode, Expression delegateInstance) { addDelegateInstanceMethods(supportedSuperType, classNode, delegateNode, delegateInstance, null); } public static void addDelegateInstanceMethods(ClassNode supportedSuperType, ClassNode classNode, ClassNode delegateNode, Expression delegateInstance, Map<String, ClassNode> genericsPlaceholders) { 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)) { addDelegateInstanceMethod(classNode, delegateInstance, declaredMethod, null, true, genericsPlaceholders); } } 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; } public static void addAnnotationIfNecessary(ClassNode classNode, Class<Entity> entityClass) { List<AnnotationNode> annotations = classNode.getAnnotations(); ClassNode annotationClassNode = new ClassNode(Entity.class); AnnotationNode annotationToAdd = new AnnotationNode(annotationClassNode); if (annotations.isEmpty()) { classNode.addAnnotation(annotationToAdd); } else { boolean foundAnn = findAnnotation(annotationClassNode, annotations) != null; if (!foundAnn) classNode.addAnnotation(annotationToAdd); } } 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) { return CollectionUtils.exists(Arrays.asList(annotationsToLookFor), new Predicate() { @SuppressWarnings({ "unchecked", "rawtypes" }) public boolean evaluate(Object object) { return hasAnnotation(classNode, (Class) object); } }); } 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(AbstractGrailsArtefactTransformer.THIS_EXPRESSION, "println", new ArgumentListExpression(new ConstantExpression(message)))); } public static ExpressionStatement createPrintlnStatement(String message, String variable) { return new ExpressionStatement(new MethodCallExpression(AbstractGrailsArtefactTransformer.THIS_EXPRESSION, "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("$"); } @Target(ElementType.CONSTRUCTOR) @Retention(RetentionPolicy.SOURCE) private static @interface GrailsDelegatingConstructor { } }