ReactorWalker.java
package network.ike.plugin.ws;
import network.ike.plugin.ws.PomSiteScanner.PomSiteSurvey;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Walks a Maven reactor from one or more root POMs, collecting every
* POM's site survey into a single {@link ReactorScan}.
*
* <p>Each root POM is the entry point to a reactor tree. The walker
* reads the root's {@code <modules>} / {@code <subprojects>} children,
* descends into each, and repeats. POMs already visited (by absolute
* normalized path) are skipped to prevent cycles.
*
* <p>Thread-safe: all methods are stateless.
*
* @see PomSiteScanner
* @see ReleasePlan
*/
final class ReactorWalker {
private ReactorWalker() {}
/**
* An ordered collection of {@link PomSiteSurvey} from every POM
* reachable via the reactor roots passed to {@link #walk}.
*
* <p>Iteration order is deterministic: rooted-DFS from each root in
* the order roots were supplied, with child modules in declaration
* order.
*
* @param surveys one survey per POM in the reactor
*/
record ReactorScan(List<PomSiteSurvey> surveys) {
ReactorScan {
surveys = List.copyOf(surveys);
}
}
/**
* Walk a single reactor rooted at {@code rootPom}, returning a scan
* of every POM reachable via {@code <modules>} / {@code <subprojects>}.
*
* @param rootPom absolute path to the reactor root pom.xml
* @return scan of every POM in the reactor tree
* @throws IOException if any POM cannot be read or parsed
*/
static ReactorScan walk(Path rootPom) throws IOException {
return walkAll(List.of(rootPom));
}
/**
* Walk multiple reactor trees and concatenate their scans. Roots
* are walked in list order; within each root the walk is
* depth-first following declared module order.
*
* <p>If two roots share a POM (one is a module of the other, or
* both transitively include the same module), the POM is scanned
* once; first-visit order wins.
*
* @param rootPoms absolute paths to each reactor root pom.xml
* @return concatenated scan of every unique POM visited
* @throws IOException if any POM cannot be read or parsed
*/
static ReactorScan walkAll(List<Path> rootPoms) throws IOException {
Set<Path> visited = new LinkedHashSet<>();
List<PomSiteSurvey> surveys = new ArrayList<>();
for (Path root : rootPoms) {
Path normalizedRoot = root.toAbsolutePath().normalize();
Deque<Path> stack = new ArrayDeque<>();
stack.push(normalizedRoot);
while (!stack.isEmpty()) {
Path pomPath = stack.pop();
if (!visited.add(pomPath)) continue;
surveys.add(PomSiteScanner.scan(pomPath));
PomModel pom = PomModel.parse(pomPath);
List<String> subs = pom.subprojects();
if (subs == null || subs.isEmpty()) continue;
// Push in reverse so declaration order is preserved in DFS
Path pomDir = pomPath.getParent();
List<Path> children = new ArrayList<>();
for (String sub : subs) {
Path childPom = pomDir.resolve(sub).resolve("pom.xml")
.toAbsolutePath().normalize();
children.add(childPom);
}
Collections.reverse(children);
for (Path child : children) {
stack.push(child);
}
}
}
return new ReactorScan(Collections.unmodifiableList(surveys));
}
}