/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.ir.transform.statement;
import gw.config.CommonServices;
import gw.internal.gosu.parser.Statement;
import gw.internal.gosu.parser.Symbol;
import gw.internal.gosu.parser.statements.CatchClause;
import gw.internal.gosu.parser.statements.TryCatchFinallyStatement;
import gw.internal.gosu.ir.transform.TopLevelTransformationContext;
import gw.lang.ir.IRStatement;
import gw.lang.ir.IRSymbol;
import gw.lang.ir.IRExpression;
import gw.lang.ir.IRType;
import gw.lang.ir.IRTypeConstants;
import gw.lang.ir.statement.IRCatchClause;
import gw.lang.ir.statement.IRTryCatchFinallyStatement;
import gw.lang.ir.statement.IRStatementList;
import gw.lang.ir.statement.IRIfStatement;
import gw.lang.ir.statement.IRThrowStatement;
import gw.lang.reflect.IType;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.java.JavaTypes;
import gw.lang.parser.EvaluationException;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
/**
*/
public class TryCatchFinallyStatementTransformer extends AbstractStatementTransformer<TryCatchFinallyStatement>
{
public static IRStatement compile( TopLevelTransformationContext cc, TryCatchFinallyStatement stmt )
{
TryCatchFinallyStatementTransformer compiler = new TryCatchFinallyStatementTransformer( cc, stmt );
return compiler.compile();
}
private TryCatchFinallyStatementTransformer( TopLevelTransformationContext cc, TryCatchFinallyStatement stmt )
{
super( cc, stmt );
}
@Override
protected IRStatement compile_impl()
{
IRStatement tryBody = _cc().compile( _stmt().getTryStatement() );
List<IRCatchClause> catchStatements = Collections.emptyList();
if (_stmt().getCatchStatements() != null ) {
catchStatements = compileCatchStatements();
}
IRStatement finallyBody = null;
if (_stmt().getFinallyStatement() != null) {
finallyBody = _cc().compile( _stmt().getFinallyStatement() );
}
return new IRTryCatchFinallyStatement( tryBody, catchStatements, finallyBody );
}
private List<IRCatchClause> compileCatchStatements( )
{
List<IRCatchClause> resultingClauses = new ArrayList<IRCatchClause>();
List<CatchClause> catchStmts = _stmt().getCatchStatements();
List<CatchClause> otherCatches = new ArrayList<CatchClause>();
boolean bThrowableFound = false;
for( CatchClause c : catchStmts )
{
Statement catchStmt = c.getCatchStmt();
_cc().pushScope( false ); // scope for the exception param
try
{
IType type = c.getCatchType();
if( (type == null || isBytecodeType( type )) && otherCatches.isEmpty() )
{
// We need to set up the symbol used within the catch clause. However, that symbol could be untyped or it
// could need to be boxed; if so those will be the first statements in the catch body.
Symbol catchSymbol = c.getSymbol();
IRSymbol exceptionParam = createCatchClauseSymbol(catchSymbol, type );
List<IRStatement> bodyStatements = new ArrayList<IRStatement>();
// We need to wrap (and box) if the declared type is null, otherwise we just need to box
if ( wrapInEvalException( type ) ) {
bodyStatements.add( wrapUndeclaredAsEvaluationException( exceptionParam, catchSymbol.getName(), catchSymbol.isValueBoxed() ) );
} else if ( catchSymbol.isValueBoxed() ) {
bodyStatements.add( boxCatchSymbol( exceptionParam.getType(), catchSymbol.getName(), identifier( exceptionParam ) ) );
}
bodyStatements.add( _cc().compile(catchStmt) );
resultingClauses.add(new IRCatchClause( exceptionParam, new IRStatementList( false, bodyStatements ) ) );
}
else
{
bThrowableFound = bThrowableFound || wrapInEvalException( type ) || type == JavaTypes.THROWABLE();
otherCatches.add( c );
}
}
finally
{
_cc().popScope();
}
}
if( !otherCatches.isEmpty() )
{
if( !bThrowableFound )
{
CatchClause catchClause = new CatchClause();
catchClause.init( JavaTypes.THROWABLE(), null, null );
otherCatches.add( catchClause );
}
compileOtherCatchStatements(_cc(), otherCatches, resultingClauses );
}
return resultingClauses;
}
private boolean wrapInEvalException( IType type )
{
return type == null && !CommonServices.getEntityAccess().getLanguageLevel().supportsNakedCatchStatements();
}
private IRSymbol createCatchClauseSymbol( Symbol symbol, IType type) {
boolean temp = symbol.isValueBoxed() || (wrapInEvalException( type ));
String name = symbol.getName();
if (temp) {
name = name + "$$temp";
}
if (type == null)
{
type = CatchClause.getNakedCatchExceptionType();
}
IRSymbol irSymbol = new IRSymbol( name, getDescriptor( type ), temp);
_cc().putSymbol( irSymbol );
return irSymbol;
}
private IRStatement wrapUndeclaredAsEvaluationException( IRSymbol catchSymbol, String properName, boolean isBoxed)
{
IRExpression wrapper = wrapCatchSymbol( identifier( catchSymbol ) );
if (isBoxed) {
return boxCatchSymbol( catchSymbol.getType(), properName, wrapper );
} else {
return reassignCatchSymbol( catchSymbol.getType(), properName, wrapper );
}
}
/**
* Handle case where a catch clause declares a non-bytecode exception type e.g., soap exception type.
* In such a case the catch clause and all subsequent catch clauses must be handled in a single
* catch-clause for Throwable. If none of the subsequent catch clauses handle Throwable explicitly
* and none of the clauses field the exception, it is rethrown. Note if a 'default' catch clause
* is declard (i.e. a JavaScript-style catch clause with no exception type declared), it is treated
* as handling Throwable explicitly; in other words no code is generated to rethrow the exception.
* <p>
* For example, the following try-catch statement declares a soap exception, which is not really
* a valid Java exception type. It's a simple case with no finally clause and no default catch, but
* it illustrates the essence of the problem.
* <pre>
* try {
* doSomething()
* }
* catch( re : RuntimeException ) {
* print( "RuntimeException" )
* }
* catch( fake : DoesntActuallyExtendThrowable ) {
* print( "DoesntActuallyExtendThrowable" )
* }
* catch( e : RealException ) {
* print( "RealException" )
* }
* </pre>
* We must treat this statement as the following (simplified) code:
* <pre>
* try {
* doSomething()
* }
* catch( re : RuntimeException ) {
* print( "RuntimeException" )
* }
* catch( t : Throwable ) {
* var rtt = TypeSystem.getFromObject( t )
* if( DoesntActuallyExtendThrowable.Type.isAssignableFrom( rtt ) ) {
* print( "DoesntActuallyExtendThrowable" )
* }
* if( RealException.Type.isAssignableFrom( rtt ) ) {
* print( "RealException" )
* }
* else {
* throw t
* }
* }
* </pre>
*/
private void compileOtherCatchStatements( final TopLevelTransformationContext cc, List<CatchClause> otherCatches, List<IRCatchClause> resultingClauses )
{
cc.pushScope( false );
try {
// Create a temp symbol to represent the exception variable for the synthetic clause
IRSymbol exceptionSymbol = new IRSymbol("exception$$temp$$var", getDescriptor( Throwable.class ), true);
cc.putSymbol( exceptionSymbol );
List<IRStatement> catchBody = new ArrayList<IRStatement>();
// Store the exception type in a temporary variable
IRSymbol exceptionTypeSymbol = new IRSymbol("exceptiontype$$temp$$var", IRTypeConstants.ITYPE(), true);
cc.putSymbol( exceptionTypeSymbol );
catchBody.add( buildAssignment( exceptionTypeSymbol, callStaticMethod( TypeSystem.class, "getFromObject", new Class[]{Object.class}, exprList( identifier( exceptionSymbol ) ) ) ) );
// Build up a nested if-statement
IRIfStatement lastStatement = null;
for( CatchClause clause : otherCatches )
{
cc.pushScope( false ); // scope for the exception param
try
{
Symbol symbol = clause.getSymbol();
IType type = clause.getCatchType();
if( type == null )
{
type = JavaTypes.THROWABLE();
}
if (symbol != null) {
if (type != JavaTypes.THROWABLE()) {
// Test for assignability
IRExpression test = callMethod( IType.class, "isAssignableFrom", new Class[]{IType.class},
pushType( type ),
exprList( identifier( exceptionTypeSymbol ) ) );
// Assign the generic variable to an appropriately typed and boxed symbol, then compile the catch body
List<IRStatement> body = new ArrayList<IRStatement>();
body.add( assignCatchClauseSymbol( exceptionSymbol, symbol.getName(), type, symbol.isValueBoxed() ) );
body.add( cc.compile( clause.getCatchStmt() ) );
IRIfStatement nestedCatchStatement = new IRIfStatement( test, new IRStatementList( false, body ), null );
// Add it to the list we're building up; if it's the first statement then set up the variable and add it to
// the list, otherwise chain it to the previous if statement
if (lastStatement == null) {
catchBody.add( nestedCatchStatement );
} else {
// It's possible that some clause follows the last if/else clause; if so, the else statement
// will already be non-null, so in that case we can just drop these statements on the floor
// since they'll never be executed
if (lastStatement.getElseStatement() == null) {
lastStatement.setElseStatement( nestedCatchStatement );
}
}
lastStatement = nestedCatchStatement;
} else {
// No point in testing for assignability, so we know it's a throwable.
List<IRStatement> body = new ArrayList<IRStatement>();
body.add( assignCatchClauseSymbol( exceptionSymbol, symbol.getName(), clause.getCatchType(), symbol.isValueBoxed() ) );
body.add( cc.compile( clause.getCatchStmt() ) );
if (lastStatement != null) {
lastStatement.setElseStatement( new IRStatementList( false, body ) );
} else {
catchBody.addAll(body);
}
// Once we've hit a catch around Throwable, exit the loop immediately
break;
}
} else {
// Uncaught, so rethrow
lastStatement.setElseStatement( new IRThrowStatement( identifier( exceptionSymbol ) ) );
}
} finally {
cc.popScope();
}
}
resultingClauses.add(new IRCatchClause( exceptionSymbol, new IRStatementList( false, catchBody ) ) );
} finally {
cc.popScope();
}
}
private IRStatement assignCatchClauseSymbol( IRSymbol genericCatchSymbol, String expectedName, IType expectedType, boolean isBoxed) {
// Either do a check-cast to the expected type or wrap the catch symbol in an EvaluationExeption
IRType descriptorType;
IRExpression root;
if (expectedType == null) {
descriptorType = getDescriptor( RuntimeException.class );
root = wrapCatchSymbol( identifier( genericCatchSymbol ) );
} else {
descriptorType = getDescriptor( expectedType );
root = buildCast( descriptorType, identifier( genericCatchSymbol ) );
}
if (isBoxed) {
return boxCatchSymbol( descriptorType, expectedName, root );
} else {
return reassignCatchSymbol( descriptorType, expectedName, root );
}
}
private IRStatement reassignCatchSymbol( IRType symbolType, String properName, IRExpression rootValue) {
IRSymbol newSymbol = new IRSymbol( properName, symbolType, false);
_cc().putSymbol( newSymbol );
return buildAssignment( newSymbol, rootValue );
}
private IRExpression wrapCatchSymbol( IRExpression rootValue) {
return callStaticMethod( EvaluationException.class, "wrap", new Class[]{Throwable.class}, exprList( rootValue ) );
}
private IRStatement boxCatchSymbol( IRType componentType, String properName, IRExpression rootValue ) {
// Create a new symbol with the correct name as an array of size 1 containing the expression
IRSymbol newSymbol = new IRSymbol( properName, componentType.getArrayType(), false);
_cc().putSymbol( newSymbol );
IRExpression arrayBox = buildInitializedArray( componentType, exprList( rootValue ) );
return buildAssignment( newSymbol, arrayBox );
}
}