/*******************************************************************************
 * Copyright (c) 2000, 2023 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 - Contribution for
 *								bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null"
 *								bug 383368 - [compiler][null] syntactic null analysis for field references
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;

import java.util.List;

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

public class BinaryExpression extends OperatorExpression {

/* Tracking helpers
 * The following are used to elaborate realistic statistics about binary
 * expressions. This must be neutralized in the released code.
 * Search the keyword BE_INSTRUMENTATION to reenable.
 * An external device must install a suitable probe so as to monitor the
 * emission of events and publish the results.
	public interface Probe {
		public void ping(int depth);
	}
	public int depthTracker;
	public static Probe probe;
 */

	public Expression left, right;
	public Constant optimizedBooleanConstant;

public BinaryExpression(Expression left, Expression right, int operator) {
	this.left = left;
	this.right = right;
	this.bits |= operator << ASTNode.OperatorSHIFT; // encode operator
	this.sourceStart = left.sourceStart;
	this.sourceEnd = right.sourceEnd;
	// BE_INSTRUMENTATION: neutralized in the released code
//	if (left instanceof BinaryExpression &&
//			((left.bits & OperatorMASK) ^ (this.bits & OperatorMASK)) == 0) {
//		this.depthTracker = ((BinaryExpression)left).depthTracker + 1;
//	} else {
//		this.depthTracker = 1;
//	}
}
public BinaryExpression(BinaryExpression expression) {
	this.left = expression.left;
	this.right = expression.right;
	this.bits = expression.bits;
	this.sourceStart = expression.sourceStart;
	this.sourceEnd = expression.sourceEnd;
}
@Override
public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
	// keep implementation in sync with CombinedBinaryExpression#analyseCode
	try {
		if (this.resolvedType.id == TypeIds.T_JavaLangString) {
			return this.right.analyseCode(
								currentScope, flowContext,
								this.left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits())
							.unconditionalInits();
		} else {
			flowInfo = this.left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits();
			this.left.checkNPE(currentScope, flowContext, flowInfo);
			if (((this.bits & OperatorMASK) >> OperatorSHIFT) != AND) {
				flowContext.expireNullCheckedFieldInfo();
			}
			flowInfo = this.right.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits();
			this.right.checkNPE(currentScope, flowContext, flowInfo);
			if (((this.bits & OperatorMASK) >> OperatorSHIFT) != AND) {
				flowContext.expireNullCheckedFieldInfo();
			}
			return flowInfo;
		}
	} finally {
		// account for exception possibly thrown by arithmetics
		flowContext.recordAbruptExit();
	}
}

@Override
protected void updateFlowOnBooleanResult(FlowInfo flowInfo, boolean result) {
	int operator = (this.bits & OperatorMASK) >> OperatorSHIFT;
	if (result ? operator == AND_AND : operator == OR_OR) {
		this.left.updateFlowOnBooleanResult(flowInfo, result);
		this.right.updateFlowOnBooleanResult(flowInfo, result);
	}
}

