IteratorPatternDetector.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.*;

/**
 * Detects iterator patterns in while and for loops.
 * 
 * <p>This detector recognizes the following patterns:</p>
 * <ul>
 *   <li>while-iterator: {@code Iterator<T> it = collection.iterator(); while (it.hasNext()) { T item = it.next(); ... }}</li>
 *   <li>for-loop-iterator: {@code for (Iterator<T> it = collection.iterator(); it.hasNext(); ) { T item = it.next(); ... }}</li>
 * </ul>
 */
public class IteratorPatternDetector {
    
    /**
     * Result of pattern detection.
     */
    public static class IteratorPattern {
        public final Expression collectionExpression;
        public final String iteratorVariableName;
        public final String elementType;
        public final Statement loopBody;
        public final boolean isForLoop; // true for for-loop pattern, false for while pattern
        
        public IteratorPattern(Expression collectionExpression, String iteratorVariableName, 
                               String elementType, Statement loopBody, boolean isForLoop) {
            this.collectionExpression = collectionExpression;
            this.iteratorVariableName = iteratorVariableName;
            this.elementType = elementType;
            this.loopBody = loopBody;
            this.isForLoop = isForLoop;
        }
    }
    
    /**
     * Attempts to detect while-iterator pattern.
     * 
     * Pattern:
     * <pre>
     * Iterator<T> it = collection.iterator();
     * while (it.hasNext()) {
     *     T item = it.next();
     *     // body
     * }
     * </pre>
     */
    public IteratorPattern detectWhilePattern(WhileStatement whileStmt, Statement previousStatement) {
        // Check condition: it.hasNext()
        Expression condition = whileStmt.getExpression();
        String iteratorVar = extractIteratorFromHasNext(condition);
        if (iteratorVar == null) {
            return null;
        }
        
        // Check previous statement: Iterator<T> it = collection.iterator();
        if (previousStatement == null || !(previousStatement instanceof VariableDeclarationStatement)) {
            return null;
        }
        
        VariableDeclarationStatement varDecl = (VariableDeclarationStatement) previousStatement;
        if (varDecl.fragments().size() != 1) {
            return null;
        }
        
        VariableDeclarationFragment fragment = (VariableDeclarationFragment) varDecl.fragments().get(0);
        if (!fragment.getName().getIdentifier().equals(iteratorVar)) {
            return null;
        }
        
        // Check initializer: collection.iterator()
        Expression initializer = fragment.getInitializer();
        Expression collectionExpr = extractCollectionFromIteratorCall(initializer);
        if (collectionExpr == null) {
            return null;
        }
        
        // Extract element type from Iterator<T>
        String elementType = extractElementType(varDecl.getType());
        
        return new IteratorPattern(collectionExpr, iteratorVar, elementType, 
                                    whileStmt.getBody(), false);
    }
    
    /**
     * Attempts to detect for-loop-iterator pattern.
     * 
     * Pattern:
     * <pre>
     * for (Iterator<T> it = collection.iterator(); it.hasNext(); ) {
     *     T item = it.next();
     *     // body
     * }
     * </pre>
     */
    public IteratorPattern detectForLoopPattern(ForStatement forStmt) {
        // Check initializers: Iterator<T> it = collection.iterator();
        if (forStmt.initializers().size() != 1) {
            return null;
        }
        
        Object init = forStmt.initializers().get(0);
        if (!(init instanceof VariableDeclarationExpression)) {
            return null;
        }
        
        VariableDeclarationExpression varDeclExpr = (VariableDeclarationExpression) init;
        if (varDeclExpr.fragments().size() != 1) {
            return null;
        }
        
        VariableDeclarationFragment fragment = (VariableDeclarationFragment) varDeclExpr.fragments().get(0);
        String iteratorVar = fragment.getName().getIdentifier();
        
        // Check initializer: collection.iterator()
        Expression initializer = fragment.getInitializer();
        Expression collectionExpr = extractCollectionFromIteratorCall(initializer);
        if (collectionExpr == null) {
            return null;
        }
        
        // Check condition: it.hasNext()
        Expression condition = forStmt.getExpression();
        String conditionIteratorVar = extractIteratorFromHasNext(condition);
        if (conditionIteratorVar == null || !conditionIteratorVar.equals(iteratorVar)) {
            return null;
        }
        
        // Updaters should be empty (no updaters in this pattern)
        if (!forStmt.updaters().isEmpty()) {
            return null;
        }
        
        // Extract element type from Iterator<T>
        String elementType = extractElementType(varDeclExpr.getType());
        
        return new IteratorPattern(collectionExpr, iteratorVar, elementType, 
                                    forStmt.getBody(), true);
    }
    
    /**
     * Extracts iterator variable from it.hasNext() expression.
     * 
     * @return iterator variable name or null if pattern doesn't match
     */
    private String extractIteratorFromHasNext(Expression expr) {
        if (!(expr instanceof MethodInvocation)) {
            return null;
        }
        
        MethodInvocation methodInv = (MethodInvocation) expr;
        if (!"hasNext".equals(methodInv.getName().getIdentifier())) {
            return null;
        }
        
        Expression iteratorExpr = methodInv.getExpression();
        if (!(iteratorExpr instanceof SimpleName)) {
            return null;
        }
        
        return ((SimpleName) iteratorExpr).getIdentifier();
    }
    
    /**
     * Extracts collection expression from collection.iterator() call.
     * 
     * @return collection expression or null if pattern doesn't match
     */
    private Expression extractCollectionFromIteratorCall(Expression expr) {
        if (!(expr instanceof MethodInvocation)) {
            return null;
        }
        
        MethodInvocation methodInv = (MethodInvocation) expr;
        if (!"iterator".equals(methodInv.getName().getIdentifier())) {
            return null;
        }
        
        // Arguments should be empty
        if (!methodInv.arguments().isEmpty()) {
            return null;
        }
        
        return methodInv.getExpression();
    }
    
    /**
     * Extracts element type from Iterator<T> type.
     * 
     * @return element type as string or "Object" if not determinable
     */
    private String extractElementType(Type type) {
        if (!(type instanceof ParameterizedType)) {
            return "Object";
        }
        
        ParameterizedType paramType = (ParameterizedType) type;
        if (paramType.typeArguments().isEmpty()) {
            return "Object";
        }
        
        Type typeArg = (Type) paramType.typeArguments().get(0);
        return typeArg.toString();
    }
    
    /**
     * Finds the previous statement before a given statement in a block.
     * 
     * @param block the block containing the statements
     * @param statement the statement to find the predecessor of
     * @return the previous statement or null if not found or first statement
     */
    public static Statement findPreviousStatement(Block block, Statement statement) {
        if (block == null) {
            return null;
        }
        
        Statement previous = null;
        for (Object obj : block.statements()) {
            Statement stmt = (Statement) obj;
            if (stmt == statement) {
                return previous;
            }
            previous = stmt;
        }
        
        return null;
    }
    
    /**
     * Checks if a statement is part of a block (required for while-iterator pattern detection).
     */
    public static boolean isStatementInBlock(Statement statement) {
        ASTNode parent = statement.getParent();
        return parent instanceof Block;
    }
}