/*******************************************************************************
 * 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.flow;

import net.sourceforge.phpdt.core.compiler.CharOperation;
import net.sourceforge.phpdt.internal.compiler.ast.ASTNode;
import net.sourceforge.phpdt.internal.compiler.ast.AbstractMethodDeclaration;
import net.sourceforge.phpdt.internal.compiler.ast.Reference;
import net.sourceforge.phpdt.internal.compiler.ast.TryStatement;
import net.sourceforge.phpdt.internal.compiler.codegen.Label;
import net.sourceforge.phpdt.internal.compiler.lookup.BlockScope;
import net.sourceforge.phpdt.internal.compiler.lookup.ReferenceBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.Scope;
import net.sourceforge.phpdt.internal.compiler.lookup.TypeBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.TypeConstants;
import net.sourceforge.phpdt.internal.compiler.lookup.VariableBinding;

/**
 * Reflects the context of code analysis, keeping track of enclosing try
 * statements, exception handlers, etc...
 */
public class FlowContext implements TypeConstants {

	public ASTNode associatedNode;

	public FlowContext parent;

	public final static FlowContext NotContinuableContext = new FlowContext(
			null, null);

	public FlowContext(FlowContext parent, ASTNode associatedNode) {

		this.parent = parent;
		this.associatedNode = associatedNode;
	}

	public Label breakLabel() {

		return null;
	}

	public void checkExceptionHandlers(TypeBinding[] raisedExceptions,
			ASTNode location, FlowInfo flowInfo, BlockScope scope) {

		// check that all the argument exception types are handled
		// JDK Compatible implementation - when an exception type is thrown,
		// all related catch blocks are marked as reachable... instead of those
		// only
		// until the point where it is safely handled (Smarter - see comment at
		// the end)
		int remainingCount; // counting the number of remaining unhandled
							// exceptions
		int raisedCount; // total number of exceptions raised
		if ((raisedExceptions == null)
				|| ((raisedCount = raisedExceptions.length) == 0))
			return;
		remainingCount = raisedCount;

		// duplicate the array of raised exceptions since it will be updated
		// (null replaces any handled exception)
		System.arraycopy(raisedExceptions, 0,
				(raisedExceptions = new TypeBinding[raisedCount]), 0,
				raisedCount);
		FlowContext traversedContext = this;

		while (traversedContext != null) {
			ASTNode sub;
			if (((sub = traversedContext.subRoutine()) != null)
					&& sub.cannotReturn()) {
				// traversing a non-returning subroutine means that all
				// unhandled
				// exceptions will actually never get sent...
				return;
			}
			// filter exceptions that are locally caught from the innermost
			// enclosing
			// try statement to the outermost ones.
			if (traversedContext instanceof ExceptionHandlingFlowContext) {
				ExceptionHandlingFlowContext exceptionContext = (ExceptionHandlingFlowContext) traversedContext;
				ReferenceBinding[] caughtExceptions;
				if ((caughtExceptions = exceptionContext.handledExceptions) != NoExceptions) {
					int caughtCount = caughtExceptions.length;
					boolean[] locallyCaught = new boolean[raisedCount]; // at
																		// most

					for (int caughtIndex = 0; caughtIndex < caughtCount; caughtIndex++) {
						ReferenceBinding caughtException = caughtExceptions[caughtIndex];
						for (int raisedIndex = 0; raisedIndex < raisedCount; raisedIndex++) {
							TypeBinding raisedException;
							if ((raisedException = raisedExceptions[raisedIndex]) != null) {
								switch (Scope.compareTypes(raisedException,
										caughtException)) {
								case EqualOrMoreSpecific:
									exceptionContext.recordHandlingException(
											caughtException, flowInfo
													.unconditionalInits(),
											raisedException, location,
											locallyCaught[raisedIndex]);
									// was already definitely caught ?
									if (!locallyCaught[raisedIndex]) {
										locallyCaught[raisedIndex] = true;
										// remember that this exception has been
										// definitely caught
										remainingCount--;
									}
									break;
								case MoreGeneric:
									exceptionContext.recordHandlingException(
											caughtException, flowInfo
													.unconditionalInits(),
											raisedException, location, false);
									// was not caught already per construction
								}
							}
						}
					}
					// remove locally caught exceptions from the remaining ones
					for (int i = 0; i < raisedCount; i++) {
						if (locallyCaught[i]) {
							raisedExceptions[i] = null; // removed from the
														// remaining ones.
						}
					}
				}
				// method treatment for unchecked exceptions
				if (exceptionContext.isMethodContext) {
					for (int i = 0; i < raisedCount; i++) {
						TypeBinding raisedException;
						if ((raisedException = raisedExceptions[i]) != null) {
							if (raisedException.isCompatibleWith(scope
									.getJavaLangRuntimeException())
									|| raisedException.isCompatibleWith(scope
											.getJavaLangError())) {
								remainingCount--;
								raisedExceptions[i] = null;
							}
						}
					}
					// anonymous constructors are allowed to throw any
					// exceptions (their thrown exceptions
					// clause will be fixed up later as per JLS 8.6).
					if (exceptionContext.associatedNode instanceof AbstractMethodDeclaration) {
						AbstractMethodDeclaration method = (AbstractMethodDeclaration) exceptionContext.associatedNode;
						if (method.isConstructor()
								&& method.binding.declaringClass
										.isAnonymousType()) {

							for (int i = 0; i < raisedCount; i++) {
								TypeBinding raisedException;
								if ((raisedException = raisedExceptions[i]) != null) {
									exceptionContext
											.mergeUnhandledException(raisedException);
								}
							}
							return; // no need to complain, will fix up
									// constructor exceptions
						}
					}
					break; // not handled anywhere, thus jump to error handling
				}
			}
			if (remainingCount == 0)
				return;

			traversedContext.recordReturnFrom(flowInfo.unconditionalInits());
			if (traversedContext.associatedNode instanceof TryStatement) {
				flowInfo = flowInfo
						.copy()
						.addInitializationsFrom(
								((TryStatement) traversedContext.associatedNode).subRoutineInits);
			}
			traversedContext = traversedContext.parent;
		}
		// if reaches this point, then there are some remaining unhandled
		// exception types.
		nextReport: for (int i = 0; i < raisedCount; i++) {
			TypeBinding exception;
			if ((exception = raisedExceptions[i]) != null) {
				// only one complaint if same exception declared to be thrown
				// more than once
				for (int j = 0; j < i; j++) {
					if (raisedExceptions[j] == exception)
						continue nextReport; // already reported
				}
				scope.problemReporter().unhandledException(exception, location);
			}
		}
	}

