IteratorWhileHandler.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.*;
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.ElementDescriptor;
import org.sandbox.functional.core.model.LoopModel;
import org.sandbox.functional.core.model.SourceDescriptor;
import org.sandbox.functional.core.terminal.ForEachTerminal;
import org.sandbox.functional.core.transformer.LoopModelTransformer;
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;

/**
 * Handler for converting iterator-based while-loops to functional stream operations.
 * 
 * <p>This handler processes:</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>Uses the ULR pipeline: {@code LoopModelBuilder → LoopModel → LoopModelTransformer → ASTStreamRenderer}.</p>
 * 
 * <p><b>Naming Note:</b> This class is named after the <i>source</i> loop type (iterator while-loop),
 * not the target format. The architecture supports bidirectional transformations, so the name
 * describes what loop pattern this handler processes.</p>
 * 
 * @see LoopModel
 * @see ASTStreamRenderer
 * @see LoopModelTransformer
 */
public class IteratorWhileHandler 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 = ReferenceHolder.create();
                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 = ReferenceHolder.create();
                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) {
            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 pattern)) {
            return;
        }
        
        ParsedBody parsedBody = bodyParser.parse(pattern.loopBody(), pattern.iteratorVariableName());
        
        if (parsedBody == null) {
            return;
        }
        
        AST ast = cuRewrite.getRoot().getAST();
        ASTRewrite rewrite = cuRewrite.getASTRewrite();
        
        // Build LoopModel using the ULR pipeline
        LoopModel model = buildLoopModel(pattern, parsedBody);
        
        // Create renderer with the original loop body for AST node access
        ASTStreamRenderer renderer = new ASTStreamRenderer(ast, rewrite, cuRewrite.getRoot(), pattern.loopBody());
        
        // Transform using LoopModelTransformer
        LoopModelTransformer<Expression> transformer = new LoopModelTransformer<>(renderer);
        Expression streamExpression = transformer.transform(model);
        
        if (streamExpression != null) {
            // For while pattern, also remove the iterator declaration
            if (node instanceof WhileStatement whileStmt) {
                Block parentBlock = (Block) whileStmt.getParent();
                Statement previousStmt = IteratorPatternDetector.findPreviousStatement(parentBlock, whileStmt);
                
                if (previousStmt != null) {
                    rewrite.remove(previousStmt, group);
                }
            }
            
            // Wrap in ExpressionStatement and replace the loop
            ExpressionStatement newStatement = ast.newExpressionStatement(streamExpression);
            rewrite.replace(node, newStatement, group);
        }
    }
    
    /**
     * Builds a LoopModel from the iterator pattern using the ULR pipeline.
     * Uses COLLECTION source type since the iterator comes from collection.iterator().
     */
    private LoopModel buildLoopModel(IteratorPattern pattern, ParsedBody parsedBody) {
        // Build COLLECTION source descriptor using the collection expression
        SourceDescriptor source = new SourceDescriptor(
            SourceDescriptor.SourceType.COLLECTION,
            pattern.collectionExpression().toString(),
            parsedBody.elementType()
        );
        
        // Build element descriptor for the loop variable
        ElementDescriptor element = new ElementDescriptor(
            parsedBody.elementVariableName(),
            parsedBody.elementType(),
            true // is a collection element
        );
        
        // Extract body statements as expression strings (strip trailing semicolons)
        List<String> bodyStatements = extractBodyStatementsAsStrings(parsedBody.actualBodyStatements());
        
        // Build ForEachTerminal
        ForEachTerminal terminal = new ForEachTerminal(bodyStatements, false);
        
        // Build and return LoopModel
        return new LoopModelBuilder()
            .source(source)
            .element(element)
            .terminal(terminal)
            .build();
    }
    
    /**
     * Converts body statements to expression strings for the ForEachTerminal.
     * Trailing semicolons are stripped because ForEachTerminal / ASTStreamRenderer.createExpression()
     * expects pure expressions, not statements.
     */
    private List<String> extractBodyStatementsAsStrings(List<Statement> statements) {
        return ExpressionHelper.bodyStatementsToStrings(statements);
    }
    
    @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);
                   }
                   """;
        }
    }
}