IteratorLoopBodyParser.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.ArrayList;
import java.util.List;
import org.eclipse.jdt.core.dom.*;

/**
 * Parses iterator loop body to extract element variable and actual body.
 * 
 * <p>The typical pattern is:</p>
 * <pre>
 * while (it.hasNext()) {
 *     T item = it.next();  // Element extraction (first statement)
 *     // ... actual body ... // Remaining statements
 * }
 * </pre>
 */
public class IteratorLoopBodyParser {
    
    /**
     * Result of parsing the loop body.
     */
    public static class ParsedBody {
        public final String elementVariableName;
        public final String elementType;
        public final List<Statement> actualBodyStatements;
        
        public ParsedBody(String elementVariableName, String elementType, 
                          List<Statement> actualBodyStatements) {
            this.elementVariableName = elementVariableName;
            this.elementType = elementType;
            this.actualBodyStatements = actualBodyStatements;
        }
    }
    
    /**
     * Parses the loop body to extract element variable and actual body statements.
     * 
     * @param loopBody the loop body to parse
     * @param iteratorVarName the name of the iterator variable
     * @return parsed body or null if pattern doesn't match
     */
    public ParsedBody parse(Statement loopBody, String iteratorVarName) {
        if (!(loopBody instanceof Block)) {
            return null;
        }
        
        Block block = (Block) loopBody;
        List<?> statements = block.statements();
        
        if (statements.isEmpty()) {
            return null;
        }
        
        // First statement should be: T item = it.next();
        Statement firstStmt = (Statement) statements.get(0);
        
        if (!(firstStmt instanceof VariableDeclarationStatement)) {
            return null;
        }
        
        VariableDeclarationStatement varDecl = (VariableDeclarationStatement) firstStmt;
        if (varDecl.fragments().size() != 1) {
            return null;
        }
        
        VariableDeclarationFragment fragment = (VariableDeclarationFragment) varDecl.fragments().get(0);
        String elementVarName = fragment.getName().getIdentifier();
        
        // Check initializer: it.next()
        Expression initializer = fragment.getInitializer();
        if (!isNextCall(initializer, iteratorVarName)) {
            return null;
        }
        
        // Extract element type
        String elementType = varDecl.getType().toString();
        
        // Remaining statements are the actual body
        List<Statement> actualBody = new ArrayList<>();
        for (int i = 1; i < statements.size(); i++) {
            actualBody.add((Statement) statements.get(i));
        }
        
        return new ParsedBody(elementVarName, elementType, actualBody);
    }
    
    /**
     * Checks if an expression is it.next() call.
     */
    private boolean isNextCall(Expression expr, String iteratorVarName) {
        if (!(expr instanceof MethodInvocation)) {
            return false;
        }
        
        MethodInvocation methodInv = (MethodInvocation) expr;
        if (!"next".equals(methodInv.getName().getIdentifier())) {
            return false;
        }
        
        Expression iteratorExpr = methodInv.getExpression();
        if (!(iteratorExpr instanceof SimpleName)) {
            return false;
        }
        
        return ((SimpleName) iteratorExpr).getIdentifier().equals(iteratorVarName);
    }
    
    /**
     * Creates a synthetic block containing the actual body statements.
     * This is useful for AST transformations.
     */
    public Block createSyntheticBlock(AST ast, ParsedBody parsedBody) {
        Block syntheticBlock = ast.newBlock();
        
        for (Statement stmt : parsedBody.actualBodyStatements) {
            syntheticBlock.statements().add(ASTNode.copySubtree(ast, stmt));
        }
        
        return syntheticBlock;
    }
}