edu.umd.cs.findbugs.DiscoverSourceDirectories Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spotbugs Show documentation
Show all versions of spotbugs Show documentation
SpotBugs: Because it's easy!
/*
* FindBugs - Find Bugs in Java programs
* Copyright (C) 2008 David H. Hovemeyer
* Copyright (C) 2008 University of Maryland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.umd.cs.findbugs;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import edu.umd.cs.findbugs.util.ClassName;
import org.objectweb.asm.ClassReader;
import edu.umd.cs.findbugs.ba.ClassNotFoundExceptionParser;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.IClassFactory;
import edu.umd.cs.findbugs.classfile.IClassPath;
import edu.umd.cs.findbugs.classfile.IClassPathBuilder;
import edu.umd.cs.findbugs.classfile.IClassPathBuilderProgress;
import edu.umd.cs.findbugs.classfile.ICodeBaseEntry;
import edu.umd.cs.findbugs.classfile.IErrorLogger;
import edu.umd.cs.findbugs.classfile.MethodDescriptor;
import edu.umd.cs.findbugs.classfile.analysis.ClassInfo;
import edu.umd.cs.findbugs.classfile.engine.ClassParserUsingASM;
import edu.umd.cs.findbugs.classfile.impl.ClassFactory;
/**
* Based on the contents of the application directories/archives in a Project,
* and a "root" source directory (under which some number of "real" source
* directories may be located), scan to find the source directories containing
* the application's source files.
*
* @author David Hovemeyer
*/
public class DiscoverSourceDirectories {
private static boolean DEBUG = SystemProperties.getBoolean("findbugs.dsd.debug");
/**
* Progress callback interface for reporting the progress of source
* directory discovery.
*/
public interface Progress extends IClassPathBuilderProgress {
public void startRecursiveDirectorySearch();
public void doneRecursiveDirectorySearch();
public void startScanningArchives(int numArchivesToScan);
public void doneScanningArchives();
public void startScanningClasses(int numClassesToScan);
public void finishClass();
public void doneScanningClasses();
}
private static class NoOpErrorLogger implements IErrorLogger {
@Override
public void reportMissingClass(ClassNotFoundException ex) {
}
@Override
public void reportMissingClass(ClassDescriptor classDescriptor) {
}
@Override
public void logError(String message) {
}
@Override
public void logError(String message, Throwable e) {
}
@Override
public void reportSkippedAnalysis(MethodDescriptor method) {
}
}
private static class NoOpProgress implements Progress {
@Override
public void startScanningArchives(int numArchivesToScan) {
}
@Override
public void doneScanningArchives() {
}
@Override
public void startScanningClasses(int numClassesToScan) {
}
@Override
public void finishClass() {
}
@Override
public void doneScanningClasses() {
}
@Override
public void finishArchive() {
}
@Override
public void startRecursiveDirectorySearch() {
}
@Override
public void doneRecursiveDirectorySearch() {
}
@Override
public void startArchive(String name) {
}
}
private Project project;
private String rootSourceDirectory;
private boolean scanForNestedArchives;
private IErrorLogger errorLogger;
private Progress progress;
private final List discoveredSourceDirectoryList;
/**
* Constructor.
*/
public DiscoverSourceDirectories() {
this.errorLogger = new NoOpErrorLogger();
this.progress = new NoOpProgress();
this.discoveredSourceDirectoryList = new LinkedList<>();
}
/**
* Set the Project for which we want to find source directories.
*
* @param project
* Project for which we want to find source directories
*/
public void setProject(Project project) {
this.project = project;
}
/**
* Set the "root" source directory: we expect all of the actual source
* directories to be underneath it.
*
* @param rootSourceDirectory
* the root source directory
*/
public void setRootSourceDirectory(String rootSourceDirectory) {
this.rootSourceDirectory = rootSourceDirectory;
}
/**
* Set whether or not to scan the project for nested archives (i.e., if
* there is a WAR or EAR file that contains jar files inside it.) Default is
* false.
*
* @param scanForNestedArchives
* true if nested archives should be scanned, false otherwise
*/
public void setScanForNestedArchives(boolean scanForNestedArchives) {
this.scanForNestedArchives = scanForNestedArchives;
}
/**
* Set the error logger to use to report errors during scanning. By default,
* a no-op error logger is used.
*
* @param errorLogger
* error logger to use to report errors during scanning
*/
public void setErrorLogger(IErrorLogger errorLogger) {
this.errorLogger = errorLogger;
}
/**
* Set the progress callback to which scanning progress should be reported.
*
* @param progress
* the progress callback
*/
public void setProgress(Progress progress) {
this.progress = progress;
}
/**
* Get the list of discovered source directories. These can be added to a
* Project.
*
* @return list of discovered source directories.
*/
public List getDiscoveredSourceDirectoryList() {
return Collections.unmodifiableList(discoveredSourceDirectoryList);
}
/**
* Execute the search for source directories.
*
* @throws edu.umd.cs.findbugs.classfile.CheckedAnalysisException
* @throws java.io.IOException
* @throws java.lang.InterruptedException
*/
public void execute() throws CheckedAnalysisException, IOException, InterruptedException {
File dir = new File(rootSourceDirectory);
if (!dir.isDirectory()) {
throw new IOException("Path " + rootSourceDirectory + " is not a directory");
}
// Find all directories underneath the root source directory
progress.startRecursiveDirectorySearch();
RecursiveFileSearch rfs = new RecursiveFileSearch(rootSourceDirectory, pathname -> pathname.isDirectory());
rfs.search();
progress.doneRecursiveDirectorySearch();
List candidateSourceDirList = rfs.getDirectoriesScanned();
// Build the classpath
IClassFactory factory = ClassFactory.instance();
IClassPathBuilder builder = factory.createClassPathBuilder(errorLogger);
try (IClassPath classPath = buildClassPath(builder, factory)) {
// From the application classes, find the full list of
// fully-qualified source file names.
List fullyQualifiedSourceFileNameList = findFullyQualifiedSourceFileNames(builder, classPath);
// Attempt to find source directories for all source files,
// and add them to the discoveredSourceDirectoryList
if (DEBUG) {
System.out.println("looking for " + fullyQualifiedSourceFileNameList.size() + " files");
}
findSourceDirectoriesForAllSourceFiles(fullyQualifiedSourceFileNameList, candidateSourceDirList);
}
}
private IClassPath buildClassPath(IClassPathBuilder builder, IClassFactory factory) throws InterruptedException, IOException,
CheckedAnalysisException {
progress.startScanningArchives(project.getFileCount());
for (String path : project.getFileList()) {
builder.addCodeBase(factory.createFilesystemCodeBaseLocator(path), true);
}
for (String path : project.getAuxClasspathEntryList()) {
builder.addCodeBase(factory.createFilesystemCodeBaseLocator(path), false);
}
IClassPath classPath = factory.createClassPath();
builder.build(classPath, progress);
progress.doneScanningArchives();
return classPath;
}
private String findFullyQualifiedSourceFileName(IClassPath classPath, ClassDescriptor classDesc) throws IOException,
CheckedAnalysisException {
try {
// Open and parse the class file to attempt
// to discover the source file name.
ICodeBaseEntry codeBaseEntry = classPath.lookupResource(classDesc.toResourceName());
ClassParserUsingASM classParser = new ClassParserUsingASM(new ClassReader(codeBaseEntry.openResource()), classDesc,
codeBaseEntry);
ClassInfo.Builder classInfoBuilder = new ClassInfo.Builder();
classParser.parse(classInfoBuilder);
ClassInfo classInfo = classInfoBuilder.build();
// Construct the fully-qualified source file name
// based on the package name and source file name.
String packageName = classDesc.getPackageName();
String sourceFile = classInfo.getSource();
if (!"".equals(packageName)) {
packageName = ClassName.toSlashedClassName(packageName);
packageName += "/";
}
String fullyQualifiedSourceFile = packageName + sourceFile;
return fullyQualifiedSourceFile;
} catch (CheckedAnalysisException e) {
errorLogger.logError("Could scan class " + classDesc.toDottedClassName(), e);
throw e;
} finally {
progress.finishClass();
}
}
private List findFullyQualifiedSourceFileNames(IClassPathBuilder builder, IClassPath classPath) {
List appClassList = builder.getAppClassList();
progress.startScanningClasses(appClassList.size());
List fullyQualifiedSourceFileNameList = new LinkedList<>();
for (ClassDescriptor classDesc : appClassList) {
try {
String fullyQualifiedSourceFileName = findFullyQualifiedSourceFileName(classPath, classDesc);
fullyQualifiedSourceFileNameList.add(fullyQualifiedSourceFileName);
} catch (IOException e) {
errorLogger.logError("Couldn't scan class " + classDesc.toDottedClassName(), e);
} catch (CheckedAnalysisException e) {
errorLogger.logError("Couldn't scan class " + classDesc.toDottedClassName(), e);
}
}
progress.doneScanningClasses();
return fullyQualifiedSourceFileNameList;
}
private void findSourceDirectoriesForAllSourceFiles(List fullyQualifiedSourceFileNameList,
List candidateSourceDirList) {
Set sourceDirsFound = new HashSet<>();
// For each source file discovered, try to locate it in one of
// the candidate source directories.
for (String fullyQualifiedSourceFileName : fullyQualifiedSourceFileNameList) {
checkCandidateSourceDirs: for (String candidateSourceDir : candidateSourceDirList) {
String path = candidateSourceDir + File.separatorChar + fullyQualifiedSourceFileName;
File f = new File(path);
if (DEBUG) {
System.out.print("Checking " + f.getPath() + "...");
}
boolean found = f.exists() && !f.isDirectory();
if (DEBUG) {
System.out.println(found ? "FOUND" : "not found");
}
if (found) {
// Bingo!
if (sourceDirsFound.add(candidateSourceDir)) {
discoveredSourceDirectoryList.add(candidateSourceDir);
sourceDirsFound.add(candidateSourceDir);
}
break checkCandidateSourceDirs;
}
}
}
}
/**
* Just for testing.
*/
public static void main(String[] args) throws IOException, CheckedAnalysisException, InterruptedException {
if (args.length != 2) {
System.err.println("Usage: " + DiscoverSourceDirectories.class.getName() + " ");
System.exit(1);
}
Project project = Project.readProject(args[0]);
IErrorLogger errorLogger = new IErrorLogger() {
@Override
public void reportMissingClass(ClassNotFoundException ex) {
String className = ClassNotFoundExceptionParser.getMissingClassName(ex);
if (className != null) {
logError("Missing class: " + className);
} else {
logError("Missing class: " + ex);
}
}
@Override
public void reportMissingClass(ClassDescriptor classDescriptor) {
logError("Missing class: " + classDescriptor.toDottedClassName());
}
@Override
public void logError(String message) {
System.err.println("Error: " + message);
}
@Override
public void logError(String message, Throwable e) {
logError(message + ": " + e.getMessage());
}
@Override
public void reportSkippedAnalysis(MethodDescriptor method) {
logError("Skipped analysis of method " + method.toString());
}
};
DiscoverSourceDirectories.Progress progress = new DiscoverSourceDirectories.Progress() {
@Override
public void startRecursiveDirectorySearch() {
System.out.print("Scanning directories...");
System.out.flush();
}
@Override
public void doneRecursiveDirectorySearch() {
System.out.println("done");
}
@Override
public void startScanningArchives(int numArchivesToScan) {
System.out.print("Scanning " + numArchivesToScan + " archives..");
System.out.flush();
}
@Override
public void doneScanningArchives() {
System.out.println("done");
}
@Override
public void startScanningClasses(int numClassesToScan) {
System.out.print("Scanning " + numClassesToScan + " classes...");
System.out.flush();
}
@Override
public void finishClass() {
System.out.print(".");
System.out.flush();
}
@Override
public void doneScanningClasses() {
System.out.println("done");
}
@Override
public void finishArchive() {
System.out.print(".");
System.out.flush();
}
@Override
public void startArchive(String name) {
// noop
}
};
DiscoverSourceDirectories discoverSourceDirectories = new DiscoverSourceDirectories();
discoverSourceDirectories.setProject(project);
discoverSourceDirectories.setRootSourceDirectory(args[1]);
discoverSourceDirectories.setErrorLogger(errorLogger);
discoverSourceDirectories.setProgress(progress);
discoverSourceDirectories.execute();
System.out.println("Found source directories:");
for (String srcDir : discoverSourceDirectories.getDiscoveredSourceDirectoryList()) {
System.out.println(" " + srcDir);
}
}
}