ASTIteratorWhileRenderer.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 org.eclipse.jdt.core.dom.*;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.text.edits.TextEditGroup;
import org.sandbox.functional.core.model.LoopModel;
import org.sandbox.functional.core.terminal.ForEachTerminal;
/**
* ULR-based renderer that converts a {@link LoopModel} into an iterator-based while-loop.
*
* <p>This renderer takes the abstract ULR model and produces JDT AST nodes for
* the iterator-while pattern:</p>
* <pre>
* Iterator<T> it = collection.iterator();
* while (it.hasNext()) {
* T item = it.next();
* // body statements
* }
* </pre>
*
* <p>This class is the iterator-while counterpart to {@link ASTStreamRenderer},
* enabling the ULR pipeline to target iterator-while loops instead of streams.</p>
*
* @see LoopModel
* @see ASTStreamRenderer
*/
public class ASTIteratorWhileRenderer {
private String iteratorName;
private final AST ast;
private final ASTRewrite rewrite;
public ASTIteratorWhileRenderer(AST ast, ASTRewrite rewrite) {
this.ast = ast;
this.rewrite = rewrite;
}
/** Lowers ULR filter/map/forEach operations using their original JDT attachments. */
public void renderPipeline(LoopModel model, JdtStreamContext context, ImportRewrite imports, TextEditGroup group) {
new ASTImperativeLoopRenderer(rewrite, imports, group).render(model, context, true);
}
/** Preserve the iterator's actual generic type and the loop variable's conversions. */
@SuppressWarnings("unchecked")
public void renderEnhancedFor(LoopModel model, EnhancedForStatement original, ImportRewrite imports, TextEditGroup group) {
reserveIteratorName(original);
var importContext = new ContextSensitiveImportRewriteContext((CompilationUnit) original.getRoot(), original.getStartPosition(), imports);
ITypeBinding iterable = Bindings.findTypeInHierarchy(original.getExpression().resolveTypeBinding(), "java.lang.Iterable"); //$NON-NLS-1$
ITypeBinding iterator = Bindings.findMethodInHierarchy(iterable, "iterator", new String[0]).getReturnType(); //$NON-NLS-1$
VariableDeclarationFragment iteratorFragment = ast.newVariableDeclarationFragment();
iteratorFragment.setName(ast.newSimpleName(iteratorName));
MethodInvocation iteratorCall = ast.newMethodInvocation();
iteratorCall.setExpression((Expression) rewrite.createCopyTarget(original.getExpression()));
iteratorCall.setName(ast.newSimpleName("iterator")); //$NON-NLS-1$
iteratorFragment.setInitializer(iteratorCall);
VariableDeclarationStatement iteratorDeclaration = ast.newVariableDeclarationStatement(iteratorFragment);
iteratorDeclaration.setType(imports.addImport(iterator, ast, importContext));
SingleVariableDeclaration parameter = original.getParameter();
VariableDeclarationFragment element = ast.newVariableDeclarationFragment();
element.setName(ast.newSimpleName(model.getElement().variableName()));
for (Object dimension : parameter.extraDimensions()) {
element.extraDimensions().add(rewrite.createCopyTarget((Dimension) dimension));
}
MethodInvocation next = ast.newMethodInvocation();
next.setExpression(ast.newSimpleName(iteratorName));
next.setName(ast.newSimpleName("next")); //$NON-NLS-1$
element.setInitializer(next);
VariableDeclarationStatement declaration = ast.newVariableDeclarationStatement(element);
declaration.setType(parameter.getType().isVar() ? imports.addImport(parameter.resolveBinding().getType(), ast, importContext)
: (Type) rewrite.createCopyTarget(parameter.getType()));
for (Object modifier : parameter.modifiers()) {
declaration.modifiers().add(rewrite.createCopyTarget((ASTNode) modifier));
}
WhileStatement loop = ast.newWhileStatement();
MethodInvocation hasNext = ast.newMethodInvocation();
hasNext.setExpression(ast.newSimpleName(iteratorName));
hasNext.setName(ast.newSimpleName("hasNext")); //$NON-NLS-1$
loop.setExpression(hasNext);
if (original.getBody() instanceof Block block) {
rewrite.getListRewrite(block, Block.STATEMENTS_PROPERTY).insertFirst(declaration, group);
loop.setBody((Statement) rewrite.createCopyTarget(block));
} else {
Block body = ast.newBlock();
body.statements().add(declaration);
body.statements().add(rewrite.createCopyTarget(original.getBody()));
loop.setBody(body);
}
Statement anchor = original;
Statement replacement = loop;
while (anchor.getParent() instanceof LabeledStatement label) {
LabeledStatement copied = ast.newLabeledStatement();
copied.setLabel((SimpleName) rewrite.createCopyTarget(label.getLabel()));
copied.setBody(replacement);
replacement = copied;
anchor = label;
}
if (anchor.getParent() instanceof Block block) {
var list = rewrite.getListRewrite(block, Block.STATEMENTS_PROPERTY);
list.insertBefore(iteratorDeclaration, anchor, group);
list.replace(anchor, replacement, group);
} else {
Block block = ast.newBlock();
block.statements().add(iteratorDeclaration);
block.statements().add(replacement);
rewrite.replace(anchor, block, group);
}
}
private void reserveIteratorName(Statement originalStatement) {
iteratorName = LoopVariableNames.fresh("it", LoopVariableNames.usedNames(originalStatement)); //$NON-NLS-1$
}
/**
* Renders the given LoopModel as an iterator-while loop, replacing the original statement.
*
* @param model the ULR LoopModel to render
* @param originalStatement the original enhanced for-loop to replace
* @param originalBody the original loop body (for createCopyTarget comment preservation)
* @param group the text edit group
*/
public void render(LoopModel model, Statement originalStatement, Statement originalBody, TextEditGroup group) {
reserveIteratorName(originalStatement);
String elementType = model.getElement().typeName();
String elementName = model.getElement().variableName();
String collectionExpr = model.getSource().expression();
// Create Iterator<T> it = collection.iterator();
VariableDeclarationStatement iteratorDecl = createIteratorDeclaration(elementType, collectionExpr);
// Create while (it.hasNext()) { T item = it.next(); body... }
WhileStatement whileStmt = createWhileStatement(elementType, elementName, originalBody);
// Replace the original statement with iterator decl + while
replaceWithIteratorAndWhile(originalStatement, iteratorDecl, whileStmt, group);
}
/**
* Renders the given LoopModel as an iterator-while loop using string-based body statements.
* Used when the original AST body is not available for createCopyTarget.
*
* @param model the ULR LoopModel to render
* @param originalStatement the original enhanced for-loop to replace
* @param group the text edit group
*/
public void renderFromModel(LoopModel model, Statement originalStatement, TextEditGroup group) {
reserveIteratorName(originalStatement);
String elementType = model.getElement().typeName();
String elementName = model.getElement().variableName();
String collectionExpr = model.getSource().expression();
// Create Iterator<T> it = collection.iterator();
VariableDeclarationStatement iteratorDecl = createIteratorDeclaration(elementType, collectionExpr);
// Create while statement with body from model terminal
WhileStatement whileStmt = createWhileStatementFromModel(elementType, elementName, model);
// Replace the original statement with iterator decl + while
replaceWithIteratorAndWhile(originalStatement, iteratorDecl, whileStmt, group);
}
/**
* Renders the given LoopModel as an iterator-while loop with body from AST statement nodes.
* Used when the body is available as AST nodes (e.g., from a lambda body) for createCopyTarget.
*
* @param model the ULR LoopModel to render
* @param originalStatement the original statement to replace
* @param bodyStatements the body statements to copy into the while-loop
* @param group the text edit group
*/
@SuppressWarnings("unchecked")
public void renderWithBodyStatements(LoopModel model, Statement originalStatement,
java.util.List<Statement> bodyStatements, TextEditGroup group) {
reserveIteratorName(originalStatement);
String elementType = model.getElement().typeName();
String elementName = model.getElement().variableName();
String collectionExpr = model.getSource().expression();
// Create Iterator<T> it = collection.iterator();
VariableDeclarationStatement iteratorDecl = createIteratorDeclaration(elementType, collectionExpr);
// Create while statement with body from provided statements
MethodInvocation hasNextCall = ast.newMethodInvocation();
hasNextCall.setExpression(ast.newSimpleName(iteratorName));
hasNextCall.setName(ast.newSimpleName("hasNext")); //$NON-NLS-1$
WhileStatement whileStmt = ast.newWhileStatement();
whileStmt.setExpression(hasNextCall);
Block whileBody = ast.newBlock();
whileBody.statements().add(createItemDeclaration(elementType, elementName));
for (Statement stmt : bodyStatements) {
if (stmt.getParent() != null) {
whileBody.statements().add(rewrite.createCopyTarget(stmt));
} else {
whileBody.statements().add(stmt);
}
}
whileStmt.setBody(whileBody);
// Replace the original statement with iterator decl + while
replaceWithIteratorAndWhile(originalStatement, iteratorDecl, whileStmt, group);
}
/**
* Replaces the original statement with an iterator declaration followed by a while-loop.
* Handles both block-level (inserts before + replaces) and non-block parents (wraps in new block).
*/
@SuppressWarnings("unchecked")
private void replaceWithIteratorAndWhile(Statement originalStatement,
VariableDeclarationStatement iteratorDecl, WhileStatement whileStmt, TextEditGroup group) {
ASTNode parent = originalStatement.getParent();
if (parent instanceof Block parentBlock) {
ListRewrite listRewrite = rewrite.getListRewrite(parentBlock, Block.STATEMENTS_PROPERTY);
listRewrite.insertBefore(iteratorDecl, originalStatement, group);
listRewrite.replace(originalStatement, whileStmt, group);
} else {
Block newBlock = ast.newBlock();
newBlock.statements().add(iteratorDecl);
newBlock.statements().add(whileStmt);
rewrite.replace(originalStatement, newBlock, group);
}
}
@SuppressWarnings("unchecked")
private VariableDeclarationStatement createIteratorDeclaration(String elementType, String collectionExpr) {
// Iterator<T>
ParameterizedType iteratorType = ast.newParameterizedType(ast.newSimpleType(ast.newName("Iterator"))); //$NON-NLS-1$
iteratorType.typeArguments().add(ast.newSimpleType(ast.newName(elementType)));
// it = collection.iterator()
VariableDeclarationFragment fragment = ast.newVariableDeclarationFragment();
fragment.setName(ast.newSimpleName(iteratorName));
MethodInvocation iteratorCall = ast.newMethodInvocation();
iteratorCall.setExpression(createExpression(collectionExpr));
iteratorCall.setName(ast.newSimpleName("iterator")); //$NON-NLS-1$
fragment.setInitializer(iteratorCall);
VariableDeclarationStatement decl = ast.newVariableDeclarationStatement(fragment);
decl.setType(iteratorType);
return decl;
}
@SuppressWarnings("unchecked")
private WhileStatement createWhileStatement(String elementType, String elementName, Statement originalBody) {
// while (it.hasNext())
MethodInvocation hasNextCall = ast.newMethodInvocation();
hasNextCall.setExpression(ast.newSimpleName(iteratorName));
hasNextCall.setName(ast.newSimpleName("hasNext")); //$NON-NLS-1$
WhileStatement whileStmt = ast.newWhileStatement();
whileStmt.setExpression(hasNextCall);
// T item = it.next();
Block whileBody = ast.newBlock();
whileBody.statements().add(createItemDeclaration(elementType, elementName));
// Copy original body statements, preserving comments via createCopyTarget
if (originalBody instanceof Block origBlock) {
for (Object stmt : origBlock.statements()) {
whileBody.statements().add(rewrite.createCopyTarget((Statement) stmt));
}
} else {
whileBody.statements().add(rewrite.createCopyTarget(originalBody));
}
whileStmt.setBody(whileBody);
return whileStmt;
}
@SuppressWarnings("unchecked")
private WhileStatement createWhileStatementFromModel(String elementType, String elementName, LoopModel model) {
// while (it.hasNext())
MethodInvocation hasNextCall = ast.newMethodInvocation();
hasNextCall.setExpression(ast.newSimpleName(iteratorName));
hasNextCall.setName(ast.newSimpleName("hasNext")); //$NON-NLS-1$
WhileStatement whileStmt = ast.newWhileStatement();
whileStmt.setExpression(hasNextCall);
// T item = it.next();
Block whileBody = ast.newBlock();
whileBody.statements().add(createItemDeclaration(elementType, elementName));
// Add body from model terminal
if (model.getTerminal() instanceof ForEachTerminal forEachTerminal) {
List<String> bodyStatements = forEachTerminal.bodyStatements();
for (String stmtStr : bodyStatements) {
String withSemicolon = stmtStr.endsWith(";") ? stmtStr : stmtStr + ";"; //$NON-NLS-1$ //$NON-NLS-2$
ASTNode parsedStmt = rewrite.createStringPlaceholder(withSemicolon, ASTNode.EXPRESSION_STATEMENT);
whileBody.statements().add(parsedStmt);
}
}
whileStmt.setBody(whileBody);
return whileStmt;
}
private VariableDeclarationStatement createItemDeclaration(String elementType, String elementName) {
// T item = it.next();
MethodInvocation nextCall = ast.newMethodInvocation();
nextCall.setExpression(ast.newSimpleName(iteratorName));
nextCall.setName(ast.newSimpleName("next")); //$NON-NLS-1$
VariableDeclarationFragment fragment = ast.newVariableDeclarationFragment();
fragment.setName(ast.newSimpleName(elementName));
fragment.setInitializer(nextCall);
VariableDeclarationStatement decl = ast.newVariableDeclarationStatement(fragment);
decl.setType(ast.newSimpleType(ast.newName(elementType)));
return decl;
}
private Expression createExpression(String expressionStr) {
return ExpressionHelper.createExpression(ast, rewrite, expressionStr);
}
}