LoopTree.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.functional.core.tree;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
/**
* Represents a tree structure of nested loops for conversion analysis.
*
* <p>This class manages the hierarchical structure of loops in source code,
* allowing bottom-up analysis (inner loops first) to determine which loops
* can be safely converted to functional stream operations.</p>
*
* <p><b>Usage Pattern:</b></p>
* <pre>{@code
* LoopTree tree = new LoopTree();
*
* // When entering a loop:
* LoopTreeNode node = tree.pushLoop(LoopKind.ENHANCED_FOR);
* node.setAstNodeReference(astNode);
*
* // ... analyze loop body ...
*
* // When exiting a loop:
* LoopTreeNode node = tree.popLoop();
* // Decide if convertible based on children
* node.setDecision(decision);
*
* // After traversal:
* List<LoopTreeNode> convertible = tree.getConvertibleNodes();
* }</pre>
*
* @since 1.0.0
*/
public class LoopTree {
private final List<LoopTreeNode> roots = new ArrayList<>();
private final Deque<LoopTreeNode> stack = new ArrayDeque<>();
/**
* Pushes a new loop onto the stack and adds it to the tree.
*
* <p>If called when the stack is empty, the loop becomes a root.
* Otherwise, it becomes a child of the current loop.</p>
*
* @param kind the kind of loop being pushed
* @return the newly created loop tree node
*/
public LoopTreeNode pushLoop(LoopKind kind) {
ScopeInfo parentScope = stack.isEmpty() ? new ScopeInfo() : stack.peek().getScopeInfo();
ScopeInfo childScope = parentScope.createChildScope();
LoopTreeNode node = new LoopTreeNode(kind, childScope);
if (stack.isEmpty()) {
roots.add(node);
} else {
stack.peek().addChild(node);
}
stack.push(node);
return node;
}
/**
* Pops the current loop from the stack.
*
* <p>This should be called when exiting a loop after analysis is complete.</p>
*
* @return the popped loop tree node
* @throws java.util.NoSuchElementException if the stack is empty
*/
public LoopTreeNode popLoop() {
return stack.pop();
}
/**
* Gets the current (innermost) loop being processed.
*
* @return the current loop tree node, or null if the stack is empty
*/
public LoopTreeNode current() {
return stack.peek();
}
/**
* Checks if currently inside a loop.
*
* @return true if the stack is not empty
*/
public boolean isInsideLoop() {
return !stack.isEmpty();
}
/**
* Gets all nodes marked as CONVERTIBLE in the tree.
*
* <p>This performs a depth-first traversal and collects all nodes
* that have been marked as convertible after analysis.</p>
*
* @return an unmodifiable list of convertible nodes
*/
public List<LoopTreeNode> getConvertibleNodes() {
List<LoopTreeNode> result = new ArrayList<>();
for (LoopTreeNode root : roots) {
collectConvertible(root, result);
}
return Collections.unmodifiableList(result);
}
private void collectConvertible(LoopTreeNode node, List<LoopTreeNode> result) {
for (LoopTreeNode child : node.getChildren()) {
collectConvertible(child, result);
}
if (node.getDecision() == ConversionDecision.CONVERTIBLE) {
result.add(node);
}
}
/**
* Gets the root nodes of the loop tree.
*
* <p>Root nodes are top-level loops that are not nested inside
* other loops.</p>
*
* @return an unmodifiable list of root nodes
*/
public List<LoopTreeNode> getRoots() {
return Collections.unmodifiableList(roots);
}
}