/*******************************************************************************
 * Copyright (c) 2000, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Stephan Herrmann - Contributions for
 *								bug 186342 - [compiler][null] Using annotations for null checking
 *								bug 331649 - [compiler][null] consider null annotations for fields
 *								bug 383368 - [compiler][null] syntactic null analysis for field references
 *								bug 382069 - [null] Make the null analysis consider JUnit's assertNotNull similarly to assertions
 *								bug 403086 - [compiler][null] include the effect of 'assert' in syntactic null analysis for fields
 *******************************************************************************/
package org.aspectj.org.eclipse.jdt.internal.compiler.ast;

import org.aspectj.org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.aspectj.org.eclipse.jdt.internal.compiler.impl.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.aspectj.org.eclipse.jdt.internal.compiler.codegen.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.flow.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.*;

public class EqualExpression extends BinaryExpression {

	public EqualExpression(Expression left, Expression right,int operator) {
		super(left,right,operator);
	}
	private void checkNullComparison(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, FlowInfo initsWhenTrue, FlowInfo initsWhenFalse) {

		// collect null status of child nodes:
		int rightStatus = this.right.nullStatus(flowInfo, flowContext);
		int leftStatus = this.left.nullStatus(flowInfo, flowContext);

		boolean leftNonNullChecked = false;
		boolean rightNonNullChecked = false;

		// check if either is a non-local expression known to be nonnull and compared to null, candidates are
		// - method/field annotated @NonNull
		// - allocation expression, some literals, this reference (see inside expressionNonNullComparison(..))
		// these checks do not leverage the flowInfo.
		boolean checkEquality = ((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL;
		if ((flowContext.tagBits & FlowContext.HIDE_NULL_COMPARISON_WARNING_MASK) == 0) {
			if (leftStatus == FlowInfo.NON_NULL && rightStatus == FlowInfo.NULL) {
				leftNonNullChecked = scope.problemReporter().expressionNonNullComparison(this.left, checkEquality);
			} else if (leftStatus == FlowInfo.NULL && rightStatus == FlowInfo.NON_NULL) {
				rightNonNullChecked = scope.problemReporter().expressionNonNullComparison(this.right, checkEquality);
			}
		}
		
		boolean contextualCheckEquality = checkEquality ^ ((flowContext.tagBits & FlowContext.INSIDE_NEGATION) != 0);
		// perform flowInfo-based checks for variables and record info for syntactic null analysis for fields:
		if (!leftNonNullChecked) {
			LocalVariableBinding local = this.left.localVariableBinding();
			if (local != null) {
				if ((local.type.tagBits & TagBits.IsBaseType) == 0) {
					checkVariableComparison(scope, flowContext, flowInfo, initsWhenTrue, initsWhenFalse, local, rightStatus, this.left);
				}
			} else if (this.left instanceof Reference
							&& ((!contextualCheckEquality && rightStatus == FlowInfo.NULL) 
									|| (contextualCheckEquality && rightStatus == FlowInfo.NON_NULL))
							&& scope.compilerOptions().enableSyntacticNullAnalysisForFields)
			{
				FieldBinding field = ((Reference)this.left).lastFieldBinding();
				if (field != null && (field.type.tagBits & TagBits.IsBaseType) == 0) {
					flowContext.recordNullCheckedFieldReference((Reference) this.left, 1);
				}
			}
		}
		if (!rightNonNullChecked) {
			LocalVariableBinding local = this.right.localVariableBinding();
			if (local != null) { 
				if ((local.type.tagBits & TagBits.IsBaseType) == 0) {
					checkVariableComparison(scope, flowContext, flowInfo, initsWhenTrue, initsWhenFalse, local, leftStatus, this.right);
				}
			} else if (this.right instanceof Reference
							&& ((!contextualCheckEquality && leftStatus == FlowInfo.NULL) 
									|| (contextualCheckEquality && leftStatus == FlowInfo.NON_NULL))
							&& scope.compilerOptions().enableSyntacticNullAnalysisForFields) 
			{
				FieldBinding field = ((Reference)this.right).lastFieldBinding();
				if (field != null && (field.type.tagBits & TagBits.IsBaseType) == 0) {
					flowContext.recordNullCheckedFieldReference((Reference) this.right, 1);
				}				
			}
		}

		// handle reachability:
		if (leftNonNullChecked || rightNonNullChecked) {
			// above checks have not propagated unreachable into the corresponding branch, do it now:
			if (checkEquality) {
				initsWhenTrue.setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
			} else {
				initsWhenFalse.setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
			}
		}
	}
	private void checkVariableComparison(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, FlowInfo initsWhenTrue, FlowInfo initsWhenFalse, LocalVariableBinding local, int nullStatus, Expression reference) {
		switch (nullStatus) {
			case FlowInfo.NULL :
				if (((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL) {
					flowContext.recordUsingNullReference(scope, local, reference,
							FlowContext.CAN_ONLY_NULL_NON_NULL | FlowContext.IN_COMPARISON_NULL, flowInfo);
					initsWhenTrue.markAsComparedEqualToNull(local); // from thereon it is set
					initsWhenFalse.markAsComparedEqualToNonNull(local); // from thereon it is set
				} else {
					flowContext.recordUsingNullReference(scope, local, reference,
							FlowContext.CAN_ONLY_NULL_NON_NULL | FlowContext.IN_COMPARISON_NON_NULL, flowInfo);
					initsWhenTrue.markAsComparedEqualToNonNull(local); // from thereon it is set
					initsWhenFalse.markAsComparedEqualToNull(local); // from thereon it is set
				}
				break;
			case FlowInfo.NON_NULL :
				if (((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL) {
					flowContext.recordUsingNullReference(scope, local, reference,
							FlowContext.CAN_ONLY_NULL | FlowContext.IN_COMPARISON_NON_NULL, flowInfo);
					initsWhenTrue.markAsComparedEqualToNonNull(local); // from thereon it is set
				} else {
					flowContext.recordUsingNullReference(scope, local, reference,
							FlowContext.CAN_ONLY_NULL | FlowContext.IN_COMPARISON_NULL, flowInfo);
				}
				break;
		}
		// we do not impact enclosing try context because this kind of protection
		// does not preclude the variable from being null in an enclosing scope
	}
	private void analyzeLocalVariable(Expression exp, FlowInfo flowInfo) {
		if (exp instanceof SingleNameReference && (exp.bits & Binding.LOCAL) != 0 ) {
			LocalVariableBinding localBinding = (LocalVariableBinding) ((SingleNameReference ) exp).binding;
			if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) == 0) {
				localBinding.useFlag = LocalVariableBinding.USED;
			} else if (localBinding.useFlag == LocalVariableBinding.UNUSED ) {
				localBinding.useFlag = LocalVariableBinding.FAKE_USED;
			}
	}
	}
	@Override
	public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
		FlowInfo result;
		if (((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL) {
			if ((this.left.constant != Constant.NotAConstant) && (this.left.constant.typeID() == T_boolean)) {
				if (this.left.constant.booleanValue()) { //  true == anything
					//  this is equivalent to the right argument inits
					result = this.right.analyseCode(currentScope, flowContext, flowInfo);
				} else { // false == anything
					//  this is equivalent to the right argument inits negated
					result = this.right.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition();
					analyzeLocalVariable(this.left, flowInfo);
				}
			}
			else if ((this.right.constant != Constant.NotAConstant) && (this.right.constant.typeID() == T_boolean)) {
				if (this.right.constant.booleanValue()) { //  anything == true
					//  this is equivalent to the left argument inits
					result = this.left.analyseCode(currentScope, flowContext, flowInfo);
				} else { // anything == false
					//  this is equivalent to the right argument inits negated
					result = this.left.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition();
					analyzeLocalVariable(this.right, flowInfo);
				}
			}
			else {
				result = this.right.analyseCode(
					currentScope, flowContext,
					this.left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits()).unconditionalInits();
			}
		} else { //NOT_EQUAL :
			if ((this.left.constant != Constant.NotAConstant) && (this.left.constant.typeID() == T_boolean)) {
				if (!this.left.constant.booleanValue()) { //  false != anything
					//  this is equivalent to the right argument inits
					result = this.right.analyseCode(currentScope, flowContext, flowInfo);
					analyzeLocalVariable(this.left, flowInfo);
				} else { // true != anything
					//  this is equivalent to the right argument inits negated
					result = this.right.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition();
				}
			}
			else if ((this.right.constant != Constant.NotAConstant) && (this.right.constant.typeID() == T_boolean)) {
				if (!this.right.constant.booleanValue()) { //  anything != false
					//  this is equivalent to the right argument inits
					result = this.left.analyseCode(currentScope, flowContext, flowInfo);
					analyzeLocalVariable(this.right, flowInfo);
				} else { // anything != true
					//  this is equivalent to the right argument inits negated
					result = this.left.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition();
				}
			}
			else {
				result = this.right.analyseCode(
					currentScope, flowContext,
					this.left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits()).
					/* unneeded since we flatten it: asNegatedCondition(). */
					unconditionalInits();
			}
		}
		if (result instanceof UnconditionalFlowInfo &&
				(result.tagBits & FlowInfo.UNREACHABLE) == 0) { // the flow info is flat
			result = FlowInfo.conditional(result.copy(), result.copy());
			// TODO (maxime) check, reintroduced copy
		}
	  checkNullComparison(currentScope, flowContext, result, result.initsWhenTrue(), result.initsWhenFalse());
	  return result;
	}

	public final void computeConstant(TypeBinding leftType, TypeBinding rightType) {
		if ((this.left.constant != Constant.NotAConstant) && (this.right.constant != Constant.NotAConstant)) {
			this.constant =
				Constant.computeConstantOperationEQUAL_EQUAL(
					this.left.constant,
					leftType.id,
					this.right.constant,
					rightType.id);
			if (((this.bits & OperatorMASK) >> OperatorSHIFT) == NOT_EQUAL)
				this.constant = BooleanConstant.fromValue(!this.constant.booleanValue());
		} else {
			this.constant = Constant.NotAConstant;
			// no optimization for null == null
		}
	}
	/**
	 * Normal == or != code generation.
	 *
	 * @param currentScope org.aspectj.org.eclipse.jdt.internal.compiler.lookup.BlockScope
	 * @param codeStream org.aspectj.org.eclipse.jdt.internal.compiler.codegen.CodeStream
	 * @param valueRequired boolean
	 */
	@Override
	public void generateCode(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) {

		int pc = codeStream.position;
		if (this.constant != Constant.NotAConstant) {
			if (valueRequired)
				codeStream.generateConstant(this.constant, this.implicitConversion);
			codeStream.recordPositionsFrom(pc, this.sourceStart);
			return;
		}

		if ((this.left.implicitConversion & COMPILE_TYPE_MASK) /*compile-time*/ == T_boolean) {
			generateBooleanEqual(currentScope, codeStream, valueRequired);
		} else {
			generateNonBooleanEqual(currentScope, codeStream, valueRequired);
		}
		if (valueRequired) {
			codeStream.generateImplicitConversion(this.implicitConversion);
		}
		codeStream.recordPositionsFrom(pc, this.sourceStart);
	}
	/**
	 * Boolean operator code generation
	 *	Optimized operations are: == and !=
	 */
	@Override
	public void generateOptimizedBoolean(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel falseLabel, boolean valueRequired) {

		if (this.constant != Constant.NotAConstant) {
			super.generateOptimizedBoolean(currentScope, codeStream, trueLabel, falseLabel, valueRequired);
			return;
		}
		if (((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL) {
			if ((this.left.implicitConversion & COMPILE_TYPE_MASK) /*compile-time*/ == T_boolean) {
				generateOptimizedBooleanEqual(currentScope, codeStream, trueLabel, falseLabel, valueRequired);
			} else {
				generateOptimizedNonBooleanEqual(currentScope, codeStream, trueLabel, falseLabel, valueRequired);
			}
		} else {
			if ((this.left.implicitConversion & COMPILE_TYPE_MASK) /*compile-time*/ == T_boolean) {
				generateOptimizedBooleanEqual(currentScope, codeStream, falseLabel, trueLabel, valueRequired);
			} else {
				generateOptimizedNonBooleanEqual(currentScope, codeStream, falseLabel, trueLabel, valueRequired);
			}
		}
	}

	/**
	 * Boolean generation for == with boolean operands
	 *
	 * Note this code does not optimize conditional constants !!!!
	 */
	public void generateBooleanEqual(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) {

		// optimized cases: <something equivalent to true> == x, <something equivalent to false> == x,
		// optimized cases: <something equivalent to false> != x, <something equivalent to true> != x,
		boolean isEqualOperator = ((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL;
		Constant cst = this.left.optimizedBooleanConstant();
		if (cst != Constant.NotAConstant) {
			Constant rightCst = this.right.optimizedBooleanConstant();
			if (rightCst != Constant.NotAConstant) {
				// <something equivalent to true> == <something equivalent to true>, <something equivalent to false> != <something equivalent to true>
				// <something equivalent to true> == <something equivalent to false>, <something equivalent to false> != <something equivalent to false>
				this.left.generateCode(currentScope, codeStream, false);
				this.right.generateCode(currentScope, codeStream, false);
				if (valueRequired) {
					boolean leftBool = cst.booleanValue();
					boolean rightBool = rightCst.booleanValue();
					if (isEqualOperator) {
						if (leftBool == rightBool) {
							codeStream.iconst_1();
						} else {
							codeStream.iconst_0();
						}
					} else {
						if (leftBool != rightBool) {
							codeStream.iconst_1();
						} else {
							codeStream.iconst_0();
						}
					}
				}
			} else if (cst.booleanValue() == isEqualOperator) {
				// <something equivalent to true> == x, <something equivalent to false> != x
				this.left.generateCode(currentScope, codeStream, false);
				this.right.generateCode(currentScope, codeStream, valueRequired);
			} else {
				// <something equivalent to false> == x, <something equivalent to true> != x
				if (valueRequired) {
					BranchLabel falseLabel = new BranchLabel(codeStream);
					this.left.generateCode(currentScope, codeStream, false);
					this.right.generateOptimizedBoolean(currentScope, codeStream, null, falseLabel, valueRequired);
					// comparison is TRUE
					codeStream.iconst_0();
					if ((this.bits & IsReturnedValue) != 0){
						codeStream.generateImplicitConversion(this.implicitConversion);
						codeStream.generateReturnBytecode(this);
						// comparison is FALSE
						falseLabel.place();
						codeStream.iconst_1();
					} else {
						BranchLabel endLabel = new BranchLabel(codeStream);
						codeStream.goto_(endLabel);
						codeStream.decrStackSize(1);
						// comparison is FALSE
						falseLabel.place();
						codeStream.iconst_1();
						endLabel.place();
					}
				} else {
					this.left.generateCode(currentScope, codeStream, false);
					this.right.generateCode(currentScope, codeStream, false);
				}
//				left.generateCode(currentScope, codeStream, false);
//				right.generateCode(currentScope, codeStream, valueRequired);
//				if (valueRequired) {
//					codeStream.iconst_1();
//					codeStream.ixor(); // negate
//				}
			}
			return;
		}
		cst = this.right.optimizedBooleanConstant();
		if (cst != Constant.NotAConstant) {
			if (cst.booleanValue() == isEqualOperator) {
				// x == <something equivalent to true>, x != <something equivalent to false>
				this.left.generateCode(currentScope, codeStream, valueRequired);
				this.right.generateCode(currentScope, codeStream, false);
			} else {
				// x == <something equivalent to false>, x != <something equivalent to true>
				if (valueRequired) {
					BranchLabel falseLabel = new BranchLabel(codeStream);
					this.left.generateOptimizedBoolean(currentScope, codeStream, null, falseLabel, valueRequired);
					this.right.generateCode(currentScope, codeStream, false);
					// comparison is TRUE
					codeStream.iconst_0();
					if ((this.bits & IsReturnedValue) != 0){
						codeStream.generateImplicitConversion(this.implicitConversion);
						codeStream.generateReturnBytecode(this);
						// comparison is FALSE
						falseLabel.place();
						codeStream.iconst_1();
					} else {
						BranchLabel endLabel = new BranchLabel(codeStream);
						codeStream.goto_(endLabel);
						codeStream.decrStackSize(1);
						// comparison is FALSE
						falseLabel.place();
						codeStream.iconst_1();
						endLabel.place();
					}
				} else {
					this.left.generateCode(currentScope, codeStream, false);
					this.right.generateCode(currentScope, codeStream, false);
				}
//				left.generateCode(currentScope, codeStream, valueRequired);
//				right.generateCode(currentScope, codeStream, false);
//				if (valueRequired) {
//					codeStream.iconst_1();
//					codeStream.ixor(); // negate
//				}
			}
			return;
		}
		// default case
		this.left.generateCode(currentScope, codeStream, valueRequired);
		this.right.generateCode(currentScope, codeStream, valueRequired);

		if (valueRequired) {
			if (isEqualOperator) {
				BranchLabel falseLabel;
				codeStream.if_icmpne(falseLabel = new BranchLabel(codeStream));
				// comparison is TRUE
				codeStream.iconst_1();
				if ((this.bits & IsReturnedValue) != 0){
					codeStream.generateImplicitConversion(this.implicitConversion);
					codeStream.generateReturnBytecode(this);
					// comparison is FALSE
					falseLabel.place();
					codeStream.iconst_0();
				} else {
					BranchLabel endLabel = new BranchLabel(codeStream);
					codeStream.goto_(endLabel);
					codeStream.decrStackSize(1);
					// comparison is FALSE
					falseLabel.place();
					codeStream.iconst_0();
					endLabel.place();
				}
			} else {
				codeStream.ixor();
			}
		}
	}

	/**
	 * Boolean generation for == with boolean operands
	 *
	 * Note this code does not optimize conditional constants !!!!
	 */
	public void generateOptimizedBooleanEqual(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel falseLabel, boolean valueRequired) {

		// optimized cases: true == x, false == x
		if (this.left.constant != Constant.NotAConstant) {
			boolean inline = this.left.constant.booleanValue();
			this.right.generateOptimizedBoolean(currentScope, codeStream, (inline ? trueLabel : falseLabel), (inline ? falseLabel : trueLabel), valueRequired);
			return;
		} // optimized cases: x == true, x == false
		if (this.right.constant != Constant.NotAConstant) {
			boolean inline = this.right.constant.booleanValue();
			this.left.generateOptimizedBoolean(currentScope, codeStream, (inline ? trueLabel : falseLabel), (inline ? falseLabel : trueLabel), valueRequired);
			return;
		}
		// default case
		this.left.generateCode(currentScope, codeStream, valueRequired);
		this.right.generateCode(currentScope, codeStream, valueRequired);
		int pc = codeStream.position;
		if (valueRequired) {
			if (falseLabel == null) {
				if (trueLabel != null) {
					// implicit falling through the FALSE case
					codeStream.if_icmpeq(trueLabel);
				}
			} else {
				// implicit falling through the TRUE case
				if (trueLabel == null) {
					codeStream.if_icmpne(falseLabel);
				} else {
					// no implicit fall through TRUE/FALSE --> should never occur
				}
			}
		}
		codeStream.recordPositionsFrom(pc, this.sourceEnd);
	}
	/**
	 * Boolean generation for == with non-boolean operands
	 *
	 */
	public void generateNonBooleanEqual(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) {

		boolean isEqualOperator = ((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL;
		if (((this.left.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) == T_int) {
			Constant cst;
			if ((cst = this.left.constant) != Constant.NotAConstant && cst.intValue() == 0) {
				// optimized case: 0 == x, 0 != x
				this.right.generateCode(currentScope, codeStream, valueRequired);
				if (valueRequired) {
					BranchLabel falseLabel = new BranchLabel(codeStream);
					if (isEqualOperator) {
						codeStream.ifne(falseLabel);
					} else {
						codeStream.ifeq(falseLabel);
					}
					// comparison is TRUE
					codeStream.iconst_1();
					if ((this.bits & IsReturnedValue) != 0){
						codeStream.generateImplicitConversion(this.implicitConversion);
						codeStream.generateReturnBytecode(this);
						// comparison is FALSE
						falseLabel.place();
						codeStream.iconst_0();
					} else {
						BranchLabel endLabel = new BranchLabel(codeStream);
						codeStream.goto_(endLabel);
						codeStream.decrStackSize(1);
						// comparison is FALSE
						falseLabel.place();
						codeStream.iconst_0();
						endLabel.place();
					}
				}
				return;
			}
			if ((cst = this.right.constant) != Constant.NotAConstant && cst.intValue() == 0) {
				// optimized case: x == 0, x != 0
				this.left.generateCode(currentScope, codeStream, valueRequired);
				if (valueRequired) {
					BranchLabel falseLabel = new BranchLabel(codeStream);
					if (isEqualOperator) {
						codeStream.ifne(falseLabel);
					} else {
						codeStream.ifeq(falseLabel);
					}
					// comparison is TRUE
					codeStream.iconst_1();
					if ((this.bits & IsReturnedValue) != 0){
						codeStream.generateImplicitConversion(this.implicitConversion);
						codeStream.generateReturnBytecode(this);
						// comparison is FALSE
						falseLabel.place();
						codeStream.iconst_0();
					} else {
						BranchLabel endLabel = new BranchLabel(codeStream);
						codeStream.goto_(endLabel);
						codeStream.decrStackSize(1);
						// comparison is FALSE
						falseLabel.place();
						codeStream.iconst_0();
						endLabel.place();
					}
				}
				return;
			}
		}

		// null cases
		if (this.right instanceof NullLiteral) {
			if (this.left instanceof NullLiteral) {
				// null == null, null != null
				if (valueRequired) {
					if (isEqualOperator) {
						codeStream.iconst_1();
					} else {
						codeStream.iconst_0();
					}
				}
			} else {
				// x == null, x != null
				this.left.generateCode(currentScope, codeStream, valueRequired);
				if (valueRequired) {
					BranchLabel falseLabel = new BranchLabel(codeStream);
					if (isEqualOperator) {
						codeStream.ifnonnull(falseLabel);
					} else {
						codeStream.ifnull(falseLabel);
					}
					// comparison is TRUE
					codeStream.iconst_1();
					if ((this.bits & IsReturnedValue) != 0){
						codeStream.generateImplicitConversion(this.implicitConversion);
						codeStream.generateReturnBytecode(this);
						// comparison is FALSE
						falseLabel.place();
						codeStream.iconst_0();
					} else {
						BranchLabel endLabel = new BranchLabel(codeStream);
						codeStream.goto_(endLabel);
						codeStream.decrStackSize(1);
						// comparison is FALSE
						falseLabel.place();
						codeStream.iconst_0();
						endLabel.place();
					}
				}
			}
			return;
		} else if (this.left instanceof NullLiteral) {
			// null = x, null != x
			this.right.generateCode(currentScope, codeStream, valueRequired);
			if (valueRequired) {
				BranchLabel falseLabel = new BranchLabel(codeStream);
				if (isEqualOperator) {
					codeStream.ifnonnull(falseLabel);
				} else {
					codeStream.ifnull(falseLabel);
				}
				// comparison is TRUE
				codeStream.iconst_1();
				if ((this.bits & IsReturnedValue) != 0){
					codeStream.generateImplicitConversion(this.implicitConversion);
					codeStream.generateReturnBytecode(this);
					// comparison is FALSE
					falseLabel.place();
					codeStream.iconst_0();
				} else {
					BranchLabel endLabel = new BranchLabel(codeStream);
					codeStream.goto_(endLabel);
					codeStream.decrStackSize(1);
					// comparison is FALSE
					falseLabel.place();
					codeStream.iconst_0();
					endLabel.place();
				}
			}
			return;
		}

		// default case
		this.left.generateCode(currentScope, codeStream, valueRequired);
		this.right.generateCode(currentScope, codeStream, valueRequired);
		if (valueRequired) {
			BranchLabel falseLabel = new BranchLabel(codeStream);
			if (isEqualOperator) {
				switch ((this.left.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) { // operand runtime type
					case T_int :
						codeStream.if_icmpne(falseLabel);
						break;
					case T_float :
						codeStream.fcmpl();
						codeStream.ifne(falseLabel);
						break;
					case T_long :
						codeStream.lcmp();
						codeStream.ifne(falseLabel);
						break;
					case T_double :
						codeStream.dcmpl();
						codeStream.ifne(falseLabel);
						break;
					default :
						codeStream.if_acmpne(falseLabel);
				}
			} else {
				switch ((this.left.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) { // operand runtime type
					case T_int :
						codeStream.if_icmpeq(falseLabel);
						break;
					case T_float :
						codeStream.fcmpl();
						codeStream.ifeq(falseLabel);
						break;
					case T_long :
						codeStream.lcmp();
						codeStream.ifeq(falseLabel);
						break;
					case T_double :
						codeStream.dcmpl();
						codeStream.ifeq(falseLabel);
						break;
					default :
						codeStream.if_acmpeq(falseLabel);
				}
			}
			// comparison is TRUE
			codeStream.iconst_1();
			if ((this.bits & IsReturnedValue) != 0){
				codeStream.generateImplicitConversion(this.implicitConversion);
				codeStream.generateReturnBytecode(this);
				// comparison is FALSE
				falseLabel.place();
				codeStream.iconst_0();
			} else {
				BranchLabel endLabel = new BranchLabel(codeStream);
				codeStream.goto_(endLabel);
				codeStream.decrStackSize(1);
				// comparison is FALSE
				falseLabel.place();
				codeStream.iconst_0();
				endLabel.place();
			}
		}
	}

	/**
	 * Boolean generation for == with non-boolean operands
	 *
	 */
	public void generateOptimizedNonBooleanEqual(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel falseLabel, boolean valueRequired) {

		int pc = codeStream.position;
		Constant inline;
		if ((inline = this.right.constant) != Constant.NotAConstant) {
			// optimized case: x == 0
			if ((((this.left.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) == T_int) && (inline.intValue() == 0)) {
				this.left.generateCode(currentScope, codeStream, valueRequired);
				if (valueRequired) {
					if (falseLabel == null) {
						if (trueLabel != null) {
							// implicit falling through the FALSE case
							codeStream.ifeq(trueLabel);
						}
					} else {
						// implicit falling through the TRUE case
						if (trueLabel == null) {
							codeStream.ifne(falseLabel);
						} else {
							// no implicit fall through TRUE/FALSE --> should never occur
						}
					}
				}
				codeStream.recordPositionsFrom(pc, this.sourceStart);
				return;
			}
		}
		if ((inline = this.left.constant) != Constant.NotAConstant) {
			// optimized case: 0 == x
			if ((((this.left.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) == T_int)
				&& (inline.intValue() == 0)) {
				this.right.generateCode(currentScope, codeStream, valueRequired);
				if (valueRequired) {
					if (falseLabel == null) {
						if (trueLabel != null) {
							// implicit falling through the FALSE case
							codeStream.ifeq(trueLabel);
						}
					} else {
						// implicit falling through the TRUE case
						if (trueLabel == null) {
							codeStream.ifne(falseLabel);
						} else {
							// no implicit fall through TRUE/FALSE --> should never occur
						}
					}
				}
				codeStream.recordPositionsFrom(pc, this.sourceStart);
				return;
			}
		}
		// null cases
		// optimized case: x == null
		if (this.right instanceof NullLiteral) {
			if (this.left instanceof NullLiteral) {
				// null == null
				if (valueRequired) {
					if (falseLabel == null) {
						// implicit falling through the FALSE case
						if (trueLabel != null) {
							codeStream.goto_(trueLabel);
						}
					}
				}
			} else {
				this.left.generateCode(currentScope, codeStream, valueRequired);
				if (valueRequired) {
					if (falseLabel == null) {
						if (trueLabel != null) {
							// implicit falling through the FALSE case
							codeStream.ifnull(trueLabel);
						}
					} else {
						// implicit falling through the TRUE case
						if (trueLabel == null) {
							codeStream.ifnonnull(falseLabel);
						} else {
							// no implicit fall through TRUE/FALSE --> should never occur
						}
					}
				}
			}
			codeStream.recordPositionsFrom(pc, this.sourceStart);
			return;
		} else if (this.left instanceof NullLiteral) { // optimized case: null == x
			this.right.generateCode(currentScope, codeStream, valueRequired);
			if (valueRequired) {
				if (falseLabel == null) {
					if (trueLabel != null) {
						// implicit falling through the FALSE case
						codeStream.ifnull(trueLabel);
					}
				} else {
					// implicit falling through the TRUE case
					if (trueLabel == null) {
						codeStream.ifnonnull(falseLabel);
					} else {
						// no implicit fall through TRUE/FALSE --> should never occur
					}
				}
			}
			codeStream.recordPositionsFrom(pc, this.sourceStart);
			return;
		}

		// default case
		this.left.generateCode(currentScope, codeStream, valueRequired);
		this.right.generateCode(currentScope, codeStream, valueRequired);
		if (valueRequired) {
			if (falseLabel == null) {
				if (trueLabel != null) {
					// implicit falling through the FALSE case
					switch ((this.left.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) { // operand runtime type
						case T_int :
							codeStream.if_icmpeq(trueLabel);
							break;
						case T_float :
							codeStream.fcmpl();
							codeStream.ifeq(trueLabel);
							break;
						case T_long :
							codeStream.lcmp();
							codeStream.ifeq(trueLabel);
							break;
						case T_double :
							codeStream.dcmpl();
							codeStream.ifeq(trueLabel);
							break;
						default :
							codeStream.if_acmpeq(trueLabel);
					}
				}
			} else {
				// implicit falling through the TRUE case
				if (trueLabel == null) {
					switch ((this.left.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) { // operand runtime type
						case T_int :
							codeStream.if_icmpne(falseLabel);
							break;
						case T_float :
							codeStream.fcmpl();
							codeStream.ifne(falseLabel);
							break;
						case T_long :
							codeStream.lcmp();
							codeStream.ifne(falseLabel);
							break;
						case T_double :
							codeStream.dcmpl();
							codeStream.ifne(falseLabel);
							break;
						default :
							codeStream.if_acmpne(falseLabel);
					}
				} else {
					// no implicit fall through TRUE/FALSE --> should never occur
				}
			}
		}
		codeStream.recordPositionsFrom(pc, this.sourceStart);
	}
	@Override
	public boolean isCompactableOperation() {
		return false;
	}
	@Override
	public TypeBinding resolveType(BlockScope scope) {

			boolean leftIsCast, rightIsCast;
			if ((leftIsCast = this.left instanceof CastExpression) == true) this.left.bits |= DisableUnnecessaryCastCheck; // will check later on
			TypeBinding originalLeftType = this.left.resolveType(scope);

			if ((rightIsCast = this.right instanceof CastExpression) == true) this.right.bits |= DisableUnnecessaryCastCheck; // will check later on
			TypeBinding originalRightType = this.right.resolveType(scope);

		// always return BooleanBinding
		if (originalLeftType == null || originalRightType == null){
			this.constant = Constant.NotAConstant;
			return null;
		}

		final CompilerOptions compilerOptions = scope.compilerOptions();
		if (compilerOptions.complainOnUninternedIdentityComparison && originalRightType.hasTypeBit(TypeIds.BitUninternedType) && originalLeftType.hasTypeBit(TypeIds.BitUninternedType))
			scope.problemReporter().uninternedIdentityComparison(this, originalLeftType, originalRightType, scope.referenceCompilationUnit());

		// autoboxing support
		boolean use15specifics = compilerOptions.sourceLevel >= ClassFileConstants.JDK1_5;
		TypeBinding leftType = originalLeftType, rightType = originalRightType;
		if (use15specifics) {
			if (leftType != TypeBinding.NULL && leftType.isBaseType()) {
				if (!rightType.isBaseType()) {
					rightType = scope.environment().computeBoxingType(rightType);
				}
			} else {
				if (rightType != TypeBinding.NULL && rightType.isBaseType()) {
					leftType = scope.environment().computeBoxingType(leftType);
				}
			}
		}
		// both base type
		if (leftType.isBaseType() && rightType.isBaseType()) {
			int leftTypeID = leftType.id;
			int rightTypeID = rightType.id;

			// the code is an int
			// (cast)  left   == (cast)  right --> result
			//  0000   0000       0000   0000      0000
			//  <<16   <<12       <<8    <<4       <<0
			int operatorSignature = OperatorSignatures[EQUAL_EQUAL][ (leftTypeID << 4) + rightTypeID];
			this.left.computeConversion(scope, TypeBinding.wellKnownType(scope, (operatorSignature >>> 16) & 0x0000F), originalLeftType);
			this.right.computeConversion(scope, TypeBinding.wellKnownType(scope, (operatorSignature >>> 8) & 0x0000F), originalRightType);
			this.bits |= operatorSignature & 0xF;
			if ((operatorSignature & 0x0000F) == T_undefined) {
				this.constant = Constant.NotAConstant;
				scope.problemReporter().invalidOperator(this, leftType, rightType);
				return null;
			}
			// check need for operand cast
			if (leftIsCast || rightIsCast) {
				CastExpression.checkNeedForArgumentCasts(scope, EQUAL_EQUAL, operatorSignature, this.left, leftType.id, leftIsCast, this.right, rightType.id, rightIsCast);
			}
			computeConstant(leftType, rightType);

			// check whether comparing identical expressions
			Binding leftDirect = Expression.getDirectBinding(this.left);
			if (leftDirect != null && leftDirect == Expression.getDirectBinding(this.right)) {
				if (leftTypeID != TypeIds.T_double && leftTypeID != TypeIds.T_float
						&&(!(this.right instanceof Assignment))) // https://bugs.eclipse.org/bugs/show_bug.cgi?id=281776
					scope.problemReporter().comparingIdenticalExpressions(this);
			} else if (this.constant != Constant.NotAConstant) {
				// https://bugs.eclipse.org/bugs/show_bug.cgi?id=276740
				int operator = (this.bits & OperatorMASK) >> OperatorSHIFT;
				if ((operator == EQUAL_EQUAL && this.constant == BooleanConstant.fromValue(true))
						|| (operator == NOT_EQUAL && this.constant == BooleanConstant.fromValue(false)))
					scope.problemReporter().comparingIdenticalExpressions(this);
			}
			return this.resolvedType = TypeBinding.BOOLEAN;
		}

		// Object references
		// spec 15.20.3
		if ((!leftType.isBaseType() || leftType == TypeBinding.NULL) // cannot compare: Object == (int)0
				&& (!rightType.isBaseType() || rightType == TypeBinding.NULL)
				&& (checkCastTypesCompatibility(scope, leftType, rightType, null)
						|| checkCastTypesCompatibility(scope, rightType, leftType, null))) {

			// (special case for String)
			if ((rightType.id == T_JavaLangString) && (leftType.id == T_JavaLangString)) {
				computeConstant(leftType, rightType);
			} else {
				this.constant = Constant.NotAConstant;
			}
			TypeBinding objectType = scope.getJavaLangObject();
			this.left.computeConversion(scope, objectType, leftType);
			this.right.computeConversion(scope, objectType, rightType);
			// check need for operand cast
			boolean unnecessaryLeftCast = (this.left.bits & UnnecessaryCast) != 0;
			boolean unnecessaryRightCast = (this.right.bits & UnnecessaryCast) != 0;
			if (unnecessaryLeftCast || unnecessaryRightCast) {
				TypeBinding alternateLeftType = unnecessaryLeftCast ? ((CastExpression)this.left).expression.resolvedType : leftType;
				TypeBinding alternateRightType = unnecessaryRightCast ? ((CastExpression)this.right).expression.resolvedType : rightType;
				if (checkCastTypesCompatibility(scope, alternateLeftType, alternateRightType, null)
						|| checkCastTypesCompatibility(scope, alternateRightType, alternateLeftType, null)) {
					if (unnecessaryLeftCast) scope.problemReporter().unnecessaryCast((CastExpression)this.left);
					if (unnecessaryRightCast) scope.problemReporter().unnecessaryCast((CastExpression)this.right);
				}
			}
			// check whether comparing identical expressions
			Binding leftDirect = Expression.getDirectBinding(this.left);
			if (leftDirect != null && leftDirect == Expression.getDirectBinding(this.right)) {
				if (!(this.right instanceof Assignment)) {
					scope.problemReporter().comparingIdenticalExpressions(this);
				}
			}
			return this.resolvedType = TypeBinding.BOOLEAN;
		}
		this.constant = Constant.NotAConstant;
		scope.problemReporter().notCompatibleTypesError(this, leftType, rightType);
		return null;
	}
	@Override
	public void traverse(ASTVisitor visitor, BlockScope scope) {
		if (visitor.visit(this, scope)) {
			this.left.traverse(visitor, scope);
			this.right.traverse(visitor, scope);
		}
		visitor.endVisit(this, scope);
	}
}