	public void checkExceptionHandlers(TypeBinding raisedException,
			ASTNode location, FlowInfo flowInfo, BlockScope scope) {

		// LIGHT-VERSION OF THE EQUIVALENT WITH AN ARRAY OF EXCEPTIONS
		// check that all the argument exception types are handled
		// JDK Compatible implementation - when an exception type is thrown,
		// all related catch blocks are marked as reachable... instead of those
		// only
		// until the point where it is safely handled (Smarter - see comment at
		// the end)
		FlowContext traversedContext = this;
		while (traversedContext != null) {
			ASTNode sub;
			if (((sub = traversedContext.subRoutine()) != null)
					&& sub.cannotReturn()) {
				// traversing a non-returning subroutine means that all
				// unhandled
				// exceptions will actually never get sent...
				return;
			}

			// filter exceptions that are locally caught from the innermost
			// enclosing
			// try statement to the outermost ones.
			if (traversedContext instanceof ExceptionHandlingFlowContext) {
				ExceptionHandlingFlowContext exceptionContext = (ExceptionHandlingFlowContext) traversedContext;
				ReferenceBinding[] caughtExceptions;
				if ((caughtExceptions = exceptionContext.handledExceptions) != NoExceptions) {
					boolean definitelyCaught = false;
					for (int caughtIndex = 0, caughtCount = caughtExceptions.length; caughtIndex < caughtCount; caughtIndex++) {
						ReferenceBinding caughtException = caughtExceptions[caughtIndex];
						switch (Scope.compareTypes(raisedException,
								caughtException)) {
						case EqualOrMoreSpecific:
							exceptionContext
									.recordHandlingException(caughtException,
											flowInfo.unconditionalInits(),
											raisedException, location,
											definitelyCaught);
							// was it already definitely caught ?
							definitelyCaught = true;
							break;
						case MoreGeneric:
							exceptionContext.recordHandlingException(
									caughtException, flowInfo
											.unconditionalInits(),
									raisedException, location, false);
							// was not caught already per construction
						}
					}
					if (definitelyCaught)
						return;
				}
				// method treatment for unchecked exceptions
				if (exceptionContext.isMethodContext) {
					if (raisedException.isCompatibleWith(scope
							.getJavaLangRuntimeException())
							|| raisedException.isCompatibleWith(scope
									.getJavaLangError()))
						return;

					// anonymous constructors are allowed to throw any
					// exceptions (their thrown exceptions
					// clause will be fixed up later as per JLS 8.6).
					if (exceptionContext.associatedNode instanceof AbstractMethodDeclaration) {
						AbstractMethodDeclaration method = (AbstractMethodDeclaration) exceptionContext.associatedNode;
						if (method.isConstructor()
								&& method.binding.declaringClass
										.isAnonymousType()) {

							exceptionContext
									.mergeUnhandledException(raisedException);
							return; // no need to complain, will fix up
									// constructor exceptions
						}
					}
					break; // not handled anywhere, thus jump to error handling
				}
			}

			traversedContext.recordReturnFrom(flowInfo.unconditionalInits());
			if (traversedContext.associatedNode instanceof TryStatement) {
				flowInfo = flowInfo
						.copy()
						.addInitializationsFrom(
								((TryStatement) traversedContext.associatedNode).subRoutineInits);
			}
			traversedContext = traversedContext.parent;
		}
		// if reaches this point, then there are some remaining unhandled
		// exception types.
		scope.problemReporter().unhandledException(raisedException, location);
	}

