/*
 * Copyright (c) 1994, 2003, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.tools.tree;

import sun.tools.java.*;
import sun.tools.asm.Assembler;
import sun.tools.asm.Label;
import sun.tools.asm.SwitchData;
import java.io.PrintStream;
import java.util.Hashtable;

/**
 * WARNING: The contents of this source file are not part of any
 * supported API.  Code that depends on them does so at its own risk:
 * they are subject to change or removal without notice.
 */
public
class SwitchStatement extends Statement {
    Expression expr;
    Statement args[];

    /**
     * Constructor
     */
    public SwitchStatement(long where, Expression expr, Statement args[]) {
        super(SWITCH, where);
        this.expr = expr;
        this.args = args;
    }

    /**
     * Check statement
     */
    Vset check(Environment env, Context ctx, Vset vset, Hashtable<Object, Object> exp) {
        checkLabel(env, ctx);
        CheckContext newctx = new CheckContext(ctx, this);
        vset = expr.checkValue(env, newctx, reach(env, vset), exp);
        Type switchType = expr.type;

        expr = convert(env, newctx, Type.tInt, expr);

        Hashtable<Expression, Statement> tab = new Hashtable<>();
        boolean hasDefault = false;
        // Note that vs is reset to vset.copy() on every case label.
        // If the first substatement is not a case label, it is unreached.
        Vset vs = DEAD_END;

        for (int i = 0 ; i < args.length ; i++) {
            Statement s = args[i];

            if (s.op == CASE) {

                vs = s.check(env, newctx, vs.join(vset.copy()), exp);

                Expression lbl = ((CaseStatement)s).expr;
                if (lbl != null) {
                    if (lbl instanceof IntegerExpression) {
                        Integer Ivalue =
                            (Integer)(((IntegerExpression)lbl).getValue());
                        int ivalue = Ivalue.intValue();
                        if (tab.get(lbl) != null) {
                            env.error(s.where, "duplicate.label", Ivalue);
                        } else {
                            tab.put(lbl, s);
                            boolean overflow;
                            switch (switchType.getTypeCode()) {
                                case TC_BYTE:
                                    overflow = (ivalue != (byte)ivalue); break;
                                case TC_SHORT:
                                    overflow = (ivalue != (short)ivalue); break;
                                case TC_CHAR:
                                    overflow = (ivalue != (char)ivalue); break;
                                default:
                                    overflow = false;
                            }
                            if (overflow) {
                                env.error(s.where, "switch.overflow",
                                          Ivalue, switchType);
                            }
                        }
                    } else {
                        // Suppose a class got an error early on during
                        // checking.  It will set all of its members to
                        // have the status "ERROR".  Now suppose that a
                        // case label refers to one of this class's
                        // fields.  When we check the case label, the
                        // compiler will try to inline the FieldExpression.
                        // Since the expression has ERROR status, it doesn't
                        // inline.  This means that instead of the case
                        // label being an IntegerExpression, it will still
                        // be a FieldExpression, and we will end up in this
                        // else block.  So, before we just assume that
                        // the expression isn't constant, do a check to
                        // see if it was constant but unable to inline.
                        // This eliminates some spurious error messages.
                        // (Bug id 4067498).
                        if (!lbl.isConstant() ||
                            lbl.getType() != Type.tInt) {
                            env.error(s.where, "const.expr.required");
                        }
                    }
                } else {
                    if (hasDefault) {
                        env.error(s.where, "duplicate.default");
                    }
                    hasDefault = true;
                }
            } else {
                vs = s.checkBlockStatement(env, newctx, vs, exp);
            }
        }
        if (!vs.isDeadEnd()) {
            newctx.vsBreak = newctx.vsBreak.join(vs);
        }
        if (hasDefault)
            vset = newctx.vsBreak;
        return ctx.removeAdditionalVars(vset);
    }

    /**
     * Inline
     */
    public Statement inline(Environment env, Context ctx) {
        ctx = new Context(ctx, this);
        expr = expr.inlineValue(env, ctx);
        for (int i = 0 ; i < args.length ; i++) {
            if (args[i] != null) {
                args[i] = args[i].inline(env, ctx);
            }
        }
        return this;
    }

    /**
     * Create a copy of the statement for method inlining
     */
    public Statement copyInline(Context ctx, boolean valNeeded) {
        SwitchStatement s = (SwitchStatement)clone();
        s.expr = expr.copyInline(ctx);
        s.args = new Statement[args.length];
        for (int i = 0 ; i < args.length ; i++) {
            if (args[i] != null) {
                s.args[i] = args[i].copyInline(ctx, valNeeded);
            }
        }
        return s;
    }

    /**
     * The cost of inlining this statement
     */
    public int costInline(int thresh, Environment env, Context ctx) {
        int cost = expr.costInline(thresh, env, ctx);
        for (int i = 0 ; (i < args.length) && (cost < thresh) ; i++) {
            if (args[i] != null) {
                cost += args[i].costInline(thresh, env, ctx);
            }
        }
        return cost;
    }

    /**
     * Code
     */
    public void code(Environment env, Context ctx, Assembler asm) {
        CodeContext newctx = new CodeContext(ctx, this);

        expr.codeValue(env, newctx, asm);

        SwitchData sw = new SwitchData();
        boolean hasDefault = false;

        for (int i = 0 ; i < args.length ; i++) {
            Statement s = args[i];
            if ((s != null) && (s.op == CASE)) {
                Expression e = ((CaseStatement)s).expr;
                if (e != null) {
                    sw.add(((IntegerExpression)e).value, new Label());
                }
// JCOV
                else {
                    hasDefault = true;
                }
// end JCOV
            }
        }

// JCOV
        if (env.coverage())
            sw.initTableCase();
// end JCOV
        asm.add(where, opc_tableswitch, sw);

        for (int i = 0 ; i < args.length ; i++) {
            Statement s = args[i];
            if (s != null) {
                if (s.op == CASE) {
                    Expression e = ((CaseStatement)s).expr;
                    if (e != null) {
                        asm.add(sw.get(((IntegerExpression)e).value));
// JCOV
                        sw.addTableCase(((IntegerExpression)e).value, s.where);
// end JCOV
                    } else {
                        asm.add(sw.getDefaultLabel());
// JCOV
                        sw.addTableDefault(s.where);
// end JCOV
/* JCOV                 hasDefault = true;   end JCOV */
                    }
                } else {
                    s.code(env, newctx, asm);
                }
            }
        }

        if (!hasDefault) {
            asm.add(sw.getDefaultLabel());
        }
        asm.add(newctx.breakLabel);
    }

    /**
     * Print
     */
    public void print(PrintStream out, int indent) {
        super.print(out, indent);
        out.print("switch (");
        expr.print(out);
        out.print(") {\n");
        for (int i = 0 ; i < args.length ; i++) {
            if (args[i] != null) {
                printIndent(out, indent + 1);
                args[i].print(out, indent + 1);
                out.print("\n");
            }
        }
        printIndent(out, indent);
        out.print("}");
    }
}
