added -J option to preserve unmodified files in preexisting jarfile
[org.ibex.tool.git] / src / org / eclipse / jdt / internal / compiler / ast / SwitchStatement.java
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
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package org.eclipse.jdt.internal.compiler.ast;
12
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.*;
19
20 public class SwitchStatement extends Statement {
21
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;
30         public int caseCount;
31         int[] constants;
32         
33         // for local variables table attributes
34         int preSwitchInitStateIndex = -1;
35         int mergedInitStateIndex = -1;
36
37         public FlowInfo analyseCode(
38                         BlockScope currentScope,
39                         FlowContext flowContext,
40                         FlowInfo flowInfo) {
41
42             try {
43                         flowInfo = expression.analyseCode(currentScope, flowContext, flowInfo);
44                         SwitchFlowContext switchContext =
45                                 new SwitchFlowContext(flowContext, this, (breakLabel = new Label()));
46         
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);
53                         int caseIndex = 0;
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
60                                                 caseIndex++;
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
67                                         }
68                                         if (!statement.complainIfUnreachable(caseInits, scope, didAlreadyComplain)) {
69                                                 caseInits = statement.analyseCode(scope, switchContext, caseInits);
70                                         } else {
71                                                 didAlreadyComplain = true;
72                                         }
73                                 }
74                         }
75         
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);
83                                 return flowInfo;
84                         }
85         
86                         // merge all branches inits
87                         FlowInfo mergedInfo = caseInits.mergedWith(switchContext.initsOnBreak);
88                         mergedInitStateIndex =
89                                 currentScope.methodScope().recordInitializationStates(mergedInfo);
90                         return mergedInfo;
91             } finally {
92                 if (this.scope != null) this.scope.switchCase = null; // no longer inside switch case block
93             }
94         }
95
96         /**
97          * Switch code generation
98          *
99          * @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope
100          * @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream
101          */
102         public void generateCode(BlockScope currentScope, CodeStream codeStream) {
103
104             try {
105                         if ((bits & IsReachableMASK) == 0) {
106                                 return;
107                         }
108                         int pc = codeStream.position;
109         
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));
116                         }
117                         CaseLabel defaultLabel = new CaseLabel(codeStream);
118                         if (defaultCase != null) {
119                                 defaultCase.targetLabel = defaultLabel;
120                         }
121                         // generate expression testes
122                         expression.generateCode(currentScope, codeStream, needSwitch);
123                         // generate the appropriate switch table/lookup bytecode
124                         if (needSwitch) {
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;
129                                 }
130                                 int[] localKeysCopy;
131                                 System.arraycopy(this.constants, 0, (localKeysCopy = new int[this.caseCount]), 0, this.caseCount);
132                                 CodeStream.sort(localKeysCopy, 0, this.caseCount - 1, sortedIndexes);
133
134                                 // for enum constants, actually switch on constant ordinal()
135                                 if (this.expression.resolvedType.isEnum()) {
136                                         codeStream.invokeEnumOrdinal(this.expression.resolvedType.constantPoolName());
137                                 }
138                                 int max = localKeysCopy[this.caseCount - 1];
139                                 int min = localKeysCopy[0];
140                                 if ((long) (caseCount * 2.5) > ((long) max - (long) min)) {
141                                         
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);
146         
147                                         } else {
148                                                 codeStream.tableswitch(
149                                                         defaultLabel,
150                                                         min,
151                                                         max,
152                                                         this.constants,
153                                                         sortedIndexes,
154                                                         caseLabels);
155                                         }
156                                 } else {
157                                         codeStream.lookupswitch(defaultLabel, this.constants, sortedIndexes, caseLabels);
158                                 }
159                                 codeStream.updateLastRecordedEndPC(codeStream.position);
160                         }
161                         
162                         // generate the switch block statements
163                         int caseIndex = 0;
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);
171                                                 }
172                                                 caseIndex++;
173                                         } else {
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);
178                                                         }
179                                                 }
180                                         }
181                                         statement.generateCode(scope, codeStream);
182                                 }
183                         }
184                         // place the trailing labels (for break and default case)
185                         this.breakLabel.place();
186                         if (defaultCase == null) {
187                                 defaultLabel.place();
188                         }
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);
193                         }
194                         if (scope != currentScope) {
195                                 codeStream.exitUserScope(this.scope);
196                         }
197                         codeStream.recordPositionsFrom(pc, this.sourceStart);
198             } finally {
199                 if (this.scope != null) this.scope.switchCase = null; // no longer inside switch case block
200             }           
201         }
202
203         public StringBuffer printStatement(int indent, StringBuffer output) {
204
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++) {
209                                 output.append('\n');
210                                 if (statements[i] instanceof CaseStatement) {
211                                         statements[i].printStatement(indent, output);
212                                 } else {
213                                         statements[i].printStatement(indent+2, output);
214                                 }
215                         }
216                 }
217                 output.append("\n"); //$NON-NLS-1$
218                 return printIndent(indent, output).append('}');
219         }
220
221         public void resolve(BlockScope upperScope) {
222         
223             try {
224                         TypeBinding expressionType = expression.resolveType(upperScope);
225                         if (expressionType == null)
226                                 return;
227                         expression.computeConversion(upperScope, expressionType, expressionType);
228                         checkType: {
229                                 if (expressionType.isBaseType()) {
230                                         if (expression.isConstantValueOfTypeAssignableToType(expressionType, IntBinding))
231                                                 break checkType;
232                                         if (expressionType.isCompatibleWith(IntBinding))
233                                                 break checkType;
234                                 } else if (expressionType.isEnum()) {
235                                         break checkType;
236                                 } else if (upperScope.isBoxingCompatibleWith(expressionType, IntBinding)) {
237                                         expression.computeConversion(upperScope, IntBinding, expressionType);
238                                         break checkType;
239                                 }
240                                 upperScope.problemReporter().incorrectSwitchType(expression, expressionType);
241                                 // TODO (philippe) could keep analyzing switch statements in case of error
242                                 return;
243                         }
244                         if (statements != null) {
245                                 scope = explicitDeclarations == 0 ? upperScope : new BlockScope(upperScope);
246                                 int length;
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;
252                                 int counter = 0;
253                                 for (int i = 0; i < length; i++) {
254                                         Constant constant;
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;
268                                                                 } else {
269                                                                         boolean found = false;
270                                                                         searchReportedDuplicate: for (int k = 2; k < duplicateCaseStatementsCounter; k++) {
271                                                                                 if (duplicateCaseStatements[k] == statement) {
272                                                                                         found = true;
273                                                                                         break searchReportedDuplicate;
274                                                                                 }
275                                                                         }
276                                                                         if (!found) {
277                                                                                 scope.problemReporter().duplicateCase(currentCaseStatement);
278                                                                                 duplicateCaseStatements[duplicateCaseStatementsCounter++] = currentCaseStatement;
279                                                                         }
280                                                                 }
281                                                         }
282                                                 }
283                                                 this.constants[counter++] = key;
284                                         }
285                                 }
286                                 if (length != counter) { // resize constants array
287                                         System.arraycopy(this.constants, 0, this.constants = new int[counter], 0, counter);
288                                 }
289                         } else {
290                                 if ((this.bits & UndocumentedEmptyBlockMASK) != 0) {
291                                         upperScope.problemReporter().undocumentedEmptyBlock(this.blockStart, this.sourceEnd);
292                                 }
293                         }
294             } finally {
295                 if (this.scope != null) this.scope.switchCase = null; // no longer inside switch case block
296             }
297         }
298
299         public void traverse(
300                         ASTVisitor visitor,
301                         BlockScope blockScope) {
302
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);
309                         }
310                 }
311                 visitor.endVisit(this, blockScope);
312         }
313         
314         /**
315          * Dispatch the call on its last statement.
316          */
317         public void branchChainTo(Label label) {
318                 
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);
326                 }
327         }
328 }