org.codehaus.groovy.transform.sc.transformers.ConstructorCallTransformer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of groovy Show documentation
Show all versions of groovy Show documentation
Groovy: A powerful multi-faceted language for the JVM
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.transform.sc.transformers;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.GroovyCodeVisitor;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ExpressionTransformer;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.MapExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.classgen.AsmClassGenerator;
import org.codehaus.groovy.classgen.BytecodeExpression;
import org.codehaus.groovy.classgen.asm.BytecodeHelper;
import org.codehaus.groovy.classgen.asm.CompileStack;
import org.codehaus.groovy.classgen.asm.OperandStack;
import org.codehaus.groovy.classgen.asm.WriterController;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor;
import org.objectweb.asm.MethodVisitor;
import java.util.List;
import static org.codehaus.groovy.ast.tools.GeneralUtils.binX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.bytecodeX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
import static org.codehaus.groovy.transform.stc.StaticTypesMarker.DIRECT_METHOD_CALL_TARGET;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ASTORE;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.NEW;
public class ConstructorCallTransformer {
private final StaticCompilationTransformer staticCompilationTransformer;
public ConstructorCallTransformer(final StaticCompilationTransformer staticCompilationTransformer) {
this.staticCompilationTransformer = staticCompilationTransformer;
}
Expression transformConstructorCall(final ConstructorCallExpression expr) {
ConstructorNode node = expr.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
if (node == null) return expr;
Parameter[] params = node.getParameters();
if ((params.length == 1 || params.length == 2) // 2 is for inner class case
&& StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(params[params.length - 1].getType(), ClassHelper.MAP_TYPE)
&& node.getCode() == StaticTypeCheckingVisitor.GENERATED_EMPTY_STATEMENT) {
Expression arguments = expr.getArguments();
if (arguments instanceof TupleExpression) {
TupleExpression tupleExpression = (TupleExpression) arguments;
List expressions = tupleExpression.getExpressions();
if (expressions.size() == 1 || expressions.size() == 2) { // 2 = inner class case
Expression expression = expressions.get(expressions.size() - 1);
if (expression instanceof MapExpression) {
MapExpression map = (MapExpression) expression;
// check that the node doesn't belong to the list of declared constructors
ClassNode declaringClass = node.getDeclaringClass();
for (ConstructorNode constructorNode : declaringClass.getDeclaredConstructors()) {
if (constructorNode == node) {
return staticCompilationTransformer.superTransform(expr);
}
}
// replace call to (Map) or (this, Map)
// with a call to () or (this) + appropriate setters
// for example, foo(x:1, y:2) is replaced with:
// { def tmp = new Foo(); tmp.x = 1; tmp.y = 2; return tmp }()
MapStyleConstructorCall result = new MapStyleConstructorCall(
staticCompilationTransformer,
declaringClass,
map,
expr
);
return result;
}
}
}
}
return staticCompilationTransformer.superTransform(expr);
}
private static class MapStyleConstructorCall extends BytecodeExpression {
private final StaticCompilationTransformer staticCompilationTransformer;
private AsmClassGenerator acg;
private final ClassNode declaringClass;
private final MapExpression map;
private final ConstructorCallExpression originalCall;
private final boolean innerClassCall;
public MapStyleConstructorCall(
final StaticCompilationTransformer transformer,
final ClassNode declaringClass,
final MapExpression map,
final ConstructorCallExpression originalCall) {
super(declaringClass);
this.staticCompilationTransformer = transformer;
this.declaringClass = declaringClass;
this.map = map;
this.originalCall = originalCall;
this.setSourcePosition(originalCall);
this.copyNodeMetaData(originalCall);
List originalExpressions = originalCall.getArguments() instanceof TupleExpression
? ((TupleExpression) originalCall.getArguments()).getExpressions()
: null;
this.innerClassCall = originalExpressions != null && originalExpressions.size() == 2;
}
@Override
public Expression transformExpression(final ExpressionTransformer transformer) {
Expression result = new MapStyleConstructorCall(
staticCompilationTransformer, declaringClass,
(MapExpression) map.transformExpression(transformer),
(ConstructorCallExpression) originalCall.transformExpression(transformer)
);
result.copyNodeMetaData(this);
result.setSourcePosition(this);
return result;
}
@Override
public void visit(final GroovyCodeVisitor visitor) {
if (visitor instanceof AsmClassGenerator) {
acg = (AsmClassGenerator) visitor;
} else {
originalCall.visit(visitor);
}
super.visit(visitor);
}
@Override
public void visit(final MethodVisitor mv) {
WriterController controller = acg.getController();
CompileStack compileStack = controller.getCompileStack();
OperandStack operandStack = controller.getOperandStack();
// create a temporary variable to store the constructed object
int tmpObj = compileStack.defineTemporaryVariable("tmpObj", declaringClass, false);
String classInternalName = BytecodeHelper.getClassInternalName(declaringClass);
mv.visitTypeInsn(NEW, classInternalName);
mv.visitInsn(DUP);
String desc = "()V";
if (innerClassCall && declaringClass.isRedirectNode() && declaringClass.redirect() instanceof InnerClassNode) {
// load "this"
mv.visitVarInsn(ALOAD, 0);
InnerClassNode icn = (InnerClassNode) declaringClass.redirect();
Parameter[] params = {new Parameter(icn.getOuterClass(), "$p$")};
desc = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, params);
}
mv.visitMethodInsn(INVOKESPECIAL, classInternalName, "", desc, false);
mv.visitVarInsn(ASTORE, tmpObj); // store it into tmp variable
// load every field
for (MapEntryExpression entryExpression : map.getMapEntryExpressions()) {
Expression keyExpression = staticCompilationTransformer.transform(entryExpression.getKeyExpression());
Expression valueExpression = staticCompilationTransformer.transform(entryExpression.getValueExpression());
BinaryExpression bexp = binX(
propX(
bytecodeX(declaringClass, v -> v.visitVarInsn(ALOAD, tmpObj)),
keyExpression
),
Token.newSymbol("=", entryExpression.getLineNumber(), entryExpression.getColumnNumber()),
valueExpression
);
bexp.setSourcePosition(entryExpression);
bexp.visit(acg);
operandStack.pop(); // consume argument
}
// load object
mv.visitVarInsn(ALOAD, tmpObj);
// cleanup stack
compileStack.removeVar(tmpObj);
}
}
}