public void computeConstant(BlockScope scope, int leftId, int rightId) {
	//compute the constant when valid
	if ((this.left.constant != Constant.NotAConstant)
		&& (this.right.constant != Constant.NotAConstant)) {
		try {
			this.constant =
				Constant.computeConstantOperation(
					this.left.constant,
					leftId,
					(this.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT,
					this.right.constant,
					rightId);
		} catch (ArithmeticException e) {
			this.constant = Constant.NotAConstant;
			// 1.2 no longer throws an exception at compile-time
			//scope.problemReporter().compileTimeConstantThrowsArithmeticException(this);
		}
	} else {
		this.constant = Constant.NotAConstant;
		//add some work for the boolean operators & |
		this.optimizedBooleanConstant(
			leftId,
			(this.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT,
			rightId);
	}
}

@Override
public Constant optimizedBooleanConstant() {
	return this.optimizedBooleanConstant == null ? this.constant : this.optimizedBooleanConstant;
}

/**
 * Code generation for a binary operation
 */
// given the current focus of CombinedBinaryExpression on strings concatenation,
// we do not provide a general, non-recursive implementation of generateCode,
// but rely upon generateOptimizedStringConcatenationCreation instead
@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;
	}
	switch ((this.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT) {
		case PLUS :
			switch (this.bits & ASTNode.ReturnTypeIDMASK) {
				case T_JavaLangString :
					// BE_INSTRUMENTATION: neutralized in the released code
//					if (probe != null) {
//						probe.ping(this.depthTracker);
//					}
					codeStream.generateStringConcatenationAppend(currentScope, this.left, this.right);
					if (!valueRequired)
						codeStream.pop();
					break;
				case T_int :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.iadd();
					break;
				case T_long :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.ladd();
					break;
				case T_double :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.dadd();
					break;
				case T_float :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.fadd();
					break;
			}
			break;
		case MINUS :
			switch (this.bits & ASTNode.ReturnTypeIDMASK) {
				case T_int :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.isub();
					break;
				case T_long :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.lsub();
					break;
				case T_double :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.dsub();
					break;
				case T_float :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.fsub();
					break;
			}
			break;
		case MULTIPLY :
			switch (this.bits & ASTNode.ReturnTypeIDMASK) {
				case T_int :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.imul();
					break;
				case T_long :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.lmul();
					break;
				case T_double :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.dmul();
					break;
				case T_float :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.fmul();
					break;
			}
			break;
		case DIVIDE :
			switch (this.bits & ASTNode.ReturnTypeIDMASK) {
				case T_int :
					this.left.generateCode(currentScope, codeStream, true);
					this.right.generateCode(currentScope, codeStream, true);
					codeStream.idiv();
					if (!valueRequired)
						codeStream.pop();
					break;
				case T_long :
					this.left.generateCode(currentScope, codeStream, true);
					this.right.generateCode(currentScope, codeStream, true);
					codeStream.ldiv();
					if (!valueRequired)
						codeStream.pop2();
					break;
				case T_double :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.ddiv();
					break;
				case T_float :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.fdiv();
					break;
			}
			break;
		case REMAINDER :
			switch (this.bits & ASTNode.ReturnTypeIDMASK) {
				case T_int :
					this.left.generateCode(currentScope, codeStream, true);
					this.right.generateCode(currentScope, codeStream, true);
					codeStream.irem();
					if (!valueRequired)
						codeStream.pop();
					break;
				case T_long :
					this.left.generateCode(currentScope, codeStream, true);
					this.right.generateCode(currentScope, codeStream, true);
					codeStream.lrem();
					if (!valueRequired)
						codeStream.pop2();
					break;
				case T_double :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.drem();
					break;
				case T_float :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.frem();
					break;
			}
			break;
		case AND :
			switch (this.bits & ASTNode.ReturnTypeIDMASK) {
				case T_int :
					// 0 & x
					if ((this.left.constant != Constant.NotAConstant)
						&& (this.left.constant.typeID() == TypeIds.T_int)
						&& (this.left.constant.intValue() == 0)) {
						this.right.generateCode(currentScope, codeStream, false);
						if (valueRequired)
							codeStream.iconst_0();
					} else {
						// x & 0
						if ((this.right.constant != Constant.NotAConstant)
							&& (this.right.constant.typeID() == TypeIds.T_int)
							&& (this.right.constant.intValue() == 0)) {
							this.left.generateCode(currentScope, codeStream, false);
							if (valueRequired)
								codeStream.iconst_0();
						} else {
							this.left.generateCode(currentScope, codeStream, valueRequired);
							this.right.generateCode(currentScope, codeStream, valueRequired);
							if (valueRequired)
								codeStream.iand();
						}
					}
					break;
				case T_long :
					// 0 & x
					if ((this.left.constant != Constant.NotAConstant)
						&& (this.left.constant.typeID() == TypeIds.T_long)
						&& (this.left.constant.longValue() == 0L)) {
						this.right.generateCode(currentScope, codeStream, false);
						if (valueRequired)
							codeStream.lconst_0();
					} else {
						// x & 0
						if ((this.right.constant != Constant.NotAConstant)
							&& (this.right.constant.typeID() == TypeIds.T_long)
							&& (this.right.constant.longValue() == 0L)) {
							this.left.generateCode(currentScope, codeStream, false);
							if (valueRequired)
								codeStream.lconst_0();
						} else {
							this.left.generateCode(currentScope, codeStream, valueRequired);
							this.right.generateCode(currentScope, codeStream, valueRequired);
							if (valueRequired)
								codeStream.land();
						}
					}
					break;
				case T_boolean : // logical and
					generateLogicalAnd(currentScope, codeStream, valueRequired);
					break;
			}
			break;
		case OR :
			switch (this.bits & ASTNode.ReturnTypeIDMASK) {
				case T_int :
					// 0 | x
					if ((this.left.constant != Constant.NotAConstant)
						&& (this.left.constant.typeID() == TypeIds.T_int)
						&& (this.left.constant.intValue() == 0)) {
						this.right.generateCode(currentScope, codeStream, valueRequired);
					} else {
						// x | 0
						if ((this.right.constant != Constant.NotAConstant)
							&& (this.right.constant.typeID() == TypeIds.T_int)
							&& (this.right.constant.intValue() == 0)) {
							this.left.generateCode(currentScope, codeStream, valueRequired);
						} else {
							this.left.generateCode(currentScope, codeStream, valueRequired);
							this.right.generateCode(currentScope, codeStream, valueRequired);
							if (valueRequired)
								codeStream.ior();
						}
					}
					break;
				case T_long :
					// 0 | x
					if ((this.left.constant != Constant.NotAConstant)
						&& (this.left.constant.typeID() == TypeIds.T_long)
						&& (this.left.constant.longValue() == 0L)) {
						this.right.generateCode(currentScope, codeStream, valueRequired);
					} else {
						// x | 0
						if ((this.right.constant != Constant.NotAConstant)
							&& (this.right.constant.typeID() == TypeIds.T_long)
							&& (this.right.constant.longValue() == 0L)) {
							this.left.generateCode(currentScope, codeStream, valueRequired);
						} else {
							this.left.generateCode(currentScope, codeStream, valueRequired);
							this.right.generateCode(currentScope, codeStream, valueRequired);
							if (valueRequired)
								codeStream.lor();
						}
					}
					break;
				case T_boolean : // logical or
					generateLogicalOr(currentScope, codeStream, valueRequired);
					break;
			}
			break;
		case XOR :
			switch (this.bits & ASTNode.ReturnTypeIDMASK) {
				case T_int :
					// 0 ^ x
					if ((this.left.constant != Constant.NotAConstant)
						&& (this.left.constant.typeID() == TypeIds.T_int)
						&& (this.left.constant.intValue() == 0)) {
						this.right.generateCode(currentScope, codeStream, valueRequired);
					} else {
						// x ^ 0
						if ((this.right.constant != Constant.NotAConstant)
							&& (this.right.constant.typeID() == TypeIds.T_int)
							&& (this.right.constant.intValue() == 0)) {
							this.left.generateCode(currentScope, codeStream, valueRequired);
						} else {
							this.left.generateCode(currentScope, codeStream, valueRequired);
							this.right.generateCode(currentScope, codeStream, valueRequired);
							if (valueRequired)
								codeStream.ixor();
						}
					}
					break;
				case T_long :
					// 0 ^ x
					if ((this.left.constant != Constant.NotAConstant)
						&& (this.left.constant.typeID() == TypeIds.T_long)
						&& (this.left.constant.longValue() == 0L)) {
						this.right.generateCode(currentScope, codeStream, valueRequired);
					} else {
						// x ^ 0
						if ((this.right.constant != Constant.NotAConstant)
							&& (this.right.constant.typeID() == TypeIds.T_long)
							&& (this.right.constant.longValue() == 0L)) {
							this.left.generateCode(currentScope, codeStream, valueRequired);
						} else {
							this.left.generateCode(currentScope, codeStream, valueRequired);
							this.right.generateCode(currentScope, codeStream, valueRequired);
							if (valueRequired)
								codeStream.lxor();
						}
					}
					break;
				case T_boolean :
					generateLogicalXor(currentScope, 	codeStream, valueRequired);
					break;
			}
			break;
		case LEFT_SHIFT :
			switch (this.bits & ASTNode.ReturnTypeIDMASK) {
				case T_int :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.ishl();
					break;
				case T_long :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.lshl();
			}
			break;
		case RIGHT_SHIFT :
			switch (this.bits & ASTNode.ReturnTypeIDMASK) {
				case T_int :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.ishr();
					break;
				case T_long :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.lshr();
			}
			break;
		case UNSIGNED_RIGHT_SHIFT :
			switch (this.bits & ASTNode.ReturnTypeIDMASK) {
				case T_int :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.iushr();
					break;
				case T_long :
					this.left.generateCode(currentScope, codeStream, valueRequired);
					this.right.generateCode(currentScope, codeStream, valueRequired);
					if (valueRequired)
						codeStream.lushr();
			}
			break;
		case GREATER :
			BranchLabel falseLabel, endLabel;
			generateOptimizedGreaterThan(
				currentScope,
				codeStream,
				null,
				(falseLabel = new BranchLabel(codeStream)),
				valueRequired);
			if (valueRequired) {
				codeStream.iconst_1();
				if ((this.bits & ASTNode.IsReturnedValue) != 0) {
					codeStream.generateImplicitConversion(this.implicitConversion);
					codeStream.generateReturnBytecode(this);
					falseLabel.place();
					codeStream.iconst_0();
				} else {
					codeStream.goto_(endLabel = new BranchLabel(codeStream));
					codeStream.decrStackSize(1);
					falseLabel.place();
					codeStream.iconst_0();
					endLabel.place();
				}
			}
			break;
		case GREATER_EQUAL :
			generateOptimizedGreaterThanOrEqual(
				currentScope,
				codeStream,
				null,
				(falseLabel = new BranchLabel(codeStream)),
				valueRequired);
			if (valueRequired) {
				codeStream.iconst_1();
				if ((this.bits & ASTNode.IsReturnedValue) != 0) {
					codeStream.generateImplicitConversion(this.implicitConversion);
					codeStream.generateReturnBytecode(this);
					falseLabel.place();
					codeStream.iconst_0();
				} else {
					codeStream.goto_(endLabel = new BranchLabel(codeStream));
					codeStream.decrStackSize(1);
					falseLabel.place();
					codeStream.iconst_0();
					endLabel.place();
				}
			}
			break;
		case LESS :
			generateOptimizedLessThan(
				currentScope,
				codeStream,
				null,
				(falseLabel = new BranchLabel(codeStream)),
				valueRequired);
			if (valueRequired) {
				codeStream.iconst_1();
				if ((this.bits & ASTNode.IsReturnedValue) != 0) {
					codeStream.generateImplicitConversion(this.implicitConversion);
					codeStream.generateReturnBytecode(this);
					falseLabel.place();
					codeStream.iconst_0();
				} else {
					codeStream.goto_(endLabel = new BranchLabel(codeStream));
					codeStream.decrStackSize(1);
					falseLabel.place();
					codeStream.iconst_0();
					endLabel.place();
				}
			}
			break;
		case LESS_EQUAL :
			generateOptimizedLessThanOrEqual(
				currentScope,
				codeStream,
				null,
				(falseLabel = new BranchLabel(codeStream)),
				valueRequired);
			if (valueRequired) {
				codeStream.iconst_1();
				if ((this.bits & ASTNode.IsReturnedValue) != 0) {
					codeStream.generateImplicitConversion(this.implicitConversion);
					codeStream.generateReturnBytecode(this);
					falseLabel.place();
					codeStream.iconst_0();
				} else {
					codeStream.goto_(endLabel = new BranchLabel(codeStream));
					codeStream.decrStackSize(1);
					falseLabel.place();
					codeStream.iconst_0();
					endLabel.place();
				}
			}
	}
	if (valueRequired) {
		codeStream.generateImplicitConversion(this.implicitConversion);
	}
	codeStream.recordPositionsFrom(pc, this.sourceStart);
}

/**
 * Boolean operator code generation
 *	Optimized operations are: <, <=, >, >=, &, |, ^
 */
@Override
public void generateOptimizedBoolean(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel falseLabel, boolean valueRequired) {
	if ((this.constant != Constant.NotAConstant) && (this.constant.typeID() == TypeIds.T_boolean)) {
		super.generateOptimizedBoolean(
			currentScope,
			codeStream,
			trueLabel,
			falseLabel,
			valueRequired);
		return;
	}
	switch ((this.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT) {
		case LESS :
			generateOptimizedLessThan(
				currentScope,
				codeStream,
				trueLabel,
				falseLabel,
				valueRequired);
			return;
		case LESS_EQUAL :
			generateOptimizedLessThanOrEqual(
				currentScope,
				codeStream,
				trueLabel,
				falseLabel,
				valueRequired);
			return;
		case GREATER :
			generateOptimizedGreaterThan(
				currentScope,
				codeStream,
				trueLabel,
				falseLabel,
				valueRequired);
			return;
		case GREATER_EQUAL :
			generateOptimizedGreaterThanOrEqual(
				currentScope,
				codeStream,
				trueLabel,
				falseLabel,
				valueRequired);
			return;
		case AND :
			generateOptimizedLogicalAnd(
				currentScope,
				codeStream,
				trueLabel,
				falseLabel,
				valueRequired);
			return;
		case OR :
			generateOptimizedLogicalOr(
				currentScope,
				codeStream,
				trueLabel,
				falseLabel,
				valueRequired);
			return;
		case XOR :
			generateOptimizedLogicalXor(
				currentScope,
				codeStream,
				trueLabel,
				falseLabel,
				valueRequired);
			return;
	}
	super.generateOptimizedBoolean(
		currentScope,
		codeStream,
		trueLabel,
		falseLabel,
		valueRequired);
}

/**
 * Boolean generation for >
 */
public void generateOptimizedGreaterThan(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel falseLabel, boolean valueRequired) {
	int promotedTypeID = (this.left.implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4;
	// both sides got promoted in the same way
	if (promotedTypeID == TypeIds.T_int) {
		// 0 > x
		if ((this.left.constant != Constant.NotAConstant) && (this.left.constant.intValue() == 0)) {
			this.right.generateCode(currentScope, codeStream, valueRequired);
			if (valueRequired) {
				if (falseLabel == null) {
					if (trueLabel != null) {
						// implicitly falling through the FALSE case
						codeStream.iflt(trueLabel);
					}
				} else {
					if (trueLabel == null) {
						// implicitly falling through the TRUE case
						codeStream.ifge(falseLabel);
					} else {
						// no implicit fall through TRUE/FALSE --> should never occur
					}
				}
			}
			// reposition the endPC
			codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			return;
		}
		// x > 0
		if ((this.right.constant != Constant.NotAConstant) && (this.right.constant.intValue() == 0)) {
			this.left.generateCode(currentScope, codeStream, valueRequired);
			if (valueRequired) {
				if (falseLabel == null) {
					if (trueLabel != null) {
						// implicitly falling through the FALSE case
						codeStream.ifgt(trueLabel);
					}
				} else {
					if (trueLabel == null) {
						// implicitly falling through the TRUE case
						codeStream.ifle(falseLabel);
					} else {
						// no implicit fall through TRUE/FALSE --> should never occur
					}
				}
			}
			// reposition the endPC
			codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			return;
		}
	}
	// default comparison
	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 (promotedTypeID) {
					case T_int :
						codeStream.if_icmpgt(trueLabel);
						break;
					case T_float :
						codeStream.fcmpl();
						codeStream.ifgt(trueLabel);
						break;
					case T_long :
						codeStream.lcmp();
						codeStream.ifgt(trueLabel);
						break;
					case T_double :
						codeStream.dcmpl();
						codeStream.ifgt(trueLabel);
				}
				// reposition the endPC
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
				return;
			}
		} else {
			if (trueLabel == null) {
				// implicit falling through the TRUE case
				switch (promotedTypeID) {
					case T_int :
						codeStream.if_icmple(falseLabel);
						break;
					case T_float :
						codeStream.fcmpl();
						codeStream.ifle(falseLabel);
						break;
					case T_long :
						codeStream.lcmp();
						codeStream.ifle(falseLabel);
						break;
					case T_double :
						codeStream.dcmpl();
						codeStream.ifle(falseLabel);
				}
				// reposition the endPC
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
				return;
			} else {
				// no implicit fall through TRUE/FALSE --> should never occur
			}
		}
	}
}

