/*
* Copyright 2008-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.griffon.compile.core.ast.transform;
import griffon.core.GriffonApplication;
import org.codehaus.griffon.compile.core.MethodDescriptor;
import org.codehaus.griffon.compile.core.ast.GriffonASTUtils;
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.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.FieldExpression;
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.messages.SimpleMessage;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import org.codehaus.groovy.syntax.SyntaxException;
import org.codehaus.groovy.transform.ASTTransformation;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static griffon.util.GriffonNameUtils.getGetterName;
import static griffon.util.GriffonNameUtils.isBlank;
import static java.lang.reflect.Modifier.PRIVATE;
import static java.lang.reflect.Modifier.PUBLIC;
import static java.lang.reflect.Modifier.isPrivate;
import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.NO_ARGS;
import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.NO_EXCEPTIONS;
import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.NO_PARAMS;
import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.THIS;
import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.args;
import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.call;
import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.field;
import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.injectField;
import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.injectMethod;
import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.returns;
import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.stmnt;
import static org.codehaus.griffon.compile.core.ast.GriffonASTUtils.var;
/**
* Base class for all of Griffon's ASTTransformation implementations.
*
* @author Andres Almiray
* @since 2.0.0
*/
public abstract class AbstractASTTransformation implements ASTTransformation {
public static final ClassNode COLLECTIONS_CLASS = makeClassSafe(Collections.class);
public static final ClassNode GRIFFON_APPLICATION_TYPE = makeClassSafe(GriffonApplication.class);
public static final ClassNode INJECT_TYPE = makeClassSafe(Inject.class);
public static final ClassNode NAMED_TYPE = makeClassSafe(Named.class);
private static final String PROPERTY_APPLICATION = "application";
private static final String METHOD_GET_APPLICATION = "getApplication";
public static Expression emptyMap() {
return new StaticMethodCallExpression
(COLLECTIONS_CLASS, "emptyMap", NO_ARGS);
}
@Nonnull
public static Expression applicationExpression(@Nonnull ClassNode classNode) {
FieldNode field = classNode.getDeclaredField(PROPERTY_APPLICATION);
if (field != null) {
return new FieldExpression(field);
}
field = classNode.getField(PROPERTY_APPLICATION);
if (field != null && !isPrivate(field.getModifiers())) {
return new FieldExpression(field);
}
MethodNode method = classNode.getDeclaredMethod(METHOD_GET_APPLICATION, NO_PARAMS);
if (method != null) {
return call(THIS, METHOD_GET_APPLICATION, NO_ARGS);
}
method = classNode.getMethod(METHOD_GET_APPLICATION, NO_PARAMS);
if (method != null && !isPrivate(method.getModifiers())) {
return call(THIS, METHOD_GET_APPLICATION, NO_ARGS);
}
throw new IllegalStateException("Cannot resolve application field nor getApplication() method on class " + classNode.getName());
}
@Nonnull
public static Expression applicationProperty(@Nonnull ClassNode classNode, @Nonnull String property) {
return call(applicationExpression(classNode), getGetterName(property), NO_ARGS);
}
public static void injectApplication(@Nonnull ClassNode classNode) {
try {
applicationExpression(classNode);
} catch (IllegalStateException iae) {
FieldNode field = injectField(classNode, PROPERTY_APPLICATION, PRIVATE, GRIFFON_APPLICATION_TYPE, null, false);
field.addAnnotation(new AnnotationNode(INJECT_TYPE));
}
MethodNode method = classNode.getDeclaredMethod(METHOD_GET_APPLICATION, NO_PARAMS);
if (method == null) {
injectMethod(classNode, new MethodNode(
METHOD_GET_APPLICATION,
PUBLIC,
GRIFFON_APPLICATION_TYPE,
NO_PARAMS,
NO_EXCEPTIONS,
returns(field(classNode, PROPERTY_APPLICATION))
));
}
method = classNode.getMethod(METHOD_GET_APPLICATION, NO_PARAMS);
if (method == null && !isPrivate(method.getModifiers())) {
injectMethod(classNode, new MethodNode(
METHOD_GET_APPLICATION,
PUBLIC,
GRIFFON_APPLICATION_TYPE,
NO_PARAMS,
NO_EXCEPTIONS,
returns(field(classNode, PROPERTY_APPLICATION))
));
}
}
public static FieldExpression injectedField(@Nonnull ClassNode owner, @Nonnull ClassNode type, @Nonnull String name) {
return injectedField(owner, type, name, null);
}
public static FieldExpression injectedField(@Nonnull ClassNode owner, @Nonnull ClassNode type, @Nonnull String name, @Nullable String qualifierName) {
FieldNode fieldNode = GriffonASTUtils.injectField(owner, name, Modifier.PRIVATE, type, null, false);
fieldNode.addAnnotation(new AnnotationNode(INJECT_TYPE));
if (!isBlank(qualifierName)) {
AnnotationNode namedAnnotation = new AnnotationNode(NAMED_TYPE);
namedAnnotation.addMember("value", new ConstantExpression(qualifierName));
fieldNode.addAnnotation(namedAnnotation);
}
return new FieldExpression(fieldNode);
}
protected static ClassNode newClass(ClassNode classNode) {
return classNode.getPlainNodeReference();
}
public static ClassNode makeClassSafe(String className) {
return makeClassSafeWithGenerics(className);
}
public static ClassNode makeClassSafe(Class<?> klass) {
return makeClassSafeWithGenerics(klass);
}
public static ClassNode makeClassSafe(ClassNode classNode) {
return makeClassSafeWithGenerics(classNode);
}
public static ClassNode makeClassSafeWithGenerics(String className, String... genericTypes) {
GenericsType[] gtypes = new GenericsType[0];
if (genericTypes != null) {
gtypes = new GenericsType[genericTypes.length];
for (int i = 0; i < gtypes.length; i++) {
gtypes[i] = new GenericsType(makeClassSafe(genericTypes[i]));
}
}
return makeClassSafe0(ClassHelper.make(className), gtypes);
}
public static ClassNode makeClassSafeWithGenerics(Class<?> klass, Class<?>... genericTypes) {
GenericsType[] gtypes = new GenericsType[0];
if (genericTypes != null) {
gtypes = new GenericsType[genericTypes.length];
for (int i = 0; i < gtypes.length; i++) {
gtypes[i] = new GenericsType(makeClassSafe(genericTypes[i]));
}
}
return makeClassSafe0(ClassHelper.make(klass), gtypes);
}
public static ClassNode makeClassSafeWithGenerics(ClassNode classNode, ClassNode... genericTypes) {
GenericsType[] gtypes = new GenericsType[0];
if (genericTypes != null) {
gtypes = new GenericsType[genericTypes.length];
for (int i = 0; i < gtypes.length; i++) {
gtypes[i] = new GenericsType(newClass(genericTypes[i]));
}
}
return makeClassSafe0(classNode, gtypes);
}
public static GenericsType makeGenericsType(String className, String[] upperBounds, String lowerBound, boolean placeHolder) {
ClassNode[] up = new ClassNode[0];
if (upperBounds != null) {
up = new ClassNode[upperBounds.length];
for (int i = 0; i < up.length; i++) {
up[i] = makeClassSafe(upperBounds[i]);
}
}
return makeGenericsType(makeClassSafe(className), up, makeClassSafe(lowerBound), placeHolder);
}
public static GenericsType makeGenericsType(Class<?> klass, Class<?>[] upperBounds, Class<?> lowerBound, boolean placeHolder) {
ClassNode[] up = new ClassNode[0];
if (upperBounds != null) {
up = new ClassNode[upperBounds.length];
for (int i = 0; i < up.length; i++) {
up[i] = makeClassSafe(upperBounds[i]);
}
}
return makeGenericsType(makeClassSafe(klass), up, makeClassSafe(lowerBound), placeHolder);
}
public static GenericsType makeGenericsType(ClassNode classNode, ClassNode[] upperBounds, ClassNode lowerBound, boolean placeHolder) {
classNode = newClass(classNode);
classNode.setGenericsPlaceHolder(placeHolder);
return new GenericsType(classNode, upperBounds, lowerBound);
}
public static ClassNode makeClassSafe0(ClassNode classNode, GenericsType... genericTypes) {
ClassNode plainNodeReference = newClass(classNode);
if (genericTypes != null && genericTypes.length > 0) {
plainNodeReference.setGenericsTypes(genericTypes);
}
return plainNodeReference;
}
public static boolean needsDelegate(@Nonnull ClassNode classNode, @Nonnull SourceUnit sourceUnit,
@Nonnull MethodDescriptor[] methods, @Nonnull String annotationType,
@Nonnull String delegateType) {
boolean implemented = false;
int implementedCount = 0;
ClassNode consideredClass = classNode;
while (consideredClass != null) {
for (MethodNode method : consideredClass.getMethods()) {
for (MethodDescriptor md : methods) {
if (method.getName().equals(md.methodName) && method.getParameters().length == md.arguments.length) {
implemented |= true;
implementedCount++;
}
if (implementedCount == methods.length) {
return false;
}
}
}
consideredClass = consideredClass.getSuperClass();
}
if (implemented) {
sourceUnit.getErrorCollector().addErrorAndContinue(
new SimpleMessage("@" + annotationType + " cannot be processed on "
+ classNode.getName()
+ " because some but not all methods from "
+ delegateType
+ " were declared in the current class or super classes.",
sourceUnit)
);
return false;
}
return true;
}
public static void addDelegateMethods(@Nonnull ClassNode classNode, @Nonnull ClassNode delegateType, @Nonnull Expression delegate) {
for (MethodNode method : delegateType.getMethods()) {
List<Expression> variables = new ArrayList<>();
Parameter[] parameters = new Parameter[method.getParameters().length];
ClassNode[] exceptions = new ClassNode[method.getExceptions().length];
for (int i = 0; i < method.getParameters().length; i++) {
Parameter p = method.getParameters()[i];
parameters[i] = new Parameter(makeClassSafe(p.getType()), p.getName());
parameters[i].getType().setGenericsTypes(p.getType().getGenericsTypes());
parameters[i].getType().setGenericsPlaceHolder(p.getType().isGenericsPlaceHolder());
parameters[i].getType().setUsingGenerics(p.getType().isUsingGenerics());
parameters[i].addAnnotations(p.getAnnotations());
variables.add(var(p.getName()));
}
for (int i = 0; i < method.getExceptions().length; i++) {
ClassNode ex = method.getExceptions()[i];
exceptions[i] = makeClassSafe(ex);
}
ClassNode returnType = makeClassSafe(method.getReturnType());
returnType.addAnnotations(method.getReturnType().getAnnotations());
returnType.setGenericsTypes(method.getReturnType().getGenericsTypes());
returnType.setGenericsPlaceHolder(method.getReturnType().isGenericsPlaceHolder());
returnType.setUsingGenerics(method.getReturnType().isUsingGenerics());
boolean isVoid = ClassHelper.VOID_TYPE.equals(method.getReturnType());
Expression delegateExpression = call(
delegate,
method.getName(),
args(variables));
MethodNode newMethod = new MethodNode(
method.getName(),
method.getModifiers() - Modifier.ABSTRACT,
returnType,
parameters,
exceptions,
isVoid ? stmnt(delegateExpression) : returns(delegateExpression)
);
newMethod.setGenericsTypes(method.getGenericsTypes());
injectMethod(classNode, newMethod);
}
}
public void addError(String msg, ASTNode expr, SourceUnit source) {
int line = expr.getLineNumber();
int col = expr.getColumnNumber();
source.getErrorCollector().addErrorAndContinue(
new SyntaxErrorMessage(new SyntaxException(msg + '\n', line, col), source)
);
}
protected void checkNodesForAnnotationAndType(ASTNode node1, ASTNode node2) {
if (!(node1 instanceof AnnotationNode) || !(node2 instanceof ClassNode)) {
throw new IllegalArgumentException("Internal error: wrong types: " + node1.getClass() + " / " + node2.getClass());
}
}
protected boolean memberHasValue(AnnotationNode node, String name, Object value) {
final Expression member = node.getMember(name);
return member != null && member instanceof ConstantExpression && ((ConstantExpression) member).getValue().equals(value);
}
}