--- /dev/null
+/*******************************************************************************
+ * 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.jdt.internal.compiler.ast;
+
+import org.eclipse.jdt.internal.compiler.ASTVisitor;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.codegen.CodeStream;
+import org.eclipse.jdt.internal.compiler.codegen.Label;
+import org.eclipse.jdt.internal.compiler.flow.FlowContext;
+import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
+import org.eclipse.jdt.internal.compiler.flow.LoopingFlowContext;
+import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
+import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
+import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
+import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+import org.eclipse.jdt.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 TypeBinding collectionElementType;
+
+ // 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 condInfo = flowInfo.copy().unconditionalInits().discardNullRelatedInitializations();
+ condInfo = this.collection.analyseCode(scope, flowContext, condInfo);
+
+ // element variable will be assigned when iterating
+ condInfo.markAsDefinitelyAssigned(this.elementVariable.binding);
+
+ this.postCollectionInitStateIndex = currentScope.methodScope().recordInitializationStates(condInfo);
+
+ // process the action
+ LoopingFlowContext loopingContext = new LoopingFlowContext(flowContext, this, breakLabel, continueLabel, scope);
+ FlowInfo actionInfo = condInfo.initsWhenTrue().copy();
+ FlowInfo exitBranch;
+ 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
+ exitBranch = condInfo.initsWhenFalse();
+ exitBranch.addInitializationsFrom(flowInfo); // recover null inits from before condition analysis
+ if (!actionInfo.isReachable() && !loopingContext.initsOnContinue.isReachable()) {
+ continueLabel = null;
+ } else {
+ actionInfo = actionInfo.mergedWith(loopingContext.initsOnContinue.unconditionalInits());
+ loopingContext.complainOnDeferredChecks(scope, actionInfo);
+ exitBranch.addPotentialInitializationsFrom(actionInfo.unconditionalInits());
+ }
+ } else {
+ exitBranch = condInfo.initsWhenFalse();
+ }
+
+ // 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,
+ exitBranch,
+ false,
+ true /*for(;;){}while(true); unreachable(); */);
+ mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(mergedInfo);
+ return mergedInfo;
+ }
+
+ /**
+ * For statement code generation
+ *
+ * @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope
+ * @param codeStream org.eclipse.jdt.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.collectionElementType.id);
+ 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) {
+ if (this.elementVariableImplicitWidening != -1) {
+ codeStream.checkcast(this.collectionElementType);
+ codeStream.generateImplicitConversion(this.elementVariableImplicitWidening);
+ } else {
+ 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;
+ this.collectionElementType = ((ArrayBinding) collectionType).elementsType();
+ if (!collectionElementType.isCompatibleWith(elementType)
+ && !scope.isBoxingCompatibleWith(collectionElementType, elementType)) {
+ scope.problemReporter().notCompatibleTypesErrorInForeach(collection, collectionElementType, elementType);
+ }
+ // in case we need to do a conversion
+ int compileTimeTypeID = collectionElementType.id;
+ if (elementType.isBaseType()) {
+ if (!collectionElementType.isBaseType()) {
+ compileTimeTypeID = scope.environment().computeBoxingType(collectionElementType).id;
+ this.elementVariableImplicitWidening = UNBOXING;
+ if (elementType.isBaseType()) {
+ this.elementVariableImplicitWidening |= (elementType.id << 4) + compileTimeTypeID;
+ }
+ } else {
+ this.elementVariableImplicitWidening = (elementType.id << 4) + compileTimeTypeID;
+ }
+ } else {
+ if (collectionElementType.isBaseType()) {
+ int boxedID = scope.environment().computeBoxingType(collectionElementType).id;
+ this.elementVariableImplicitWidening = BOXING | (compileTimeTypeID << 4) | compileTimeTypeID; // use primitive type in implicit conversion
+ compileTimeTypeID = boxedID;
+ }
+ }
+ } 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;
+ this.collectionElementType = parameterizedType.arguments[0];
+ if (!collectionElementType.isCompatibleWith(elementType)
+ && !scope.isBoxingCompatibleWith(collectionElementType, elementType)) {
+ scope.problemReporter().notCompatibleTypesErrorInForeach(collection, collectionElementType, elementType);
+ }
+ int compileTimeTypeID = collectionElementType.id;
+ // no conversion needed as only for reference types
+ if (elementType.isBaseType()) {
+ if (!collectionElementType.isBaseType()) {
+ compileTimeTypeID = scope.environment().computeBoxingType(collectionElementType).id;
+ this.elementVariableImplicitWidening = UNBOXING;
+ if (elementType.isBaseType()) {
+ this.elementVariableImplicitWidening |= (elementType.id << 4) + compileTimeTypeID;
+ }
+ } else {
+ this.elementVariableImplicitWidening = (elementType.id << 4) + compileTimeTypeID;
+ }
+ } else {
+ if (collectionElementType.isBaseType()) {
+ int boxedID = scope.environment().computeBoxingType(collectionElementType).id;
+ this.elementVariableImplicitWidening = BOXING | (compileTimeTypeID << 4) | compileTimeTypeID; // use primitive type in implicit conversion
+ compileTimeTypeID = boxedID;
+ }
+ }
+ }
+ } else if (iterableType.isGenericType()) { // for (T t : Iterable<T>) - in case used inside Iterable itself
+ if (iterableType.typeVariables().length == 1) {
+ this.kind = GENERIC_ITERABLE;
+ this.collectionElementType = iterableType.typeVariables()[0];
+ if (!collectionElementType.isCompatibleWith(elementType)
+ && !scope.isBoxingCompatibleWith(collectionElementType, elementType)) {
+ scope.problemReporter().notCompatibleTypesErrorInForeach(collection, collectionElementType, elementType);
+ }
+ int compileTimeTypeID = collectionElementType.id;
+ // no conversion needed as only for reference types
+ if (elementType.isBaseType()) {
+ if (!collectionElementType.isBaseType()) {
+ compileTimeTypeID = scope.environment().computeBoxingType(collectionElementType).id;
+ this.elementVariableImplicitWidening = UNBOXING;
+ if (elementType.isBaseType()) {
+ this.elementVariableImplicitWidening |= (elementType.id << 4) + compileTimeTypeID;
+ }
+ } else {
+ this.elementVariableImplicitWidening = (elementType.id << 4) + compileTimeTypeID;
+ }
+ } else {
+ if (collectionElementType.isBaseType()) {
+ int boxedID = scope.environment().computeBoxingType(collectionElementType).id;
+ this.elementVariableImplicitWidening = BOXING | (compileTimeTypeID << 4) | compileTimeTypeID; // use primitive type in implicit conversion
+ compileTimeTypeID = boxedID;
+ }
+ }
+ }
+ } else if (iterableType.isRawType()) { // for(Object o : Iterable)
+ this.kind = RAW_ITERABLE;
+ this.collectionElementType = scope.getJavaLangObject();
+ if (!collectionElementType.isCompatibleWith(elementType)
+ && !scope.isBoxingCompatibleWith(collectionElementType, 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);
+ }
+}