/*
* Copyright 2009-2010 MBTE Sweden AB.
*
* 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.mbte.groovypp.compiler;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.syntax.ASTHelper;
import org.codehaus.groovy.syntax.SyntaxException;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.GroovyASTTransformation;
import org.codehaus.groovy.classgen.Verifier;
import org.objectweb.asm.Opcodes;
import java.util.LinkedList;
import java.util.List;
import java.util.ArrayList;
@GroovyASTTransformation(phase = CompilePhase.CONVERSION)
public class TraitASTTransform implements ASTTransformation, Opcodes {
public void visit(ASTNode[] nodes, final SourceUnit source) {
ModuleNode module = (ModuleNode) nodes[0];
List<ClassNode> toProcess = new LinkedList<ClassNode>();
final boolean forceTyped = source.getName().endsWith(".gpp");
AnnotationNode pkgTypedAnn = getTypedAnnotation(module.getPackage());
for (ClassNode classNode : module.getClasses()) {
boolean process = false;
boolean typed = false;
for (AnnotationNode ann : classNode.getAnnotations()) {
final String withoutPackage = ann.getClassNode().getNameWithoutPackage();
if (withoutPackage.equals("Trait")) {
process = true;
}
if (withoutPackage.equals("Typed")) {
typed = true;
ann.getClassNode().setRedirect(TypeUtil.TYPED);
}
}
if (forceTyped && !typed) {
typed = true;
classNode.addAnnotation(pkgTypedAnn != null ? pkgTypedAnn : new AnnotationNode(TypeUtil.TYPED));
}
if (process) {
toProcess.add(classNode);
if (!typed) {
classNode.addAnnotation(pkgTypedAnn != null ? pkgTypedAnn : new AnnotationNode(TypeUtil.TYPED));
}
}
}
for (ClassNode classNode : toProcess) {
if (classNode.isInterface() || classNode.isEnum() || classNode.isAnnotationDefinition()) {
source.addError(new SyntaxException("@Trait can be applied only to <class>", classNode.getLineNumber(), classNode.getColumnNumber()));
continue;
}
if (classNode.getDeclaredConstructors().size() > 0) {
ConstructorNode constructor = classNode.getDeclaredConstructors().get(0);
source.addError(new SyntaxException("Constructors are not allowed in traits", constructor.getLineNumber(), constructor.getColumnNumber()));
continue;
}
if (classNode.getObjectInitializerStatements().size() > 0) {
Statement initializer = classNode.getObjectInitializerStatements().get(0);
source.addError(new SyntaxException("Object initializers are not allowed in traits", initializer.getLineNumber(), initializer.getColumnNumber()));
continue;
}
int mod = classNode.getModifiers();
mod &= ~Opcodes.ACC_FINAL;
mod |= Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE;
classNode.setModifiers(mod);
String name = classNode.getNameWithoutPackage() + "$TraitImpl";
String fullName = ASTHelper.dot(classNode.getPackageName(), name);
InnerClassNode innerClassNode = new InnerClassNode(classNode, fullName, ACC_PUBLIC|ACC_STATIC|ACC_ABSTRACT, ClassHelper.OBJECT_TYPE, new ClassNode[]{classNode}, null);
AnnotationNode typedAnn = new AnnotationNode(TypeUtil.TYPED);
final Expression member = getTypedAnnotation(classNode).getMember("debug");
if (member != null && member instanceof ConstantExpression && ((ConstantExpression)member).getValue().equals(Boolean.TRUE))
typedAnn.addMember("debug", ConstantExpression.TRUE);
innerClassNode.addAnnotation(typedAnn);
innerClassNode.setGenericsTypes(classNode.getGenericsTypes());
ClassNode superClass = classNode.getSuperClass();
if (!ClassHelper.OBJECT_TYPE.equals(superClass)) {
ClassNode[] ifaces = classNode.getInterfaces();
ClassNode[] newIfaces = new ClassNode[ifaces.length + 1];
newIfaces[0] = superClass;
System.arraycopy(ifaces, 0, newIfaces, 1, ifaces.length);
classNode.setSuperClass(ClassHelper.OBJECT_TYPE);
classNode.setInterfaces(newIfaces);
}
classNode.getModule().addClass(innerClassNode);
innerClassNode.addMethod("getMetaClass", ACC_PUBLIC|ACC_ABSTRACT, ClassHelper.METACLASS_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null);
innerClassNode.addMethod("setMetaClass", ACC_PUBLIC|ACC_ABSTRACT, ClassHelper.VOID_TYPE, new Parameter[]{new Parameter(ClassHelper.METACLASS_TYPE, "value")}, ClassNode.EMPTY_ARRAY, null);
innerClassNode.addMethod("getProperty", ACC_PUBLIC|ACC_ABSTRACT, ClassHelper.OBJECT_TYPE, new Parameter[]{new Parameter(ClassHelper.STRING_TYPE, "name")}, ClassNode.EMPTY_ARRAY, null);
innerClassNode.addMethod("setProperty", ACC_PUBLIC|ACC_ABSTRACT, ClassHelper.VOID_TYPE, new Parameter[]{new Parameter(ClassHelper.STRING_TYPE, "name"), new Parameter(ClassHelper.OBJECT_TYPE, "value")}, ClassNode.EMPTY_ARRAY, null);
innerClassNode.addMethod("invokeMethod", ACC_PUBLIC|ACC_ABSTRACT, ClassHelper.OBJECT_TYPE, new Parameter[]{new Parameter(ClassHelper.STRING_TYPE, "name"), new Parameter(ClassHelper.OBJECT_TYPE, "args")}, ClassNode.EMPTY_ARRAY, null);
for (FieldNode fieldNode : classNode.getFields()) {
// if (fieldNode.isStatic())
// continue;
final String getterName = "get" + Verifier.capitalize(fieldNode.getName());
MethodNode getter = classNode.getGetterMethod(getterName);
if (getter == null) {
getter = classNode.addMethod(getterName, ACC_PUBLIC | ACC_ABSTRACT, fieldNode.getType(), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null);
addFieldAnnotation(innerClassNode, fieldNode, getter);
}
// We need the second getter (and setter) to compile non-synthetic first one referring to the field.
// The references inside the first one will be retargeted to the second one.
final MethodNode realGetter = classNode.addMethod("get$" + fieldNode.getName(), ACC_PUBLIC |
ACC_ABSTRACT, fieldNode.getType(), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null);
addFieldAnnotation(innerClassNode, fieldNode, realGetter);
final String setterName = "set" + Verifier.capitalize(fieldNode.getName());
Parameter valueParam = new Parameter(fieldNode.getType(), "$value");
MethodNode setter = classNode.getSetterMethod(setterName);
if (setter == null) {
setter = classNode.addMethod(setterName, ACC_PUBLIC | ACC_ABSTRACT, ClassHelper.VOID_TYPE, new Parameter[]{valueParam}, ClassNode.EMPTY_ARRAY, null);
addFieldAnnotation(innerClassNode, fieldNode, setter);
}
final MethodNode realSetter = classNode.addMethod("set$" + fieldNode.getName(), ACC_PUBLIC |
ACC_ABSTRACT, ClassHelper.VOID_TYPE, new Parameter[]{valueParam}, ClassNode.EMPTY_ARRAY, null);
addFieldAnnotation(innerClassNode, fieldNode, realSetter);
if (fieldNode.hasInitialExpression()) {
final Expression initial = fieldNode.getInitialValueExpression();
fieldNode.setInitialValueExpression(null);
final MethodNode initMethod = innerClassNode.addMethod("__init_" + fieldNode.getName(), ACC_PUBLIC | ACC_STATIC, ClassHelper.VOID_TYPE, new Parameter[]{new Parameter(classNode, "$self")}, ClassNode.EMPTY_ARRAY, new BlockStatement());
final PropertyExpression prop = new PropertyExpression(new VariableExpression("$self"), fieldNode.getName());
prop.setSourcePosition(fieldNode);
final CastExpression cast = new CastExpression(fieldNode.getType(), initial);
cast.setSourcePosition(initial);
final BinaryExpression assign = new BinaryExpression(prop, Token.newSymbol(Types.ASSIGN, -1, -1), cast);
assign.setSourcePosition(initial);
final ExpressionStatement assignExpr = new ExpressionStatement(assign);
assignExpr.setSourcePosition(initial);
((BlockStatement)initMethod.getCode()).addStatement(assignExpr);
}
innerClassNode.addField(fieldNode);
}
classNode.getFields().clear();
classNode.getProperties().clear();
for (MethodNode methodNode : classNode.getMethods()) {
if (methodNode.getCode() == null)
continue;
if (methodNode.isStatic()) {
source.addError(new SyntaxException("Static methods are not allowed in traits", methodNode.getLineNumber(), methodNode.getColumnNumber()));
}
if (!methodNode.isPublic()) {
source.addError(new SyntaxException("Non-public methods are not allowed in traits", methodNode.getLineNumber(), methodNode.getColumnNumber()));
}
mod = methodNode.getModifiers();
mod &= ~(Opcodes.ACC_FINAL | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
mod |= Opcodes.ACC_ABSTRACT | Opcodes.ACC_PUBLIC;
methodNode.setModifiers(mod);
Parameter[] parameters = methodNode.getParameters();
Parameter[] newParameters = new Parameter[parameters.length + 1];
final Parameter self = new Parameter(classNode, "$self");
newParameters[0] = self;
System.arraycopy(parameters, 0, newParameters, 1, parameters.length);
MethodNode newMethod = innerClassNode.addMethod(methodNode.getName(), ACC_PUBLIC, methodNode.getReturnType(), newParameters, ClassNode.EMPTY_ARRAY, methodNode.getCode());
ArrayList<GenericsType> gt = new ArrayList<GenericsType>();
if (classNode.getGenericsTypes() != null)
for (int i = 0; i < classNode.getGenericsTypes().length; i++) {
GenericsType genericsType = classNode.getGenericsTypes()[i];
gt.add(genericsType);
}
if (methodNode.getGenericsTypes() != null)
for (int i = 0; i < methodNode.getGenericsTypes().length; i++) {
GenericsType genericsType = methodNode.getGenericsTypes()[i];
gt.add(genericsType);
}
if (!gt.isEmpty())
newMethod.setGenericsTypes(gt.toArray(new GenericsType[gt.size()]));
AnnotationNode annotationNode = new AnnotationNode(TypeUtil.HAS_DEFAULT_IMPLEMENTATION);
annotationNode.addMember("value", new ClassExpression(innerClassNode));
methodNode.addAnnotation(annotationNode);
methodNode.setCode(null);
}
}
}
private AnnotationNode getTypedAnnotation(AnnotatedNode node) {
AnnotationNode pkgTyped = null;
if(node != null) {
for (AnnotationNode ann : node.getAnnotations()) {
final String withoutPackage = ann.getClassNode().getNameWithoutPackage();
if (withoutPackage.equals("Typed")) {
pkgTyped = ann;
}
}
}
return pkgTyped;
}
private void addFieldAnnotation(InnerClassNode innerClassNode, FieldNode fieldNode, MethodNode getter) {
AnnotationNode value = new AnnotationNode(TypeUtil.HAS_DEFAULT_IMPLEMENTATION);
value.addMember("value", new ClassExpression(innerClassNode));
value.addMember("fieldName", new ConstantExpression(fieldNode.getName()));
getter.addAnnotation(value);
}
}