/*******************************************************************************
 * Copyright (c) 2000, 2017 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Stephan Herrmann - Contribution for
 *								bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null"
 *								bug 402993 - [null] Follow up of bug 401088: Missing warning about redundant null check
 *								Bug 453483 - [compiler][null][loop] Improve null analysis for loops
 *								Bug 421035 - [resource] False alarm of resource leak warning when casting a closeable in its assignment
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.flow;

import java.util.ArrayList;

import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.UnionTypeReference;
import org.eclipse.jdt.internal.compiler.ast.SubRoutineStatement;
import org.eclipse.jdt.internal.compiler.ast.TryStatement;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.codegen.ObjectCache;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.CatchParameterBinding;
import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;

/**
 * Reflects the context of code analysis, keeping track of enclosing
 *	try statements, exception handlers, etc...
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public class ExceptionHandlingFlowContext extends FlowContext {

	public final static int BitCacheSize = 32; // 32 bits per int

	public ReferenceBinding[] handledExceptions;
	int[] isReached;
	int[] isNeeded;
	// WARNING: This is an array that maps to catch blocks, not caught exceptions (which could be more than catch blocks in a multi-catch block)
	UnconditionalFlowInfo[] initsOnExceptions; 
	ObjectCache indexes = new ObjectCache();
	boolean isMethodContext;

	public UnconditionalFlowInfo initsOnReturn;
	public FlowContext initializationParent; // special parent relationship only for initialization purpose
	
	// for dealing with anonymous constructor thrown exceptions
	public ArrayList extendedExceptions;

	private static final Argument[] NO_ARGUMENTS = new Argument[0];
	public  Argument [] catchArguments;

	private int[] exceptionToCatchBlockMap;

public ExceptionHandlingFlowContext(
			FlowContext parent,
			ASTNode associatedNode,
			ReferenceBinding[] handledExceptions,
			FlowContext initializationParent,
			BlockScope scope,
			UnconditionalFlowInfo flowInfo) {
	this(parent, associatedNode, handledExceptions, null, NO_ARGUMENTS, initializationParent, scope, flowInfo);
}
public ExceptionHandlingFlowContext(
		FlowContext parent,
		TryStatement tryStatement,
		ReferenceBinding[] handledExceptions,
		int [] exceptionToCatchBlockMap,
		FlowContext initializationParent,
		BlockScope scope,
		FlowInfo flowInfo) {
	this(parent, tryStatement, handledExceptions, exceptionToCatchBlockMap, 
			tryStatement.catchArguments, initializationParent, scope, flowInfo.unconditionalInits());
	UnconditionalFlowInfo unconditionalCopy = flowInfo.unconditionalCopy();
	unconditionalCopy.iNBit = -1L;
	unconditionalCopy.iNNBit = -1L;
	unconditionalCopy.tagBits |= FlowInfo.UNROOTED;
	this.initsOnFinally = unconditionalCopy;
}
ExceptionHandlingFlowContext(
		FlowContext parent,
		ASTNode associatedNode,
		ReferenceBinding[] handledExceptions,
		int [] exceptionToCatchBlockMap,
		Argument [] catchArguments,
		FlowContext initializationParent,
		BlockScope scope,
		UnconditionalFlowInfo flowInfo) {

	super(parent, associatedNode, true);
	this.isMethodContext = scope == scope.methodScope();
	this.handledExceptions = handledExceptions;
	this.catchArguments = catchArguments;
	this.exceptionToCatchBlockMap = exceptionToCatchBlockMap;
	int count = handledExceptions.length, cacheSize = (count / ExceptionHandlingFlowContext.BitCacheSize) + 1;
	this.isReached = new int[cacheSize]; // none is reached by default
	this.isNeeded = new int[cacheSize]; // none is needed by default
	this.initsOnExceptions = new UnconditionalFlowInfo[count];
	boolean markExceptionsAndThrowableAsReached =
		!this.isMethodContext || scope.compilerOptions().reportUnusedDeclaredThrownExceptionExemptExceptionAndThrowable;
	for (int i = 0; i < count; i++) {
		ReferenceBinding handledException = handledExceptions[i];
		int catchBlock = this.exceptionToCatchBlockMap != null? this.exceptionToCatchBlockMap[i] : i;
		this.indexes.put(handledException, i); // key type  -> value index
		if (handledException.isUncheckedException(true)) {
			if (markExceptionsAndThrowableAsReached ||
					handledException.id != TypeIds.T_JavaLangThrowable &&
					handledException.id != TypeIds.T_JavaLangException) {
				this.isReached[i / ExceptionHandlingFlowContext.BitCacheSize] |= 1 << (i % ExceptionHandlingFlowContext.BitCacheSize);
			}
			this.initsOnExceptions[catchBlock] = flowInfo.unconditionalCopy();
		} else {
			this.initsOnExceptions[catchBlock] = FlowInfo.DEAD_END;
		}
	}
	if (!this.isMethodContext) {
		System.arraycopy(this.isReached, 0, this.isNeeded, 0, cacheSize);
	}
	this.initsOnReturn = FlowInfo.DEAD_END;
	this.initializationParent = initializationParent;
}

public void complainIfUnusedExceptionHandlers(AbstractMethodDeclaration method) {
	MethodScope scope = method.scope;
	// can optionally skip overriding methods
	if ((method.binding.modifiers & (ExtraCompilerModifiers.AccOverriding | ExtraCompilerModifiers.AccImplementing)) != 0
	        && !scope.compilerOptions().reportUnusedDeclaredThrownExceptionWhenOverriding) {
	    return;
	}

	// report errors for unreachable exception handlers
	TypeBinding[] docCommentReferences = null;
	int docCommentReferencesLength = 0;
	if (scope.compilerOptions().
				reportUnusedDeclaredThrownExceptionIncludeDocCommentReference &&
			method.javadoc != null &&
			method.javadoc.exceptionReferences != null &&
			(docCommentReferencesLength = method.javadoc.exceptionReferences.length) > 0) {
		docCommentReferences = new TypeBinding[docCommentReferencesLength];
		for (int i = 0; i < docCommentReferencesLength; i++) {
			docCommentReferences[i] = method.javadoc.exceptionReferences[i].resolvedType;
		}
	}
	nextHandledException: for (int i = 0, count = this.handledExceptions.length; i < count; i++) {
		int index = this.indexes.get(this.handledExceptions[i]);
		if ((this.isReached[index / ExceptionHandlingFlowContext.BitCacheSize] & 1 << (index % ExceptionHandlingFlowContext.BitCacheSize)) == 0) {
			for (int j = 0; j < docCommentReferencesLength; j++) {
				if (TypeBinding.equalsEquals(docCommentReferences[j], this.handledExceptions[i])) {
					continue nextHandledException;
				}
			}
			scope.problemReporter().unusedDeclaredThrownException(
				this.handledExceptions[index],
				method,
				method.thrownExceptions[index]);
		}
	}
}

public void complainIfUnusedExceptionHandlers(BlockScope scope,TryStatement tryStatement) {
	// report errors for unreachable exception handlers
	for (int index = 0, count = this.handledExceptions.length; index < count; index++) {
		int cacheIndex = index / ExceptionHandlingFlowContext.BitCacheSize;
		int bitMask = 1 << (index % ExceptionHandlingFlowContext.BitCacheSize);
		if ((this.isReached[cacheIndex] & bitMask) == 0) {
			scope.problemReporter().unreachableCatchBlock(
				this.handledExceptions[index],
				getExceptionType(index));
		} else {
			if ((this.isNeeded[cacheIndex] & bitMask) == 0) {
				scope.problemReporter().hiddenCatchBlock(
					this.handledExceptions[index],
					getExceptionType(index));
			}
		}
	}
}

private ASTNode getExceptionType(int index) {	
	if (this.exceptionToCatchBlockMap == null) {
		return this.catchArguments[index].type;
	}
	int catchBlock = this.exceptionToCatchBlockMap[index];
	ASTNode node = this.catchArguments[catchBlock].type;
	if (node instanceof UnionTypeReference) {
		TypeReference[] typeRefs = ((UnionTypeReference)node).typeReferences;
		for (int i = 0, len = typeRefs.length; i < len; i++) {
			TypeReference typeRef = typeRefs[i];
			if (TypeBinding.equalsEquals(typeRef.resolvedType, this.handledExceptions[index])) return typeRef;
		}	
	} 
	return node;
}

@Override
public FlowContext getInitializationContext() {
	return this.initializationParent;
}

@Override
public String individualToString() {
	StringBuffer buffer = new StringBuffer("Exception flow context"); //$NON-NLS-1$
	int length = this.handledExceptions.length;
	for (int i = 0; i < length; i++) {
		int cacheIndex = i / ExceptionHandlingFlowContext.BitCacheSize;
		int bitMask = 1 << (i % ExceptionHandlingFlowContext.BitCacheSize);
		buffer.append('[').append(this.handledExceptions[i].readableName());
		if ((this.isReached[cacheIndex] & bitMask) != 0) {
			if ((this.isNeeded[cacheIndex] & bitMask) == 0) {
				buffer.append("-masked"); //$NON-NLS-1$
			} else {
				buffer.append("-reached"); //$NON-NLS-1$
			}
		} else {
			buffer.append("-not reached"); //$NON-NLS-1$
		}
		int catchBlock = this.exceptionToCatchBlockMap != null? this.exceptionToCatchBlockMap[i] : i;
		buffer.append('-').append(this.initsOnExceptions[catchBlock].toString()).append(']');
	}
	buffer.append("[initsOnReturn -").append(this.initsOnReturn.toString()).append(']'); //$NON-NLS-1$
	return buffer.toString();
}

// WARNING: index is the catch block index as in the program order, before any normalization is
// applied for multi catch
public UnconditionalFlowInfo initsOnException(int index) {
	return this.initsOnExceptions[index];
}

@Override
public UnconditionalFlowInfo initsOnReturn(){
	return this.initsOnReturn;
}

/*
 * Compute a merged list of unhandled exception types (keeping only the most generic ones).
 * This is necessary to add synthetic thrown exceptions for anonymous type constructors (JLS 8.6).
 */