/**
 * Boolean generation for >=
 */
public void generateOptimizedGreaterThanOrEqual(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel falseLabel, boolean valueRequired) {
	int promotedTypeID = (this.left.implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4;
	// both sides got promoted in the same way
	if (promotedTypeID == TypeIds.T_int) {
		// 0 >= x
		if ((this.left.constant != Constant.NotAConstant) && (this.left.constant.intValue() == 0)) {
			this.right.generateCode(currentScope, codeStream, valueRequired);
			if (valueRequired) {
				if (falseLabel == null) {
					if (trueLabel != null) {
						// implicitly falling through the FALSE case
						codeStream.ifle(trueLabel);
					}
				} else {
					if (trueLabel == null) {
						// implicitly falling through the TRUE case
						codeStream.ifgt(falseLabel);
					} else {
						// no implicit fall through TRUE/FALSE --> should never occur
					}
				}
			}
			// reposition the endPC
			codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			return;
		}
		// x >= 0
		if ((this.right.constant != Constant.NotAConstant) && (this.right.constant.intValue() == 0)) {
			this.left.generateCode(currentScope, codeStream, valueRequired);
			if (valueRequired) {
				if (falseLabel == null) {
					if (trueLabel != null) {
						// implicitly falling through the FALSE case
						codeStream.ifge(trueLabel);
					}
				} else {
					if (trueLabel == null) {
						// implicitly falling through the TRUE case
						codeStream.iflt(falseLabel);
					} else {
						// no implicit fall through TRUE/FALSE --> should never occur
					}
				}
			}
			// reposition the endPC
			codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			return;
		}
	}
	// default comparison
	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 (promotedTypeID) {
					case T_int :
						codeStream.if_icmpge(trueLabel);
						break;
					case T_float :
						codeStream.fcmpl();
						codeStream.ifge(trueLabel);
						break;
					case T_long :
						codeStream.lcmp();
						codeStream.ifge(trueLabel);
						break;
					case T_double :
						codeStream.dcmpl();
						codeStream.ifge(trueLabel);
				}
				// reposition the endPC
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
				return;
			}
		} else {
			if (trueLabel == null) {
				// implicit falling through the TRUE case
				switch (promotedTypeID) {
					case T_int :
						codeStream.if_icmplt(falseLabel);
						break;
					case T_float :
						codeStream.fcmpl();
						codeStream.iflt(falseLabel);
						break;
					case T_long :
						codeStream.lcmp();
						codeStream.iflt(falseLabel);
						break;
					case T_double :
						codeStream.dcmpl();
						codeStream.iflt(falseLabel);
				}
				// reposition the endPC
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
				return;
			} else {
				// no implicit fall through TRUE/FALSE --> should never occur
			}
		}
	}
}

