EnhancedForToIteratorWhile.java
/*******************************************************************************
* Copyright (c) 2026 Carsten Hammer.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Carsten Hammer
*******************************************************************************/
package org.sandbox.jdt.internal.corext.fix.helper;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.text.edits.TextEditGroup;
import org.sandbox.functional.core.builder.LoopModelBuilder;
import org.sandbox.functional.core.model.LoopModel;
import org.sandbox.functional.core.model.SourceDescriptor;
import org.sandbox.functional.core.terminal.ForEachTerminal;
import org.sandbox.jdt.internal.common.ReferenceHolder;
import org.sandbox.jdt.internal.corext.fix.UseFunctionalCallFixCore;
/**
* Transformer for converting enhanced for-loops to iterator-based while-loops.
*
* <p>Transformation: {@code for (T item : collection) { ... }} → {@code Iterator<T> it = c.iterator(); while (it.hasNext()) { T item = it.next(); ... }}</p>
*
* <p>Uses the ULR pipeline: {@code LoopModelBuilder → LoopModel → ASTIteratorWhileRenderer}.</p>
*
* <p><b>Safety rules:</b></p>
* <ul>
* <li>Rejects array sources — arrays don't have .iterator() method</li>
* </ul>
*
* @see LoopModel
* @see ASTIteratorWhileRenderer
* @see <a href="https://github.com/carstenartur/sandbox/issues/453">Issue #453</a>
* @see <a href="https://github.com/carstenartur/sandbox/issues/549">Issue #549</a>
*/
public class EnhancedForToIteratorWhile extends AbstractFunctionalCall<ASTNode> {
@Override
public void find(UseFunctionalCallFixCore fixcore, CompilationUnit compilationUnit,
Set<CompilationUnitRewriteOperation> operations, Set<ASTNode> nodesprocessed) {
org.sandbox.jdt.internal.common.HelperVisitorFactory.callEnhancedForStatementVisitor(compilationUnit,
new ReferenceHolder<Integer, Object>(), nodesprocessed, (visited, aholder) -> {
// Safety: reject arrays — arrays don't have .iterator() method
Expression iterable = visited.getExpression();
ITypeBinding typeBinding = iterable.resolveTypeBinding();
if (typeBinding != null && typeBinding.isArray()) {
return false;
}
operations.add(fixcore.rewrite(visited, new ReferenceHolder<>()));
nodesprocessed.add(visited);
return false;
});
}
@Override
public void rewrite(UseFunctionalCallFixCore useExplicitEncodingFixCore, ASTNode visited,
CompilationUnitRewrite cuRewrite, TextEditGroup group, ReferenceHolder<ASTNode, Object> data)
throws CoreException {
if (!(visited instanceof EnhancedForStatement forStmt)) {
return;
}
AST ast = cuRewrite.getAST();
ASTRewrite rewrite = cuRewrite.getASTRewrite();
// Build LoopModel from the enhanced for-loop using ULR pipeline
LoopModel model = buildLoopModel(forStmt);
// Render iterator-while loop using ULR-based renderer
ASTIteratorWhileRenderer renderer = new ASTIteratorWhileRenderer(ast, rewrite);
renderer.render(model, forStmt, forStmt.getBody(), group);
// Add Iterator import
cuRewrite.getImportRewrite().addImport("java.util.Iterator"); //$NON-NLS-1$
}
/**
* Builds a LoopModel from an enhanced for-loop using the ULR pipeline.
*/
private LoopModel buildLoopModel(EnhancedForStatement forStmt) {
String paramName = forStmt.getParameter().getName().getIdentifier();
String paramType = forStmt.getParameter().getType().toString();
String collectionExpr = forStmt.getExpression().toString();
// Extract body statements as expression strings
List<String> bodyStatements = extractBodyStatements(forStmt.getBody());
return new LoopModelBuilder()
.source(SourceDescriptor.SourceType.COLLECTION, collectionExpr, paramType)
.element(paramName, paramType, false)
.terminal(new ForEachTerminal(bodyStatements, false))
.build();
}
/**
* Extracts body statements as strings from the loop body.
*/
private List<String> extractBodyStatements(Statement body) {
return ExpressionHelper.bodyStatementsToStrings(body);
}
@Override
public String getPreview(boolean afterRefactoring) {
if (afterRefactoring) {
return """
Iterator<String> it = items.iterator();
while (it.hasNext()) {
String item = it.next();
System.out.println(item);
}
""";
}
return """
for (String item : items) {
System.out.println(item);
}
""";
}
}