/*******************************************************************************
 * Copyright (c) 2006 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
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.flow;

import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;

/**
 * A degenerate form of UnconditionalFlowInfo explicitly meant to capture
 * the effects of null related operations within try blocks. Given the fact
 * that a try block might exit at any time, a null related operation that
 * occurs within such a block mitigates whatever we know about the previous
 * null status of involved variables. NullInfoRegistry handles that
 * by negating upstream definite information that clashes with what a given
 * statement contends about the same variable. It also implements 
 * {@link #mitigateNullInfoOf(FlowInfo) mitigateNullInfo} so as to elaborate the
 * flow info presented in input of finally blocks.
 */
public class NullInfoRegistry extends UnconditionalFlowInfo {
	// significant states at this level:
  	// def. non null, def. null, def. unknown, prot. non null

// PREMATURE implement coverage and low level tests

/**
 * Make a new null info registry, using an upstream flow info. All definite
 * assignments of the upstream are carried forward, since a try block may
 * exit before its first statement.
 * @param upstream - UnconditionalFlowInfo: the flow info before we enter the
 * 		try block; only definite assignments are considered; this parameter is
 *  	not modified by this constructor
 */
public NullInfoRegistry(UnconditionalFlowInfo upstream) {
	if ((upstream.tagBits & NULL_FLAG_MASK) != 0) {
		long u1, u2, u3, u4, nu2, nu3, nu4;
		this.nullBit2 = (u1 = upstream.nullBit1)
			& (u2 = upstream.nullBit2)
			& (nu3 = ~(u3 = upstream.nullBit3))
			& (nu4 = ~(u4 = upstream.nullBit4));
		this.nullBit3 =	u1 & (nu2 = ~u2) & u3 & nu4;
		this.nullBit4 =	u1 & nu2 &nu3 & u4;
		if ((this.nullBit2 | this.nullBit3 | this.nullBit4) != 0) {
			this.tagBits |= NULL_FLAG_MASK;
		}
		if (upstream.extra != null) {
			this.extra = new long[extraLength][];
			int length = upstream.extra[2].length;
			for (int i = 2; i < extraLength; i++) {
				this.extra[i] = new long[length];
			}
			for (int i = 0; i < length; i++) {
        		this.extra[2 + 1][i] = (u1 = upstream.extra[1 + 1][i])
        			& (u2 = upstream.extra[2 + 1][i])
        			& (nu3 = ~(u3 = upstream.extra[3 + 1][i]))
        			& (nu4 = ~(u4 = upstream.extra[4 + 1][i]));
        		this.extra[3 + 1][i] =	u1 & (nu2 = ~u2) & u3 & nu4;
        		this.extra[4 + 1][i] =	u1 & nu2 &nu3 & u4;
        		if ((this.extra[2 + 1][i] | this.extra[3 + 1][i] | this.extra[4 + 1][i]) != 0) {
        			this.tagBits |= NULL_FLAG_MASK;
        		}
			}
		}
	}
}

/**
 * Add the information held by another NullInfoRegistry instance to this,
 * then return this.
 * @param other - NullInfoRegistry: the information to add to this
 * @return this, modified to carry the information held by other
 */
public NullInfoRegistry add(NullInfoRegistry other) {
	if ((other.tagBits & NULL_FLAG_MASK) == 0) {
		return this;
	}
	this.tagBits |= NULL_FLAG_MASK;
	this.nullBit1 |= other.nullBit1;
	this.nullBit2 |= other.nullBit2;
	this.nullBit3 |= other.nullBit3;
	this.nullBit4 |= other.nullBit4;
	if (other.extra != null) {
		if (this.extra == null) {
			this.extra = new long[extraLength][];
			for (int i = 2, length = other.extra[2].length; i < extraLength; i++) {
				System.arraycopy(other.extra[i], 0, 
					(this.extra[i] = new long[length]), 0, length);
			}
		} else {
			int length = this.extra[2].length, otherLength = other.extra[2].length;
			if (otherLength > length) {
				for (int i = 2; i < extraLength; i++) {
					System.arraycopy(this.extra[i], 0, 
						(this.extra[i] = new long[otherLength]), 0, length);
					System.arraycopy(other.extra[i], length, 
						this.extra[i], length, otherLength - length);
				}
			} else if (otherLength < length) {
				length = otherLength;
			}
			for (int i = 2; i < extraLength; i++) {
				for (int j = 0; j < length; j++) {
					this.extra[i][j] |= other.extra[i][j];
				}
			}
		}
	}
	return this;
}

public void markAsComparedEqualToNonNull(LocalVariableBinding local) {
	// protected from non-object locals in calling methods
	if (this != DEAD_END) {
    	this.tagBits |= NULL_FLAG_MASK;
    	int position;
    	// position is zero-based
    	if ((position = local.id + this.maxFieldCount) < BitCacheSize) { // use bits
    		// set protected non null
    		this.nullBit1 |= (1L << position);
    		if (coverageTestFlag && coverageTestId == 290) {
    		  	this.nullBit1 = 0;
    		}
    	} 
    	else {
    		// use extra vector
			int vectorIndex = (position / BitCacheSize) - 1;
			if (this.extra == null) {
				int length = vectorIndex + 1;
				this.extra = new long[extraLength][];
				for (int j = 2; j < extraLength; j++) {
					this.extra[j] = new long[length];
				}
			} 
			else {
				int oldLength; // might need to grow the arrays
				if (vectorIndex >= (oldLength = this.extra[2].length)) {
					for (int j = 2; j < extraLength; j++) {
						System.arraycopy(this.extra[j], 0, 
							(this.extra[j] = new long[vectorIndex + 1]), 0, 
							oldLength);
					}
				}
			}
    		this.extra[2][vectorIndex] |= (1L << (position % BitCacheSize));
    		if (coverageTestFlag && coverageTestId == 300) {
    		  	this.extra[5][vectorIndex] = ~0;
    		}
    	}
	}
}

public void markAsDefinitelyNonNull(LocalVariableBinding local) {
	// protected from non-object locals in calling methods
	if (this != DEAD_END) {
    	this.tagBits |= NULL_FLAG_MASK;
    	int position;
    	// position is zero-based
    	if ((position = local.id + this.maxFieldCount) < BitCacheSize) { // use bits
    		// set assigned non null
    		this.nullBit3 |= (1L << position);
    		if (coverageTestFlag && coverageTestId == 290) {
    		  	this.nullBit1 = 0;
    		}
    	} 
    	else {
    		// use extra vector
			int vectorIndex = (position / BitCacheSize) - 1;
			if (this.extra == null) {
				int length = vectorIndex + 1;
				this.extra = new long[extraLength][];
				for (int j = 2; j < extraLength; j++) {
					this.extra[j] = new long[length];
				}
			} 
			else {
				int oldLength; // might need to grow the arrays
				if (vectorIndex >= (oldLength = this.extra[2].length)) {
					for (int j = 2; j < extraLength; j++) {
						System.arraycopy(this.extra[j], 0, 
							(this.extra[j] = new long[vectorIndex + 1]), 0, 
							oldLength);
					}
				}
			}
    		this.extra[4][vectorIndex] |= (1L << (position % BitCacheSize));
    		if (coverageTestFlag && coverageTestId == 300) {
    		  	this.extra[5][vectorIndex] = ~0;
    		}
    	}
	}
}
// PREMATURE consider ignoring extra 0 to 2 included - means a1 should not be used either
// PREMATURE project protected non null onto something else
public void markAsDefinitelyNull(LocalVariableBinding local) {
	// protected from non-object locals in calling methods
	if (this != DEAD_END) {
    	this.tagBits |= NULL_FLAG_MASK;
    	int position;
    	// position is zero-based
    	if ((position = local.id + this.maxFieldCount) < BitCacheSize) { // use bits
    		// set assigned null
    		this.nullBit2 |= (1L << position);
    		if (coverageTestFlag && coverageTestId == 290) {
    		  	this.nullBit1 = 0;
    		}
    	} 
    	else {
    		// use extra vector
			int vectorIndex = (position / BitCacheSize) - 1;
			if (this.extra == null) {
				int length = vectorIndex + 1;
				this.extra = new long[extraLength][];
				for (int j = 2; j < extraLength; j++) {
					this.extra[j] = new long[length];
				}
			} 
			else {
				int oldLength; // might need to grow the arrays
				if (vectorIndex >= (oldLength = this.extra[2].length)) {
					for (int j = 2; j < extraLength; j++) {
						System.arraycopy(this.extra[j], 0, 
							(this.extra[j] = new long[vectorIndex + 1]), 0, 
							oldLength);
					}
				}
			}
    		this.extra[3][vectorIndex] |= (1L << (position % BitCacheSize));
    		if (coverageTestFlag && coverageTestId == 300) {
    		  	this.extra[5][vectorIndex] = ~0;
    		}
    	}
	}
}

public void markAsDefinitelyUnknown(LocalVariableBinding local) {
	// protected from non-object locals in calling methods
	if (this != DEAD_END) {
    	this.tagBits |= NULL_FLAG_MASK;
    	int position;
    	// position is zero-based
    	if ((position = local.id + this.maxFieldCount) < BitCacheSize) { // use bits
    		// set assigned unknown
    		this.nullBit4 |= (1L << position);
    		if (coverageTestFlag && coverageTestId == 290) {
    		  	this.nullBit1 = 0;
    		}
    	} 
    	else {
    		// use extra vector
			int vectorIndex = (position / BitCacheSize) - 1;
			if (this.extra == null) {
				int length = vectorIndex + 1;
				this.extra = new long[extraLength][];
				for (int j = 2; j < extraLength; j++) {
					this.extra[j] = new long[length];
				}
			} 
			else {
				int oldLength; // might need to grow the arrays
				if (vectorIndex >= (oldLength = this.extra[2].length)) {
					for (int j = 2; j < extraLength; j++) {
						System.arraycopy(this.extra[j], 0, 
							(this.extra[j] = new long[vectorIndex + 1]), 0, 
							oldLength);
					}
				}
			}
    		this.extra[5][vectorIndex] |= (1L << (position % BitCacheSize));
    		if (coverageTestFlag && coverageTestId == 300) {
    		  	this.extra[5][vectorIndex] = ~0;
    		}
    	}
	}
}

/**
 * Mitigate the definite and protected info of flowInfo, depending on what 
 * this null info registry knows about potential assignments and messages
 * sends involving locals. May return flowInfo unchanged, or a modified,
 * fresh copy of flowInfo.
 * @param flowInfo - FlowInfo: the flow information that this null info
 * 		registry may mitigate
 * @return a copy of flowInfo carrying mitigated information, or else
 * 		flowInfo unchanged
 */
public UnconditionalFlowInfo mitigateNullInfoOf(FlowInfo flowInfo) {
	if ((this.tagBits & NULL_FLAG_MASK) == 0) {
		return flowInfo.unconditionalInits();
	}
	long m, m1, nm1, m2, nm2, m3, a2, a3, a4, s1, s2, ns2, s3, ns3, s4, ns4;
	boolean newCopy = false;
	UnconditionalFlowInfo source = flowInfo.unconditionalInits();
	// clear incompatible protections
	m1 = (s1 = source.nullBit1) & (s3 = source.nullBit3) 
				& (s4 = source.nullBit4)
			// prot. non null
		& ((a2 = this.nullBit2) | (a4 = this.nullBit4));
			// null or unknown
	m2 = s1 & (s2 = this.nullBit2) & (s3 ^ s4)
			// prot. null
		& ((a3 = this.nullBit3) | a4);
			// non null or unknown
	// clear incompatible assignments
	// PREMATURE check effect of protected non null (no NPE on call)
	// TODO (maxime) code extensive implementation tests
	m3 = s1	& (s2 & (ns3 = ~s3) & (ns4 = ~s4) & (a3 | a4)
				| (ns2 = ~s2) & s3 & ns4 & (a2 | a4)
				| ns2 & ns3 & s4 & (a2 | a3)); 
	if ((m = (m1 | m2 | m3)) != 0) {
		newCopy = true;
		source = source.unconditionalCopy();
		source.nullBit1 &= ~m;
		source.nullBit2 &= (nm1 = ~m1) & ((nm2 = ~m2) | a4);
		source.nullBit3 &= (nm1 | a2) & nm2;
		source.nullBit4 &= nm1 & nm2;
	}
	if (this.extra != null && source.extra != null) {
		int length = this.extra[2].length, sourceLength = source.extra[0].length;
		if (sourceLength < length) {
			length = sourceLength;
		}
		for (int i = 0; i < length; i++) {
        	m1 = (s1 = source.extra[1 + 1][i]) & (s3 = source.extra[3 + 1][i]) 
        				& (s4 = source.extra[4 + 1][i])
        		& ((a2 = this.extra[2 + 1][i]) | (a4 = this.extra[4 + 1][i]));
        	m2 = s1 & (s2 = this.extra[2 + 1][i]) & (s3 ^ s4)
        		& ((a3 = this.extra[3 + 1][i]) | a4);
        	m3 = s1	& (s2 & (ns3 = ~s3) & (ns4 = ~s4) & (a3 | a4)
        				| (ns2 = ~s2) & s3 & ns4 & (a2 | a4)
        				| ns2 & ns3 & s4 & (a2 | a3)); 
        	if ((m = (m1 | m2 | m3)) != 0) {
        	  	if (! newCopy) {
            		newCopy = true;
            		source = source.unconditionalCopy();
        	  	}
        		source.extra[1 + 1][i] &= ~m;
        		source.extra[2 + 1][i] &= (nm1 = ~m1) & ((nm2 = ~m2) | a4);
        		source.extra[3 + 1][i] &= (nm1 | a2) & nm2;
        		source.extra[4 + 1][i] &= nm1 & nm2;
        	}
		}
	}
	return source;
}

public String toString(){
	if (this.extra == null) {
		return "NullInfoRegistry<" + this.nullBit1 //$NON-NLS-1$
			+ this.nullBit2 + this.nullBit3 + this.nullBit4
			+ ">"; //$NON-NLS-1$
	}
	else {
		String nullS = "NullInfoRegistry<[" + this.nullBit1 //$NON-NLS-1$
			+ this.nullBit2 + this.nullBit3 + this.nullBit4;
			int i, ceil;
			for (i = 0, ceil = this.extra[0].length > 3 ? 
								3 : 
								this.extra[0].length;
				i < ceil; i++) {
				nullS += "," + this.extra[2][i] //$NON-NLS-1$
				    + this.extra[3][i] + this.extra[4][i] + this.extra[5][i];
			}
			if (ceil < this.extra[0].length) {
				nullS += ",..."; //$NON-NLS-1$
			}
			return nullS + "]>"; //$NON-NLS-1$
	}
}
}