/**
 * Boolean generation for <
 */
public void generateOptimizedLessThan(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel falseLabel, boolean valueRequired) {
	int promotedTypeID = (this.left.implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4;
	// both sides got promoted in the same way
	if (promotedTypeID == TypeIds.T_int) {
		// 0 < x
		if ((this.left.constant != Constant.NotAConstant) && (this.left.constant.intValue() == 0)) {
			this.right.generateCode(currentScope, codeStream, valueRequired);
			if (valueRequired) {
				if (falseLabel == null) {
					if (trueLabel != null) {
						// implicitly falling through the FALSE case
						codeStream.ifgt(trueLabel);
					}
				} else {
					if (trueLabel == null) {
						// implicitly falling through the TRUE case
						codeStream.ifle(falseLabel);
					} else {
						// no implicit fall through TRUE/FALSE --> should never occur
					}
				}
			}
			codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			return;
		}
		// x < 0
		if ((this.right.constant != Constant.NotAConstant) && (this.right.constant.intValue() == 0)) {
			this.left.generateCode(currentScope, codeStream, valueRequired);
			if (valueRequired) {
				if (falseLabel == null) {
					if (trueLabel != null) {
						// implicitly falling through the FALSE case
						codeStream.iflt(trueLabel);
					}
				} else {
					if (trueLabel == null) {
						// implicitly falling through the TRUE case
						codeStream.ifge(falseLabel);
					} else {
						// no implicit fall through TRUE/FALSE --> should never occur
					}
				}
			}
			codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			return;
		}
	}
	// default comparison
	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 (promotedTypeID) {
					case T_int :
						codeStream.if_icmplt(trueLabel);
						break;
					case T_float :
						codeStream.fcmpg();
						codeStream.iflt(trueLabel);
						break;
					case T_long :
						codeStream.lcmp();
						codeStream.iflt(trueLabel);
						break;
					case T_double :
						codeStream.dcmpg();
						codeStream.iflt(trueLabel);
				}
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
				return;
			}
		} else {
			if (trueLabel == null) {
				// implicit falling through the TRUE case
				switch (promotedTypeID) {
					case T_int :
						codeStream.if_icmpge(falseLabel);
						break;
					case T_float :
						codeStream.fcmpg();
						codeStream.ifge(falseLabel);
						break;
					case T_long :
						codeStream.lcmp();
						codeStream.ifge(falseLabel);
						break;
					case T_double :
						codeStream.dcmpg();
						codeStream.ifge(falseLabel);
				}
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
				return;
			} else {
				// no implicit fall through TRUE/FALSE --> should never occur
			}
		}
	}
}

/**
 * Boolean generation for <=
 */
public void generateOptimizedLessThanOrEqual(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel falseLabel, boolean valueRequired) {
	int promotedTypeID = (this.left.implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4;
	// both sides got promoted in the same way
	if (promotedTypeID == TypeIds.T_int) {
		// 0 <= x
		if ((this.left.constant != Constant.NotAConstant) && (this.left.constant.intValue() == 0)) {
			this.right.generateCode(currentScope, codeStream, valueRequired);
			if (valueRequired) {
				if (falseLabel == null) {
					if (trueLabel != null) {
						// implicitly falling through the FALSE case
						codeStream.ifge(trueLabel);
					}
				} else {
					if (trueLabel == null) {
						// implicitly falling through the TRUE case
						codeStream.iflt(falseLabel);
					} else {
						// no implicit fall through TRUE/FALSE --> should never occur
					}
				}
			}
			// reposition the endPC
			codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			return;
		}
		// x <= 0
		if ((this.right.constant != Constant.NotAConstant) && (this.right.constant.intValue() == 0)) {
			this.left.generateCode(currentScope, codeStream, valueRequired);
			if (valueRequired) {
				if (falseLabel == null) {
					if (trueLabel != null) {
						// implicitly falling through the FALSE case
						codeStream.ifle(trueLabel);
					}
				} else {
					if (trueLabel == null) {
						// implicitly falling through the TRUE case
						codeStream.ifgt(falseLabel);
					} else {
						// no implicit fall through TRUE/FALSE --> should never occur
					}
				}
			}
			// reposition the endPC
			codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			return;
		}
	}
	// default comparison
	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 (promotedTypeID) {
					case T_int :
						codeStream.if_icmple(trueLabel);
						break;
					case T_float :
						codeStream.fcmpg();
						codeStream.ifle(trueLabel);
						break;
					case T_long :
						codeStream.lcmp();
						codeStream.ifle(trueLabel);
						break;
					case T_double :
						codeStream.dcmpg();
						codeStream.ifle(trueLabel);
				}
				// reposition the endPC
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
				return;
			}
		} else {
			if (trueLabel == null) {
				// implicit falling through the TRUE case
				switch (promotedTypeID) {
					case T_int :
						codeStream.if_icmpgt(falseLabel);
						break;
					case T_float :
						codeStream.fcmpg();
						codeStream.ifgt(falseLabel);
						break;
					case T_long :
						codeStream.lcmp();
						codeStream.ifgt(falseLabel);
						break;
					case T_double :
						codeStream.dcmpg();
						codeStream.ifgt(falseLabel);
				}
				// reposition the endPC
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
				return;
			} else {
				// no implicit fall through TRUE/FALSE --> should never occur
			}
		}
	}
}

