IteratorLoopToFunctional.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.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.dom.*;
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.jdt.internal.common.ReferenceHolder;
import org.sandbox.jdt.internal.corext.fix.UseFunctionalCallFixCore;
import org.sandbox.jdt.internal.corext.fix.helper.IteratorLoopAnalyzer.SafetyAnalysis;
import org.sandbox.jdt.internal.corext.fix.helper.IteratorLoopBodyParser.ParsedBody;
import org.sandbox.jdt.internal.corext.fix.helper.IteratorPatternDetector.IteratorPattern;

/**
 * Converts iterator-based loops to functional stream operations.
 * 
 * <p>This converter handles:</p>
 * <ul>
 *   <li>while-iterator pattern: {@code Iterator<T> it = coll.iterator(); while (it.hasNext()) { T item = it.next(); ... }}</li>
 *   <li>for-loop-iterator pattern: {@code for (Iterator<T> it = coll.iterator(); it.hasNext(); ) { T item = it.next(); ... }}</li>
 * </ul>
 * 
 * <p>The conversion creates a synthetic EnhancedForStatement and delegates to the existing
 * LoopToFunctional infrastructure for the actual stream generation.</p>
 */
public class IteratorLoopToFunctional extends AbstractFunctionalCall<ASTNode> {
    
    private final IteratorPatternDetector patternDetector = new IteratorPatternDetector();
    private final IteratorLoopAnalyzer loopAnalyzer = new IteratorLoopAnalyzer();
    private final IteratorLoopBodyParser bodyParser = new IteratorLoopBodyParser();
    
    @Override
    public void find(UseFunctionalCallFixCore fixCore, CompilationUnit compilationUnit,
                     Set<CompilationUnitRewriteOperation> operations, Set<ASTNode> nodesProcessed) {
        
        compilationUnit.accept(new ASTVisitor() {
            
            @Override
            public boolean visit(WhileStatement node) {
                if (nodesProcessed.contains(node)) {
                    return false;
                }
                
                // Find previous statement for while-iterator pattern
                if (!IteratorPatternDetector.isStatementInBlock(node)) {
                    return true;
                }
                
                Block parentBlock = (Block) node.getParent();
                Statement previousStmt = IteratorPatternDetector.findPreviousStatement(parentBlock, node);
                
                IteratorPattern pattern = patternDetector.detectWhilePattern(node, previousStmt);
                if (pattern == null) {
                    return true;
                }
                
                if (!analyzeAndValidate(pattern)) {
                    return true;
                }
                
                // Mark both the iterator declaration and the while loop as processed
                nodesProcessed.add(previousStmt);
                nodesProcessed.add(node);
                
                ReferenceHolder<ASTNode, Object> holder = new ReferenceHolder<>();
                holder.put(node, pattern);
                
                operations.add(fixCore.rewrite(node, holder));
                
                return false;
            }
            
            @Override
            public boolean visit(ForStatement node) {
                if (nodesProcessed.contains(node)) {
                    return false;
                }
                
                IteratorPattern pattern = patternDetector.detectForLoopPattern(node);
                if (pattern == null) {
                    return true;
                }
                
                if (!analyzeAndValidate(pattern)) {
                    return true;
                }
                
                nodesProcessed.add(node);
                
                ReferenceHolder<ASTNode, Object> holder = new ReferenceHolder<>();
                holder.put(node, pattern);
                
                operations.add(fixCore.rewrite(node, holder));
                
                return false;
            }
        });
    }
    
    /**
     * Analyzes and validates that the pattern can be safely converted.
     */
    private boolean analyzeAndValidate(IteratorPattern pattern) {
        // Analyze for safety
        SafetyAnalysis analysis = loopAnalyzer.analyze(pattern.loopBody, pattern.iteratorVariableName);
        if (!analysis.isSafe) {
            // Cannot convert unsafe patterns
            return false;
        }
        
        // Parse body to ensure it has the expected structure
        ParsedBody parsedBody = bodyParser.parse(pattern.loopBody, pattern.iteratorVariableName);
        if (parsedBody == null) {
            // Body doesn't match expected pattern
            return false;
        }
        
        return true;
    }
    
