/*******************************************************************************
 * Copyright (c) 2000, 2011 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Stephan Herrmann <stephan@cs.tu-berlin.de> - Contribution for bug 292478 - Report potentially null across variable assignment
 *******************************************************************************/
package org.eclipse.che.ide.ext.java.jdt.internal.compiler.ast;

import org.eclipse.che.ide.ext.java.jdt.core.compiler.CharOperation;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.ASTVisitor;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.ClassFileConstants;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.codegen.BranchLabel;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.flow.FlowContext;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.impl.Constant;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.impl.ReferenceContext;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.ArrayBinding;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.BaseTypeBinding;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.Binding;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.Scope;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.TagBits;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.TypeVariableBinding;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.WildcardBinding;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.problem.ShouldNotImplement;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.util.Messages;

import java.util.ArrayList;

public abstract class Expression extends Statement {

    public Constant constant;

    public int statementEnd = -1;

    //Some expression may not be used - from a java semantic point
    //of view only - as statements. Other may. In order to avoid the creation
    //of wrappers around expression in order to tune them as expression
    //Expression is a subclass of Statement. See the message isValidJavaStatement()

    public int implicitConversion;

    public TypeBinding resolvedType;

    public static final boolean isConstantValueRepresentable(Constant constant, int constantTypeID, int targetTypeID) {
        //true if there is no loss of precision while casting.
        // constantTypeID == constant.typeID
        if (targetTypeID == constantTypeID) {
            return true;
        }
        switch (targetTypeID) {
            case T_char:
                switch (constantTypeID) {
                    case T_char:
                        return true;
                    case T_double:
                        return constant.doubleValue() == constant.charValue();
                    case T_float:
                        return constant.floatValue() == constant.charValue();
                    case T_int:
                        return constant.intValue() == constant.charValue();
                    case T_short:
                        return constant.shortValue() == constant.charValue();
                    case T_byte:
                        return constant.byteValue() == constant.charValue();
                    case T_long:
                        return constant.longValue() == constant.charValue();
                    default:
                        return false;//boolean
                }

            case T_float:
                switch (constantTypeID) {
                    case T_char:
                        return constant.charValue() == constant.floatValue();
                    case T_double:
                        return constant.doubleValue() == constant.floatValue();
                    case T_float:
                        return true;
                    case T_int:
                        return constant.intValue() == constant.floatValue();
                    case T_short:
                        return constant.shortValue() == constant.floatValue();
                    case T_byte:
                        return constant.byteValue() == constant.floatValue();
                    case T_long:
                        return constant.longValue() == constant.floatValue();
                    default:
                        return false;//boolean
                }

            case T_double:
                switch (constantTypeID) {
                    case T_char:
                        return constant.charValue() == constant.doubleValue();
                    case T_double:
                        return true;
                    case T_float:
                        return constant.floatValue() == constant.doubleValue();
                    case T_int:
                        return constant.intValue() == constant.doubleValue();
                    case T_short:
                        return constant.shortValue() == constant.doubleValue();
                    case T_byte:
                        return constant.byteValue() == constant.doubleValue();
                    case T_long:
                        return constant.longValue() == constant.doubleValue();
                    default:
                        return false; //boolean
                }

            case T_byte:
                switch (constantTypeID) {
                    case T_char:
                        return constant.charValue() == constant.byteValue();
                    case T_double:
                        return constant.doubleValue() == constant.byteValue();
                    case T_float:
                        return constant.floatValue() == constant.byteValue();
                    case T_int:
                        return constant.intValue() == constant.byteValue();
                    case T_short:
                        return constant.shortValue() == constant.byteValue();
                    case T_byte:
                        return true;
                    case T_long:
                        return constant.longValue() == constant.byteValue();
                    default:
                        return false; //boolean
                }

            case T_short:
                switch (constantTypeID) {
                    case T_char:
                        return constant.charValue() == constant.shortValue();
                    case T_double:
                        return constant.doubleValue() == constant.shortValue();
                    case T_float:
                        return constant.floatValue() == constant.shortValue();
                    case T_int:
                        return constant.intValue() == constant.shortValue();
                    case T_short:
                        return true;
                    case T_byte:
                        return constant.byteValue() == constant.shortValue();
                    case T_long:
                        return constant.longValue() == constant.shortValue();
                    default:
                        return false; //boolean
                }

            case T_int:
                switch (constantTypeID) {
                    case T_char:
                        return constant.charValue() == constant.intValue();
                    case T_double:
                        return constant.doubleValue() == constant.intValue();
                    case T_float:
                        return constant.floatValue() == constant.intValue();
                    case T_int:
                        return true;
                    case T_short:
                        return constant.shortValue() == constant.intValue();
                    case T_byte:
                        return constant.byteValue() == constant.intValue();
                    case T_long:
                        return constant.longValue() == constant.intValue();
                    default:
                        return false; //boolean
                }

            case T_long:
                switch (constantTypeID) {
                    case T_char:
                        return constant.charValue() == constant.longValue();
                    case T_double:
                        return constant.doubleValue() == constant.longValue();
                    case T_float:
                        return constant.floatValue() == constant.longValue();
                    case T_int:
                        return constant.intValue() == constant.longValue();
                    case T_short:
                        return constant.shortValue() == constant.longValue();
                    case T_byte:
                        return constant.byteValue() == constant.longValue();
                    case T_long:
                        return true;
                    default:
                        return false; //boolean
                }

            default:
                return false; //boolean
        }
    }

