/*******************************************************************************
 * Copyright (c) 2000, 2003 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package net.sourceforge.phpdt.internal.compiler.ast;

import net.sourceforge.phpdt.internal.compiler.ASTVisitor;
import net.sourceforge.phpdt.internal.compiler.codegen.Label;
import net.sourceforge.phpdt.internal.compiler.flow.ExceptionHandlingFlowContext;
import net.sourceforge.phpdt.internal.compiler.flow.FinallyFlowContext;
import net.sourceforge.phpdt.internal.compiler.flow.FlowContext;
import net.sourceforge.phpdt.internal.compiler.flow.FlowInfo;
import net.sourceforge.phpdt.internal.compiler.flow.InsideSubRoutineFlowContext;
import net.sourceforge.phpdt.internal.compiler.flow.UnconditionalFlowInfo;
import net.sourceforge.phpdt.internal.compiler.lookup.BlockScope;
import net.sourceforge.phpdt.internal.compiler.lookup.LocalVariableBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.MethodBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.MethodScope;
import net.sourceforge.phpdt.internal.compiler.lookup.ReferenceBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.TypeBinding;

public class TryStatement extends Statement {

	public Block tryBlock;

	public Block[] catchBlocks;

	public Argument[] catchArguments;

	public Block finallyBlock;

	BlockScope scope;

	public boolean subRoutineCannotReturn = true;

	public UnconditionalFlowInfo subRoutineInits;

	// should rename into subRoutineComplete to be set to false by default

	ReferenceBinding[] caughtExceptionTypes;

	boolean tryBlockExit;

	boolean[] catchExits;

	public int[] preserveExceptionHandler;

	Label subRoutineStartLabel;

	public LocalVariableBinding anyExceptionVariable, returnAddressVariable,
			secretReturnValue;

	public final static char[] SecretReturnName = " returnAddress".toCharArray(); //$NON-NLS-1$

	public final static char[] SecretAnyHandlerName = " anyExceptionHandler".toCharArray(); //$NON-NLS-1$

	public static final char[] SecretLocalDeclarationName = " returnValue".toCharArray(); //$NON-NLS-1$

	// for local variables table attributes
	int preTryInitStateIndex = -1;

	int mergedInitStateIndex = -1;

	public FlowInfo analyseCode(BlockScope currentScope,
			FlowContext flowContext, FlowInfo flowInfo) {

		// Consider the try block and catch block so as to compute the
		// intersection of initializations and
		// the minimum exit relative depth amongst all of them. Then consider
		// the subroutine, and append its
		// initialization to the try/catch ones, if the subroutine completes
		// normally. If the subroutine does not
		// complete, then only keep this result for the rest of the analysis

		// process the finally block (subroutine) - create a context for the
		// subroutine

		preTryInitStateIndex = currentScope.methodScope()
				.recordInitializationStates(flowInfo);

		if (anyExceptionVariable != null) {
			anyExceptionVariable.useFlag = LocalVariableBinding.USED;
		}
		if (returnAddressVariable != null) {
			returnAddressVariable.useFlag = LocalVariableBinding.USED;
		}
		InsideSubRoutineFlowContext insideSubContext;
		FinallyFlowContext finallyContext;
		UnconditionalFlowInfo subInfo;
		if (subRoutineStartLabel == null) {
			// no finally block
			insideSubContext = null;
			finallyContext = null;
			subInfo = null;
		} else {
			// analyse finally block first
			insideSubContext = new InsideSubRoutineFlowContext(flowContext,
					this);
			subInfo = finallyBlock.analyseCode(
					currentScope,
					finallyContext = new FinallyFlowContext(flowContext,
							finallyBlock), flowInfo.copy())
					.unconditionalInits();
			if (subInfo.isReachable()) {
				subRoutineCannotReturn = false;
			}
			this.subRoutineInits = subInfo;
		}
		// process the try block in a context handling the local exceptions.
		ExceptionHandlingFlowContext handlingContext = new ExceptionHandlingFlowContext(
				insideSubContext == null ? flowContext : insideSubContext,
				tryBlock, caughtExceptionTypes, scope, flowInfo
						.unconditionalInits());

		FlowInfo tryInfo;
		if (tryBlock.statements == null) {
			tryInfo = flowInfo;
			tryBlockExit = false;
		} else {
			tryInfo = tryBlock.analyseCode(currentScope, handlingContext,
					flowInfo.copy());
			tryBlockExit = !tryInfo.isReachable();
		}

		// check unreachable catch blocks
		// handlingContext.complainIfUnusedExceptionHandlers(catchBlocks, scope,
		// this);

		// process the catch blocks - computing the minimal exit depth amongst
		// try/catch
		if (catchArguments != null) {
			int catchCount;
			catchExits = new boolean[catchCount = catchBlocks.length];
			for (int i = 0; i < catchCount; i++) {
				// keep track of the inits that could potentially have led to
				// this exception handler (for final assignments diagnosis)
				FlowInfo catchInfo = flowInfo.copy().unconditionalInits()
						.addPotentialInitializationsFrom(
								handlingContext.initsOnException(
										caughtExceptionTypes[i])
										.unconditionalInits())
						.addPotentialInitializationsFrom(
								tryInfo.unconditionalInits())
						.addPotentialInitializationsFrom(
								handlingContext.initsOnReturn);

				// catch var is always set
				catchInfo.markAsDefinitelyAssigned(catchArguments[i].binding);
				/*
				 * "If we are about to consider an unchecked exception handler,
				 * potential inits may have occured inside the try block that
				 * need to be detected , e.g. try { x = 1; throwSomething();}
				 * catch(Exception e){ x = 2} " "(uncheckedExceptionTypes notNil
				 * and: [uncheckedExceptionTypes at: index]) ifTrue: [catchInits
				 * addPotentialInitializationsFrom: tryInits]."
				 */
				// TODO: should only tag as unreachable if the catchblock cannot
				// be reached
				// ??? if
				// (!handlingContext.initsOnException(caughtExceptionTypes[i]).isReachable()){
				if (tryBlock.statements == null) {
					catchInfo.setReachMode(FlowInfo.UNREACHABLE);
				}
				catchInfo = catchBlocks[i].analyseCode(currentScope,
						insideSubContext == null ? flowContext
								: insideSubContext, catchInfo);
				catchExits[i] = !catchInfo.isReachable();
				tryInfo = tryInfo.mergedWith(catchInfo.unconditionalInits());
			}
		}
		if (subRoutineStartLabel == null) {
			mergedInitStateIndex = currentScope.methodScope()
					.recordInitializationStates(tryInfo);
			return tryInfo;
		}

		// we also need to check potential multiple assignments of final
		// variables inside the finally block
		// need to include potential inits from returns inside the try/catch
		// parts - 1GK2AOF
		finallyContext
				.complainOnRedundantFinalAssignments(
						tryInfo.isReachable() ? (tryInfo
								.addPotentialInitializationsFrom(insideSubContext.initsOnReturn))
								: insideSubContext.initsOnReturn, currentScope);
		if (subInfo == FlowInfo.DEAD_END) {
			mergedInitStateIndex = currentScope.methodScope()
					.recordInitializationStates(subInfo);
			return subInfo;
		} else {
			FlowInfo mergedInfo = tryInfo.addInitializationsFrom(subInfo);
			mergedInitStateIndex = currentScope.methodScope()
					.recordInitializationStates(mergedInfo);
			return mergedInfo;
		}
	}

	public boolean cannotReturn() {

		return subRoutineCannotReturn;
	}

	/**
	 * Try statement code generation
	 * 
	 */
	// public void generateCode(BlockScope currentScope, CodeStream codeStream)
	// {
	//
	// if ((bits & IsReachableMASK) == 0) {
	// return;
	// }
	// if (tryBlock.isEmptyBlock()) {
	// if (subRoutineStartLabel != null) {
	// // since not passing the finallyScope, the block generation will
	// exitUserScope(finallyScope)
	// finallyBlock.generateCode(scope, codeStream);
	// }
	// // May loose some local variable initializations : affecting the local
	// variable attributes
	// if (mergedInitStateIndex != -1) {
	// codeStream.removeNotDefinitelyAssignedVariables(
	// currentScope,
	// mergedInitStateIndex);
	// }
	// // no local bytecode produced so no need for position remembering
	// return;
	// }
	// int pc = codeStream.position;
	// Label endLabel = new Label(codeStream);
	// boolean requiresNaturalJsr = false;
	//
	// // preparing exception labels
	// int maxCatches;
	// ExceptionLabel[] exceptionLabels =
	// new ExceptionLabel[maxCatches =
	// catchArguments == null ? 0 : catchArguments.length];
	// for (int i = 0; i < maxCatches; i++) {
	// boolean preserveCurrentHandler =
	// (preserveExceptionHandler[i
	// / ExceptionHandlingFlowContext.BitCacheSize]
	// & (1 << (i % ExceptionHandlingFlowContext.BitCacheSize)))
	// != 0;
	// if (preserveCurrentHandler) {
	// exceptionLabels[i] =
	// new ExceptionLabel(
	// codeStream,
	// (ReferenceBinding) catchArguments[i].binding.type);
	// }
	// }
	// ExceptionLabel anyExceptionLabel = null;
	// if (subRoutineStartLabel != null) {
	// subRoutineStartLabel.codeStream = codeStream;
	// anyExceptionLabel = new ExceptionLabel(codeStream, null);
	// }
	// // generate the try block
	// tryBlock.generateCode(scope, codeStream);
	// boolean tryBlockHasSomeCode = codeStream.position != pc;
	// // flag telling if some bytecodes were issued inside the try block
	//
	// // natural exit: only if necessary
	// boolean nonReturningSubRoutine =
	// (subRoutineStartLabel != null) && subRoutineCannotReturn;
	// if ((!tryBlockExit) && tryBlockHasSomeCode) {
	// int position = codeStream.position;
	// if (nonReturningSubRoutine) {
	// codeStream.goto_(subRoutineStartLabel);
	// } else {
	// requiresNaturalJsr = true;
	// codeStream.goto_(endLabel);
	// }
	// codeStream.updateLastRecordedEndPC(position);
	// //goto is tagged as part of the try block
	// }
	// // place end positions of user-defined exception labels
	// if (tryBlockHasSomeCode) {
	// for (int i = 0; i < maxCatches; i++) {
	// boolean preserveCurrentHandler =
	// (preserveExceptionHandler[i / ExceptionHandlingFlowContext.BitCacheSize]
	// & (1 << (i % ExceptionHandlingFlowContext.BitCacheSize)))
	// != 0;
	// if (preserveCurrentHandler) {
	// exceptionLabels[i].placeEnd();
	// }
	// }
	// /* generate sequence of handler, all starting by storing the TOS
	// (exception
	// thrown) into their own catch variables, the one specified in the source
	// that must denote the handled exception.
	// */
	// if (catchArguments == null) {
	// if (anyExceptionLabel != null) {
	// anyExceptionLabel.placeEnd();
	// }
	// } else {
	// for (int i = 0; i < maxCatches; i++) {
	// boolean preserveCurrentHandler =
	// (preserveExceptionHandler[i / ExceptionHandlingFlowContext.BitCacheSize]
	// & (1 << (i % ExceptionHandlingFlowContext.BitCacheSize)))
	// != 0;
	// if (preserveCurrentHandler) {
	// // May loose some local variable initializations : affecting the local
	// variable attributes
	// if (preTryInitStateIndex != -1) {
	// codeStream.removeNotDefinitelyAssignedVariables(
	// currentScope,
	// preTryInitStateIndex);
	// }
	// exceptionLabels[i].place();
	// codeStream.incrStackSize(1);
	// // optimizing the case where the exception variable is not actually used
	// LocalVariableBinding catchVar;
	// int varPC = codeStream.position;
	// if ((catchVar = catchArguments[i].binding).resolvedPosition != -1) {
	// codeStream.store(catchVar, false);
	// catchVar.recordInitializationStartPC(codeStream.position);
	// codeStream.addVisibleLocalVariable(catchVar);
	// } else {
	// codeStream.pop();
	// }
	// codeStream.recordPositionsFrom(varPC, catchArguments[i].sourceStart);
	// // Keep track of the pcs at diverging point for computing the local
	// attribute
	// // since not passing the catchScope, the block generation will
	// exitUserScope(catchScope)
	// catchBlocks[i].generateCode(scope, codeStream);
	// }
	// if (i == maxCatches - 1) {
	// if (anyExceptionLabel != null) {
	// anyExceptionLabel.placeEnd();
	// }
	// if (subRoutineStartLabel != null) {
	// if (!catchExits[i] && preserveCurrentHandler) {
	// requiresNaturalJsr = true;
	// codeStream.goto_(endLabel);
	// }
	// }
	// } else {
	// if (!catchExits[i] && preserveCurrentHandler) {
	// if (nonReturningSubRoutine) {
	// codeStream.goto_(subRoutineStartLabel);
	// } else {
	// requiresNaturalJsr = true;
	// codeStream.goto_(endLabel);
	// }
	// }
	// }
	// }
	// }
	// // addition of a special handler so as to ensure that any uncaught
	// exception (or exception thrown
	// // inside catch blocks) will run the finally block
	// int finallySequenceStartPC = codeStream.position;
	// if (subRoutineStartLabel != null) {
	// // the additional handler is doing: jsr finallyBlock and rethrow
	// TOS-exception
	// anyExceptionLabel.place();
	//
	// if (preTryInitStateIndex != -1) {
	// // reset initialization state, as for a normal catch block
	// codeStream.removeNotDefinitelyAssignedVariables(
	// currentScope,
	// preTryInitStateIndex);
	// }
	//
	// codeStream.incrStackSize(1);
	// if (nonReturningSubRoutine) {
	// codeStream.pop();
	// // "if subroutine cannot return, no need to jsr/jump to subroutine since
	// it will be entered in sequence
	// } else {
	// codeStream.store(anyExceptionVariable, false);
	// codeStream.jsr(subRoutineStartLabel);
	// codeStream.load(anyExceptionVariable);
	// codeStream.athrow();
	// }
	// }
	// // end of catch sequence, place label that will correspond to the finally
	// block beginning, or end of statement
	// endLabel.place();
	// if (subRoutineStartLabel != null) {
	// if (nonReturningSubRoutine) {
	// requiresNaturalJsr = false;
	// }
	// Label veryEndLabel = new Label(codeStream);
	// if (requiresNaturalJsr) {
	// codeStream.jsr(subRoutineStartLabel);
	// codeStream.goto_(veryEndLabel);
	// }
	// subRoutineStartLabel.place();
	// if (!nonReturningSubRoutine) {
	// codeStream.incrStackSize(1);
	// codeStream.store(returnAddressVariable, false);
	// }
	// codeStream.recordPositionsFrom(
	// finallySequenceStartPC,
	// finallyBlock.sourceStart);
	// // entire sequence for finally is associated to finally block
	// finallyBlock.generateCode(scope, codeStream);
	// if (!nonReturningSubRoutine) {
	// int position = codeStream.position;
	// codeStream.ret(returnAddressVariable.resolvedPosition);
	// codeStream.updateLastRecordedEndPC(position);
	// // the ret bytecode is part of the subroutine
	// }
	// if (requiresNaturalJsr) {
	// veryEndLabel.place();
	// }
	// }
	// } else {
	// // try block had no effect, only generate the body of the finally block
	// if any
	// if (subRoutineStartLabel != null) {
	// finallyBlock.generateCode(scope, codeStream);
	// }
	// }
	// // May loose some local variable initializations : affecting the local
	// variable attributes
	// if (mergedInitStateIndex != -1) {
	// codeStream.removeNotDefinitelyAssignedVariables(
	// currentScope,
	// mergedInitStateIndex);
	// codeStream.addDefinitelyAssignedVariables(currentScope,
	// mergedInitStateIndex);
	// }
	// codeStream.recordPositionsFrom(pc, this.sourceStart);
	// }
	public void resetStateForCodeGeneration() {
		if (this.subRoutineStartLabel != null) {
			this.subRoutineStartLabel.resetStateForCodeGeneration();
		}
	}

	public StringBuffer printStatement(int indent, StringBuffer output) {
		printIndent(indent, output).append("try \n"); //$NON-NLS-1$
		tryBlock.printStatement(indent + 1, output); //$NON-NLS-1$

		// catches
		if (catchBlocks != null)
			for (int i = 0; i < catchBlocks.length; i++) {
				output.append('\n');
				printIndent(indent, output).append("catch ("); //$NON-NLS-1$
				catchArguments[i].print(0, output).append(") "); //$NON-NLS-1$
				catchBlocks[i].printStatement(indent + 1, output);
			}
		// finally
		if (finallyBlock != null) {
			output.append('\n');
			printIndent(indent, output).append("finally\n"); //$NON-NLS-1$
			finallyBlock.printStatement(indent + 1, output);
		}

		return output;
	}

	public void resolve(BlockScope upperScope) {

		// special scope for secret locals optimization.
		this.scope = new BlockScope(upperScope);

		BlockScope tryScope = new BlockScope(scope);
		BlockScope finallyScope = null;

		if (finallyBlock != null && finallyBlock.statements != null) {

			finallyScope = new BlockScope(scope, false); // don't add it yet
															// to parent scope

			// provision for returning and forcing the finally block to run
			MethodScope methodScope = scope.methodScope();

			// the type does not matter as long as it is not a base type
			this.returnAddressVariable = new LocalVariableBinding(
					SecretReturnName, upperScope.getJavaLangObject(),
					AccDefault, false);
			finallyScope.addLocalVariable(returnAddressVariable);
			this.returnAddressVariable.constant = NotAConstant; // not inlinable
			this.subRoutineStartLabel = new Label();

			this.anyExceptionVariable = new LocalVariableBinding(
					SecretAnyHandlerName, scope.getJavaLangThrowable(),
					AccDefault, false);
			finallyScope.addLocalVariable(this.anyExceptionVariable);
			this.anyExceptionVariable.constant = NotAConstant; // not inlinable

			if (!methodScope.isInsideInitializer()) {
				MethodBinding methodBinding = ((AbstractMethodDeclaration) methodScope.referenceContext).binding;
				if (methodBinding != null) {
					TypeBinding methodReturnType = methodBinding.returnType;
					if (methodReturnType.id != T_void) {
						this.secretReturnValue = new LocalVariableBinding(
								SecretLocalDeclarationName, methodReturnType,
								AccDefault, false);
						finallyScope.addLocalVariable(this.secretReturnValue);
						this.secretReturnValue.constant = NotAConstant; // not
																		// inlinable
					}
				}
			}
			finallyBlock.resolveUsing(finallyScope);
			// force the finally scope to have variable positions shifted after
			// its try scope and catch ones
			finallyScope.shiftScopes = new BlockScope[catchArguments == null ? 1
					: catchArguments.length + 1];
			finallyScope.shiftScopes[0] = tryScope;
		}
		this.tryBlock.resolveUsing(tryScope);

		// arguments type are checked against JavaLangThrowable in
		// resolveForCatch(..)
		if (this.catchBlocks != null) {
			int length = this.catchArguments.length;
			TypeBinding[] argumentTypes = new TypeBinding[length];
			for (int i = 0; i < length; i++) {
				BlockScope catchScope = new BlockScope(scope);
				if (finallyScope != null) {
					finallyScope.shiftScopes[i + 1] = catchScope;
				}
				// side effect on catchScope in resolveForCatch(..)
				if ((argumentTypes[i] = catchArguments[i]
						.resolveForCatch(catchScope)) == null)
					return;
				catchBlocks[i].resolveUsing(catchScope);
			}

			// Verify that the catch clause are ordered in the right way:
			// more specialized first.
			this.caughtExceptionTypes = new ReferenceBinding[length];
			for (int i = 0; i < length; i++) {
				caughtExceptionTypes[i] = (ReferenceBinding) argumentTypes[i];
				for (int j = 0; j < i; j++) {
					if (caughtExceptionTypes[i]
							.isCompatibleWith(argumentTypes[j])) {
						scope.problemReporter()
								.wrongSequenceOfExceptionTypesError(this, i, j);
						// cannot return - since may still proceed if
						// unreachable code is ignored (21203)
					}
				}
			}
		} else {
			caughtExceptionTypes = new ReferenceBinding[0];
		}

		if (finallyScope != null) {
			// add finallyScope as last subscope, so it can be shifted behind
			// try/catch subscopes.
			// the shifting is necessary to achieve no overlay in between the
			// finally scope and its
			// sibling in term of local variable positions.
			this.scope.addSubscope(finallyScope);
		}
	}

	public String toString(int tab) {
		String s = tabString(tab);
		// try
		s = s + "try "; //$NON-NLS-1$
		if (tryBlock == Block.None)
			s = s + "{}"; //$NON-NLS-1$
		else
			s = s + "\n" + tryBlock.toString(tab + 1); //$NON-NLS-1$

		// catches
		if (catchBlocks != null)
			for (int i = 0; i < catchBlocks.length; i++)
				s = s + "\n" + tabString(tab) + "catch (" //$NON-NLS-2$ //$NON-NLS-1$
						+ catchArguments[i].toString(0) + ") " //$NON-NLS-1$
						+ catchBlocks[i].toString(tab + 1);
		// finally
		if (finallyBlock != null) {
			if (finallyBlock == Block.None)
				s = s + "\n" + tabString(tab) + "finally {}"; //$NON-NLS-2$ //$NON-NLS-1$
			else
				s = s + "\n" + tabString(tab) + "finally\n" + //$NON-NLS-2$ //$NON-NLS-1$
						finallyBlock.toString(tab + 1);
		}

		return s;
	}

	public void traverse(ASTVisitor visitor, BlockScope blockScope) {

		if (visitor.visit(this, blockScope)) {
			tryBlock.traverse(visitor, scope);
			if (catchArguments != null) {
				for (int i = 0, max = catchBlocks.length; i < max; i++) {
					catchArguments[i].traverse(visitor, scope);
					catchBlocks[i].traverse(visitor, scope);
				}
			}
			if (finallyBlock != null)
				finallyBlock.traverse(visitor, scope);
		}
		visitor.endVisit(this, blockScope);
	}
}