/**
 * Boolean generation for &
 */
public void generateLogicalAnd(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) {
	Constant condConst;
	if ((this.left.implicitConversion & TypeIds.COMPILE_TYPE_MASK) == TypeIds.T_boolean) {
		if ((condConst = this.left.optimizedBooleanConstant()) != Constant.NotAConstant) {
			if (condConst.booleanValue() == true) {
				// <something equivalent to true> & x
				this.left.generateCode(currentScope, codeStream, false);
				this.right.generateCode(currentScope, codeStream, valueRequired);
			} else {
				// <something equivalent to false> & x
				this.left.generateCode(currentScope, codeStream, false);
				this.right.generateCode(currentScope, codeStream, false);
				if (valueRequired) {
					codeStream.iconst_0();
				}
				// reposition the endPC
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			}
			return;
		}
		if ((condConst = this.right.optimizedBooleanConstant()) != Constant.NotAConstant) {
			if (condConst.booleanValue() == true) {
				// x & <something equivalent to true>
				this.left.generateCode(currentScope, codeStream, valueRequired);
				this.right.generateCode(currentScope, codeStream, false);
			} else {
				// x & <something equivalent to false>
				this.left.generateCode(currentScope, codeStream, false);
				this.right.generateCode(currentScope, codeStream, false);
				if (valueRequired) {
					codeStream.iconst_0();
				}
				// reposition the endPC
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			}
			return;
		}
	}
	// default case
	this.left.generateCode(currentScope, codeStream, valueRequired);
	this.right.generateCode(currentScope, codeStream, valueRequired);
	if (valueRequired) {
		codeStream.iand();
	}
	codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
}

/**
 * Boolean generation for |
 */
public void generateLogicalOr(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) {
	Constant condConst;
	if ((this.left.implicitConversion & TypeIds.COMPILE_TYPE_MASK) == TypeIds.T_boolean) {
		if ((condConst = this.left.optimizedBooleanConstant()) != Constant.NotAConstant) {
			if (condConst.booleanValue() == true) {
				// <something equivalent to true> | x
				this.left.generateCode(currentScope, codeStream, false);
				this.right.generateCode(currentScope, codeStream, false);
				if (valueRequired) {
					codeStream.iconst_1();
				}
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			} else {
				// <something equivalent to false> | x
				this.left.generateCode(currentScope, codeStream, false);
				this.right.generateCode(currentScope, codeStream, valueRequired);
			}
			return;
		}
		if ((condConst = this.right.optimizedBooleanConstant()) != Constant.NotAConstant) {
			if (condConst.booleanValue() == true) {
				// x | <something equivalent to true>
				this.left.generateCode(currentScope, codeStream, false);
				this.right.generateCode(currentScope, codeStream, false);
				if (valueRequired) {
					codeStream.iconst_1();
				}
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			} else {
				// x | <something equivalent to false>
				this.left.generateCode(currentScope, codeStream, valueRequired);
				this.right.generateCode(currentScope, codeStream, false);
			}
			return;
		}
	}
	// default case
	this.left.generateCode(currentScope, codeStream, valueRequired);
	this.right.generateCode(currentScope, codeStream, valueRequired);
	if (valueRequired) {
		codeStream.ior();
	}
	codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
}

/**
 * Boolean generation for ^
 */
public void generateLogicalXor(BlockScope currentScope,	CodeStream codeStream, boolean valueRequired) {
	Constant condConst;
	if ((this.left.implicitConversion & TypeIds.COMPILE_TYPE_MASK) == TypeIds.T_boolean) {
		if ((condConst = this.left.optimizedBooleanConstant()) != Constant.NotAConstant) {
			if (condConst.booleanValue() == true) {
				// <something equivalent to true> ^ x
				this.left.generateCode(currentScope, codeStream, false);
				if (valueRequired) {
					codeStream.iconst_1();
				}
				this.right.generateCode(currentScope, codeStream, valueRequired);
				if (valueRequired) {
					codeStream.ixor(); // negate
					codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
				}
			} else {
				// <something equivalent to false> ^ x
				this.left.generateCode(currentScope, codeStream, false);
				this.right.generateCode(currentScope, codeStream, valueRequired);
			}
			return;
		}
		if ((condConst = this.right.optimizedBooleanConstant()) != Constant.NotAConstant) {
			if (condConst.booleanValue() == true) {
				// x ^ <something equivalent to true>
				this.left.generateCode(currentScope, codeStream, valueRequired);
				this.right.generateCode(currentScope, codeStream, false);
				if (valueRequired) {
					codeStream.iconst_1();
					codeStream.ixor(); // negate
					codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
				}
			} else {
				// x ^ <something equivalent to false>
				this.left.generateCode(currentScope, codeStream, valueRequired);
				this.right.generateCode(currentScope, codeStream, false);
			}
			return;
		}
	}
	// default case
	this.left.generateCode(currentScope, codeStream, valueRequired);
	this.right.generateCode(currentScope, codeStream, valueRequired);
	if (valueRequired) {
		codeStream.ixor();
	}
	codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
}

/**
 * Boolean generation for &
 */
public void generateOptimizedLogicalAnd(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel falseLabel, boolean valueRequired) {
	Constant condConst;
	if ((this.left.implicitConversion & TypeIds.COMPILE_TYPE_MASK) == TypeIds.T_boolean) {
		if ((condConst = this.left.optimizedBooleanConstant()) != Constant.NotAConstant) {
			if (condConst.booleanValue() == true) {
				// <something equivalent to true> & x
				this.left.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					false);
				this.right.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					valueRequired);
			} else {
				// <something equivalent to false> & x
				this.left.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					false);
				this.right.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					false);
				if (valueRequired) {
					if (falseLabel != null) {
						// implicit falling through the TRUE case
						codeStream.goto_(falseLabel);
					}
				}
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			}
			return;
		}
		if ((condConst = this.right.optimizedBooleanConstant()) != Constant.NotAConstant) {
			if (condConst.booleanValue() == true) {
				// x & <something equivalent to true>
				this.left.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					valueRequired);
				this.right.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					false);
			} else {
				// x & <something equivalent to false>
				BranchLabel internalTrueLabel = new BranchLabel(codeStream);
				this.left.generateOptimizedBoolean(
					currentScope,
					codeStream,
					internalTrueLabel,
					falseLabel,
					false);
				internalTrueLabel.place();
				this.right.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					false);
				if (valueRequired) {
					if (falseLabel != null) {
						// implicit falling through the TRUE case
						codeStream.goto_(falseLabel);
					}
				}
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			}
			return;
		}
	}
	// default case
	this.left.generateCode(currentScope, codeStream, valueRequired);
	this.right.generateCode(currentScope, codeStream, valueRequired);
	if (valueRequired) {
		codeStream.iand();
		if (falseLabel == null) {
			if (trueLabel != null) {
				// implicit falling through the FALSE case
				codeStream.ifne(trueLabel);
			}
		} else {
			// implicit falling through the TRUE case
			if (trueLabel == null) {
				codeStream.ifeq(falseLabel);
			} else {
				// no implicit fall through TRUE/FALSE --> should never occur
			}
		}
	}
	codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
}

