blob: b22df095bc198d08b16baa8f9b544dd33e713e35 [file] [log] [blame]
/*
* 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.apache.groovy.parser.antlr4;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.BooleanExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.CatchStatement;
import org.codehaus.groovy.ast.stmt.EmptyStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.IfStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.ast.stmt.ThrowStatement;
import org.codehaus.groovy.ast.stmt.TryCatchStatement;
import org.codehaus.groovy.syntax.Types;
import org.objectweb.asm.Opcodes;
import java.util.Collections;
import java.util.List;
import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.nullX;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean;
import static org.codehaus.groovy.syntax.Token.newSymbol;
/**
* Transform try-with-resources to try-catch-finally
* Reference JLS "14.20.3. try-with-resources"(https://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html)
*/
public class TryWithResourcesASTTransformation {
private AstBuilder astBuilder;
public TryWithResourcesASTTransformation(AstBuilder astBuilder) {
this.astBuilder = astBuilder;
}
/**
* Reference JLS "14.20.3. try-with-resources"(https://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html)
*
* @param tryCatchStatement the try-with-resources statement to transform
* @return try-catch-finally statement, which contains no resources clause
*/
public Statement transform(TryCatchStatement tryCatchStatement) {
if (!asBoolean(tryCatchStatement.getResourceStatements())) {
return tryCatchStatement;
}
if (this.isBasicTryWithResourcesStatement(tryCatchStatement)) {
return this.transformBasicTryWithResourcesStatement(tryCatchStatement);
} else {
return this.transformExtendedTryWithResourcesStatement(tryCatchStatement);
}
}
private boolean isBasicTryWithResourcesStatement(TryCatchStatement tryCatchStatement) {
if (tryCatchStatement.getFinallyStatement() instanceof EmptyStatement &&
!asBoolean(tryCatchStatement.getCatchStatements())) {
return true;
}
return false;
}
private ExpressionStatement makeVariableDeclarationFinal(ExpressionStatement variableDeclaration) {
if (!asBoolean(variableDeclaration)) {
return variableDeclaration;
}
if (!(variableDeclaration.getExpression() instanceof DeclarationExpression)) {
throw new IllegalArgumentException("variableDeclaration is not a declaration statement");
}
DeclarationExpression declarationExpression = (DeclarationExpression) variableDeclaration.getExpression();
if (!(declarationExpression.getLeftExpression() instanceof VariableExpression)) {
throw astBuilder.createParsingFailedException("The expression statement is not a variable delcaration statement", variableDeclaration);
}
VariableExpression variableExpression = (VariableExpression) declarationExpression.getLeftExpression();
variableExpression.setModifiers(variableExpression.getModifiers() | Opcodes.ACC_FINAL);
return variableDeclaration;
}
private int primaryExcCnt = 0;
private String genPrimaryExcName() {
return "__$$primaryExc" + primaryExcCnt++;
}
/*
* try ResourceSpecification
* Block
* Catchesopt
* Finallyopt
*
* **The above AST should be transformed to the following AST**
*
* try {
* try ResourceSpecification
* Block
* }
* Catchesopt
* Finallyopt
*/
private Statement transformExtendedTryWithResourcesStatement(TryCatchStatement tryCatchStatement) {
/*
* try ResourceSpecification
* Block
*/
TryCatchStatement newTryWithResourcesStatement =
new TryCatchStatement(
tryCatchStatement.getTryStatement(),
EmptyStatement.INSTANCE);
tryCatchStatement.getResourceStatements().forEach(newTryWithResourcesStatement::addResource);
/*
* try {
* << the following try-with-resources has been transformed >>
* try ResourceSpecification
* Block
* }
* Catchesopt
* Finallyopt
*/
TryCatchStatement newTryCatchStatement =
new TryCatchStatement(
astBuilder.createBlockStatement(this.transform(newTryWithResourcesStatement)),
tryCatchStatement.getFinallyStatement());
tryCatchStatement.getCatchStatements().forEach(newTryCatchStatement::addCatch);
return newTryCatchStatement;
}
/*
* try (VariableModifiersopt R Identifier = Expression ...)
* Block
*
* **The above AST should be transformed to the following AST**
*
* {
* final VariableModifiers_minus_final R Identifier = Expression;
* Throwable #primaryExc = null;
*
* try ResourceSpecification_tail
* Block
* catch (Throwable #t) {
* #primaryExc = #t;
* throw #t;
* } finally {
* if (Identifier != null) {
* if (#primaryExc != null) {
* try {
* Identifier.close();
* } catch (Throwable #suppressedExc) {
* #primaryExc.addSuppressed(#suppressedExc);
* }
* } else {
* Identifier.close();
* }
* }
* }
* }
*
*/
private Statement transformBasicTryWithResourcesStatement(TryCatchStatement tryCatchStatement) {
// { ... }
BlockStatement blockStatement = new BlockStatement();
// final VariableModifiers_minus_final R Identifier = Expression;
ExpressionStatement firstResourceStatement =
this.makeVariableDeclarationFinal(
tryCatchStatement.getResourceStatement(0));
astBuilder.appendStatementsToBlockStatement(blockStatement, firstResourceStatement);
// Throwable #primaryExc = null;
String primaryExcName = this.genPrimaryExcName();
VariableExpression primaryExcX = localVarX(primaryExcName, ClassHelper.make(Throwable.class));
ExpressionStatement primaryExcDeclarationStatement =
new ExpressionStatement(
new DeclarationExpression(
primaryExcX,
newSymbol(Types.ASSIGN, -1, -1),
nullX()
)
);
astBuilder.appendStatementsToBlockStatement(blockStatement, primaryExcDeclarationStatement);
// The generated try-catch-finally statement
String firstResourceIdentifierName =
((DeclarationExpression) tryCatchStatement.getResourceStatement(0).getExpression()).getLeftExpression().getText();
TryCatchStatement newTryCatchStatement =
new TryCatchStatement(
tryCatchStatement.getTryStatement(),
this.createFinallyBlockForNewTryCatchStatement(primaryExcName, firstResourceIdentifierName));
List<ExpressionStatement> resourceStatements = tryCatchStatement.getResourceStatements();
// 2nd, 3rd, ..., n'th resources declared in resources
List<ExpressionStatement> tailResourceStatements = resourceStatements.subList(1, resourceStatements.size());
tailResourceStatements.stream().forEach(newTryCatchStatement::addResource);
newTryCatchStatement.addCatch(this.createCatchBlockForOuterNewTryCatchStatement(primaryExcName));
astBuilder.appendStatementsToBlockStatement(blockStatement, this.transform(newTryCatchStatement));
return blockStatement;
}
/*
* catch (Throwable #t) {
* #primaryExc = #t;
* throw #t;
* }
*
*/
private CatchStatement createCatchBlockForOuterNewTryCatchStatement(String primaryExcName) {
// { ... }
BlockStatement blockStatement = new BlockStatement();
String tExcName = this.genTExcName();
// #primaryExc = #t;
ExpressionStatement primaryExcAssignStatement =
new ExpressionStatement(
new BinaryExpression(
new VariableExpression(primaryExcName),
newSymbol(Types.ASSIGN, -1, -1),
new VariableExpression(tExcName)));
astBuilder.appendStatementsToBlockStatement(blockStatement, primaryExcAssignStatement);
// throw #t;
ThrowStatement throwTExcStatement = new ThrowStatement(new VariableExpression(tExcName));
astBuilder.appendStatementsToBlockStatement(blockStatement, throwTExcStatement);
// Throwable #t
Parameter tExcParameter = new Parameter(ClassHelper.make(Throwable.class), tExcName);
return new CatchStatement(tExcParameter, blockStatement);
}
private int tExcCnt = 0;
private String genTExcName() {
return "__$$t" + tExcCnt++;
}
/*
* finally {
* if (Identifier != null) {
* if (#primaryExc != null) {
* try {
* Identifier.close();
* } catch (Throwable #suppressedExc) {
* #primaryExc.addSuppressed(#suppressedExc);
* }
* } else {
* Identifier.close();
* }
* }
* }
*
* We can simplify the above code to a Groovy version as follows:
*
* finally {
* if (#primaryExc != null)
* try {
* Identifier?.close();
* } catch (Throwable #suppressedExc) {
* #primaryExc.addSuppressed(#suppressedExc);
* }
* else
* Identifier?.close();
*
* }
*
*/
private BlockStatement createFinallyBlockForNewTryCatchStatement(String primaryExcName, String firstResourceIdentifierName) {
BlockStatement finallyBlock = new BlockStatement();
// primaryExc != null
BooleanExpression conditionExpression =
new BooleanExpression(
new BinaryExpression(
new VariableExpression(primaryExcName),
newSymbol(Types.COMPARE_NOT_EQUAL, -1, -1),
new ConstantExpression(null)));
// try-catch statement
TryCatchStatement newTryCatchStatement =
new TryCatchStatement(
astBuilder.createBlockStatement(this.createCloseResourceStatement(firstResourceIdentifierName)), // { Identifier?.close(); }
EmptyStatement.INSTANCE);
String suppressedExcName = this.genSuppressedExcName();
newTryCatchStatement.addCatch(
// catch (Throwable #suppressedExc) { .. }
new CatchStatement(
new Parameter(ClassHelper.make(Throwable.class), suppressedExcName),
astBuilder.createBlockStatement(this.createAddSuppressedStatement(primaryExcName, suppressedExcName)) // #primaryExc.addSuppressed(#suppressedExc);
)
);
// if (#primaryExc != null) { ... }
IfStatement ifStatement =
new IfStatement(
conditionExpression,
newTryCatchStatement,
this.createCloseResourceStatement(firstResourceIdentifierName) // Identifier?.close();
);
astBuilder.appendStatementsToBlockStatement(finallyBlock, ifStatement);
return astBuilder.createBlockStatement(finallyBlock);
}
private int suppressedExcCnt = 0;
private String genSuppressedExcName() {
return "__$$suppressedExc" + suppressedExcCnt++;
}
/*
* Identifier?.close();
*/
private ExpressionStatement createCloseResourceStatement(String firstResourceIdentifierName) {
MethodCallExpression closeMethodCallExpression =
new MethodCallExpression(new VariableExpression(firstResourceIdentifierName), "close", new ArgumentListExpression());
closeMethodCallExpression.setImplicitThis(false);
closeMethodCallExpression.setSafe(true);
return new ExpressionStatement(closeMethodCallExpression);
}
/*
* #primaryExc.addSuppressed(#suppressedExc);
*/
private ExpressionStatement createAddSuppressedStatement(String primaryExcName, String suppressedExcName) {
MethodCallExpression addSuppressedMethodCallExpression =
new MethodCallExpression(
new VariableExpression(primaryExcName),
"addSuppressed",
new ArgumentListExpression(Collections.singletonList(new VariableExpression(suppressedExcName))));
addSuppressedMethodCallExpression.setImplicitThis(false);
addSuppressedMethodCallExpression.setSafe(true);
return new ExpressionStatement(addSuppressedMethodCallExpression);
}
/**
* See https://docs.oracle.com/javase/specs/jls/se9/html/jls-14.html
* 14.20.3.1. Basic try-with-resources
*
* If a basic try-with-resource statement is of the form:
* try (VariableAccess ...)
* Block
*
* then the resource is first converted to a local variable declaration by the following translation:
* try (T #r = VariableAccess ...) {
* Block
* }
*/
public BinaryExpression transformResourceAccess(Expression variableAccessExpression) {
return new BinaryExpression(
new VariableExpression(genResourceName()),
newSymbol(Types.ASSIGN, -1, -1),
variableAccessExpression
);
}
private int resourceCnt = 0;
private String genResourceName() {
return "__$$resource" + resourceCnt++;
}
}