CleanupSourceSetClassifier.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
*******************************************************************************/
package org.sandbox.jdt.core.cleanupapp;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* Classifies Java files by conventional main/test source-set roots.
* Classification is relative to the nearest Eclipse project or the explicitly
* requested source root, so unrelated workspace ancestors and Java package
* names cannot silently change the selected cleanup scope.
*/
final class CleanupSourceSetClassifier {
private enum SourceSet {
MAIN, TEST, UNKNOWN
}
private final Path requestedRoot;
private final Path projectRoot;
private final SourceSet rootSourceSet;
private CleanupSourceSetClassifier(Path requestedRoot) {
this.requestedRoot= requestedRoot.toAbsolutePath().normalize();
this.projectRoot= findProjectRoot(this.requestedRoot);
this.rootSourceSet= sourceSetContaining(this.requestedRoot);
}
static CleanupSourceSetClassifier create(Path requestedRoot) {
return new CleanupSourceSetClassifier(requestedRoot);
}
boolean isTestSource(Path path) {
Path normalizedPath= path.toAbsolutePath().normalize();
if (projectRoot != null && normalizedPath.startsWith(projectRoot)) {
SourceSet projectSourceSet= sourceSetWithin(projectRoot.relativize(normalizedPath), true);
return projectSourceSet == SourceSet.TEST;
}
if (rootSourceSet != SourceSet.UNKNOWN) {
return rootSourceSet == SourceSet.TEST;
}
if (isTestSegment(requestedRoot.getFileName())) {
return true;
}
if (normalizedPath.startsWith(requestedRoot)) {
boolean rootIsSourceContainer= "src".equals(fileName(requestedRoot)); //$NON-NLS-1$
return sourceSetWithin(requestedRoot.relativize(normalizedPath), rootIsSourceContainer)
== SourceSet.TEST;
}
return false;
}
private static Path findProjectRoot(Path start) {
for (Path current= start; current != null; current= current.getParent()) {
if (Files.isRegularFile(current.resolve(".project"))) { //$NON-NLS-1$
return current;
}
}
return null;
}
private static SourceSet sourceSetContaining(Path path) {
for (int index= path.getNameCount() - 2; index >= 0; index--) {
if (!"src".equals(path.getName(index).toString())) { //$NON-NLS-1$
continue;
}
SourceSet sourceSet= sourceSet(path.getName(index + 1).toString());
if (sourceSet != SourceSet.UNKNOWN) {
return sourceSet;
}
}
return SourceSet.UNKNOWN;
}
private static SourceSet sourceSetWithin(Path path, boolean leadingTestDirectory) {
if (leadingTestDirectory && path.getNameCount() > 0 && isTestSegment(path.getName(0))) {
return SourceSet.TEST;
}
for (int index= 0; index + 1 < path.getNameCount(); index++) {
if (!"src".equals(path.getName(index).toString())) { //$NON-NLS-1$
continue;
}
SourceSet sourceSet= sourceSet(path.getName(index + 1).toString());
if (sourceSet != SourceSet.UNKNOWN) {
return sourceSet;
}
}
return SourceSet.UNKNOWN;
}
private static SourceSet sourceSet(String name) {
if ("main".equals(name)) { //$NON-NLS-1$
return SourceSet.MAIN;
}
return isTestSegment(name) ? SourceSet.TEST : SourceSet.UNKNOWN;
}
private static boolean isTestSegment(Path segment) {
return segment != null && isTestSegment(segment.toString());
}
private static boolean isTestSegment(String name) {
return "test".equals(name) || "tests".equals(name); //$NON-NLS-1$ //$NON-NLS-2$
}
private static String fileName(Path path) {
Path fileName= path.getFileName();
return fileName == null ? "" : fileName.toString(); //$NON-NLS-1$
}
}