/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2008 David H. Hovemeyer <david.hovemeyer@gmail.com>
 * 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.FileFilter;
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 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
/** * 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.
/** * 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<String> discoveredSourceDirectoryList;
Constructor.
/** * Constructor. */
public DiscoverSourceDirectories() { this.errorLogger = new NoOpErrorLogger(); this.progress = new NoOpProgress(); this.discoveredSourceDirectoryList = new LinkedList<String>(); }
Set the Project for which we want to find source directories.
Params:
  • project – Project for which we want to find source directories
/** * 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.
Params:
  • rootSourceDirectory – the root source directory
/** * 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.
Params:
  • scanForNestedArchives – true if nested archives should be scanned, false otherwise
/** * 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.
Params:
  • errorLogger – error logger to use to report errors during scanning
/** * 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.
Params:
  • progress – the progress callback
/** * 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.
Returns:list of discovered source directories.
/** * Get the list of discovered source directories. These can be added to a * Project. * * @return list of discovered source directories. */
public List<String> getDiscoveredSourceDirectoryList() { return Collections.unmodifiableList(discoveredSourceDirectoryList); }
Execute the search for source directories.
Throws:
/** * 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, new FileFilter() { @Override public boolean accept(File pathname) { return pathname.isDirectory(); } }); rfs.search(); progress.doneRecursiveDirectorySearch(); List<String> candidateSourceDirList = rfs.getDirectoriesScanned(); // Build the classpath IClassPath classPath = null; try { IClassFactory factory = ClassFactory.instance(); IClassPathBuilder builder = factory.createClassPathBuilder(errorLogger); classPath = buildClassPath(builder, factory); // From the application classes, find the full list of // fully-qualified source file names. List<String> 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); } finally { if (classPath != null) { classPath.close(); } } } 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 = packageName.replace('.', '/'); 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<String> findFullyQualifiedSourceFileNames(IClassPathBuilder builder, IClassPath classPath) { List<ClassDescriptor> appClassList = builder.getAppClassList(); progress.startScanningClasses(appClassList.size()); List<String> fullyQualifiedSourceFileNameList = new LinkedList<String>(); 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<String> fullyQualifiedSourceFileNameList, List<String> candidateSourceDirList) { Set<String> sourceDirsFound = new HashSet<String>(); // 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.
/** * Just for testing. */
public static void main(String[] args) throws IOException, CheckedAnalysisException, InterruptedException { if (args.length != 2) { System.err.println("Usage: " + DiscoverSourceDirectories.class.getName() + " <project file> <root source dir>"); 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); } } }