	public Label continueLabel() {

		return null;
	}

	/*
	 * lookup through break labels
	 */
	public FlowContext getTargetContextForBreakLabel(char[] labelName) {

		FlowContext current = this, lastNonReturningSubRoutine = null;
		while (current != null) {
			if (current.isNonReturningContext()) {
				lastNonReturningSubRoutine = current;
			}
			char[] currentLabelName;
			if (((currentLabelName = current.labelName()) != null)
					&& CharOperation.equals(currentLabelName, labelName)) {
				if (lastNonReturningSubRoutine == null) {
					return current;
				} else {
					return lastNonReturningSubRoutine;
				}
			}
			current = current.parent;
		}
		// not found
		return null;
	}

	/*
	 * lookup through continue labels
	 */
	public FlowContext getTargetContextForContinueLabel(char[] labelName) {

		FlowContext current = this;
		FlowContext lastContinuable = null;
		FlowContext lastNonReturningSubRoutine = null;

		while (current != null) {
			if (current.isNonReturningContext()) {
				lastNonReturningSubRoutine = current;
			} else {
				if (current.isContinuable()) {
					lastContinuable = current;
				}
			}

			char[] currentLabelName;
			if ((currentLabelName = current.labelName()) != null
					&& CharOperation.equals(currentLabelName, labelName)) {

				// matching label found
				if ((lastContinuable != null)
						&& (current.associatedNode.concreteStatement() == lastContinuable.associatedNode)) {

					if (lastNonReturningSubRoutine == null) {
						return lastContinuable;
					} else {
						return lastNonReturningSubRoutine;
					}
				} else {
					// label is found, but not a continuable location
					return NotContinuableContext;
				}
			}
			current = current.parent;
		}
		// not found
		return null;
	}

	/*
	 * lookup a default break through breakable locations
	 */
	public FlowContext getTargetContextForDefaultBreak() {

		FlowContext current = this, lastNonReturningSubRoutine = null;
		while (current != null) {
			if (current.isNonReturningContext()) {
				lastNonReturningSubRoutine = current;
			}
			if (current.isBreakable() && current.labelName() == null) {
				if (lastNonReturningSubRoutine == null) {
					return current;
				} else {
					return lastNonReturningSubRoutine;
				}
			}
			current = current.parent;
		}
		// not found
		return null;
	}

	/*
	 * lookup a default continue amongst continuable locations
	 */
	public FlowContext getTargetContextForDefaultContinue() {

		FlowContext current = this, lastNonReturningSubRoutine = null;
		while (current != null) {
			if (current.isNonReturningContext()) {
				lastNonReturningSubRoutine = current;
			}
			if (current.isContinuable()) {
				if (lastNonReturningSubRoutine == null) {
					return current;
				} else {
					return lastNonReturningSubRoutine;
				}
			}
			current = current.parent;
		}
		// not found
		return null;
	}

	public String individualToString() {

		return "Flow context"; //$NON-NLS-1$
	}

	public FlowInfo initsOnBreak() {

		return FlowInfo.DEAD_END;
	}

	public UnconditionalFlowInfo initsOnReturn() {

		return FlowInfo.DEAD_END;
	}

	public boolean isBreakable() {

		return false;
	}

	public boolean isContinuable() {

		return false;
	}

	public boolean isNonReturningContext() {

		return false;
	}

	public boolean isSubRoutine() {

		return false;
	}

	public char[] labelName() {

		return null;
	}

	public void recordBreakFrom(FlowInfo flowInfo) {
	}

	public void recordContinueFrom(FlowInfo flowInfo) {
	}

	boolean recordFinalAssignment(VariableBinding variable,
			Reference finalReference) {

		return true; // keep going
	}

	public void recordReturnFrom(FlowInfo flowInfo) {
	}

	public void recordSettingFinal(VariableBinding variable,
			Reference finalReference) {

		// for initialization inside looping statement that effectively loops
		FlowContext context = this;
		while (context != null) {
			if (!context.recordFinalAssignment(variable, finalReference)) {
				break; // no need to keep going
			}
			context = context.parent;
		}
	}

	void removeFinalAssignmentIfAny(Reference reference) {
	}

	public ASTNode subRoutine() {

		return null;
	}

	public String toString() {

		StringBuffer buffer = new StringBuffer();
		FlowContext current = this;
		int parentsCount = 0;
		while ((current = current.parent) != null) {
			parentsCount++;
		}
		FlowContext[] parents = new FlowContext[parentsCount + 1];
		current = this;
		int index = parentsCount;
		while (index >= 0) {
			parents[index--] = current;
			current = current.parent;
		}
		for (int i = 0; i < parentsCount; i++) {
			for (int j = 0; j < i; j++)
				buffer.append('\t');
			buffer.append(parents[i].individualToString()).append('\n');
		}
		buffer.append('*');
		for (int j = 0; j < parentsCount + 1; j++)
			buffer.append('\t');
		buffer.append(individualToString()).append('\n');
		return buffer.toString();
	}
}
