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

/**
 * Analyzes iterator loops for safety and convertibility.
 * 
 * <p>This analyzer checks for:</p>
 * <ul>
 *   <li>iterator.remove() calls (not safe for stream conversion)</li>
 *   <li>Multiple iterator.next() calls (not safe)</li>
 *   <li>break statements (requires special handling)</li>
 *   <li>labeled continue statements (not safe)</li>
 * </ul>
 */
public class IteratorLoopAnalyzer {
    
    /**
     * Analysis result for an iterator loop.
     */
    public static class SafetyAnalysis {
        public final boolean isSafe;
        public final String reason; // reason if not safe
        public final boolean hasRemove;
        public final boolean hasMultipleNext;
        public final boolean hasBreak;
        public final boolean hasLabeledContinue;
        
        private SafetyAnalysis(boolean isSafe, String reason, boolean hasRemove, 
                               boolean hasMultipleNext, boolean hasBreak, boolean hasLabeledContinue) {
            this.isSafe = isSafe;
            this.reason = reason;
            this.hasRemove = hasRemove;
            this.hasMultipleNext = hasMultipleNext;
            this.hasBreak = hasBreak;
            this.hasLabeledContinue = hasLabeledContinue;
        }
        
        public static SafetyAnalysis safe() {
            return new SafetyAnalysis(true, null, false, false, false, false);
        }
        
        public static SafetyAnalysis unsafe(String reason, boolean hasRemove, 
                                            boolean hasMultipleNext, boolean hasBreak, boolean hasLabeledContinue) {
            return new SafetyAnalysis(false, reason, hasRemove, hasMultipleNext, hasBreak, hasLabeledContinue);
        }
    }
    
    /**
     * Analyzes an iterator loop body for safety.
     * 
     * @param loopBody the loop body to analyze
     * @param iteratorVarName the name of the iterator variable
     * @return safety analysis result
     */
    public SafetyAnalysis analyze(Statement loopBody, String iteratorVarName) {
        IteratorUsageVisitor visitor = new IteratorUsageVisitor(iteratorVarName);
        loopBody.accept(visitor);
        
        if (visitor.hasRemove) {
            return SafetyAnalysis.unsafe("Iterator.remove() is not supported in stream operations", 
                                         true, visitor.hasMultipleNext, visitor.hasBreak, visitor.hasLabeledContinue);
        }
        
        if (visitor.hasMultipleNext) {
            return SafetyAnalysis.unsafe("Multiple iterator.next() calls detected", 
                                         false, true, visitor.hasBreak, visitor.hasLabeledContinue);
        }
        
        if (visitor.hasLabeledContinue) {
            return SafetyAnalysis.unsafe("Labeled continue statement is not supported", 
                                         false, false, visitor.hasBreak, true);
        }
        
        // break statements are not yet supported for conversion
        // Future enhancement: could be converted using takeWhile or filter with short-circuit
        if (visitor.hasBreak) {
            return SafetyAnalysis.unsafe("break statements in the loop body are not yet supported for stream conversion",
                                         false, false, true, false);
        }
        
        return SafetyAnalysis.safe();
    }
    
    /**
     * Visitor that checks for unsafe iterator usage patterns.
     */
    private static class IteratorUsageVisitor extends ASTVisitor {
        private final String iteratorVarName;
        private boolean hasRemove = false;
        private int nextCallCount = 0;
        private boolean hasMultipleNext = false;
        private boolean hasBreak = false;
        private boolean hasLabeledContinue = false;
        
        public IteratorUsageVisitor(String iteratorVarName) {
            this.iteratorVarName = iteratorVarName;
        }
        
        @Override
        public boolean visit(MethodInvocation node) {
            Expression expr = node.getExpression();
            if (expr instanceof SimpleName) {
                SimpleName name = (SimpleName) expr;
                if (name.getIdentifier().equals(iteratorVarName)) {
                    String methodName = node.getName().getIdentifier();
                    
                    if ("remove".equals(methodName)) {
                        hasRemove = true;
                    } else if ("next".equals(methodName)) {
                        nextCallCount++;
                        if (nextCallCount > 1) {
                            hasMultipleNext = true;
                        }
                    }
                }
            }
            return true;
        }
        
        @Override
        public boolean visit(BreakStatement node) {
            hasBreak = true;
            return false;
        }
        
        @Override
        public boolean visit(ContinueStatement node) {
            if (node.getLabel() != null) {
                hasLabeledContinue = true;
            }
            return false;
        }
        
        @Override
        public boolean visit(WhileStatement node) {
            // Don't visit nested loops
            return false;
        }
        
        @Override
        public boolean visit(ForStatement node) {
            // Don't visit nested loops
            return false;
        }
        
        @Override
        public boolean visit(EnhancedForStatement node) {
            // Don't visit nested loops
            return false;
        }
        
        @Override
        public boolean visit(DoStatement node) {
            // Don't visit nested loops
            return false;
        }
    }
}