    @Override
    public void rewrite(UseFunctionalCallFixCore fixCore, ASTNode node, 
                        CompilationUnitRewrite cuRewrite, TextEditGroup group,
                        ReferenceHolder<ASTNode, Object> holder) throws CoreException {
        
        Object data = holder.get(node);
        if (!(data instanceof IteratorPattern)) {
            return;
        }
        
        IteratorPattern pattern = (IteratorPattern) data;
        ParsedBody parsedBody = bodyParser.parse(pattern.loopBody, pattern.iteratorVariableName);
        
        if (parsedBody == null) {
            return;
        }
        
        AST ast = cuRewrite.getAST();
        ASTRewrite rewrite = cuRewrite.getASTRewrite();
        
        // Create a stream pipeline expression directly
        Expression streamPipeline = createStreamPipeline(ast, pattern, parsedBody);
        
        if (streamPipeline != null) {
            // Replace the original loop with the stream pipeline
            if (node instanceof WhileStatement) {
                // For while pattern, also remove the iterator declaration
                Block parentBlock = (Block) node.getParent();
                Statement previousStmt = IteratorPatternDetector.findPreviousStatement(parentBlock, (Statement) node);
                
                if (previousStmt != null) {
                    rewrite.remove(previousStmt, group);
                }
            }
            
            // Create an expression statement for the stream pipeline
            ExpressionStatement streamStmt = ast.newExpressionStatement(streamPipeline);
            rewrite.replace(node, streamStmt, group);
        }
    }
    
    /**
     * Creates a stream pipeline expression.
     * This creates a simple forEach conversion directly without using StreamPipelineBuilder.
     */
    private Expression createStreamPipeline(AST ast, IteratorPattern pattern, ParsedBody parsedBody) {
        // collection.stream().forEach(item -> { ... })
        
        // collection.stream()
        MethodInvocation streamCall = ast.newMethodInvocation();
        streamCall.setExpression((Expression) ASTNode.copySubtree(ast, pattern.collectionExpression));
        streamCall.setName(ast.newSimpleName("stream"));
        
        // .forEach(item -> { ... })
        MethodInvocation forEachCall = ast.newMethodInvocation();
        forEachCall.setExpression(streamCall);
        forEachCall.setName(ast.newSimpleName("forEach"));
        
        // Lambda: item -> { ... }
        LambdaExpression lambda = ast.newLambdaExpression();
        VariableDeclarationFragment lambdaParam = ast.newVariableDeclarationFragment();
        lambdaParam.setName(ast.newSimpleName(parsedBody.elementVariableName));
        lambda.parameters().add(lambdaParam);
        lambda.setParentheses(false);
        
        // Lambda body
        if (parsedBody.actualBodyStatements.isEmpty()) {
            // Empty body - use empty block
            lambda.setBody(ast.newBlock());
        } else if (parsedBody.actualBodyStatements.size() == 1) {
            Statement stmt = parsedBody.actualBodyStatements.get(0);
            if (stmt instanceof ExpressionStatement) {
                // Single expression - use as lambda body
                ExpressionStatement exprStmt = (ExpressionStatement) stmt;
                lambda.setBody((Expression) ASTNode.copySubtree(ast, exprStmt.getExpression()));
            } else {
                // Single non-expression statement - wrap in block
                Block lambdaBlock = ast.newBlock();
                lambdaBlock.statements().add(ASTNode.copySubtree(ast, stmt));
                lambda.setBody(lambdaBlock);
            }
        } else {
            // Multiple statements - create block
            Block lambdaBlock = bodyParser.createSyntheticBlock(ast, parsedBody);
            lambda.setBody(lambdaBlock);
        }
        
        forEachCall.arguments().add(lambda);
        return forEachCall;
    }
    
    @Override
    public String getPreview(boolean afterRefactoring) {
        if (afterRefactoring) {
            return """
                   items.stream()
                       .forEach(item -> System.out.println(item));
                   """;
        } else {
            return """
                   Iterator<String> it = items.iterator();
                   while (it.hasNext()) {
                       String item = it.next();
                       System.out.println(item);
                   }
                   """;
        }
    }
}