    public Expression() {
        super();
    }

    @Override
    public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
        return flowInfo;
    }

    /**
     * More sophisticated for of the flow analysis used for analyzing expressions, and be able to optimize out
     * portions of expressions where no actual value is required.
     *
     * @param currentScope
     * @param flowContext
     * @param flowInfo
     * @param valueRequired
     * @return The state of initialization after the analysis of the current expression
     */
    public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo,
                                boolean valueRequired) {
        return analyseCode(currentScope, flowContext, flowInfo);
    }

    /** Returns false if cast is not legal. */
    public final boolean checkCastTypesCompatibility(Scope scope, TypeBinding castType, TypeBinding expressionType,
                                                     Expression expression) {
        // see specifications 5.5
        // handle errors and process constant when needed

        // if either one of the type is null ==>
        // some error has been already reported some where ==>
        // we then do not report an obvious-cascade-error.

        if (castType == null || expressionType == null) {
            return true;
        }

        // identity conversion cannot be performed upfront, due to side-effects
        // like constant propagation
        boolean use15specifics = scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5;
        boolean use17specifics = scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_7;
        if (castType.isBaseType()) {
            if (expressionType.isBaseType()) {
                if (expressionType == castType) {
                    if (expression != null) {
                        this.constant = expression.constant; //use the same constant
                    }
                    tagAsUnnecessaryCast(scope, castType);
                    return true;
                }
                boolean necessary = false;
                if (expressionType.isCompatibleWith(castType)
                    || (necessary = BaseTypeBinding.isNarrowing(castType.id, expressionType.id))) {
                    if (expression != null) {
                        expression.implicitConversion = (castType.id << 4) + expressionType.id;
                        if (expression.constant != Constant.NotAConstant) {
                            this.constant = expression.constant.castTo(expression.implicitConversion);
                        }
                    }
                    if (!necessary) {
                        tagAsUnnecessaryCast(scope, castType);
                    }
                    return true;

                }
            } else if (use17specifics && expressionType.id == TypeIds.T_JavaLangObject) {
                // cast from Object to base type allowed from 1.7, see JLS $5.5
                return true;
            } else if (use15specifics && scope.environment().computeBoxingType(expressionType)
                                              .isCompatibleWith(castType)) { // unboxing - only widening match is allowed
                tagAsUnnecessaryCast(scope, castType);
                return true;
            }
            return false;
        } else if (use15specifics && expressionType.isBaseType()
                   && scope.environment().computeBoxingType(expressionType)
                           .isCompatibleWith(castType)) { // boxing - only widening match is allowed
            tagAsUnnecessaryCast(scope, castType);
            return true;
        }

        switch (expressionType.kind()) {
            case Binding.BASE_TYPE:
                //-----------cast to something which is NOT a base type--------------------------
                if (expressionType == TypeBinding.NULL) {
                    tagAsUnnecessaryCast(scope, castType);
                    return true; //null is compatible with every thing
                }
                return false;

            case Binding.ARRAY_TYPE:
                if (castType == expressionType) {
                    tagAsUnnecessaryCast(scope, castType);
                    return true; // identity conversion
                }
                switch (castType.kind()) {
                    case Binding.ARRAY_TYPE:
                        // ( ARRAY ) ARRAY
                        TypeBinding castElementType = ((ArrayBinding)castType).elementsType();
                        TypeBinding exprElementType = ((ArrayBinding)expressionType).elementsType();
                        if (exprElementType.isBaseType() || castElementType.isBaseType()) {
                            if (castElementType == exprElementType) {
                                tagAsNeedCheckCast();
                                return true;
                            }
                            return false;
                        }
                        // recurse on array type elements
                        return checkCastTypesCompatibility(scope, castElementType, exprElementType, expression);

                    case Binding.TYPE_PARAMETER:
                        // ( TYPE_PARAMETER ) ARRAY
                        TypeBinding match = expressionType.findSuperTypeOriginatingFrom(castType);
                        if (match == null) {
                            checkUnsafeCast(scope, castType, expressionType, null /*no match*/, true);
                        }
                        // recurse on the type variable upper bound
                        return checkCastTypesCompatibility(scope, ((TypeVariableBinding)castType).upperBound(),
                                                           expressionType, expression);

                    default:
                        // ( CLASS/INTERFACE ) ARRAY
                        switch (castType.id) {
                            case T_JavaLangCloneable:
                            case T_JavaIoSerializable:
                                tagAsNeedCheckCast();
                                return true;
                            case T_JavaLangObject:
                                tagAsUnnecessaryCast(scope, castType);
                                return true;
                            default:
                                return false;
                        }
                }

            case Binding.TYPE_PARAMETER:
                TypeBinding match = expressionType.findSuperTypeOriginatingFrom(castType);
                if (match != null) {
                    return checkUnsafeCast(scope, castType, expressionType, match, false);
                }
                // recursively on the type variable upper bound
                return checkCastTypesCompatibility(scope, castType, ((TypeVariableBinding)expressionType).upperBound(),
                                                   expression);

            case Binding.WILDCARD_TYPE:
            case Binding.INTERSECTION_TYPE:
                match = expressionType.findSuperTypeOriginatingFrom(castType);
                if (match != null) {
                    return checkUnsafeCast(scope, castType, expressionType, match, false);
                }
                // recursively on the type variable upper bound
                return checkCastTypesCompatibility(scope, castType, ((WildcardBinding)expressionType).bound, expression);

            default:
                if (expressionType.isInterface()) {
                    switch (castType.kind()) {
                        case Binding.ARRAY_TYPE:
                            // ( ARRAY ) INTERFACE
                            switch (expressionType.id) {
                                case T_JavaLangCloneable:
                                case T_JavaIoSerializable:
                                    tagAsNeedCheckCast();
                                    return true;
                                default:
                                    return false;
                            }

                        case Binding.TYPE_PARAMETER:
                            // ( INTERFACE ) TYPE_PARAMETER
                            match = expressionType.findSuperTypeOriginatingFrom(castType);
                            if (match == null) {
                                checkUnsafeCast(scope, castType, expressionType, null /*no match*/, true);
                            }
                            // recurse on the type variable upper bound
                            return checkCastTypesCompatibility(scope, ((TypeVariableBinding)castType).upperBound(),
                                                               expressionType, expression);

                        default:
                            if (castType.isInterface()) {
                                // ( INTERFACE ) INTERFACE
                                ReferenceBinding interfaceType = (ReferenceBinding)expressionType;
                                match = interfaceType.findSuperTypeOriginatingFrom(castType);
                                if (match != null) {
                                    return checkUnsafeCast(scope, castType, interfaceType, match, false);
                                }
                                tagAsNeedCheckCast();
                                match = castType.findSuperTypeOriginatingFrom(interfaceType);
                                if (match != null) {
                                    return checkUnsafeCast(scope, castType, interfaceType, match, true);
                                }
                                if (use15specifics) {
                                    checkUnsafeCast(scope, castType, expressionType, null /*no match*/, true);
                                    // ensure there is no collision between both interfaces: i.e. I1 extends List<String>,
                                    // I2 extends List<Object>
                                    if (scope.compilerOptions().complianceLevel < ClassFileConstants.JDK1_7) {
                                        if (interfaceType.hasIncompatibleSuperType((ReferenceBinding)castType)) {
                                            return false;
                                        }
                                    } else if (!castType.isRawType()
                                               && interfaceType.hasIncompatibleSuperType((ReferenceBinding)castType)) {
                                        return false;
                                    }
                                } else {
                                    // pre1.5 semantics - no covariance allowed (even if 1.5 compliant, but 1.4 source)
                                    // look at original methods rather than the parameterized variants at 1.4 to detect
                                    // covariance. Otherwise when confronted with one raw type and one parameterized type,
                                    // we could mistakenly detect covariance and scream foul. See https://bugs.eclipse.org/bugs/show_bug
                                    // .cgi?id=332744
                                    MethodBinding[] castTypeMethods = getAllOriginalInheritedMethods((ReferenceBinding)castType);
                                    MethodBinding[] expressionTypeMethods =
                                            getAllOriginalInheritedMethods((ReferenceBinding)expressionType);
                                    int exprMethodsLength = expressionTypeMethods.length;
                                    for (int i = 0, castMethodsLength = castTypeMethods.length; i < castMethodsLength; i++) {
                                        for (int j = 0; j < exprMethodsLength; j++) {
                                            if ((castTypeMethods[i].returnType != expressionTypeMethods[j].returnType)
                                                && (CharOperation.equals(castTypeMethods[i].selector,
                                                                         expressionTypeMethods[j].selector))
                                                && castTypeMethods[i].areParametersEqual(expressionTypeMethods[j])) {
                                                return false;

                                            }
                                        }
                                    }
                                }
                                return true;
                            } else {
                                // ( CLASS ) INTERFACE
                                if (castType.id == TypeIds.T_JavaLangObject) { // no runtime error
                                    tagAsUnnecessaryCast(scope, castType);
                                    return true;
                                }
                                // can only be a downcast
                                tagAsNeedCheckCast();
                                match = castType.findSuperTypeOriginatingFrom(expressionType);
                                if (match != null) {
                                    return checkUnsafeCast(scope, castType, expressionType, match, true);
                                }
                                if (((ReferenceBinding)castType).isFinal()) {
                                    // no subclass for castType, thus compile-time check is invalid
                                    return false;
                                }
                                if (use15specifics) {
                                    checkUnsafeCast(scope, castType, expressionType, null /*no match*/, true);
                                    // ensure there is no collision between both interfaces: i.e. I1 extends List<String>,
                                    // I2 extends List<Object>
                                    if (scope.compilerOptions().complianceLevel < ClassFileConstants.JDK1_7) {
                                        if (((ReferenceBinding)castType)
                                                .hasIncompatibleSuperType((ReferenceBinding)expressionType)) {
                                            return false;
                                        }
                                    } else if (!castType.isRawType()
                                               && ((ReferenceBinding)castType)
                                            .hasIncompatibleSuperType((ReferenceBinding)expressionType)) {
                                        return false;
                                    }
                                }
                                return true;
                            }
                    }
                } else {
                    switch (castType.kind()) {
                        case Binding.ARRAY_TYPE:
                            // ( ARRAY ) CLASS
                            if (expressionType.id == TypeIds.T_JavaLangObject) { // potential runtime error
                                if (use15specifics) {
                                    checkUnsafeCast(scope, castType, expressionType, expressionType, true);
                                }
                                tagAsNeedCheckCast();
                                return true;
                            }
                            return false;

                        case Binding.TYPE_PARAMETER:
                            // ( TYPE_PARAMETER ) CLASS
                            match = expressionType.findSuperTypeOriginatingFrom(castType);
                            if (match == null) {
                                checkUnsafeCast(scope, castType, expressionType, null, true);
                            }
                            // recurse on the type variable upper bound
                            return checkCastTypesCompatibility(scope, ((TypeVariableBinding)castType).upperBound(),
                                                               expressionType, expression);

                        default:
                            if (castType.isInterface()) {
                                // ( INTERFACE ) CLASS
                                ReferenceBinding refExprType = (ReferenceBinding)expressionType;
                                match = refExprType.findSuperTypeOriginatingFrom(castType);
                                if (match != null) {
                                    return checkUnsafeCast(scope, castType, expressionType, match, false);
                                }
                                // unless final a subclass may implement the interface ==> no check at compile time
                                if (refExprType.isFinal()) {
                                    return false;
                                }
                                tagAsNeedCheckCast();
                                match = castType.findSuperTypeOriginatingFrom(expressionType);
                                if (match != null) {
                                    return checkUnsafeCast(scope, castType, expressionType, match, true);
                                }
                                if (use15specifics) {
                                    checkUnsafeCast(scope, castType, expressionType, null /*no match*/, true);
                                    // ensure there is no collision between both interfaces: i.e. I1 extends List<String>,
                                    // I2 extends List<Object>
                                    if (scope.compilerOptions().complianceLevel < ClassFileConstants.JDK1_7) {
                                        if (refExprType.hasIncompatibleSuperType((ReferenceBinding)castType)) {
                                            return false;
                                        }
                                    } else if (!castType.isRawType()
                                               && refExprType.hasIncompatibleSuperType((ReferenceBinding)castType)) {
                                        return false;
                                    }
                                }
                                return true;
                            } else {
                                // ( CLASS ) CLASS
                                match = expressionType.findSuperTypeOriginatingFrom(castType);
                                if (match != null) {
                                    if (expression != null && castType.id == TypeIds.T_JavaLangString) {
                                        this.constant = expression.constant; // (String) cst is still a constant
                                    }
                                    return checkUnsafeCast(scope, castType, expressionType, match, false);
                                }
                                match = castType.findSuperTypeOriginatingFrom(expressionType);
                                if (match != null) {
                                    tagAsNeedCheckCast();
                                    return checkUnsafeCast(scope, castType, expressionType, match, true);
                                }
                                return false;
                            }
                    }
                }
        }
    }

    /**
     * Check the local variable of this expression, if any, against potential NPEs
     * given a flow context and an upstream flow info. If so, report the risk to
     * the context. Marks the local as checked, which affects the flow info.
     *
     * @param scope
     *         the scope of the analysis
     * @param flowContext
     *         the current flow context
     * @param flowInfo
     *         the upstream flow info; caveat: may get modified
     */
    public void checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) {
        LocalVariableBinding local = localVariableBinding();
        if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) {
            if ((this.bits & ASTNode.IsNonNull) == 0) {
                flowContext.recordUsingNullReference(scope, local, this, FlowContext.MAY_NULL, flowInfo);
            }
            flowInfo.markAsComparedEqualToNonNull(local);
            // from thereon it is set
            if ((flowContext.tagBits & FlowContext.HIDE_NULL_COMPARISON_WARNING) != 0) {
                flowInfo.markedAsNullOrNonNullInAssertExpression(local);
            }
            if (flowContext.initsOnFinally != null) {
                flowContext.initsOnFinally.markAsComparedEqualToNonNull(local);
                if ((flowContext.tagBits & FlowContext.HIDE_NULL_COMPARISON_WARNING) != 0) {
                    flowContext.initsOnFinally.markedAsNullOrNonNullInAssertExpression(local);
                }
            }
        }
    }

    public boolean checkUnsafeCast(Scope scope, TypeBinding castType, TypeBinding expressionType, TypeBinding match,
                                   boolean isNarrowing) {
        if (match == castType) {
            if (!isNarrowing) {
                tagAsUnnecessaryCast(scope, castType);
            }
            return true;
        }
        if (match != null && (!castType.isReifiable() || !expressionType.isReifiable())) {
            if (isNarrowing ? match.isProvablyDistinct(expressionType) : castType.isProvablyDistinct(match)) {
                return false;
            }
        }
        if (!isNarrowing) {
            tagAsUnnecessaryCast(scope, castType);
        }
        return true;
    }

    /**
     * Base types need that the widening is explicitly done by the compiler using some bytecode like i2f.
     * Also check unsafe type operations.
     */
    public void computeConversion(Scope scope, TypeBinding runtimeType, TypeBinding compileTimeType) {
        if (runtimeType == null || compileTimeType == null) {
            return;
        }
        if (this.implicitConversion != 0) {
            return; // already set independantly
        }

        // it is possible for a Byte to be unboxed to a byte & then converted to an int
        // but it is not possible for a byte to become Byte & then assigned to an Integer,
        // or to become an int before boxed into an Integer
        if (runtimeType != TypeBinding.NULL && runtimeType.isBaseType()) {
            if (!compileTimeType.isBaseType()) {
                TypeBinding unboxedType = scope.environment().computeBoxingType(compileTimeType);
                this.implicitConversion = TypeIds.UNBOXING;
                scope.problemReporter().autoboxing(this, compileTimeType, runtimeType);
                compileTimeType = unboxedType;
            }
        } else if (compileTimeType != TypeBinding.NULL && compileTimeType.isBaseType()) {
            TypeBinding boxedType = scope.environment().computeBoxingType(runtimeType);
            if (boxedType == runtimeType) {
                boxedType = compileTimeType;
            }
            this.implicitConversion = TypeIds.BOXING | (boxedType.id << 4) + compileTimeType.id;
            scope.problemReporter().autoboxing(this, compileTimeType, scope.environment().computeBoxingType(boxedType));
            return;
        } else if (this.constant != Constant.NotAConstant && this.constant.typeID() != TypeIds.T_JavaLangString) {
            this.implicitConversion = TypeIds.BOXING;
            return;
        }
        int compileTimeTypeID, runtimeTypeID;
        if ((compileTimeTypeID = compileTimeType.id) == TypeIds.NoId) { // e.g. ? extends String  ==> String (103227)
            compileTimeTypeID =
                    compileTimeType.erasure().id == TypeIds.T_JavaLangString ? TypeIds.T_JavaLangString
                                                                             : TypeIds.T_JavaLangObject;
        }
        switch (runtimeTypeID = runtimeType.id) {
            case T_byte:
            case T_short:
            case T_char:
                if (compileTimeTypeID == TypeIds.T_JavaLangObject) {
                    this.implicitConversion |= (runtimeTypeID << 4) + compileTimeTypeID;
                } else {
                    this.implicitConversion |= (TypeIds.T_int << 4) + compileTimeTypeID;
                }
                break;
            case T_JavaLangString:
            case T_float:
            case T_boolean:
            case T_double:
            case T_int: //implicitConversion may result in i2i which will result in NO code gen
            case T_long:
                this.implicitConversion |= (runtimeTypeID << 4) + compileTimeTypeID;
                break;
            default: // regular object ref
                //				if (compileTimeType.isRawType() && runtimeTimeType.isBoundParameterizedType()) {
                //				    scope.problemReporter().unsafeRawExpression(this, compileTimeType, runtimeTimeType);
                //				}
        }
    }

    /**
     * Expression statements are plain expressions, however they generate like
     * normal expressions with no value required.
     *
     * @param currentScope
     *         org.eclipse.che.ide.java.client.internal.compiler.lookup.BlockScope
     * @param codeStream
     *         org.eclipse.che.ide.java.client.internal.compiler.codegen.CodeStream
     */
    @Override
    public void generateCode(BlockScope currentScope) {
        if ((this.bits & ASTNode.IsReachable) == 0) {
            return;
        }
        generateCode(currentScope, false);
    }

    /**
     * Every expression is responsible for generating its implicit conversion when necessary.
     *
     * @param currentScope
     *         org.eclipse.che.ide.java.client.internal.compiler.lookup.BlockScope
     * @param codeStream
     *         org.eclipse.che.ide.java.client.internal.compiler.codegen.CodeStream
     * @param valueRequired
     *         boolean
     */
    public void generateCode(BlockScope currentScope, boolean valueRequired) {
        if (this.constant != Constant.NotAConstant) {

        } else {
            // actual non-constant code generation
            throw new ShouldNotImplement(Messages.instance.ast_missingCode());
        }
    }

    /**
     * Default generation of a boolean value
     *
     * @param currentScope
     * @param codeStream
     * @param trueLabel
     * @param falseLabel
     * @param valueRequired
     */
    public void generateOptimizedBoolean(BlockScope currentScope, BranchLabel trueLabel, BranchLabel falseLabel,
                                         boolean valueRequired) {
        // a label valued to nil means: by default we fall through the case...
        // both nil means we leave the value on the stack

        Constant cst = optimizedBooleanConstant();
        generateCode(currentScope, valueRequired && cst == Constant.NotAConstant);
    }

    /* Optimized (java) code generation for string concatenations that involve StringBuffer
     * creation: going through this path means that there is no need for a new StringBuffer
     * creation, further operands should rather be only appended to the current one.
     * By default: no optimization.
     */
    public void generateOptimizedStringConcatenation(BlockScope blockScope, int typeID) {
        if (typeID == TypeIds.T_JavaLangString && this.constant != Constant.NotAConstant
            && this.constant.stringValue().length() == 0) {
            return; // optimize str + ""
        }
        generateCode(blockScope, true);
    }

    private MethodBinding[] getAllOriginalInheritedMethods(ReferenceBinding binding) {
        ArrayList collector = new ArrayList();
        getAllInheritedMethods0(binding, collector);
        for (int i = 0, len = collector.size(); i < len; i++) {
            collector.set(i, ((MethodBinding)collector.get(i)).original());
        }
        return (MethodBinding[])collector.toArray(new MethodBinding[collector.size()]);
    }

    private void getAllInheritedMethods0(ReferenceBinding binding, ArrayList collector) {
        if (!binding.isInterface()) {
            return;
        }
        MethodBinding[] methodBindings = binding.methods();
        for (int i = 0, max = methodBindings.length; i < max; i++) {
            collector.add(methodBindings[i]);
        }
        ReferenceBinding[] superInterfaces = binding.superInterfaces();
        for (int i = 0, max = superInterfaces.length; i < max; i++) {
            getAllInheritedMethods0(superInterfaces[i], collector);
        }
    }

    public static Binding getDirectBinding(Expression someExpression) {
        if ((someExpression.bits & ASTNode.IgnoreNoEffectAssignCheck) != 0) {
            return null;
        }
        if (someExpression instanceof SingleNameReference) {
            return ((SingleNameReference)someExpression).binding;
        } else if (someExpression instanceof FieldReference) {
            FieldReference fieldRef = (FieldReference)someExpression;
            if (fieldRef.receiver.isThis() && !(fieldRef.receiver instanceof QualifiedThisReference)) {
                return fieldRef.binding;
            }
        } else if (someExpression instanceof Assignment) {
            Expression lhs = ((Assignment)someExpression).lhs;
            if ((lhs.bits & ASTNode.IsStrictlyAssigned) != 0) {
                // i = i = ...; // eq to int i = ...;
                return getDirectBinding(((Assignment)someExpression).lhs);
            } else if (someExpression instanceof PrefixExpression) {
                // i = i++; // eq to ++i;
                return getDirectBinding(((Assignment)someExpression).lhs);
            }
        } else if (someExpression instanceof QualifiedNameReference) {
            QualifiedNameReference qualifiedNameReference = (QualifiedNameReference)someExpression;
            if (qualifiedNameReference.indexOfFirstFieldBinding != 1 && qualifiedNameReference.otherBindings == null) {
                // case where a static field is retrieved using ClassName.fieldname
                return qualifiedNameReference.binding;
            }
        } else if (someExpression.isThis()) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=276741
            return someExpression.resolvedType;
        }
        //		} else if (someExpression instanceof PostfixExpression) { // recurse for postfix: i++ --> i
        //			// note: "b = b++" is equivalent to doing nothing, not to "b++"
        //			return getDirectBinding(((PostfixExpression) someExpression).lhs);
        return null;
    }

    public boolean isCompactableOperation() {
        return false;
    }

    //Return true if the conversion is done AUTOMATICALLY by the vm
    //while the javaVM is an int based-machine, thus for example pushing
    //a byte onto the stack , will automatically create an int on the stack
    //(this request some work d be done by the VM on signed numbers)
    public boolean isConstantValueOfTypeAssignableToType(TypeBinding constantType, TypeBinding targetType) {

        if (this.constant == Constant.NotAConstant) {
            return false;
        }
        if (constantType == targetType) {
            return true;
        }
        //No free assignment conversion from anything but to integral ones.
        if (BaseTypeBinding.isWidening(TypeIds.T_int, constantType.id)
            && (BaseTypeBinding.isNarrowing(targetType.id, TypeIds.T_int))) {
            //use current explicit conversion in order to get some new value to compare with current one
            return isConstantValueRepresentable(this.constant, constantType.id, targetType.id);
        }
        return false;
    }

    public boolean isTypeReference() {
        return false;
    }

    /**
     * Returns the local variable referenced by this node. Can be a direct reference (SingleNameReference)
     * or thru a cast expression etc...
     */
    public LocalVariableBinding localVariableBinding() {
        return null;
    }

    /**
     * Mark this expression as being non null, per a specific tag in the
     * source code.
     */
    // this is no more called for now, waiting for inter procedural null reference analysis
    public void markAsNonNull() {
        this.bits |= ASTNode.IsNonNull;
    }

    public int nullStatus(FlowInfo flowInfo) {

        if (/* (this.bits & IsNonNull) != 0 || */
                this.constant != null && this.constant != Constant.NotAConstant) {
            return FlowInfo.NON_NULL; // constant expression cannot be null
        }

        LocalVariableBinding local = localVariableBinding();
        if (local != null) {
            return flowInfo.nullStatus(local);
        }
        return FlowInfo.NON_NULL;
    }

    /**
     * Constant usable for bytecode pattern optimizations, but cannot be inlined
     * since it is not strictly equivalent to the definition of constant expressions.
     * In particular, some side-effects may be required to occur (only the end value
     * is known).
     *
     * @return Constant known to be of boolean type
     */
    public Constant optimizedBooleanConstant() {
        return this.constant;
    }

    /**
     * Returns the type of the expression after required implicit conversions. When expression type gets promoted
     * or inserted a generic cast, the converted type will differ from the resolved type (surface side-effects from
     * #computeConversion(...)).
     *
     * @return the type after implicit conversion
     */
    public TypeBinding postConversionType(Scope scope) {
        TypeBinding convertedType = this.resolvedType;
        int runtimeType = (this.implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4;
        switch (runtimeType) {
            case T_boolean:
                convertedType = TypeBinding.BOOLEAN;
                break;
            case T_byte:
                convertedType = TypeBinding.BYTE;
                break;
            case T_short:
                convertedType = TypeBinding.SHORT;
                break;
            case T_char:
                convertedType = TypeBinding.CHAR;
                break;
            case T_int:
                convertedType = TypeBinding.INT;
                break;
            case T_float:
                convertedType = TypeBinding.FLOAT;
                break;
            case T_long:
                convertedType = TypeBinding.LONG;
                break;
            case T_double:
                convertedType = TypeBinding.DOUBLE;
                break;
            default:
        }
        if ((this.implicitConversion & TypeIds.BOXING) != 0) {
            convertedType = scope.environment().computeBoxingType(convertedType);
        }
        return convertedType;
    }

    @Override
    public StringBuffer print(int indent, StringBuffer output) {
        printIndent(indent, output);
        return printExpression(indent, output);
    }

    public abstract StringBuffer printExpression(int indent, StringBuffer output);

    @Override
    public StringBuffer printStatement(int indent, StringBuffer output) {
        return print(indent, output).append(";"); //$NON-NLS-1$
    }

    @Override
    public void resolve(BlockScope scope) {
        // drops the returning expression's type whatever the type is.
        this.resolveType(scope);
        return;
    }

    /**
     * Resolve the type of this expression in the context of a blockScope
     *
     * @param scope
     * @return Return the actual type of this expression after resolution
     */
    public TypeBinding resolveType(BlockScope scope) {
        // by default... subclasses should implement a better TB if required.
        return null;
    }

    /**
     * Resolve the type of this expression in the context of a classScope
     *
     * @param scope
     * @return Return the actual type of this expression after resolution
     */
    public TypeBinding resolveType(ClassScope scope) {
        // by default... subclasses should implement a better TB if required.
        return null;
    }

    public TypeBinding resolveTypeExpecting(BlockScope scope, TypeBinding expectedType) {
        setExpectedType(expectedType); // needed in case of generic method invocation
        TypeBinding expressionType = this.resolveType(scope);
        if (expressionType == null) {
            return null;
        }
        if (expressionType == expectedType) {
            return expressionType;
        }

        if (!expressionType.isCompatibleWith(expectedType)) {
            if (scope.isBoxingCompatibleWith(expressionType, expectedType)) {
                computeConversion(scope, expectedType, expressionType);
            } else {
                scope.problemReporter().typeMismatchError(expressionType, expectedType, this, null);
                return null;
            }
        }
        return expressionType;
    }

    /**
     * Returns true if the receiver is forced to be of raw type either to satisfy the contract imposed
     * by a super type or because it *is* raw and the current type has no control over it (i.e the rawness
     * originates from some other file.)
     */
    public boolean forcedToBeRaw(ReferenceContext referenceContext) {
        if (this instanceof NameReference) {
            final Binding receiverBinding = ((NameReference)this).binding;
            if (receiverBinding.isParameter()
                && (((LocalVariableBinding)receiverBinding).tagBits & TagBits.ForcedToBeRawType) != 0) {
                return true; // parameter is forced to be raw since super method uses raw types.
            } else if (receiverBinding instanceof FieldBinding) {
                FieldBinding field = (FieldBinding)receiverBinding;
                if (field.type.isRawType()) {
                    if (referenceContext instanceof AbstractMethodDeclaration) {
                        AbstractMethodDeclaration methodDecl = (AbstractMethodDeclaration)referenceContext;
                        if (field.declaringClass !=
                            methodDecl.binding.declaringClass) { // inherited raw field, see https://bugs.eclipse.org/bugs/show_bug
                            // .cgi?id=337962
                            return true;
                        }
                    } else if (referenceContext instanceof TypeDeclaration) {
                        TypeDeclaration type = (TypeDeclaration)referenceContext;
                        if (field.declaringClass !=
                            type.binding) { // inherited raw field, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=337962
                            return true;
                        }
                    }
                }
            }
        } else if (this instanceof MessageSend) {
            if (!CharOperation.equals(((MessageSend)this).binding.declaringClass.getFileName(), referenceContext
                    .compilationResult().getFileName())) { // problem is rooted elsewhere
                return true;
            }
        } else if (this instanceof FieldReference) {
            FieldBinding field = ((FieldReference)this).binding;
            if (!CharOperation.equals(field.declaringClass.getFileName(), referenceContext.compilationResult()
                                                                                          .getFileName())) { // problem is rooted elsewhere
                return true;
            }
            if (field.type.isRawType()) {
                if (referenceContext instanceof AbstractMethodDeclaration) {
                    AbstractMethodDeclaration methodDecl = (AbstractMethodDeclaration)referenceContext;
                    if (field.declaringClass !=
                        methodDecl.binding.declaringClass) { // inherited raw field, see https://bugs.eclipse.org/bugs/show_bug
                        // .cgi?id=337962
                        return true;
                    }
                } else if (referenceContext instanceof TypeDeclaration) {
                    TypeDeclaration type = (TypeDeclaration)referenceContext;
                    if (field.declaringClass !=
                        type.binding) { // inherited raw field, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=337962
                        return true;
                    }
                }
            }
        } else if (this instanceof ConditionalExpression) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=337751
            ConditionalExpression ternary = (ConditionalExpression)this;
            if (ternary.valueIfTrue.forcedToBeRaw(referenceContext)
                || ternary.valueIfFalse.forcedToBeRaw(referenceContext)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns an object which can be used to identify identical JSR sequence targets
     * (see TryStatement subroutine codegen)
     * or <code>null</null> if not reusable
     */
    public Object reusableJSRTarget() {
        if (this.constant != Constant.NotAConstant) {
            return this.constant;
        }
        return null;
    }

    /**
     * Record the type expectation before this expression is typechecked.
     * e.g. String s = foo();, foo() will be tagged as being expected of type String
     * Used to trigger proper inference of generic method invocations.
     *
     * @param expectedType
     *         The type denoting an expectation in the context of an assignment conversion
     */
    public void setExpectedType(TypeBinding expectedType) {
        // do nothing by default
    }

    public void tagAsNeedCheckCast() {
        // do nothing by default
    }

    /**
     * Record the fact a cast expression got detected as being unnecessary.
     *
     * @param scope
     * @param castType
     */
    public void tagAsUnnecessaryCast(Scope scope, TypeBinding castType) {
        // do nothing by default
    }

    public Expression toTypeReference() {
        //by default undefined

        //this method is meanly used by the parser in order to transform
        //an expression that is used as a type reference in a cast ....
        //--appreciate the fact that castExpression and ExpressionWithParenthesis
        //--starts with the same pattern.....

        return this;
    }

    /**
     * Traverse an expression in the context of a blockScope
     *
     * @param visitor
     * @param scope
     */
    @Override
    public void traverse(ASTVisitor visitor, BlockScope scope) {
        // nothing to do
    }

    /**
     * Traverse an expression in the context of a classScope
     *
     * @param visitor
     * @param scope
     */
    public void traverse(ASTVisitor visitor, ClassScope scope) {
        // nothing to do
    }
}