/**
 * Boolean generation for |
 */
public void generateOptimizedLogicalOr(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel falseLabel, boolean valueRequired) {
	Constant condConst;
	if ((this.left.implicitConversion & TypeIds.COMPILE_TYPE_MASK) == TypeIds.T_boolean) {
		if ((condConst = this.left.optimizedBooleanConstant()) != Constant.NotAConstant) {
			if (condConst.booleanValue() == true) {
				// <something equivalent to true> | x
				this.left.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					false);
				BranchLabel internalFalseLabel = new BranchLabel(codeStream);
				this.right.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					internalFalseLabel,
					false);
				internalFalseLabel.place();
				if (valueRequired) {
					if (trueLabel != null) {
						codeStream.goto_(trueLabel);
					}
				}
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			} else {
				// <something equivalent to false> | x
				this.left.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					false);
				this.right.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					valueRequired);
			}
			return;
		}
		if ((condConst = this.right.optimizedBooleanConstant()) != Constant.NotAConstant) {
			if (condConst.booleanValue() == true) {
				// x | <something equivalent to true>
				BranchLabel internalFalseLabel = new BranchLabel(codeStream);
				this.left.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					internalFalseLabel,
					false);
				internalFalseLabel.place();
				this.right.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					false);
				if (valueRequired) {
					if (trueLabel != null) {
						codeStream.goto_(trueLabel);
					}
				}
				codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
			} else {
				// x | <something equivalent to false>
				this.left.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					valueRequired);
				this.right.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					false);
			}
			return;
		}
	}
	// default case
	this.left.generateCode(currentScope, codeStream, valueRequired);
	this.right.generateCode(currentScope, codeStream, valueRequired);
	if (valueRequired) {
		codeStream.ior();
		if (falseLabel == null) {
			if (trueLabel != null) {
				// implicit falling through the FALSE case
				codeStream.ifne(trueLabel);
			}
		} else {
			// implicit falling through the TRUE case
			if (trueLabel == null) {
				codeStream.ifeq(falseLabel);
			} else {
				// no implicit fall through TRUE/FALSE --> should never occur
			}
		}
	}
	codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
}

/**
 * Boolean generation for ^
 */
