/*
* 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.ExpressionStatement;
import static org.codehaus.groovy.ast.ClassHelper.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.classgen.BytecodeHelper;
import org.codehaus.groovy.classgen.BytecodeInstruction;
import org.codehaus.groovy.classgen.BytecodeSequence;
import org.codehaus.groovy.classgen.Verifier;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.GroovyASTTransformation;
import org.codehaus.groovy.syntax.SyntaxException;
import org.mbte.groovypp.compiler.bytecode.BytecodeExpr;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION)
public class TraitASTTransformFinal implements ASTTransformation, Opcodes {
public void visit(ASTNode[] nodes, final SourceUnit source) {
ModuleNode module = (ModuleNode) nodes[0];
for (ClassNode classNode : module.getClasses()) {
if (classNode instanceof InnerClassNode && classNode.getName().endsWith("$TraitImpl")) {
makeImplementationMethodsStatic(classNode, source);
}
}
for (ClassNode classNode : module.getClasses()) {
if (classNode instanceof InnerClassNode && classNode.getName().endsWith("$TraitImpl")) {
continue;
}
VolatileFieldUpdaterTransform.addUpdaterForVolatileFields(classNode);
try {
new OpenVerifier().visitClass(classNode);
}
catch (MultipleCompilationErrorsException err) {
throw err;
}
catch (Throwable t) {
int line = classNode.getLineNumber();
int col = classNode.getColumnNumber();
t.printStackTrace();
source.getErrorCollector().addError(new SyntaxErrorMessage(new SyntaxException(t.getMessage() + '\n', line, col), source), true);
}
improveAbstractMethods(classNode);
}
}
private void makeImplementationMethodsStatic(final ClassNode classNode, final SourceUnit source) {
for (final MethodNode methodNode : classNode.getMethods()) {
if (methodNode.isStatic() || methodNode.isSynthetic() || methodNode.isAbstract())
continue;
final Parameter[] parameters = methodNode.getParameters();
methodNode.setModifiers(methodNode.getModifiers() | Opcodes.ACC_STATIC);
methodNode.getVariableScope().setInStaticContext(true);
final VariableExpression self = new VariableExpression(parameters[0]);
final String propNameCapitalized = getPropertyNameCapitalized(methodNode);
ClassCodeExpressionTransformer thisToSelf = new ClassCodeExpressionTransformer() {
protected SourceUnit getSourceUnit() {
return source;
}
public Expression transform(Expression exp) {
if (exp instanceof VariableExpression && isExplicitThis((VariableExpression) exp))
return self;
else if (exp instanceof PropertyExpression) {
final PropertyExpression propExp = (PropertyExpression) exp;
if (propExp.isImplicitThis() || (propExp.getObjectExpression() instanceof VariableExpression &&
isExplicitThis((VariableExpression) propExp.getObjectExpression()))) {
final String name = propExp.getPropertyAsString();
final FieldNode field = classNode.getField(name);
if (field != null && Verifier.capitalize(field.getName()).equals(propNameCapitalized))
return new PropertyExpression(self, "$" + name);
}
}
return super.transform(exp);
}
private boolean isExplicitThis(VariableExpression exp) {
return "this".equals(exp.getName());
}
};
thisToSelf.visitMethod(methodNode);
}
}
public static void improveAbstractMethods(final ClassNode classNode) {
if ((classNode.getModifiers() & ACC_ABSTRACT) != 0)
return;
List<MethodNode> abstractMethods = getAbstractMethods(classNode);
boolean addInit = false;
if (abstractMethods != null) {
for (final MethodNode method : abstractMethods) {
List<AnnotationNode> list = method.getAnnotations(TypeUtil.HAS_DEFAULT_IMPLEMENTATION);
if (list != null && !list.isEmpty()) {
Expression klazz = list.get(0).getMember("value");
Expression field = list.get(0).getMember("fieldName");
if (field == null || (field instanceof ConstantExpression) && (((ConstantExpression)field).getValue() == null || "".equals((((ConstantExpression)field).getValue())))) {
final Parameter[] oldParams = method.getParameters();
final Parameter[] params = new Parameter[oldParams.length + 1];
params[0] = new Parameter(method.getDeclaringClass(), "$self");
System.arraycopy(oldParams, 0, params, 1, oldParams.length);
final MethodNode found = klazz.getType().getMethod(method.getName(), params);
if (found != null) {
addImplMethod(classNode, method, oldParams, found);
}
} else {
if ((classNode.getModifiers() & Opcodes.ACC_ABSTRACT) != 0)
continue;
final ClassNode fieldType;
final Parameter[] parameters;
final boolean getter;
if (method.getName().startsWith("get")) {
fieldType = TypeUtil.mapTypeFromSuper(method.getReturnType(), method.getDeclaringClass(), classNode);
parameters = Parameter.EMPTY_ARRAY;
getter = true;
} else {
fieldType = TypeUtil.mapTypeFromSuper(method.getParameters()[0].getType(), method.getDeclaringClass(), classNode);
parameters = new Parameter[] {new Parameter(fieldType, method.getParameters()[0].getName())};
getter = false;
}
final String fieldName = (String) ((ConstantExpression) field).getValue();
FieldNode klazzField = classNode.getField(fieldName);
if (klazzField == null) {
klazzField = classNode.addField(fieldName, ACC_PRIVATE, fieldType, null);
final MethodNode initMethod = klazz.getType().getMethod("__init_" + fieldName, new Parameter[]{new Parameter(method.getDeclaringClass(), "$self")});
if (initMethod != null) {
classNode.getObjectInitializerStatements().add(new ExpressionStatement(new StaticMethodCallExpression(klazz.getType(), initMethod.getName(), new ArgumentListExpression(VariableExpression.THIS_EXPRESSION))));
klazzField.addAnnotation(new AnnotationNode(TypeUtil.NO_EXTERNAL_INITIALIZATION));
addInit = true;
}
}
createGetterSetter(classNode, method, fieldType, parameters, getter, fieldName);
}
}
else {
// very special case of clone ()
if (method.getName().equals("clone") && method.getParameters().length == 0) {
classNode.addMethod(method.getName(), ACC_PUBLIC, method.getReturnType(), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY,
new BytecodeSequence(new BytecodeInstruction() {
public void visit(MethodVisitor mv) {
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(classNode.getSuperClass().getName()), "clone", "()Ljava/lang/Object;");
if (!ClassHelper.OBJECT_TYPE.equals(method.getReturnType()))
mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(method.getReturnType()));
BytecodeExpr.doReturn(mv, ClassHelper.OBJECT_TYPE);
}
}));
}
}
}
}
if (addInit && !classNode.getDeclaredConstructors().isEmpty()) {
new OpenVerifier().addInitialization(classNode);
classNode.getObjectInitializerStatements().clear();
}
}
private static void createGetterSetter(final ClassNode classNode, MethodNode method, final ClassNode fieldType, Parameter[] parameters, final boolean getter, final String fieldName) {
classNode.addMethod(method.getName(), ACC_PUBLIC, getter ? fieldType : ClassHelper.VOID_TYPE, parameters, ClassNode.EMPTY_ARRAY,
new BytecodeSequence(new BytecodeInstruction() {
public void visit(MethodVisitor mv) {
if (getter) {
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, BytecodeHelper.getClassInternalName(classNode), fieldName, BytecodeHelper.getTypeDescription(fieldType));
BytecodeExpr.doReturn(mv, fieldType);
} else {
mv.visitVarInsn(ALOAD, 0);
if (fieldType == double_TYPE) {
mv.visitVarInsn(Opcodes.DLOAD, 1);
} else if (fieldType == float_TYPE) {
mv.visitVarInsn(Opcodes.FLOAD, 1);
} else if (fieldType == long_TYPE) {
mv.visitVarInsn(Opcodes.LLOAD, 1);
} else if (
fieldType == boolean_TYPE
|| fieldType == char_TYPE
|| fieldType == byte_TYPE
|| fieldType == int_TYPE
|| fieldType == short_TYPE) {
mv.visitVarInsn(Opcodes.ILOAD, 1);
} else {
mv.visitVarInsn(Opcodes.ALOAD, 1);
}
mv.visitFieldInsn(PUTFIELD, BytecodeHelper.getClassInternalName(classNode), fieldName, BytecodeHelper.getTypeDescription(fieldType));
mv.visitInsn(RETURN);
}
}
}));
}
private static void addImplMethod(ClassNode classNode, MethodNode method, final Parameter[] oldParams, final MethodNode found) {
final ClassNode returnType = TypeUtil.mapTypeFromSuper(method.getReturnType(), method.getDeclaringClass(), classNode);
Parameter[] newParams = new Parameter[oldParams.length];
for (int i = 0; i < oldParams.length; i++) {
ClassNode t = TypeUtil.mapTypeFromSuper(oldParams[i].getType(), method.getDeclaringClass(), classNode);
newParams[i] = new Parameter(t, oldParams[i].getName());
}
ClassNode[] oldExns = method.getExceptions();
ClassNode[] newExns = new ClassNode[oldExns.length];
for (int i = 0; i < oldExns.length; i++) {
newExns[i] = TypeUtil.mapTypeFromSuper(oldExns[i], method.getDeclaringClass(), classNode);
}
final MethodNode added = classNode.addMethod(method.getName(), Opcodes.ACC_PUBLIC, returnType, newParams, newExns, new BytecodeSequence(new BytecodeInstruction() {
public void visit(MethodVisitor mv) {
mv.visitVarInsn(ALOAD, 0);
int cur = 1;
for (int i = 0; i != oldParams.length; ++i) {
if (!ClassHelper.isPrimitiveType(oldParams[i].getType())) {
mv.visitVarInsn(ALOAD, cur);
cur++;
} else {
if (oldParams[i].getType().equals(ClassHelper.long_TYPE)) {
mv.visitVarInsn(LLOAD, cur);
cur += 2;
} else {
if (oldParams[i].getType().equals(ClassHelper.double_TYPE)) {
mv.visitVarInsn(DLOAD, cur);
cur += 2;
} else {
if (oldParams[i].getType().equals(ClassHelper.float_TYPE)) {
mv.visitVarInsn(FLOAD, cur);
cur++;
} else {
mv.visitVarInsn(ILOAD, cur);
cur++;
}
}
}
}
}
mv.visitMethodInsn(INVOKESTATIC, BytecodeHelper.getClassInternalName(found.getDeclaringClass()), found.getName(), BytecodeHelper.getMethodDescriptor(found.getReturnType(), found.getParameters()));
if (!TypeUtil.isDirectlyAssignableFrom(returnType, found.getReturnType())) {
BytecodeExpr.box(found.getReturnType(), mv);
BytecodeExpr.cast(TypeUtil.wrapSafely(found.getReturnType()),
TypeUtil.wrapSafely(returnType), mv);
BytecodeExpr.unbox(returnType, mv);
}
BytecodeExpr.doReturn(mv, returnType);
}
}));
added.setGenericsTypes(method.getGenericsTypes());
}
private static Map<String, MethodNode> getDeclaredMethodsMap(ClassNode klazz) {
// Start off with the methods from the superclass.
ClassNode parent = klazz.getSuperClass();
Map<String, MethodNode> result;
if (parent != null) {
result = getDeclaredMethodsMap(parent);
} else {
result = new HashMap<String, MethodNode>();
}
// add in unimplemented abstract methods from the interfaces
for (ClassNode iface : klazz.getInterfaces()) {
Map<String, MethodNode> ifaceMethodsMap = getDeclaredMethodsMap(iface);
for (Map.Entry<String, MethodNode> methSig : ifaceMethodsMap.entrySet()) {
MethodNode methNode = result.get(methSig.getKey());
if (methNode == null || !methNode.isPublic() || methNode.isAbstract() && (!hasDefaultImpl(methNode) || hasDefaultImpl(methSig.getValue()) && methSig.getValue().getDeclaringClass().implementsInterface(methNode.getDeclaringClass()))) {
result.put(methSig.getKey(), methSig.getValue());
}
}
}
// And add in the methods implemented in this class.
for (MethodNode method : klazz.getMethods()) {
if (method.isSynthetic())
continue;
String sig = method.getTypeDescriptor();
if (!method.isAbstract())
result.put(sig, method);
else {
MethodNode methNode = result.get(sig);
if (methNode == null || methNode.isAbstract() && (!hasDefaultImpl(methNode) || hasDefaultImpl(method) && method.getDeclaringClass().implementsInterface(methNode.getDeclaringClass()))) {
result.put(sig, method);
}
}
}
return result;
}
private static List<MethodNode> getAbstractMethods(ClassNode klazz) {
List<MethodNode> result = new ArrayList<MethodNode>(3);
for (MethodNode method : getDeclaredMethodsMap(klazz).values()) {
if (method.isAbstract()) {
result.add(method);
}
}
if (result.isEmpty()) {
return null;
} else {
return result;
}
}
private static boolean hasDefaultImpl(MethodNode method) {
List<AnnotationNode> list = method.getAnnotations(TypeUtil.HAS_DEFAULT_IMPLEMENTATION);
return (list != null && !list.isEmpty());
}
// Includes synthetic '$self' parameter!
private static String getPropertyNameCapitalized(MethodNode accessor) {
final String accessorName = accessor.getName();
if (accessorName.startsWith("get") && accessorName.length() > 3 && accessor.getParameters().length == 1) return accessorName.substring(3);
if (accessorName.startsWith("is") && accessorName.length() > 2 && accessor.getParameters().length == 1) return accessorName.substring(2);
if (accessorName.startsWith("set") && accessorName.length() > 3 && accessor.getParameters().length == 2) return accessorName.substring(3);
return null;
}
}