JdtLoopExtractor.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 org.eclipse.jdt.core.dom.*;
import org.sandbox.functional.core.builder.LoopModelBuilder;
import org.sandbox.functional.core.model.*;
import org.sandbox.functional.core.model.SourceDescriptor.SourceType;
/**
* Extracts a LoopModel from JDT AST nodes.
* This bridges the JDT world with the abstract ULR model.
*/
public class JdtLoopExtractor {
/**
* Extracts a LoopModel from an EnhancedForStatement.
*/
/**
* Enhanced wrapper that includes both the abstract LoopModel and the original AST nodes.
* This allows the renderer to use the actual AST nodes instead of parsing strings.
*/
public static class ExtractedLoop {
public final LoopModel model;
public final Statement originalBody;
public ExtractedLoop(LoopModel model, Statement originalBody) {
this.model = model;
this.originalBody = originalBody;
}
}
public ExtractedLoop extract(EnhancedForStatement forStatement) {
Expression iterable = forStatement.getExpression();
SingleVariableDeclaration parameter = forStatement.getParameter();
Statement body = forStatement.getBody();
// Determine source type
SourceType sourceType = determineSourceType(iterable);
String sourceExpression = iterable.toString();
String elementType = parameter.getType().toString();
// Element info
String varName = parameter.getName().getIdentifier();
boolean isFinal = Modifier.isFinal(parameter.getModifiers());
// Analyze metadata
LoopBodyAnalyzer analyzer = new LoopBodyAnalyzer();
body.accept(analyzer);
// Build model
LoopModelBuilder builder = new LoopModelBuilder()
.source(sourceType, sourceExpression, elementType)
.element(varName, elementType, isFinal)
.metadata(analyzer.hasBreak(), analyzer.hasContinue(),
analyzer.hasReturn(), analyzer.modifiesCollection(), true);
// Analyze body and add operations/terminal
analyzeAndAddOperations(body, builder, varName);
LoopModel model = builder.build();
return new ExtractedLoop(model, body);
}
private SourceType determineSourceType(Expression iterable) {
ITypeBinding binding = iterable.resolveTypeBinding();
if (binding != null) {
if (binding.isArray()) {
return SourceType.ARRAY;
}
// Check for Collection
if (isCollection(binding)) {
return SourceType.COLLECTION;
}
}
return SourceType.ITERABLE;
}
private boolean isCollection(ITypeBinding binding) {
if (binding == null) {
return false;
}
ITypeBinding erasure = binding.getErasure();
if (erasure == null) {
return false;
}
String qualifiedName = erasure.getQualifiedName();
if ("java.util.Collection".equals(qualifiedName)
|| "java.util.List".equals(qualifiedName)
|| "java.util.Set".equals(qualifiedName)
|| "java.util.Queue".equals(qualifiedName)
|| "java.util.Deque".equals(qualifiedName)) {
return true;
}
// Check interfaces
for (ITypeBinding iface : erasure.getInterfaces()) {
if (isCollection(iface)) {
return true;
}
}
// Check superclass
ITypeBinding superclass = erasure.getSuperclass();
if (superclass != null && isCollection(superclass)) {
return true;
}
return false;
}
private void analyzeAndAddOperations(Statement body, LoopModelBuilder builder, String varName) {
// For now, treat the entire body as a forEach terminal
// More sophisticated analysis will be added later
java.util.List<String> bodyStatements = new java.util.ArrayList<>();
if (body instanceof Block) {
Block block = (Block) body;
for (Object stmt : block.statements()) {
String stmtStr = stmt.toString();
// Strip trailing semicolon for expression statements
// This allows the renderer to use them as lambda expressions
if (stmtStr.endsWith(";")) {
stmtStr = stmtStr.substring(0, stmtStr.length() - 1).trim();
}
bodyStatements.add(stmtStr);
}
} else {
String stmtStr = body.toString();
// Strip trailing semicolon for expression statements
if (stmtStr.endsWith(";")) {
stmtStr = stmtStr.substring(0, stmtStr.length() - 1).trim();
}
bodyStatements.add(stmtStr);
}
builder.forEach(bodyStatements, false); // unordered for simple enhanced for
}
/**
* Visitor to analyze loop body for control flow.
*/
private static class LoopBodyAnalyzer extends ASTVisitor {
private boolean hasBreak = false;
private boolean hasContinue = false;
private boolean hasReturn = false;
private boolean modifiesCollection = false;
@Override
public boolean visit(BreakStatement node) {
hasBreak = true;
return false;
}
@Override
public boolean visit(ContinueStatement node) {
hasContinue = true;
return false;
}
@Override
public boolean visit(ReturnStatement node) {
hasReturn = true;
return false;
}
@Override
public boolean visit(MethodInvocation node) {
String name = node.getName().getIdentifier();
if ("add".equals(name) || "remove".equals(name) ||
"clear".equals(name) || "set".equals(name)) {
modifiesCollection = true;
}
return true;
}
public boolean hasBreak() { return hasBreak; }
public boolean hasContinue() { return hasContinue; }
public boolean hasReturn() { return hasReturn; }
public boolean modifiesCollection() { return modifiesCollection; }
}
}