1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.ir;
  27 
  28 import java.util.ArrayList;
  29 import java.util.Collections;
  30 import java.util.List;
  31 import jdk.nashorn.internal.codegen.Label;
  32 import jdk.nashorn.internal.ir.annotations.Immutable;
  33 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
  34 
  35 /**
  36  * IR representation of a SWITCH statement.
  37  */
  38 @Immutable
  39 public final class SwitchNode extends BreakableStatement {
  40     private static final long serialVersionUID = 1L;
  41 
  42     /** Switch expression. */
  43     private final Expression expression;
  44 
  45     /** Switch cases. */
  46     private final List<CaseNode> cases;
  47 
  48     /** Switch default index. */
  49     private final int defaultCaseIndex;
  50 
  51     /** True if all cases are 32-bit signed integer constants, without repetitions. It's a prerequisite for
  52      * using a tableswitch/lookupswitch when generating code. */
  53     private final boolean uniqueInteger;
  54 
  55     /** Tag symbol. */
  56     private final Symbol tag;
  57 
  58     /**
  59      * Constructor
  60      *
  61      * @param lineNumber  lineNumber
  62      * @param token       token
  63      * @param finish      finish
  64      * @param expression  switch expression
  65      * @param cases       cases
  66      * @param defaultCase the default case node - null if none, otherwise has to be present in cases list
  67      */
  68     public SwitchNode(final int lineNumber, final long token, final int finish, final Expression expression, final List<CaseNode> cases, final CaseNode defaultCase) {
  69         super(lineNumber, token, finish, new Label("switch_break"));
  70         this.expression       = expression;
  71         this.cases            = cases;
  72         this.defaultCaseIndex = defaultCase == null ? -1 : cases.indexOf(defaultCase);
  73         this.uniqueInteger    = false;
  74         this.tag = null;
  75     }
  76 
  77     private SwitchNode(final SwitchNode switchNode, final Expression expression, final List<CaseNode> cases,
  78             final int defaultCaseIndex, final LocalVariableConversion conversion, final boolean uniqueInteger, final Symbol tag) {
  79         super(switchNode, conversion);
  80         this.expression       = expression;
  81         this.cases            = cases;
  82         this.defaultCaseIndex = defaultCaseIndex;
  83         this.tag              = tag;
  84         this.uniqueInteger    = uniqueInteger;
  85     }
  86 
  87     @Override
  88     public Node ensureUniqueLabels(final LexicalContext lc) {
  89         final List<CaseNode> newCases = new ArrayList<>();
  90         for (final CaseNode caseNode : cases) {
  91             newCases.add(new CaseNode(caseNode, caseNode.getTest(), caseNode.getBody(), caseNode.getLocalVariableConversion()));
  92         }
  93         return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, newCases, defaultCaseIndex, conversion, uniqueInteger, tag));
  94     }
  95 
  96     @Override
  97     public boolean isTerminal() {
  98         //there must be a default case, and that including all other cases must terminate
  99         if (!cases.isEmpty() && defaultCaseIndex != -1) {
 100             for (final CaseNode caseNode : cases) {
 101                 if (!caseNode.isTerminal()) {
 102                     return false;
 103                 }
 104             }
 105             return true;
 106         }
 107         return false;
 108 
 109     }
 110 
 111     @Override
 112     public Node accept(final LexicalContext lc, final NodeVisitor<? extends LexicalContext> visitor) {
 113         if (visitor.enterSwitchNode(this)) {
 114             return visitor.leaveSwitchNode(
 115                 setExpression(lc, (Expression)expression.accept(visitor)).
 116                 setCases(lc, Node.accept(visitor, cases), defaultCaseIndex));
 117         }
 118 
 119         return this;
 120     }
 121 
 122     @Override
 123     public void toString(final StringBuilder sb, final boolean printType) {
 124         sb.append("switch (");
 125         expression.toString(sb, printType);
 126         sb.append(')');
 127     }
 128 
 129     /**
 130      * Return the case node that is default case
 131      * @return default case or null if none
 132      */
 133     public CaseNode getDefaultCase() {
 134         return defaultCaseIndex == -1 ? null : cases.get(defaultCaseIndex);
 135     }
 136 
 137     /**
 138      * Get the cases in this switch
 139      * @return a list of case nodes
 140      */
 141     public List<CaseNode> getCases() {
 142         return Collections.unmodifiableList(cases);
 143     }
 144 
 145     /**
 146      * Replace case nodes with new list. the cases have to be the same
 147      * and the default case index the same. This is typically used
 148      * by NodeVisitors who perform operations on every case node
 149      * @param lc    lexical context
 150      * @param cases list of cases
 151      * @return new switch node or same if no state was changed
 152      */
 153     public SwitchNode setCases(final LexicalContext lc, final List<CaseNode> cases) {
 154         return setCases(lc, cases, defaultCaseIndex);
 155     }
 156 
 157     private SwitchNode setCases(final LexicalContext lc, final List<CaseNode> cases, final int defaultCaseIndex) {
 158         if (this.cases == cases) {
 159             return this;
 160         }
 161         return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger, tag));
 162     }
 163 
 164     /**
 165      * Set or reset the list of cases in this switch
 166      * @param lc lexical context
 167      * @param cases a list of cases, case nodes
 168      * @param defaultCase a case in the list that is the default - must be in the list or class will assert
 169      * @return new switch node or same if no state was changed
 170      */
 171     public SwitchNode setCases(final LexicalContext lc, final List<CaseNode> cases, final CaseNode defaultCase) {
 172         return setCases(lc, cases, defaultCase == null ? -1 : cases.indexOf(defaultCase));
 173     }
 174 
 175     /**
 176      * Return the expression to switch on
 177      * @return switch expression
 178      */
 179     public Expression getExpression() {
 180         return expression;
 181     }
 182 
 183     /**
 184      * Set or reset the expression to switch on
 185      * @param lc lexical context
 186      * @param expression switch expression
 187      * @return new switch node or same if no state was changed
 188      */
 189     public SwitchNode setExpression(final LexicalContext lc, final Expression expression) {
 190         if (this.expression == expression) {
 191             return this;
 192         }
 193         return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger, tag));
 194     }
 195 
 196     /**
 197      * Get the tag symbol for this switch. The tag symbol is where
 198      * the switch expression result is stored
 199      * @return tag symbol
 200      */
 201     public Symbol getTag() {
 202         return tag;
 203     }
 204 
 205     /**
 206      * Set the tag symbol for this switch. The tag symbol is where
 207      * the switch expression result is stored
 208      * @param lc lexical context
 209      * @param tag a symbol
 210      * @return a switch node with the symbol set
 211      */
 212     public SwitchNode setTag(final LexicalContext lc, final Symbol tag) {
 213         if (this.tag == tag) {
 214             return this;
 215         }
 216         return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger, tag));
 217     }
 218 
 219     /**
 220      * Returns true if all cases of this switch statement are 32-bit signed integer constants, without repetitions.
 221      * @return true if all cases of this switch statement are 32-bit signed integer constants, without repetitions.
 222      */
 223     public boolean isUniqueInteger() {
 224         return uniqueInteger;
 225     }
 226 
 227     /**
 228      * Sets whether all cases of this switch statement are 32-bit signed integer constants, without repetitions.
 229      * @param lc lexical context
 230      * @param uniqueInteger if true, all cases of this switch statement have been determined to be 32-bit signed
 231      * integer constants, without repetitions.
 232      * @return this switch node, if the value didn't change, or a new switch node with the changed value
 233      */
 234     public SwitchNode setUniqueInteger(final LexicalContext lc, final boolean uniqueInteger) {
 235         if(this.uniqueInteger == uniqueInteger) {
 236             return this;
 237         }
 238         return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger, tag));
 239     }
 240 
 241     @Override
 242     JoinPredecessor setLocalVariableConversionChanged(final LexicalContext lc, final LocalVariableConversion conversion) {
 243         return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger, tag));
 244     }
 245 
 246 }