1 /*******************************************************************************
2 * Copyright (c) 2000, 2004 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.jdt.internal.compiler.ast;
13 import org.eclipse.jdt.internal.compiler.ASTVisitor;
14 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
15 import org.eclipse.jdt.internal.compiler.codegen.*;
16 import org.eclipse.jdt.internal.compiler.flow.*;
17 import org.eclipse.jdt.internal.compiler.impl.Constant;
18 import org.eclipse.jdt.internal.compiler.lookup.*;
20 public class SwitchStatement extends Statement {
22 public Expression expression;
23 public Statement[] statements;
24 public BlockScope scope;
25 public int explicitDeclarations;
26 public Label breakLabel;
27 public CaseStatement[] cases;
28 public CaseStatement defaultCase;
29 public int blockStart;
33 // for local variables table attributes
34 int preSwitchInitStateIndex = -1;
35 int mergedInitStateIndex = -1;
37 public FlowInfo analyseCode(
38 BlockScope currentScope,
39 FlowContext flowContext,
43 flowInfo = expression.analyseCode(currentScope, flowContext, flowInfo);
44 SwitchFlowContext switchContext =
45 new SwitchFlowContext(flowContext, this, (breakLabel = new Label()));
47 // analyse the block by considering specially the case/default statements (need to bind them
48 // to the entry point)
49 FlowInfo caseInits = FlowInfo.DEAD_END;
50 // in case of statements before the first case
51 preSwitchInitStateIndex =
52 currentScope.methodScope().recordInitializationStates(flowInfo);
54 if (statements != null) {
55 boolean didAlreadyComplain = false;
56 for (int i = 0, max = statements.length; i < max; i++) {
57 Statement statement = statements[i];
58 if ((caseIndex < caseCount) && (statement == cases[caseIndex])) { // statement is a case
59 this.scope.switchCase = cases[caseIndex]; // record entering in a switch case block
61 caseInits = caseInits.mergedWith(flowInfo.copy().unconditionalInits());
62 didAlreadyComplain = false; // reset complaint
63 } else if (statement == defaultCase) { // statement is the default case
64 this.scope.switchCase = defaultCase; // record entering in a switch case block
65 caseInits = caseInits.mergedWith(flowInfo.copy().unconditionalInits());
66 didAlreadyComplain = false; // reset complaint
68 if (!statement.complainIfUnreachable(caseInits, scope, didAlreadyComplain)) {
69 caseInits = statement.analyseCode(scope, switchContext, caseInits);
71 didAlreadyComplain = true;
76 // if no default case, then record it may jump over the block directly to the end
77 if (defaultCase == null) {
78 // only retain the potential initializations
79 flowInfo.addPotentialInitializationsFrom(
80 caseInits.mergedWith(switchContext.initsOnBreak));
81 mergedInitStateIndex =
82 currentScope.methodScope().recordInitializationStates(flowInfo);
86 // merge all branches inits
87 FlowInfo mergedInfo = caseInits.mergedWith(switchContext.initsOnBreak);
88 mergedInitStateIndex =
89 currentScope.methodScope().recordInitializationStates(mergedInfo);
92 if (this.scope != null) this.scope.switchCase = null; // no longer inside switch case block
97 * Switch code generation
99 * @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope
100 * @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream
102 public void generateCode(BlockScope currentScope, CodeStream codeStream) {
105 if ((bits & IsReachableMASK) == 0) {
108 int pc = codeStream.position;
110 // prepare the labels and constants
111 this.breakLabel.initialize(codeStream);
112 CaseLabel[] caseLabels = new CaseLabel[this.caseCount];
113 boolean needSwitch = this.caseCount != 0;
114 for (int i = 0; i < caseCount; i++) {
115 cases[i].targetLabel = (caseLabels[i] = new CaseLabel(codeStream));
117 CaseLabel defaultLabel = new CaseLabel(codeStream);
118 if (defaultCase != null) {
119 defaultCase.targetLabel = defaultLabel;
121 // generate expression testes
122 expression.generateCode(currentScope, codeStream, needSwitch);
123 // generate the appropriate switch table/lookup bytecode
125 int[] sortedIndexes = new int[this.caseCount];
126 // we sort the keys to be able to generate the code for tableswitch or lookupswitch
127 for (int i = 0; i < caseCount; i++) {
128 sortedIndexes[i] = i;
131 System.arraycopy(this.constants, 0, (localKeysCopy = new int[this.caseCount]), 0, this.caseCount);
132 CodeStream.sort(localKeysCopy, 0, this.caseCount - 1, sortedIndexes);
134 // for enum constants, actually switch on constant ordinal()
135 if (this.expression.resolvedType.isEnum()) {
136 codeStream.invokeEnumOrdinal(this.expression.resolvedType.constantPoolName());
138 int max = localKeysCopy[this.caseCount - 1];
139 int min = localKeysCopy[0];
140 if ((long) (caseCount * 2.5) > ((long) max - (long) min)) {
142 // work-around 1.3 VM bug, if max>0x7FFF0000, must use lookup bytecode
143 // see http://dev.eclipse.org/bugs/show_bug.cgi?id=21557
144 if (max > 0x7FFF0000 && currentScope.environment().options.complianceLevel < ClassFileConstants.JDK1_4) {
145 codeStream.lookupswitch(defaultLabel, this.constants, sortedIndexes, caseLabels);
148 codeStream.tableswitch(
157 codeStream.lookupswitch(defaultLabel, this.constants, sortedIndexes, caseLabels);
159 codeStream.updateLastRecordedEndPC(codeStream.position);
162 // generate the switch block statements
164 if (this.statements != null) {
165 for (int i = 0, maxCases = this.statements.length; i < maxCases; i++) {
166 Statement statement = this.statements[i];
167 if ((caseIndex < this.caseCount) && (statement == this.cases[caseIndex])) { // statements[i] is a case
168 this.scope.switchCase = this.cases[caseIndex]; // record entering in a switch case block
169 if (preSwitchInitStateIndex != -1) {
170 codeStream.removeNotDefinitelyAssignedVariables(currentScope, preSwitchInitStateIndex);
174 if (statement == this.defaultCase) { // statements[i] is a case or a default case
175 this.scope.switchCase = this.defaultCase; // record entering in a switch case block
176 if (preSwitchInitStateIndex != -1) {
177 codeStream.removeNotDefinitelyAssignedVariables(currentScope, preSwitchInitStateIndex);
181 statement.generateCode(scope, codeStream);
184 // place the trailing labels (for break and default case)
185 this.breakLabel.place();
186 if (defaultCase == null) {
187 defaultLabel.place();
189 // May loose some local variable initializations : affecting the local variable attributes
190 if (mergedInitStateIndex != -1) {
191 codeStream.removeNotDefinitelyAssignedVariables(currentScope, mergedInitStateIndex);
192 codeStream.addDefinitelyAssignedVariables(currentScope, mergedInitStateIndex);
194 if (scope != currentScope) {
195 codeStream.exitUserScope(this.scope);
197 codeStream.recordPositionsFrom(pc, this.sourceStart);
199 if (this.scope != null) this.scope.switchCase = null; // no longer inside switch case block
203 public StringBuffer printStatement(int indent, StringBuffer output) {
205 printIndent(indent, output).append("switch ("); //$NON-NLS-1$
206 expression.printExpression(0, output).append(") {"); //$NON-NLS-1$
207 if (statements != null) {
208 for (int i = 0; i < statements.length; i++) {
210 if (statements[i] instanceof CaseStatement) {
211 statements[i].printStatement(indent, output);
213 statements[i].printStatement(indent+2, output);
217 output.append("\n"); //$NON-NLS-1$
218 return printIndent(indent, output).append('}');
221 public void resolve(BlockScope upperScope) {
224 TypeBinding expressionType = expression.resolveType(upperScope);
225 if (expressionType == null)
227 expression.computeConversion(upperScope, expressionType, expressionType);
229 if (expressionType.isBaseType()) {
230 if (expression.isConstantValueOfTypeAssignableToType(expressionType, IntBinding))
232 if (expressionType.isCompatibleWith(IntBinding))
234 } else if (expressionType.isEnum()) {
236 } else if (upperScope.isBoxingCompatibleWith(expressionType, IntBinding)) {
237 expression.computeConversion(upperScope, IntBinding, expressionType);
240 upperScope.problemReporter().incorrectSwitchType(expression, expressionType);
241 // TODO (philippe) could keep analyzing switch statements in case of error
244 if (statements != null) {
245 scope = explicitDeclarations == 0 ? upperScope : new BlockScope(upperScope);
247 // collection of cases is too big but we will only iterate until caseCount
248 cases = new CaseStatement[length = statements.length];
249 this.constants = new int[length];
250 CaseStatement[] duplicateCaseStatements = null;
251 int duplicateCaseStatementsCounter = 0;
253 for (int i = 0; i < length; i++) {
255 final Statement statement = statements[i];
256 if ((constant = statement.resolveCase(scope, expressionType, this)) != Constant.NotAConstant) {
257 int key = constant.intValue();
258 //----check for duplicate case statement------------
259 for (int j = 0; j < counter; j++) {
260 if (this.constants[j] == key) {
261 final CaseStatement currentCaseStatement = (CaseStatement) statement;
262 if (duplicateCaseStatements == null) {
263 scope.problemReporter().duplicateCase(cases[j]);
264 scope.problemReporter().duplicateCase(currentCaseStatement);
265 duplicateCaseStatements = new CaseStatement[length];
266 duplicateCaseStatements[duplicateCaseStatementsCounter++] = cases[j];
267 duplicateCaseStatements[duplicateCaseStatementsCounter++] = currentCaseStatement;
269 boolean found = false;
270 searchReportedDuplicate: for (int k = 2; k < duplicateCaseStatementsCounter; k++) {
271 if (duplicateCaseStatements[k] == statement) {
273 break searchReportedDuplicate;
277 scope.problemReporter().duplicateCase(currentCaseStatement);
278 duplicateCaseStatements[duplicateCaseStatementsCounter++] = currentCaseStatement;
283 this.constants[counter++] = key;
286 if (length != counter) { // resize constants array
287 System.arraycopy(this.constants, 0, this.constants = new int[counter], 0, counter);
290 if ((this.bits & UndocumentedEmptyBlockMASK) != 0) {
291 upperScope.problemReporter().undocumentedEmptyBlock(this.blockStart, this.sourceEnd);
295 if (this.scope != null) this.scope.switchCase = null; // no longer inside switch case block
299 public void traverse(
301 BlockScope blockScope) {
303 if (visitor.visit(this, blockScope)) {
304 expression.traverse(visitor, scope);
305 if (statements != null) {
306 int statementsLength = statements.length;
307 for (int i = 0; i < statementsLength; i++)
308 statements[i].traverse(visitor, scope);
311 visitor.endVisit(this, blockScope);
315 * Dispatch the call on its last statement.
317 public void branchChainTo(Label label) {
319 // in order to improve debug attributes for stepping (11431)
320 // we want to inline the jumps to #breakLabel which already got
321 // generated (if any), and have them directly branch to a better
322 // location (the argument label).
323 // we know at this point that the breakLabel already got placed
324 if (this.breakLabel.hasForwardReferences()) {
325 label.appendForwardReferencesFrom(this.breakLabel);