/*
* 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.bytecode;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.classgen.BytecodeHelper;
import org.mbte.groovypp.compiler.*;
import org.mbte.groovypp.compiler.transformers.VariableExpressionTransformer;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.util.ArrayList;
import java.util.List;
public class ResolvedMethodBytecodeExpr extends BytecodeExpr {
protected final MethodNode methodNode;
private final BytecodeExpr object;
private final String methodName;
private final TupleExpression bargs;
private ResolvedMethodBytecodeExpr(ASTNode parent, MethodNode methodNode, BytecodeExpr object, TupleExpression bargs, CompilerTransformer compiler) {
super(parent, getReturnType(methodNode, object, bargs, compiler));
this.methodNode = methodNode;
this.object = object;
this.methodName = methodNode.getName();
this.bargs = bargs;
tryImproveClosureType(methodNode, bargs);
Parameter[] parameters = methodNode.getParameters();
boolean isVarArg = parameters.length > 0 && parameters[parameters.length - 1].getType().isArray();
if (isVarArg) {
if (bargs.getExpressions().size() == parameters.length - 1) {
// no var args
BytecodeExpr last = (BytecodeExpr) compiler.transform(new ArrayExpression(parameters[parameters.length - 1].getType().getComponentType(), new ArrayList<Expression>()));
bargs.getExpressions().add(last);
} else {
BytecodeExpr be = (BytecodeExpr) bargs.getExpressions().get(parameters.length - 1);
if (!be.getType().isArray() && !be.getType().equals(parameters[parameters.length - 1].getType())) {
int add = bargs.getExpressions().size() - (parameters.length - 1);
ArrayList<Expression> list = new ArrayList<Expression>();
if (add != 1 || !bargs.getExpressions().get(parameters.length - 1).getType().equals(TypeUtil.NULL_TYPE)) {
for (int i = 0; i != add; ++i) {
final BytecodeExpr arg = compiler.cast(
bargs.getExpressions().get(parameters.length - 1 + i),
parameters[parameters.length - 1].getType().getComponentType());
list.add(arg);
}
while (bargs.getExpressions().size() > parameters.length - 1)
bargs.getExpressions().remove(parameters.length - 1);
BytecodeExpr last = (BytecodeExpr) compiler.transform(new ArrayExpression(parameters[parameters.length - 1].getType().getComponentType(), list));
bargs.getExpressions().add(last);
}
}
}
}
while (bargs.getExpressions().size() < parameters.length)
bargs.getExpressions().add(compiler.cast(ConstantExpression.NULL, parameters[bargs.getExpressions().size()].getType()));
for (int i = 0; i != parameters.length; ++i) {
final BytecodeExpr arg = (BytecodeExpr) bargs.getExpressions().get(i);
ClassNode ptype = parameters[i].getType();
if (!ptype.equals(arg.getType()))
bargs.getExpressions().set(i, compiler.cast(arg, ptype));
}
}
public static ClassNode getReturnType(MethodNode methodNode, BytecodeExpr object, TupleExpression bargs, CompilerTransformer compiler) {
final BytecodeExpr objectCopy = object;
ClassNode returnType = methodNode.getReturnType();
boolean removeFirstArgAtTheEnd = false;
if (methodNode instanceof ClassNodeCache.DGM) {
ClassNodeCache.DGM dgm = (ClassNodeCache.DGM) methodNode;
methodNode = dgm.original;
bargs.getExpressions().add(0, object);
object = null;
removeFirstArgAtTheEnd = true;
}
if (TypeUtil.hasGenericsTypes(methodNode)) {
Parameter[] params = methodNode.getParameters();
List<Expression> exprs = bargs.getExpressions();
int length;
if (exprs.size() > params.length && params[params.length - 1].getType().isArray()) {
length = exprs.size();
} else {
length = Math.min(params.length, exprs.size());
}
if (!methodNode.isStatic())
length++;
ClassNode[] paramTypes = new ClassNode[length];
ClassNode[] argTypes = new ClassNode[length];
int delta = 0;
if (!methodNode.isStatic()) {
paramTypes [length-1] = methodNode.getDeclaringClass();
argTypes [length-1] = object != null ? object.getType() : null;
delta = 1;
}
for (int i = 0; i < length-delta; i++) {
paramTypes[i] = i > params.length - 1 ||
(i == params.length - 1 && params[i].getType().isArray() && !bargs.getExpression(i).getType().isArray()) ?
/* varargs case */ params[params.length - 1].getType().getComponentType() :
params[i].getType();
argTypes[i] = bargs.getExpression(i).getType();
}
ClassNode[] bindings = TypeUnification.inferTypeArguments(methodNode.getGenericsTypes(), paramTypes, argTypes);
returnType = TypeUtil.getSubstitutedType(returnType, methodNode, bindings);
for (int i = 0; i < length-delta; i++) {
ClassNode paramType = TypeUtil.getSubstitutedType(paramTypes[i], methodNode, bindings);
if (objectCopy != null) {
paramType = TypeUtil.getSubstitutedType(paramType, methodNode.getDeclaringClass(),
objectCopy.getType());
}
bargs.getExpressions().set(i, compiler.cast(bargs.getExpressions().get(i), paramType));
}
}
if (removeFirstArgAtTheEnd) {
bargs.getExpressions().remove(0);
}
return objectCopy != null ? TypeUtil.getSubstitutedType(returnType, methodNode.getDeclaringClass(), objectCopy.getType()) : returnType;
}
private void tryImproveClosureType(MethodNode methodNode, TupleExpression bargs) {
final Parameter[] parameters = methodNode.getParameters();
if (parameters.length > 0) {
final Parameter lastParam = parameters[parameters.length - 1];
if (lastParam.getType().getName().equals(TypeUtil.TCLOSURE.getName())) {
if (lastParam.getType().isUsingGenerics()) {
final GenericsType[] genericsTypes = lastParam.getType().getGenericsTypes();
if (genericsTypes != null) {
Object param = bargs.getExpressions().get(parameters.length - 1);
if (param instanceof CompiledClosureBytecodeExpr) {
CompiledClosureBytecodeExpr ce = (CompiledClosureBytecodeExpr) param;
ce.getType().getInterfaces()[0].setGenericsTypes(new GenericsType[]{new GenericsType(genericsTypes[0].getType())});
}
}
}
}
}
}
public void compile(MethodVisitor mv) {
int op = INVOKEVIRTUAL;
final String classInternalName;
final String methodDescriptor;
if (methodNode instanceof ClassNodeCache.DGM) {
MethodNode dgm = ((ClassNodeCache.DGM) methodNode).original;
op = INVOKESTATIC;
if (object != null) {
object.visit(mv);
if (ClassHelper.isPrimitiveType(object.getType()) && !ClassHelper.isPrimitiveType(dgm.getParameters()[0].getType()))
box(object.getType(), mv);
} else if (methodNode.isStatic()) {
// DGSM method needs fake argument.
mv.visitInsn(ACONST_NULL);
}
classInternalName = ((ClassNodeCache.DGM) methodNode).callClassInternalName;
methodDescriptor = ((ClassNodeCache.DGM) methodNode).descr;
} else {
boolean optimizedSpecial = false;
if (methodNode.isStatic())
op = INVOKESTATIC;
else if (methodNode.getDeclaringClass().isInterface())
op = INVOKEINTERFACE;
else if ((methodNode.getModifiers() & (ACC_PRIVATE)) != 0) {
op = INVOKESPECIAL;
optimizedSpecial = true;
}
if (object != null) {
if (object instanceof VariableExpressionTransformer.Super || object instanceof VariableExpressionTransformer.ThisSpecial) {
op = INVOKESPECIAL;
}
object.visit(mv);
box(object.getType(), mv);
}
if (op == INVOKESTATIC && object != null) {
if (ClassHelper.long_TYPE == object.getType() || ClassHelper.double_TYPE == object.getType())
mv.visitInsn(POP2);
else
mv.visitInsn(POP);
}
ClassNode t = op == INVOKESPECIAL && !optimizedSpecial ? object.getType() : methodNode.getDeclaringClass();
classInternalName = (t.isArray()) ? BytecodeHelper.getTypeDescription(t) : BytecodeHelper.getClassInternalName(t);
methodDescriptor = BytecodeHelper.getMethodDescriptor(methodNode.getReturnType(), methodNode.getParameters());
}
loadParams(mv, methodNode.isStatic());
mv.visitMethodInsn(op, classInternalName, methodName, methodDescriptor);
if (!methodNode.getReturnType().equals(ClassHelper.VOID_TYPE))
cast(TypeUtil.wrapSafely(methodNode.getReturnType()), TypeUtil.wrapSafely(getType()), mv);
}
protected void loadParams(MethodVisitor mv, boolean isStstic) {
Parameter[] parameters = methodNode.getParameters();
for (int i = 0; i != parameters.length; ++i) {
BytecodeExpr be = (BytecodeExpr) bargs.getExpressions().get(i);
be.visit(mv);
final ClassNode paramType = parameters[i].getType();
final ClassNode type = be.getType();
box(type, mv);
unbox(paramType, mv);
}
}
public MethodNode getMethodNode() {
return methodNode;
}
public TupleExpression getBargs() {
return bargs;
}
public BytecodeExpr getObject() {
return object;
}
public static class Setter extends ResolvedMethodBytecodeExpr {
public Setter(ASTNode parent, MethodNode methodNode, BytecodeExpr object, ArgumentListExpression bargs, CompilerTransformer compiler) {
super(parent, methodNode, object, bargs, compiler);
setType(bargs.getExpressions().get(0).getType());
}
@Override
protected void loadParams(MethodVisitor mv, boolean isStatic) {
super.loadParams(mv, isStatic);
if (isStatic && methodNode.getParameters().length == 1)
dup(getType(), mv);
else
dup_x1(getType(), mv);
}
}
public static ResolvedMethodBytecodeExpr create(ASTNode parent, MethodNode methodNode, BytecodeExpr object, TupleExpression bargs, CompilerTransformer compiler) {
if ((methodNode.getModifiers() & Opcodes.ACC_PRIVATE) != 0 && methodNode.getDeclaringClass() != compiler.classNode) {
MethodNode delegate = compiler.context.getMethodDelegate(methodNode);
return new ResolvedMethodBytecodeExpr(parent, delegate, object, bargs, compiler);
} else if (methodNode instanceof ClassNodeCache.DGM && object instanceof VariableExpressionTransformer.Super) {
compiler.addError("Cannot reference default groovy method '" + methodNode.getName() + "' using 'super'. Call the static method instead.", parent);
}
return new ResolvedMethodBytecodeExpr(parent, methodNode, object, bargs, compiler);
}
}