/******************************************************************************* | |
* Copyright (c) 2000, 2004 IBM Corporation and others. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Common Public License v1.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/cpl-v10.html | |
* | |
* Contributors: | |
* IBM Corporation - initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.wst.jsdt.internal.compiler.ast; | |
import org.eclipse.wst.jsdt.internal.compiler.ASTVisitor; | |
import org.eclipse.wst.jsdt.internal.compiler.classfmt.ClassFileConstants; | |
import org.eclipse.wst.jsdt.internal.compiler.codegen.CodeStream; | |
import org.eclipse.wst.jsdt.internal.compiler.codegen.Label; | |
import org.eclipse.wst.jsdt.internal.compiler.flow.FlowContext; | |
import org.eclipse.wst.jsdt.internal.compiler.flow.FlowInfo; | |
import org.eclipse.wst.jsdt.internal.compiler.flow.LoopingFlowContext; | |
import org.eclipse.wst.jsdt.internal.compiler.lookup.ArrayBinding; | |
import org.eclipse.wst.jsdt.internal.compiler.lookup.BlockScope; | |
import org.eclipse.wst.jsdt.internal.compiler.lookup.LocalVariableBinding; | |
import org.eclipse.wst.jsdt.internal.compiler.lookup.MethodBinding; | |
import org.eclipse.wst.jsdt.internal.compiler.lookup.ParameterizedTypeBinding; | |
import org.eclipse.wst.jsdt.internal.compiler.lookup.ReferenceBinding; | |
import org.eclipse.wst.jsdt.internal.compiler.lookup.TypeBinding; | |
import org.eclipse.wst.jsdt.internal.compiler.lookup.TypeConstants; | |
public class ForeachStatement extends Statement { | |
public LocalDeclaration elementVariable; | |
public int elementVariableImplicitWidening = -1; | |
public Expression collection; | |
public Statement action; | |
// set the kind of foreach | |
private int kind; | |
// possible kinds of iterating behavior | |
private static final int ARRAY = 0; | |
private static final int RAW_ITERABLE = 1; | |
private static final int GENERIC_ITERABLE = 2; | |
private int arrayElementTypeID; | |
// loop labels | |
private Label breakLabel; | |
private Label continueLabel; | |
public BlockScope scope; | |
// secret variables for codegen | |
public LocalVariableBinding indexVariable; | |
public LocalVariableBinding collectionVariable; // to store the collection expression value | |
public LocalVariableBinding maxVariable; | |
// secret variable names | |
private static final char[] SecretIndexVariableName = " index".toCharArray(); //$NON-NLS-1$ | |
private static final char[] SecretCollectionVariableName = " collection".toCharArray(); //$NON-NLS-1$ | |
private static final char[] SecretMaxVariableName = " max".toCharArray(); //$NON-NLS-1$ | |
int postCollectionInitStateIndex = -1; | |
int mergedInitStateIndex = -1; | |
public ForeachStatement( | |
LocalDeclaration elementVariable, | |
Expression collection, | |
int start) { | |
this.elementVariable = elementVariable; | |
this.collection = collection; | |
this.sourceStart = start; | |
this.kind = -1; | |
} | |
public FlowInfo analyseCode( | |
BlockScope currentScope, | |
FlowContext flowContext, | |
FlowInfo flowInfo) { | |
// initialize break and continue labels | |
breakLabel = new Label(); | |
continueLabel = new Label(); | |
// process the element variable and collection | |
flowInfo = this.elementVariable.analyseCode(scope, flowContext, flowInfo); | |
flowInfo = this.collection.analyseCode(scope, flowContext, flowInfo); | |
// element variable will be assigned when iterating | |
flowInfo.markAsDefinitelyAssigned(this.elementVariable.binding); | |
this.postCollectionInitStateIndex = currentScope.methodScope().recordInitializationStates(flowInfo); | |
// process the action | |
LoopingFlowContext loopingContext = new LoopingFlowContext(flowContext, this, breakLabel, continueLabel, scope); | |
FlowInfo actionInfo = flowInfo.initsWhenTrue().copy(); | |
if (!(action == null || (action.isEmptyBlock() | |
&& currentScope.environment().options.complianceLevel <= ClassFileConstants.JDK1_3))) { | |
if (!this.action.complainIfUnreachable(actionInfo, scope, false)) { | |
actionInfo = action.analyseCode(scope, loopingContext, actionInfo); | |
} | |
// code generation can be optimized when no need to continue in the loop | |
if (!actionInfo.isReachable() && !loopingContext.initsOnContinue.isReachable()) { | |
continueLabel = null; | |
} else { | |
actionInfo = actionInfo.mergedWith(loopingContext.initsOnContinue.unconditionalInits()); | |
loopingContext.complainOnFinalAssignmentsInLoop(scope, actionInfo); | |
} | |
} | |
// we need the variable to iterate the collection even if the | |
// element variable is not used | |
if (!(this.action == null | |
|| this.action.isEmptyBlock() | |
|| ((this.action.bits & IsUsefulEmptyStatementMASK) != 0))) { | |
switch(this.kind) { | |
case ARRAY : | |
this.collectionVariable.useFlag = LocalVariableBinding.USED; | |
this.indexVariable.useFlag = LocalVariableBinding.USED; | |
this.maxVariable.useFlag = LocalVariableBinding.USED; | |
break; | |
case RAW_ITERABLE : | |
case GENERIC_ITERABLE : | |
this.indexVariable.useFlag = LocalVariableBinding.USED; | |
break; | |
} | |
} | |
//end of loop | |
FlowInfo mergedInfo = FlowInfo.mergedOptimizedBranches( | |
loopingContext.initsOnBreak, | |
false, | |
flowInfo.initsWhenFalse(), | |
false, | |
true /*for(;;){}while(true); unreachable(); */); | |
return mergedInfo; | |
} | |
/** | |
* For statement code generation | |
* | |
* @param currentScope org.eclipse.wst.jsdt.internal.compiler.lookup.BlockScope | |
* @param codeStream org.eclipse.wst.jsdt.internal.compiler.codegen.CodeStream | |
*/ | |
public void generateCode(BlockScope currentScope, CodeStream codeStream) { | |
if ((bits & IsReachableMASK) == 0) { | |
return; | |
} | |
int pc = codeStream.position; | |
if (this.action == null | |
|| this.action.isEmptyBlock() | |
|| ((this.action.bits & IsUsefulEmptyStatementMASK) != 0)) { | |
codeStream.exitUserScope(scope); | |
if (mergedInitStateIndex != -1) { | |
codeStream.removeNotDefinitelyAssignedVariables(currentScope, mergedInitStateIndex); | |
codeStream.addDefinitelyAssignedVariables(currentScope, mergedInitStateIndex); | |
} | |
codeStream.recordPositionsFrom(pc, this.sourceStart); | |
return; | |
} | |
// generate the initializations | |
switch(this.kind) { | |
case ARRAY : | |
collection.generateCode(scope, codeStream, true); | |
codeStream.store(this.collectionVariable, false); | |
codeStream.iconst_0(); | |
codeStream.store(this.indexVariable, false); | |
codeStream.load(this.collectionVariable); | |
codeStream.arraylength(); | |
codeStream.store(this.maxVariable, false); | |
break; | |
case RAW_ITERABLE : | |
case GENERIC_ITERABLE : | |
collection.generateCode(scope, codeStream, true); | |
// declaringClass.iterator(); | |
final TypeBinding collectionTypeBinding = collection.resolvedType; | |
MethodBinding iteratorMethodBinding = | |
new MethodBinding( | |
AccPublic, | |
"iterator".toCharArray(),//$NON-NLS-1$ | |
scope.getJavaUtilIterator(), | |
TypeConstants.NoParameters, | |
TypeConstants.NoExceptions, | |
(ReferenceBinding) collectionTypeBinding); | |
if (collectionTypeBinding.isInterface()) { | |
codeStream.invokeinterface(iteratorMethodBinding); | |
} else { | |
codeStream.invokevirtual(iteratorMethodBinding); | |
} | |
codeStream.store(this.indexVariable, false); | |
break; | |
} | |
// label management | |
Label actionLabel = new Label(codeStream); | |
Label conditionLabel = new Label(codeStream); | |
breakLabel.initialize(codeStream); | |
if (this.continueLabel != null) { | |
this.continueLabel.initialize(codeStream); | |
} | |
// jump over the actionBlock | |
codeStream.goto_(conditionLabel); | |
// generate the loop action | |
actionLabel.place(); | |
// generate the loop action | |
if (this.elementVariable.binding.resolvedPosition != -1) { | |
switch(this.kind) { | |
case ARRAY : | |
codeStream.load(this.collectionVariable); | |
codeStream.load(this.indexVariable); | |
codeStream.arrayAt(this.arrayElementTypeID); | |
if (this.elementVariableImplicitWidening != -1) { | |
codeStream.generateImplicitConversion(this.elementVariableImplicitWidening); | |
} | |
codeStream.store(this.elementVariable.binding, false); | |
break; | |
case RAW_ITERABLE : | |
case GENERIC_ITERABLE : | |
codeStream.load(this.indexVariable); | |
codeStream.invokeJavaUtilIteratorNext(); | |
if (this.elementVariable.binding.type.id != T_JavaLangObject) { | |
codeStream.checkcast(this.elementVariable.binding.type); | |
} | |
codeStream.store(this.elementVariable.binding, false); | |
break; | |
} | |
codeStream.addVisibleLocalVariable(this.elementVariable.binding); | |
if (this.postCollectionInitStateIndex != -1) { | |
codeStream.addDefinitelyAssignedVariables( | |
currentScope, | |
this.postCollectionInitStateIndex); | |
} | |
} | |
this.action.generateCode(scope, codeStream); | |
// continuation point | |
int continuationPC = codeStream.position; | |
if (this.continueLabel != null) { | |
this.continueLabel.place(); | |
// generate the increments for next iteration | |
switch(this.kind) { | |
case ARRAY : | |
codeStream.iinc(this.indexVariable.resolvedPosition, 1); | |
break; | |
case RAW_ITERABLE : | |
case GENERIC_ITERABLE : | |
break; | |
} | |
} | |
// generate the condition | |
conditionLabel.place(); | |
if (this.postCollectionInitStateIndex != -1) { | |
codeStream.removeNotDefinitelyAssignedVariables(currentScope, postCollectionInitStateIndex); | |
} | |
switch(this.kind) { | |
case ARRAY : | |
codeStream.load(this.indexVariable); | |
codeStream.load(this.maxVariable); | |
codeStream.if_icmplt(actionLabel); | |
break; | |
case RAW_ITERABLE : | |
case GENERIC_ITERABLE : | |
codeStream.load(this.indexVariable); | |
codeStream.invokeJavaUtilIteratorHasNext(); | |
codeStream.ifne(actionLabel); | |
break; | |
} | |
codeStream.recordPositionsFrom(continuationPC, this.elementVariable.sourceStart); | |
breakLabel.place(); | |
codeStream.exitUserScope(scope); | |
if (mergedInitStateIndex != -1) { | |
codeStream.removeNotDefinitelyAssignedVariables(currentScope, mergedInitStateIndex); | |
codeStream.addDefinitelyAssignedVariables(currentScope, mergedInitStateIndex); | |
} | |
codeStream.recordPositionsFrom(pc, this.sourceStart); | |
} | |
public StringBuffer printStatement(int tab, StringBuffer output) { | |
printIndent(tab, output).append("for ("); //$NON-NLS-1$ | |
this.elementVariable.print(0, output); | |
output.append(" : ");//$NON-NLS-1$ | |
this.collection.print(0, output).append(") "); //$NON-NLS-1$ | |
//block | |
if (this.action == null) { | |
output.append(';'); | |
} else { | |
output.append('\n'); | |
this.action.printStatement(tab + 1, output); //$NON-NLS-1$ | |
} | |
return output; | |
} | |
public void resolve(BlockScope upperScope) { | |
// use the scope that will hold the init declarations | |
scope = new BlockScope(upperScope); | |
this.elementVariable.resolve(scope); // collection expression can see itemVariable | |
TypeBinding elementType = this.elementVariable.type.resolvedType; | |
TypeBinding collectionType = this.collection.resolveType(scope); | |
this.collection.computeConversion(scope, collectionType, collectionType); | |
boolean hasError = elementType == null || collectionType == null; | |
if (!hasError) { | |
if (collectionType.isArrayType()) { // for(E e : E[]) | |
this.kind = ARRAY; | |
TypeBinding collectionElementType = ((ArrayBinding) collectionType).elementsType(); | |
if (!collectionElementType.isCompatibleWith(elementType)) { | |
scope.problemReporter().notCompatibleTypesErrorInForeach(collection, collectionElementType, elementType); | |
} | |
// in case we need to do a conversion | |
this.arrayElementTypeID = collectionElementType.id; | |
if (elementType.isBaseType()) { | |
this.elementVariableImplicitWidening = (elementType.id << 4) + this.arrayElementTypeID; | |
} | |
} else if (collectionType instanceof ReferenceBinding) { | |
ReferenceBinding iterableType = ((ReferenceBinding)collectionType).findSuperTypeErasingTo(T_JavaLangIterable, false /*Iterable is not a class*/); | |
if (iterableType != null) { | |
if (iterableType.isParameterizedType()) { // for(E e : Iterable<E>) | |
ParameterizedTypeBinding parameterizedType = (ParameterizedTypeBinding)iterableType; | |
if (parameterizedType.arguments.length == 1) { // per construction can only be one | |
this.kind = GENERIC_ITERABLE; | |
TypeBinding collectionElementType = parameterizedType.arguments[0]; | |
if (!collectionElementType.isCompatibleWith(elementType)) { | |
scope.problemReporter().notCompatibleTypesErrorInForeach(collection, collectionElementType, elementType); | |
} | |
// no conversion needed as only for reference types | |
} | |
} else if (iterableType.isGenericType()) { // for (T t : Iterable<T>) - in case used inside Iterable itself | |
if (iterableType.typeVariables().length == 1) { | |
this.kind = GENERIC_ITERABLE; | |
TypeBinding collectionElementType = iterableType.typeVariables()[0]; | |
if (!collectionElementType.isCompatibleWith(elementType)) { | |
scope.problemReporter().notCompatibleTypesErrorInForeach(collection, collectionElementType, elementType); | |
} | |
} | |
} else if (iterableType.isRawType()) { // for(Object o : Iterable) | |
this.kind = RAW_ITERABLE; | |
TypeBinding collectionElementType = scope.getJavaLangObject(); | |
if (!collectionElementType.isCompatibleWith(elementType)) { | |
scope.problemReporter().notCompatibleTypesErrorInForeach(collection, collectionElementType, elementType); | |
} | |
// no conversion needed as only for reference types | |
} | |
} | |
} | |
switch(this.kind) { | |
case ARRAY : | |
// allocate #index secret variable (of type int) | |
this.indexVariable = new LocalVariableBinding(SecretIndexVariableName, IntBinding, AccDefault, false); | |
scope.addLocalVariable(this.indexVariable); | |
this.indexVariable.setConstant(NotAConstant); // not inlinable | |
// allocate #max secret variable | |
this.maxVariable = new LocalVariableBinding(SecretMaxVariableName, IntBinding, AccDefault, false); | |
scope.addLocalVariable(this.maxVariable); | |
this.maxVariable.setConstant(NotAConstant); // not inlinable | |
// add #array secret variable (of collection type) | |
this.collectionVariable = new LocalVariableBinding(SecretCollectionVariableName, collectionType, AccDefault, false); | |
scope.addLocalVariable(this.collectionVariable); | |
this.collectionVariable.setConstant(NotAConstant); // not inlinable | |
break; | |
case RAW_ITERABLE : | |
case GENERIC_ITERABLE : | |
// allocate #index secret variable (of type Iterator) | |
this.indexVariable = new LocalVariableBinding(SecretIndexVariableName, scope.getJavaUtilIterator(), AccDefault, false); | |
scope.addLocalVariable(this.indexVariable); | |
this.indexVariable.setConstant(NotAConstant); // not inlinable | |
break; | |
default : | |
scope.problemReporter().invalidTypeForCollection(collection); | |
} | |
} | |
if (action != null) { | |
action.resolve(scope); | |
} | |
} | |
public void traverse( | |
ASTVisitor visitor, | |
BlockScope blockScope) { | |
if (visitor.visit(this, blockScope)) { | |
this.elementVariable.traverse(visitor, scope); | |
this.collection.traverse(visitor, scope); | |
if (action != null) { | |
action.traverse(visitor, scope); | |
} | |
} | |
visitor.endVisit(this, blockScope); | |
} | |
} |