public void generateOptimizedLogicalXor(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel, BranchLabel falseLabel, boolean valueRequired) {
	Constant condConst;
	if ((this.left.implicitConversion & TypeIds.COMPILE_TYPE_MASK) == TypeIds.T_boolean) {
		if ((condConst = this.left.optimizedBooleanConstant()) != Constant.NotAConstant) {
			if (condConst.booleanValue() == true) {
				// <something equivalent to true> ^ x
				this.left.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					false);
				this.right.generateOptimizedBoolean(
					currentScope,
					codeStream,
					falseLabel, // negating
					trueLabel,
					valueRequired);
			} else {
				// <something equivalent to false> ^ x
				this.left.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					false);
				this.right.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					valueRequired);
			}
			return;
		}
		if ((condConst = this.right.optimizedBooleanConstant()) != Constant.NotAConstant) {
			if (condConst.booleanValue() == true) {
				// x ^ <something equivalent to true>
				this.left.generateOptimizedBoolean(
					currentScope,
					codeStream,
					falseLabel, // negating
					trueLabel,
					valueRequired);
				this.right.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					false);
			} else {
				// x ^ <something equivalent to false>
				this.left.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					valueRequired);
				this.right.generateOptimizedBoolean(
					currentScope,
					codeStream,
					trueLabel,
					falseLabel,
					false);
			}
			return;
		}
	}
	// default case
	this.left.generateCode(currentScope, codeStream, valueRequired);
	this.right.generateCode(currentScope, codeStream, valueRequired);
	if (valueRequired) {
		codeStream.ixor();
		if (falseLabel == null) {
			if (trueLabel != null) {
				// implicit falling through the FALSE case
				codeStream.ifne(trueLabel);
			}
		} else {
			// implicit falling through the TRUE case
			if (trueLabel == null) {
				codeStream.ifeq(falseLabel);
			} else {
				// no implicit fall through TRUE/FALSE --> should never occur
			}
		}
	}
	codeStream.recordPositionsFrom(codeStream.position, this.sourceEnd);
}
@Override
public void buildStringForConcatation(BlockScope blockScope, CodeStream codeStream, int typeID, StringBuilder recipe, List<TypeBinding> argTypes) {
	if ((((this.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT) == OperatorIds.PLUS)
			&& ((this.bits & ASTNode.ReturnTypeIDMASK) == TypeIds.T_JavaLangString)) {
			if (this.constant != Constant.NotAConstant) {
				super.buildStringForConcatation(blockScope, codeStream, typeID, recipe, argTypes);
			} else {
				this.left.buildStringForConcatation(blockScope, codeStream, this.left.implicitConversion & TypeIds.COMPILE_TYPE_MASK, recipe, argTypes);
				this.right.buildStringForConcatation(blockScope, codeStream, this.right.implicitConversion & TypeIds.COMPILE_TYPE_MASK, recipe, argTypes);
			}
		} else {
			super.buildStringForConcatation(blockScope, codeStream, typeID, recipe, argTypes);
		}
}
@Override
public void generateOptimizedStringConcatenation(BlockScope blockScope, CodeStream codeStream, int typeID) {
	// keep implementation in sync with CombinedBinaryExpression
	// #generateOptimizedStringConcatenation
	/* In the case trying to make a string concatenation, there is no need to create a new
	 * string buffer, thus use a lower-level API for code generation involving only the
	 * appending of arguments to the existing StringBuffer
	 */

	if ((((this.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT) == OperatorIds.PLUS)
		&& ((this.bits & ASTNode.ReturnTypeIDMASK) == TypeIds.T_JavaLangString)) {
		if (this.constant != Constant.NotAConstant) {
			codeStream.generateConstant(this.constant, this.implicitConversion);
			codeStream.invokeStringConcatenationAppendForType(this.implicitConversion & TypeIds.COMPILE_TYPE_MASK);
		} else {
			int pc = codeStream.position;
			this.left.generateOptimizedStringConcatenation(
				blockScope,
				codeStream,
				this.left.implicitConversion & TypeIds.COMPILE_TYPE_MASK);
			codeStream.recordPositionsFrom(pc, this.left.sourceStart);
			pc = codeStream.position;
			this.right.generateOptimizedStringConcatenation(
				blockScope,
				codeStream,
				this.right.implicitConversion & TypeIds.COMPILE_TYPE_MASK);
			codeStream.recordPositionsFrom(pc, this.right.sourceStart);
		}
	} else {
		super.generateOptimizedStringConcatenation(blockScope, codeStream, typeID);
	}
}

@Override
public void generateOptimizedStringConcatenationCreation(BlockScope blockScope, CodeStream codeStream, int typeID) {
	// keep implementation in sync with CombinedBinaryExpression
	// #generateOptimizedStringConcatenationCreation
	/* In the case trying to make a string concatenation, there is no need to create a new
	 * string buffer, thus use a lower-level API for code generation involving only the
	 * appending of arguments to the existing StringBuffer
	 */
	if ((((this.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT) == OperatorIds.PLUS)
		&& ((this.bits & ASTNode.ReturnTypeIDMASK) == TypeIds.T_JavaLangString)) {
		if (this.constant != Constant.NotAConstant) {
			codeStream.newStringContatenation(); // new: java.lang.StringBuffer
			codeStream.dup();
			codeStream.ldc(this.constant.stringValue());
			codeStream.invokeStringConcatenationStringConstructor();
			// invokespecial: java.lang.StringBuffer.<init>(Ljava.lang.String;)V
		} else {
			int pc = codeStream.position;
			this.left.generateOptimizedStringConcatenationCreation(
				blockScope,
				codeStream,
				this.left.implicitConversion & TypeIds.COMPILE_TYPE_MASK);
			codeStream.recordPositionsFrom(pc, this.left.sourceStart);
			pc = codeStream.position;
			this.right.generateOptimizedStringConcatenation(
				blockScope,
				codeStream,
				this.right.implicitConversion & TypeIds.COMPILE_TYPE_MASK);
			codeStream.recordPositionsFrom(pc, this.right.sourceStart);
		}
	} else {
		super.generateOptimizedStringConcatenationCreation(blockScope, codeStream, typeID);
	}
}

@Override
public boolean isCompactableOperation() {
	return true;
}

/**
 * Separates into a reusable method the subpart of {@link
 * #resolveType(BlockScope)} that needs to be executed while climbing up the
 * chain of expressions of this' leftmost branch. For use by {@link
 * CombinedBinaryExpression#resolveType(BlockScope)}.
 * @param scope the scope within which the resolution occurs
 */
void nonRecursiveResolveTypeUpwards(BlockScope scope) {
	// keep implementation in sync with BinaryExpression#resolveType
	boolean leftIsCast, rightIsCast;
	TypeBinding leftType = this.left.resolvedType;

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

	// use the id of the type to navigate into the table
	if (leftType == null || rightType == null) {
		this.constant = Constant.NotAConstant;
		return;
	}

	int leftTypeID = leftType.id;
	int rightTypeID = rightType.id;

	// autoboxing support
	boolean use15specifics = scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5;
	if (use15specifics) {
		if (!leftType.isBaseType() && rightTypeID != TypeIds.T_JavaLangString && rightTypeID != TypeIds.T_null) {
			leftTypeID = scope.environment().computeBoxingType(leftType).id;
		}
		if (!rightType.isBaseType() && leftTypeID != TypeIds.T_JavaLangString && leftTypeID != TypeIds.T_null) {
			rightTypeID = scope.environment().computeBoxingType(rightType).id;
		}
	}
	if (leftTypeID > 15
		|| rightTypeID > 15) { // must convert String + Object || Object + String
		if (leftTypeID == TypeIds.T_JavaLangString) {
			rightTypeID = TypeIds.T_JavaLangObject;
		} else if (rightTypeID == TypeIds.T_JavaLangString) {
			leftTypeID = TypeIds.T_JavaLangObject;
		} else {
			this.constant = Constant.NotAConstant;
			scope.problemReporter().invalidOperator(this, leftType, rightType);
			return;
		}
	}
	if (((this.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT) == OperatorIds.PLUS) {
		if (leftTypeID == TypeIds.T_JavaLangString) {
			this.left.computeConversion(scope, leftType, leftType);
			if (rightType.isArrayType() && TypeBinding.equalsEquals(((ArrayBinding) rightType).elementsType(), TypeBinding.CHAR)) {
				scope.problemReporter().signalNoImplicitStringConversionForCharArrayExpression(this.right);
			}
		}
		if (rightTypeID == TypeIds.T_JavaLangString) {
			this.right.computeConversion(scope, rightType, rightType);
			if (leftType.isArrayType() && TypeBinding.equalsEquals(((ArrayBinding) leftType).elementsType(), TypeBinding.CHAR)) {
				scope.problemReporter().signalNoImplicitStringConversionForCharArrayExpression(this.left);
			}
		}
	}

	// the code is an int
	// (cast)  left   Op (cast)  right --> result
	//  0000   0000       0000   0000      0000
	//  <<16   <<12       <<8    <<4       <<0

	// Don't test for result = 0. If it is zero, some more work is done.
	// On the one hand when it is not zero (correct code) we avoid doing the test
	int operator = (this.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT;
	int operatorSignature = OperatorExpression.OperatorSignatures[operator][(leftTypeID << 4) + rightTypeID];

	this.left.computeConversion(scope, 	TypeBinding.wellKnownType(scope, (operatorSignature >>> 16) & 0x0000F), leftType);
	this.right.computeConversion(scope, TypeBinding.wellKnownType(scope, (operatorSignature >>> 8) & 0x0000F), rightType);
	this.bits |= operatorSignature & 0xF;
	switch (operatorSignature & 0xF) { // record the current ReturnTypeID
		// only switch on possible result type.....
		case T_boolean :
			this.resolvedType = TypeBinding.BOOLEAN;
			break;
		case T_byte :
			this.resolvedType = TypeBinding.BYTE;
			break;
		case T_char :
			this.resolvedType = TypeBinding.CHAR;
			break;
		case T_double :
			this.resolvedType = TypeBinding.DOUBLE;
			break;
		case T_float :
			this.resolvedType = TypeBinding.FLOAT;
			break;
		case T_int :
			this.resolvedType = TypeBinding.INT;
			break;
		case T_long :
			this.resolvedType = TypeBinding.LONG;
			break;
		case T_JavaLangString :
			this.resolvedType = scope.getJavaLangString();
			break;
		default : //error........
			this.constant = Constant.NotAConstant;
			scope.problemReporter().invalidOperator(this, leftType, rightType);
			return;
	}

	// check need for operand cast
	if ((leftIsCast = (this.left instanceof CastExpression)) == true ||
			rightIsCast) {
		CastExpression.checkNeedForArgumentCasts(scope, operator, operatorSignature, this.left, leftTypeID, leftIsCast, this.right, rightTypeID, rightIsCast);
	}
	// compute the constant when valid
	computeConstant(scope, leftTypeID, rightTypeID);
}

public void optimizedBooleanConstant(int leftId, int operator, int rightId) {
	switch (operator) {
		case AND :
			if ((leftId != TypeIds.T_boolean) || (rightId != TypeIds.T_boolean))
				return;
			//$FALL-THROUGH$
		case AND_AND :
			Constant cst;
			if ((cst = this.left.optimizedBooleanConstant()) != Constant.NotAConstant) {
				if (cst.booleanValue() == false) { // left is equivalent to false
					this.optimizedBooleanConstant = cst; // constant(false)
					return;
				} else { //left is equivalent to true
					if ((cst = this.right.optimizedBooleanConstant()) != Constant.NotAConstant) {
						this.optimizedBooleanConstant = cst;
						// the conditional result is equivalent to the right conditional value
					}
					return;
				}
			}
			if ((cst = this.right.optimizedBooleanConstant()) != Constant.NotAConstant) {
				if (cst.booleanValue() == false) { // right is equivalent to false
					this.optimizedBooleanConstant = cst; // constant(false)
				}
			}
			return;
		case OR :
			if ((leftId != TypeIds.T_boolean) || (rightId != TypeIds.T_boolean))
				return;
			//$FALL-THROUGH$
		case OR_OR :
			if ((cst = this.left.optimizedBooleanConstant()) != Constant.NotAConstant) {
				if (cst.booleanValue() == true) { // left is equivalent to true
					this.optimizedBooleanConstant = cst; // constant(true)
					return;
				} else { //left is equivalent to false
					if ((cst = this.right.optimizedBooleanConstant()) != Constant.NotAConstant) {
						this.optimizedBooleanConstant = cst;
					}
					return;
				}
			}
			if ((cst = this.right.optimizedBooleanConstant()) != Constant.NotAConstant) {
				if (cst.booleanValue() == true) { // right is equivalent to true
					this.optimizedBooleanConstant = cst; // constant(true)
				}
			}
	}
}

@Override
public StringBuffer printExpressionNoParenthesis(int indent, StringBuffer output) {
	// keep implementation in sync with
	// CombinedBinaryExpression#printExpressionNoParenthesis
	this.left.printExpression(indent, output).append(' ').append(operatorToString()).append(' ');
	return this.right.printExpression(0, output);
}

@Override
public void collectPatternVariablesToScope(LocalVariableBinding[] variables, BlockScope scope) {
	this.addPatternVariablesWhenTrue(variables);
	this.left.addPatternVariablesWhenTrue(variables);
	this.left.collectPatternVariablesToScope(variables, scope);
	this.right.addPatternVariablesWhenTrue(variables);
	this.right.collectPatternVariablesToScope(variables, scope);
}
@Override
public void addPatternVariables(BlockScope scope, CodeStream codeStream) {
	this.left.addPatternVariables(scope, codeStream);
	this.right.addPatternVariables(scope, codeStream);
}
@Override
public boolean containsPatternVariable() {
	return this.left.containsPatternVariable() || this.right.containsPatternVariable();
}
@Override
public TypeBinding resolveType(BlockScope scope) {
	// keep implementation in sync with CombinedBinaryExpression#resolveType
	// and nonRecursiveResolveTypeUpwards
	if(this.patternVarsWhenFalse == null && this.patternVarsWhenTrue == null &&
			this.containsPatternVariable()) {
		// the null check is to guard against a second round of collection.
		// This usually doesn't happen,
		// except when we call collectPatternVariablesToScope() from here
		this.collectPatternVariablesToScope(null, scope);
	}
	boolean leftIsCast, rightIsCast;
	if ((leftIsCast = this.left instanceof CastExpression) == true) this.left.bits |= ASTNode.DisableUnnecessaryCastCheck; // will check later on
	TypeBinding leftType = this.left.resolveType(scope);

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

	// use the id of the type to navigate into the table
	if (leftType == null || rightType == null) {
		this.constant = Constant.NotAConstant;
		return null;
	}

	int leftTypeID = leftType.id;
	int rightTypeID = rightType.id;

	// autoboxing support
	boolean use15specifics = scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5;
	if (use15specifics) {
		if (!leftType.isBaseType() && rightTypeID != TypeIds.T_JavaLangString && rightTypeID != TypeIds.T_null) {
			leftTypeID = scope.environment().computeBoxingType(leftType).id;
		}
		if (!rightType.isBaseType() && leftTypeID != TypeIds.T_JavaLangString && leftTypeID != TypeIds.T_null) {
			rightTypeID = scope.environment().computeBoxingType(rightType).id;
		}
	}
	if (leftTypeID > 15
		|| rightTypeID > 15) { // must convert String + Object || Object + String
		if (leftTypeID == TypeIds.T_JavaLangString) {
			rightTypeID = TypeIds.T_JavaLangObject;
		} else if (rightTypeID == TypeIds.T_JavaLangString) {
			leftTypeID = TypeIds.T_JavaLangObject;
		} else {
			this.constant = Constant.NotAConstant;
			scope.problemReporter().invalidOperator(this, leftType, rightType);
			return null;
		}
	}
	if (((this.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT) == OperatorIds.PLUS) {
		if (leftTypeID == TypeIds.T_JavaLangString) {
			this.left.computeConversion(scope, leftType, leftType);
			if (rightType.isArrayType() && TypeBinding.equalsEquals(((ArrayBinding) rightType).elementsType(), TypeBinding.CHAR)) {
				scope.problemReporter().signalNoImplicitStringConversionForCharArrayExpression(this.right);
			}
		}
		if (rightTypeID == TypeIds.T_JavaLangString) {
			this.right.computeConversion(scope, rightType, rightType);
			if (leftType.isArrayType() && TypeBinding.equalsEquals(((ArrayBinding) leftType).elementsType(), TypeBinding.CHAR)) {
				scope.problemReporter().signalNoImplicitStringConversionForCharArrayExpression(this.left);
			}
		}
	}

	// the code is an int
	// (cast)  left   Op (cast)  right --> result
	//  0000   0000       0000   0000      0000
	//  <<16   <<12       <<8    <<4       <<0

	// Don't test for result = 0. If it is zero, some more work is done.
	// On the one hand when it is not zero (correct code) we avoid doing the test
	int operator = (this.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT;
	int operatorSignature = OperatorExpression.OperatorSignatures[operator][(leftTypeID << 4) + rightTypeID];

	this.left.computeConversion(scope, TypeBinding.wellKnownType(scope, (operatorSignature >>> 16) & 0x0000F), leftType);
	this.right.computeConversion(scope, TypeBinding.wellKnownType(scope, (operatorSignature >>> 8) & 0x0000F), rightType);
	this.bits |= operatorSignature & 0xF;
	switch (operatorSignature & 0xF) { // record the current ReturnTypeID
		// only switch on possible result type.....
		case T_boolean :
			this.resolvedType = TypeBinding.BOOLEAN;
			break;
		case T_byte :
			this.resolvedType = TypeBinding.BYTE;
			break;
		case T_char :
			this.resolvedType = TypeBinding.CHAR;
			break;
		case T_double :
			this.resolvedType = TypeBinding.DOUBLE;
			break;
		case T_float :
			this.resolvedType = TypeBinding.FLOAT;
			break;
		case T_int :
			this.resolvedType = TypeBinding.INT;
			break;
		case T_long :
			this.resolvedType = TypeBinding.LONG;
			break;
		case T_JavaLangString :
			this.resolvedType = scope.getJavaLangString();
			break;
		default : //error........
			this.constant = Constant.NotAConstant;
			scope.problemReporter().invalidOperator(this, leftType, rightType);
			return null;
	}

	// check need for operand cast
	if (leftIsCast || rightIsCast) {
		CastExpression.checkNeedForArgumentCasts(scope, operator, operatorSignature, this.left, leftTypeID, leftIsCast, this.right, rightTypeID, rightIsCast);
	}
	// compute the constant when valid
	computeConstant(scope, leftTypeID, rightTypeID);
	return this.resolvedType;
}

@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);
}
}
