edu.umd.cs.findbugs.Project 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) 2003-2005 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
*/
/*
* Project.java
*
* Created on March 30, 2003, 2:22 PM
*/
package edu.umd.cs.findbugs;
import static edu.umd.cs.findbugs.xml.XMLOutputUtil.writeElementList;
import static edu.umd.cs.findbugs.xml.XMLOutputUtil.writeFileList;
import static java.util.Objects.requireNonNull;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import edu.umd.cs.findbugs.ba.SourceFinder;
import edu.umd.cs.findbugs.ba.URLClassPath;
import edu.umd.cs.findbugs.charsets.UTF8;
import edu.umd.cs.findbugs.config.UserPreferences;
import edu.umd.cs.findbugs.filter.Filter;
import edu.umd.cs.findbugs.io.IO;
import edu.umd.cs.findbugs.util.Util;
import edu.umd.cs.findbugs.xml.OutputStreamXMLOutput;
import edu.umd.cs.findbugs.xml.XMLAttributeList;
import edu.umd.cs.findbugs.xml.XMLOutput;
import edu.umd.cs.findbugs.xml.XMLWriteable;
/**
* A project in the GUI. This consists of some number of Jar files to analyze
* for bugs, and optionally
*
*
* - some number of source directories, for locating the program's source code
* - some number of auxiliary classpath entries, for locating classes
* referenced by the program which the user doesn't want to analyze
* - some number of boolean options
*
*
* @author David Hovemeyer
*/
public class Project implements XMLWriteable, AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(Project.class);
private final List currentWorkingDirectoryList;
private String projectName;
/**
* List of jars/directories to analyze
*/
private List analysisTargets;
/**
* The list of source directories.
*/
private List srcDirList;
/**
* The list of auxiliary classpath entries.
*/
private List auxClasspathEntryList;
/**
* Flag to indicate that this Project has been modified.
*/
private boolean isModified;
private UserPreferences configuration;
/** key is plugin id */
private final Map enabledPlugins;
@CheckForNull
public Boolean getPluginStatus(Plugin plugin) {
return enabledPlugins.get(plugin.getPluginId());
}
public void setPluginStatusTrinary(String pluginId, Boolean enabled) {
enabledPlugins.put(pluginId, enabled);
}
public UserPreferences getConfiguration() {
return configuration;
}
/**
* @param configuration The configuration to set, non null
*/
public void setConfiguration(@Nonnull UserPreferences configuration) {
requireNonNull(configuration);
this.configuration = configuration;
}
/**
* StaticConstant used to name anonymous projects.
*/
public static final String UNNAMED_PROJECT = "<>";
private long timestampForAnalyzedClasses = 0L;
private IGuiCallback guiCallback;
@Nonnull
private Filter suppressionFilter = new Filter();
private SourceFinder sourceFinder;
/**
* Create an anonymous project.
*/
public Project() {
enabledPlugins = new HashMap<>();
configuration = UserPreferences.createDefaultUserPreferences();
analysisTargets = new LinkedList<>();
srcDirList = new LinkedList<>();
auxClasspathEntryList = new LinkedList<>();
isModified = false;
currentWorkingDirectoryList = new ArrayList<>();
}
/**
* Return an exact copy of this Project.
*/
public Project duplicate() {
Project dup = new Project();
dup.currentWorkingDirectoryList.addAll(this.currentWorkingDirectoryList);
dup.projectName = this.projectName;
dup.analysisTargets.addAll(this.analysisTargets);
dup.srcDirList.addAll(this.srcDirList);
dup.auxClasspathEntryList.addAll(this.auxClasspathEntryList);
dup.timestampForAnalyzedClasses = timestampForAnalyzedClasses;
dup.guiCallback = guiCallback;
return dup;
}
public SourceFinder getSourceFinder() {
if (sourceFinder == null) {
sourceFinder = new SourceFinder(this);
}
return sourceFinder;
}
public boolean isGuiAvaliable() {
return guiCallback != null;
}
/**
* add information from project2 to this project
*/
public void add(Project project2) {
analysisTargets = appendWithoutDuplicates(analysisTargets, project2.analysisTargets);
srcDirList = appendWithoutDuplicates(srcDirList, project2.srcDirList);
auxClasspathEntryList = appendWithoutDuplicates(auxClasspathEntryList, project2.auxClasspathEntryList);
}
public static List appendWithoutDuplicates(List lst1, List lst2) {
LinkedHashSet joined = new LinkedHashSet<>(lst1);
joined.addAll(lst2);
return new ArrayList<>(joined);
}
public void setCurrentWorkingDirectory(File f) {
if (f != null) {
addWorkingDir(f.toString());
}
}
/**
* Return whether or not this Project has unsaved modifications.
*/
public boolean isModified() {
return isModified;
}
/**
* Set whether or not this Project has unsaved modifications.
*/
public void setModified(boolean isModified) {
this.isModified = isModified;
}
/**
* Add a file to the project.
*
* @param fileName
* the file to add
* @return true if the file was added, or false if the file was already
* present
*/
public boolean addFile(String fileName) {
return addToListInternal(analysisTargets, makeAbsoluteCWD(fileName));
}
/**
* Add a source directory to the project.
*
* @param sourceDir
* The source directory to add. These can be either an absolute path
* or relative to any of the working directories in this project object.
* @return true if the source directory was added or false if it was already present
* @deprecated Calling this method repeatedly performs poorly because a SourceFinder is created each time,
* which checks all files for existence each time. Use {@link #addSourceDirs} instead.
*/
@Deprecated
public boolean addSourceDir(String sourceDir) {
return addSourceDirs(Collections.singletonList(sourceDir));
}
/**
* Add source directories to the project.
*
* @param sourceDirs
* The source directories to add. These can be either absolute paths
* or relative to any of the working directories in this project object.
* @return true if a source directory was added or false if all source
* directories were already present
*/
public boolean addSourceDirs(Collection sourceDirs) {
boolean isNew = false;
if (sourceDirs == null || sourceDirs.isEmpty()) {
return isNew;
}
for (String dirName : sourceDirs) {
for (String dir : makeAbsoluteCwdCandidates(dirName)) {
isNew = addToListInternal(srcDirList, dir) || isNew;
}
}
IO.close(sourceFinder);
sourceFinder = new SourceFinder(this);
return isNew;
}
/**
* Add a working directory to the project.
*
* @param dirName
* the directory to add
* @return true if the working directory was added, or false if the working
* directory was already present
*/
public boolean addWorkingDir(String dirName) {
if (dirName == null) {
throw new NullPointerException();
}
return addToListInternal(currentWorkingDirectoryList, new File(dirName));
}
/**
* Get the number of files in the project.
*
* @return the number of files in the project
*/
public int getFileCount() {
return analysisTargets.size();
}
/**
* Get the given file in the list of project files.
*
* @param num
* the number of the file in the list of project files
* @return the name of the file
*/
public String getFile(int num) {
return analysisTargets.get(num);
}
/**
* Remove file at the given index in the list of project files
*
* @param num
* index of the file to remove in the list of project files
*/
public void removeFile(int num) {
analysisTargets.remove(num);
isModified = true;
}
/**
* Get the list of files, directories, and zip files in the project.
*/
public List getFileList() {
return analysisTargets;
}
/**
* Get the number of source directories in the project.
*
* @return the number of source directories in the project
*/
public int getNumSourceDirs() {
return srcDirList.size();
}
/**
* Get the given source directory.
*
* @param num
* the number of the source directory
* @return the source directory
*/
public String getSourceDir(int num) {
return srcDirList.get(num);
}
/**
* Remove source directory at given index.
*
* @param num
* index of the source directory to remove
*/
public void removeSourceDir(int num) {
srcDirList.remove(num);
IO.close(sourceFinder);
sourceFinder = new SourceFinder(this);
isModified = true;
}
/**
* Get project files as an array of Strings.
*/
public String[] getFileArray() {
return analysisTargets.toArray(new String[0]);
}
/**
* Get source dirs as an array of Strings.
*/
public String[] getSourceDirArray() {
return srcDirList.toArray(new String[0]);
}
/**
* Get the source dir list.
*/
public List getSourceDirList() {
return srcDirList;
}
/**
* Add an auxiliary classpath entry
*
* @param auxClasspathEntry
* the entry
* @return true if the entry was added successfully, or false if the given
* entry is already in the list
*/
public boolean addAuxClasspathEntry(String auxClasspathEntry) {
return addToListInternal(auxClasspathEntryList, makeAbsoluteCWD(auxClasspathEntry));
}
/**
* Get the number of auxiliary classpath entries.
*/
public int getNumAuxClasspathEntries() {
return auxClasspathEntryList.size();
}
/**
* Get the n'th auxiliary classpath entry.
*/
public String getAuxClasspathEntry(int n) {
return auxClasspathEntryList.get(n);
}
/**
* Remove the n'th auxiliary classpath entry.
*/
public void removeAuxClasspathEntry(int n) {
auxClasspathEntryList.remove(n);
isModified = true;
}
/**
* Return the list of aux classpath entries.
*/
public List getAuxClasspathEntryList() {
return auxClasspathEntryList;
}
/**
* Worklist item for finding implicit classpath entries.
*/
private static class WorkListItem {
private final URL url;
/**
* Constructor.
*
* @param url
* the URL of the Jar or Zip file
*/
public WorkListItem(URL url) {
this.url = url;
}
/**
* Get URL of Jar/Zip file.
*/
public URL getURL() {
return this.url;
}
}
/**
* Worklist for finding implicit classpath entries.
*/
private static class WorkList {
private static final Logger LOG = LoggerFactory.getLogger(WorkList.class);
private final LinkedList itemList;
private final HashSet addedSet;
/**
* Constructor. Creates an empty worklist.
*/
public WorkList() {
this.itemList = new LinkedList<>();
this.addedSet = new HashSet<>();
}
/**
* Create a URL from a filename specified in the project file.
*/
public URL createURL(String fileName) throws MalformedURLException {
String protocol = URLClassPath.getURLProtocol(fileName);
if (protocol == null) {
fileName = "file:" + fileName;
}
return new URL(fileName);
}
/**
* Create a URL of a file relative to another URL.
*/
public URL createRelativeURL(URL base, String fileName) throws MalformedURLException {
return new URL(base, fileName);
}
/**
* Add a worklist item.
*
* @param item
* the WorkListItem representing a zip/jar file to be
* examined
* @return true if the item was added, false if not (because it was
* examined already)
*/
public boolean add(WorkListItem item) {
LOG.debug("Adding {}", item.getURL());
if (!addedSet.add(item.getURL().toString())) {
LOG.debug("\t==> Already processed");
return false;
}
itemList.add(item);
return true;
}
/**
* Return whether or not the worklist is empty.
*/
public boolean isEmpty() {
return itemList.isEmpty();
}
/**
* Get the next item in the worklist.
*/
public WorkListItem getNextItem() {
return itemList.removeFirst();
}
}
/**
* Return the list of implicit classpath entries. The implicit classpath is
* computed from the closure of the set of jar files that are referenced by
* the "Class-Path"
attribute of the manifest of the any jar
* file that is part of this project or by the "Class-Path"
* attribute of any directly or indirectly referenced jar. The referenced
* jar files that exist are the list of implicit classpath entries.
*
* @deprecated FindBugs2 and ClassPathBuilder take care of this
* automatically
*/
@Deprecated
public List getImplicitClasspathEntryList() {
final LinkedList implicitClasspath = new LinkedList<>();
WorkList workList = new WorkList();
// Prime the worklist by adding the zip/jar files
// in the project.
for (String fileName : analysisTargets) {
try {
URL url = workList.createURL(fileName);
WorkListItem item = new WorkListItem(url);
workList.add(item);
} catch (MalformedURLException ignore) {
// Ignore
}
}
// Scan recursively.
while (!workList.isEmpty()) {
WorkListItem item = workList.getNextItem();
processComponentJar(item.getURL(), workList, implicitClasspath);
}
return implicitClasspath;
}
/**
* Examine the manifest of a single zip/jar file for implicit classapth
* entries.
*
* @param jarFileURL
* URL of the zip/jar file
* @param workList
* worklist of zip/jar files to examine
* @param implicitClasspath
* list of implicit classpath entries found
*/
private void processComponentJar(URL jarFileURL, WorkList workList, List implicitClasspath) {
LOG.debug("Processing {}", jarFileURL);
if (!jarFileURL.toString().endsWith(".zip") && !jarFileURL.toString().endsWith(".jar")) {
return;
}
try {
URL manifestURL = new URL("jar:" + jarFileURL.toString() + "!/META-INF/MANIFEST.MF");
try (InputStream in = manifestURL.openStream()) {
Manifest manifest = new Manifest(in);
Attributes mainAttrs = manifest.getMainAttributes();
String classPath = mainAttrs.getValue("Class-Path");
if (classPath != null) {
String[] fileList = classPath.split("\\s+");
for (String jarFile : fileList) {
URL referencedURL = workList.createRelativeURL(jarFileURL, jarFile);
if (workList.add(new WorkListItem(referencedURL))) {
implicitClasspath.add(referencedURL.toString());
LOG.debug("Implicit jar: {}", referencedURL);
}
}
}
}
} catch (IOException ignore) {
// Ignore
}
}
private static final String OPTIONS_KEY = "[Options]";
private static final String JAR_FILES_KEY = "[Jar files]";
private static final String SRC_DIRS_KEY = "[Source dirs]";
private static final String AUX_CLASSPATH_ENTRIES_KEY = "[Aux classpath entries]";
// Option keys
public static final String RELATIVE_PATHS = "relative_paths";
/**
* Save the project to an output file.
*
* @param outputFile
* name of output file
* @param useRelativePaths
* true if the project should be written using only relative
* paths
* @param relativeBase
* if useRelativePaths is true, this file is taken as the base
* directory in terms of which all files should be made relative
* @throws IOException
* if an error occurs while writing
*/
@Deprecated
public void write(String outputFile, boolean useRelativePaths, String relativeBase) throws IOException {
try (PrintWriter writer = UTF8.printWriter(outputFile)) {
writer.println(JAR_FILES_KEY);
for (String jarFile : analysisTargets) {
if (useRelativePaths) {
jarFile = convertToRelative(jarFile, relativeBase);
}
writer.println(jarFile);
}
writer.println(SRC_DIRS_KEY);
for (String srcDir : srcDirList) {
if (useRelativePaths) {
srcDir = convertToRelative(srcDir, relativeBase);
}
writer.println(srcDir);
}
writer.println(AUX_CLASSPATH_ENTRIES_KEY);
for (String auxClasspathEntry : auxClasspathEntryList) {
if (useRelativePaths) {
auxClasspathEntry = convertToRelative(auxClasspathEntry, relativeBase);
}
writer.println(auxClasspathEntry);
}
if (useRelativePaths) {
writer.println(OPTIONS_KEY);
writer.println(RELATIVE_PATHS + "=true");
}
}
// Project successfully saved
isModified = false;
}
public static Project readXML(File f) throws IOException, SAXException, ParserConfigurationException {
@SuppressWarnings("resource") // will be closed by caller
Project project = new Project();
try (InputStream in = new BufferedInputStream(new FileInputStream(f))) {
String tag = Util.getXMLType(in);
SAXBugCollectionHandler handler;
if ("Project".equals(tag)) {
handler = new SAXBugCollectionHandler(project, f);
} else if ("BugCollection".equals(tag)) {
SortedBugCollection bugs = new SortedBugCollection(project);
handler = new SAXBugCollectionHandler(bugs, f);
} else {
throw new IOException("Can't load a project from a " + tag + " file");
}
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
parserFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
parserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", Boolean.TRUE);
parserFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", Boolean.FALSE);
parserFactory.setFeature("http://xml.org/sax/features/external-general-entities", Boolean.FALSE);
parserFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", Boolean.FALSE);
SAXParser parser = parserFactory.newSAXParser();
XMLReader xr = parser.getXMLReader();
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
try (Reader reader = Util.getReader(in)) {
xr.parse(new InputSource(reader));
}
} catch (IOException | SAXException | ParserConfigurationException e) {
project.close();
throw e;
}
// Presumably, project is now up-to-date
project.setModified(false);
return project;
}
public void writeXML(File f, @CheckForNull BugCollection bugCollection) throws IOException {
OutputStream out = new FileOutputStream(f);
XMLOutput xmlOutput = new OutputStreamXMLOutput(out);
try {
writeXML(xmlOutput, f, bugCollection);
} finally {
xmlOutput.finish();
}
}
/**
* Read Project from named file.
*
* @param argument
* command line argument containing project file name
* @return the Project
* @throws IOException
*/
public static Project readProject(String argument) throws IOException {
String projectFileName = argument;
File projectFile = new File(projectFileName);
if (projectFileName.endsWith(".xml") || projectFileName.endsWith(".fbp")) {
try {
return Project.readXML(projectFile);
} catch (SAXException | ParserConfigurationException e) {
IOException ioe = new IOException("Couldn't read saved FindBugs project", e);
throw ioe;
}
}
throw new IllegalArgumentException("Can't read project from " + argument);
}
/**
* Read a line from a BufferedReader, ignoring blank lines and comments.
*
private static String getLine(BufferedReader reader) throws IOException {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (!line.equals("") && !line.startsWith("#")) {
break;
}
}
return line;
}*/
/**
* Convert to a string in a nice (displayable) format.
*/
@Override
public String toString() {
if (projectName != null) {
return projectName;
}
return UNNAMED_PROJECT;
}
/**
* Transform a user-entered filename into a proper filename, by adding the
* ".fb" file extension if it isn't already present.
*/
public static String transformFilename(String fileName) {
if (!fileName.endsWith(".fb")) {
fileName = fileName + ".fb";
}
return fileName;
}
static final String JAR_ELEMENT_NAME = "Jar";
static final String AUX_CLASSPATH_ENTRY_ELEMENT_NAME = "AuxClasspathEntry";
static final String SRC_DIR_ELEMENT_NAME = "SrcDir";
static final String WRK_DIR_ELEMENT_NAME = "WrkDir";
static final String FILENAME_ATTRIBUTE_NAME = "filename";
static final String PROJECTNAME_ATTRIBUTE_NAME = "projectName";
static final String PLUGIN_ELEMENT_NAME = "Plugin";
static final String PLUGIN_ID_ATTRIBUTE_NAME = "id";
static final String PLUGIN_STATUS_ELEMENT_NAME = "enabled";
@Override
public void writeXML(XMLOutput xmlOutput) throws IOException {
writeXML(xmlOutput, null, null);
}
public void writeXML(XMLOutput xmlOutput, @CheckForNull File destination, @CheckForNull BugCollection bugCollection)
throws IOException {
{
XMLAttributeList attributeList = new XMLAttributeList();
if (getProjectName() != null) {
attributeList = attributeList.addAttribute(PROJECTNAME_ATTRIBUTE_NAME, getProjectName());
}
xmlOutput.openTag(BugCollection.PROJECT_ELEMENT_NAME, attributeList);
}
if (destination != null) {
String base = destination.getParent();
writeElementList(xmlOutput, JAR_ELEMENT_NAME, convertToRelative(analysisTargets, base));
writeElementList(xmlOutput, AUX_CLASSPATH_ENTRY_ELEMENT_NAME, convertToRelative(auxClasspathEntryList, base));
writeElementList(xmlOutput, SRC_DIR_ELEMENT_NAME, convertToRelative(srcDirList, base));
List cwdStrings = new ArrayList<>();
for (File file : currentWorkingDirectoryList) {
cwdStrings.add(file.getPath());
}
writeElementList(xmlOutput, WRK_DIR_ELEMENT_NAME, convertToRelative(cwdStrings, base));
} else {
// TODO to allow relative paths: refactor the code which uses null
// file arguments
writeElementList(xmlOutput, JAR_ELEMENT_NAME, analysisTargets);
writeElementList(xmlOutput, AUX_CLASSPATH_ENTRY_ELEMENT_NAME, auxClasspathEntryList);
writeElementList(xmlOutput, SRC_DIR_ELEMENT_NAME, srcDirList);
writeFileList(xmlOutput, WRK_DIR_ELEMENT_NAME, currentWorkingDirectoryList);
}
if (!suppressionFilter.isEmpty()) {
xmlOutput.openTag("SuppressionFilter");
suppressionFilter.writeBodyAsXML(xmlOutput);
xmlOutput.closeTag("SuppressionFilter");
}
for (Map.Entry e : enabledPlugins.entrySet()) {
String pluginId = e.getKey();
Boolean enabled = e.getValue();
Plugin plugin = Plugin.getByPluginId(pluginId);
if (plugin == null || enabled == null) {
continue;
}
XMLAttributeList pluginAttributeList = new XMLAttributeList();
pluginAttributeList.addAttribute(PLUGIN_ID_ATTRIBUTE_NAME, plugin.getPluginId());
pluginAttributeList.addAttribute(PLUGIN_STATUS_ELEMENT_NAME, enabled.toString());
xmlOutput.openCloseTag(PLUGIN_ELEMENT_NAME, pluginAttributeList);
}
xmlOutput.closeTag(BugCollection.PROJECT_ELEMENT_NAME);
}
/**
* Hack for whether files are case insensitive. For now, we'll assume that
* Windows is the only case insensitive OS. (OpenVMS users, feel free to
* submit a patch :-)
*/
private static final boolean FILE_IGNORE_CASE = SystemProperties.getProperty("os.name", "unknown").startsWith("Windows");
private Iterable convertToRelative(List paths, String base) {
List newList = new ArrayList<>(paths.size());
for (String path : paths) {
newList.add(convertToRelative(path, base));
}
return newList;
}
/**
* Converts a full path to a relative path if possible
*
* @param srcFile
* path to convert
* @return the converted filename
*/
private String convertToRelative(String srcFile, String base) {
String slash = SystemProperties.getProperty("file.separator");
if (FILE_IGNORE_CASE) {
srcFile = srcFile.toLowerCase();
base = base.toLowerCase();
}
if (base.equals(srcFile)) {
return ".";
}
if (!base.endsWith(slash)) {
base = base + slash;
}
if (base.length() <= srcFile.length()) {
String root = srcFile.substring(0, base.length());
if (root.equals(base)) {
// Strip off the base directory, make relative
return "." + SystemProperties.getProperty("file.separator") + srcFile.substring(base.length());
}
}
// See if we can build a relative path above the base using .. notation
int slashPos = srcFile.indexOf(slash);
int branchPoint;
if (slashPos >= 0) {
String subPath = srcFile.substring(0, slashPos);
if ((subPath.length() == 0) || base.startsWith(subPath)) {
branchPoint = slashPos + 1;
slashPos = srcFile.indexOf(slash, branchPoint);
while (slashPos >= 0) {
subPath = srcFile.substring(0, slashPos);
if (base.startsWith(subPath)) {
branchPoint = slashPos + 1;
} else {
break;
}
slashPos = srcFile.indexOf(slash, branchPoint);
}
int slashCount = 0;
slashPos = base.indexOf(slash, branchPoint);
while (slashPos >= 0) {
slashCount++;
slashPos = base.indexOf(slash, slashPos + 1);
}
StringBuilder path = new StringBuilder();
String upDir = ".." + slash;
for (int i = 0; i < slashCount; i++) {
path.append(upDir);
}
path.append(srcFile.substring(branchPoint));
return path.toString();
}
}
return srcFile;
}
/**
* Converts a relative path to an absolute path if possible.
*
* @param fileName
* path to convert
* @return the converted filename
*
private String convertToAbsolute(String fileName) {
// At present relative paths are only calculated if the fileName is
// below the project file. This need not be the case, and we could use
// ..
// syntax to move up the tree. (To Be Added)
File file = new File(fileName);
if (!file.isAbsolute()) {
for (File cwd : currentWorkingDirectoryList) {
File test = new File(cwd, fileName);
if (test.canRead()) {
return test.getAbsolutePath();
}
}
return file.getAbsolutePath();
}
return fileName;
}*/
/**
* Make the given filename absolute relative to the current working
* directory.
*/
private String makeAbsoluteCWD(String fileName) {
List candidates = makeAbsoluteCwdCandidates(fileName);
return candidates.get(0);
}
/**
* Make the given filename absolute relative to the current working
* directory candidates.
*
* If the given filename exists in more than one of the working directories,
* a list of these existing absolute paths is returned.
*
* The returned list is guaranteed to be non-empty. The returned paths might
* exist or not exist and might be relative or absolute.
*
* @return A list of at least one candidate path for the given filename.
*/
private List makeAbsoluteCwdCandidates(String fileName) {
List candidates = new ArrayList<>();
boolean hasProtocol = (URLClassPath.getURLProtocol(fileName) != null);
if (hasProtocol) {
candidates.add(fileName);
return candidates;
}
if (new File(fileName).isAbsolute()) {
candidates.add(fileName);
return candidates;
}
for (File currentWorkingDirectory : currentWorkingDirectoryList) {
File relativeToCurrent = new File(currentWorkingDirectory, fileName);
if (relativeToCurrent.exists()) {
candidates.add(relativeToCurrent.toString());
}
}
if (candidates.isEmpty()) {
candidates.add(fileName);
}
return candidates;
}
/**
* Add a value to given list, making the Project modified if the value is
* not already present in the list.
*
* @param list
* the list
* @param value
* the value to be added
* @return true if the value was not already present in the list, false
* otherwise
*/
private boolean addToListInternal(Collection list, T value) {
if (!list.contains(value)) {
list.add(value);
isModified = true;
return true;
} else {
return false;
}
}
/*
* Make the given list of pathnames absolute relative to the absolute path
* of the project file.
*
private void makeListAbsoluteProject(List list) {
List replace = new LinkedList();
for (String fileName : list) {
fileName = convertToAbsolute(fileName);
replace.add(fileName);
}
list.clear();
list.addAll(replace);
}*/
public void setTimestamp(long timestamp) {
this.timestampForAnalyzedClasses = timestamp;
}
public void addTimestamp(long timestamp) {
if (this.timestampForAnalyzedClasses < timestamp && FindBugs.validTimestamp(timestamp)) {
this.timestampForAnalyzedClasses = timestamp;
}
}
public long getTimestamp() {
return timestampForAnalyzedClasses;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public String getProjectName() {
return projectName;
}
public void setSuppressionFilter(@Nonnull Filter suppressionFilter) {
requireNonNull(suppressionFilter);
this.suppressionFilter = suppressionFilter;
}
@Nonnull
public Filter getSuppressionFilter() {
return suppressionFilter;
}
public void setGuiCallback(IGuiCallback guiCallback) {
this.guiCallback = guiCallback;
}
public IGuiCallback getGuiCallback() {
if (guiCallback == null) {
guiCallback = new CommandLineUiCallback();
}
return guiCallback;
}
public Iterable getResolvedSourcePaths() {
List result = new ArrayList<>();
for (String s : srcDirList) {
boolean hasProtocol = (URLClassPath.getURLProtocol(s) != null);
if (hasProtocol) {
result.add(s);
continue;
}
File f = new File(s);
if (f.isAbsolute() || currentWorkingDirectoryList.isEmpty()) {
if (f.canRead()) {
result.add(s);
}
continue;
}
for (File d : currentWorkingDirectoryList) {
if (d.canRead() && d.isDirectory()) {
File a = new File(d, s);
if (a.canRead()) {
result.add(a.getAbsolutePath());
}
}
}
}
return result;
}
@Override
public void close() {
IO.close(sourceFinder);
}
}