public void mergeUnhandledException(TypeBinding newException){
	if (this.extendedExceptions == null){
		this.extendedExceptions = new ArrayList(5);
		for (int i = 0; i < this.handledExceptions.length; i++){
			this.extendedExceptions.add(this.handledExceptions[i]);
		}
	}
	boolean isRedundant = false;

	for(int i = this.extendedExceptions.size()-1; i >= 0; i--){
		switch(Scope.compareTypes(newException, (TypeBinding)this.extendedExceptions.get(i))){
			case Scope.MORE_GENERIC :
				this.extendedExceptions.remove(i);
				break;
			case Scope.EQUAL_OR_MORE_SPECIFIC :
				isRedundant = true;
				break;
			case Scope.NOT_RELATED :
				break;
		}
	}
	if (!isRedundant){
		this.extendedExceptions.add(newException);
	}
}

public void recordHandlingException(
		ReferenceBinding exceptionType,
		UnconditionalFlowInfo flowInfo,
		TypeBinding raisedException,
		TypeBinding caughtException,
		ASTNode invocationSite,
		boolean wasAlreadyDefinitelyCaught) {

	int index = this.indexes.get(exceptionType);
	int cacheIndex = index / ExceptionHandlingFlowContext.BitCacheSize;
	int bitMask = 1 << (index % ExceptionHandlingFlowContext.BitCacheSize);
	if (!wasAlreadyDefinitelyCaught) {
		this.isNeeded[cacheIndex] |= bitMask;
	}
	this.isReached[cacheIndex] |= bitMask;
	int catchBlock = this.exceptionToCatchBlockMap != null? this.exceptionToCatchBlockMap[index] : index;
	if (caughtException != null && this.catchArguments != null && this.catchArguments.length > 0 && !wasAlreadyDefinitelyCaught) {
		CatchParameterBinding catchParameter = (CatchParameterBinding) this.catchArguments[catchBlock].binding;
		catchParameter.setPreciseType(caughtException);
	}
	this.initsOnExceptions[catchBlock] =
		(this.initsOnExceptions[catchBlock].tagBits & FlowInfo.UNREACHABLE) == 0 ?
			this.initsOnExceptions[catchBlock].mergedWith(flowInfo):
			flowInfo.unconditionalCopy();
}

@Override
public void recordReturnFrom(UnconditionalFlowInfo flowInfo) {
	if ((flowInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) == 0) {
		if ((this.initsOnReturn.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) == 0) {
			this.initsOnReturn = this.initsOnReturn.mergedWith(flowInfo);
		}
		else {
			this.initsOnReturn = (UnconditionalFlowInfo) flowInfo.copy();
		}
	}
}

/**
 * Exception handlers (with no finally block) are also included with subroutine
 * only once (in case parented with true InsideSubRoutineFlowContext).
 * Standard management of subroutines need to also operate on intermediate
 * exception handlers.
 * @see org.eclipse.jdt.internal.compiler.flow.FlowContext#subroutine()
 */
@Override
public SubRoutineStatement subroutine() {
	if (this.associatedNode instanceof SubRoutineStatement) {
		// exception handler context may be child of InsideSubRoutineFlowContext, which maps to same handler
		if (this.parent.subroutine() == this.associatedNode)
			return null;
		return (SubRoutineStatement) this.associatedNode;
	}
	return null;
}
}
