SourceFileIndexer.groovy
/*
* Copyright 2005-2026 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.mojo.spotbugs
import org.apache.maven.execution.MavenSession
import org.apache.maven.model.Resource
import org.apache.maven.plugin.MojoExecutionException
import org.apache.maven.project.MavenProject
import java.nio.file.Files
import java.nio.file.Path
import java.util.concurrent.locks.ReentrantLock
/**
* Utility class used to transform path relative to the <b>source directory</b>
* to their path relative to the <b>root directory</b>.
*/
class SourceFileIndexer {
/** Reentrant lock to ensure class is thread safe. */
private final ReentrantLock lock = new ReentrantLock()
/** List of source files found in the current Maven project. */
private List<String> allSourceFiles = []
/**
* Initialize the complete list of source files with their
*
* @param session Reference to the Maven session used to get the location of the root directory
*/
protected void buildListSourceFiles(MavenSession session) {
lock.lock()
try {
// All source files to load
allSourceFiles.clear()
// Normalized base path
String basePath = normalizePath(session.getExecutionRootDirectory())
// Get current project
MavenProject project = session.getCurrentProject()
// Resource
for (Resource resource in project.getResources()) {
scanDirectory(Path.of(resource.directory), basePath)
}
for (Resource resource in project.getTestResources()) {
scanDirectory(Path.of(resource.directory), basePath)
}
// Source files
for (String sourceRoot in project.getCompileSourceRoots()) {
scanDirectory(Path.of(sourceRoot), basePath)
}
for (String sourceRoot in project.getTestCompileSourceRoots()) {
scanDirectory(Path.of(sourceRoot), basePath)
}
// While not perfect, add the following paths will add basic support for Groovy, Kotlin, Scala and Webapp sources.
scanDirectory(project.getBasedir().toPath().resolve('src/main/groovy'), basePath)
scanDirectory(project.getBasedir().toPath().resolve('src/main/kotlin'), basePath)
scanDirectory(project.getBasedir().toPath().resolve('src/main/scala'), basePath)
scanDirectory(project.getBasedir().toPath().resolve('src/main/webapp'), basePath)
scanDirectory(project.getBasedir().toPath().resolve('src/test/groovy'), basePath)
scanDirectory(project.getBasedir().toPath().resolve('src/test/kotlin'), basePath)
scanDirectory(project.getBasedir().toPath().resolve('src/test/scala'), basePath)
scanDirectory(project.getBasedir().toPath().resolve('src/test/webapp'), basePath)
} finally {
lock.unlock()
}
}
/**
* Recursively scan the directory given and add all files found to the files array list.
* The base directory will be truncated from the path stored.
*
* @param directory Directory to scan
* @param baseDirectory This part will be truncated from path stored
*/
private void scanDirectory(Path directory, String baseDirectory) {
if (Files.notExists(directory)) {
return
}
for (File child : directory.toFile().listFiles()) {
if (child.isDirectory()) {
scanDirectory(child.toPath(), baseDirectory)
} else {
String newSourceFile = normalizePath(child.getCanonicalPath())
lock.lock()
try {
if (newSourceFile.startsWith(baseDirectory)) {
// The project will not be at the root of our file system.
// It will most likely be stored in a work directory.
// Example: /work/project-code-to-scan/src/main/java/File.java => src/main/java/File.java
// (Here baseDirectory is /work/project-code-to-scan/)
String relativePath = Path.of(baseDirectory).relativize(Path.of(newSourceFile))
allSourceFiles.add(normalizePath(relativePath))
} else {
// Use the full path instead:
// This will occurs in many cases including when the pom.xml is
// not in the same directory tree as the sources.
allSourceFiles.add(newSourceFile)
}
} finally {
lock.unlock()
}
}
}
}
/**
* Normalize path to use forward slash.
* <p>
* This will facilitate searches.
*
* @param path Path to clean up
* @return Path safe to use for comparison
*/
private static String normalizePath(String path) {
return path.replace('\\', '/')
}
/**
* Transform partial path to complete path
*
* @param filename Partial name to search
* @return Complete path found. Null is not found!
*/
protected String searchActualFilesLocation(String filename) {
lock.lock()
try {
if (allSourceFiles.isEmpty()) {
throw new MojoExecutionException('Source files cache must be built prior to searches.')
}
for (String fileFound in allSourceFiles) {
if (fileFound.endsWith(filename)) {
return fileFound
}
}
} finally {
lock.unlock()
}
// Not found
return null
}
}