/* * 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.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.runtime.DefaultGroovyMethods.asBoolean; /** * 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) * * @author <a href="mailto:realbluesun@hotmail.com">Daniel.Sun</a> * Created on 2016/11/04 */ 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 (EmptyStatement.INSTANCE.equals(tryCatchStatement.getFinallyStatement()) && !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(); ExpressionStatement primaryExcDeclarationStatement = new ExpressionStatement( new DeclarationExpression( new VariableExpression(primaryExcName, ClassHelper.make(Throwable.class)), org.codehaus.groovy.syntax.Token.newSymbol(Types.ASSIGN, -1, -1), new ConstantExpression(null) ) ); 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), org.codehaus.groovy.syntax.Token.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), org.codehaus.groovy.syntax.Token.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); } }