edu.umd.cs.findbugs.SortedBugCollection 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-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.awt.GraphicsEnvironment;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
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.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.annotation.CheckForNull;
import javax.annotation.WillClose;
import javax.annotation.WillNotClose;
import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.TransformerException;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.MissingClassException;
import edu.umd.cs.findbugs.charsets.UTF8;
import edu.umd.cs.findbugs.log.Profiler;
import edu.umd.cs.findbugs.model.ClassFeatureSet;
import edu.umd.cs.findbugs.util.Util;
import edu.umd.cs.findbugs.xml.Dom4JXMLOutput;
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.XMLOutputUtil;
/**
* An implementation of {@link BugCollection} that keeps the BugInstances sorted
* by class (using the native comparison ordering of BugInstance's compareTo()
* method as a tie-breaker).
*
* @see BugInstance
* @author David Hovemeyer
*/
public class SortedBugCollection implements BugCollection {
private static final boolean REPORT_SUMMARY_HTML = SystemProperties.getBoolean("findbugs.report.SummaryHTML");
long analysisTimestamp = System.currentTimeMillis();
String analysisVersion = Version.VERSION_STRING;
boolean earlyStats = SystemProperties.getBoolean("findbugs.report.summaryFirst");
boolean bugsPopulated = false;
private boolean withMessages = false;
private boolean minimalXML = false;
private boolean applySuppressions = false;
long timeStartedLoading, timeFinishedLoading;
String dataSource = "";
private final Comparator comparator;
private final TreeSet bugSet;
private final LinkedHashSet errorList;
private final TreeSet missingClassSet;
@CheckForNull
private String summaryHTML;
final Project project;
private final ProjectStats projectStats;
private final Map classFeatureSetMap;
private final List appVersionList;
private boolean preciseHashOccurrenceNumbersAvailable = false;
/**
* Sequence number of the most-recently analyzed version of the code.
*/
private long sequence;
/**
* Release name of the analyzed application.
*/
private String releaseName;
/**
* Current timestamp for the code being analyzed
*/
private long timestamp;
public long getTimeStartedLoading() {
return timeStartedLoading;
}
public long getTimeFinishedLoading() {
return timeFinishedLoading;
}
public String getDataSource() {
return dataSource;
}
@Override
public Project getProject() {
return project;
}
@Override
public boolean isApplySuppressions() {
return applySuppressions;
}
@Override
public void setApplySuppressions(boolean applySuppressions) {
this.applySuppressions = applySuppressions;
}
@Override
public long getAnalysisTimestamp() {
return analysisTimestamp;
}
@Override
public void setAnalysisTimestamp(long timestamp) {
analysisTimestamp = timestamp;
}
/**
* Add a Collection of BugInstances to this BugCollection object. This just
* calls add(BugInstance) for each instance in the input collection.
*
* @param collection
* the Collection of BugInstances to add
*/
public void addAll(Collection collection) {
for (BugInstance bug : collection) {
add(bug);
}
}
/**
* Add a Collection of BugInstances to this BugCollection object.
*
* @param collection
* the Collection of BugInstances to add
* @param updateActiveTime
* true if active time of added BugInstances should be updated to
* match collection: false if not
*/
public void addAll(Collection collection, boolean updateActiveTime) {
for (BugInstance warning : collection) {
add(warning, updateActiveTime);
}
}
/**
* Add a BugInstance to this BugCollection. This just calls add(bugInstance,
* true).
*
* @param bugInstance
* the BugInstance
* @return true if the BugInstance was added, or false if a matching
* BugInstance was already in the BugCollection
*/
@Override
public boolean add(BugInstance bugInstance) {
return add(bugInstance,
bugInstance.getFirstVersion() == 0L && bugInstance.getLastVersion() == 0L);
}
/**
* Add an analysis error.
*
* @param message
* the error message
*/
@Override
public void addError(String message) {
addError(message, null);
}
/**
* Get the current AppVersion.
*/
@Override
public AppVersion getCurrentAppVersion() {
return new AppVersion(getSequenceNumber()).setReleaseName(getReleaseName()).setTimestamp(getTimestamp())
.setNumClasses(getProjectStats().getNumClasses()).setCodeSize(getProjectStats().getCodeSize());
}
/**
* Read XML data from given file into this object, populating given Project
* as a side effect.
*
* @param fileName
* name of the file to read
*/
@Override
public void readXML(String fileName) throws IOException, DocumentException {
readXML(new File(fileName));
}
/**
* Read XML data from given file into this object, populating given Project
* as a side effect.
*
* @param file
* the file
*/
public void readXML(File file) throws IOException, DocumentException {
project.setCurrentWorkingDirectory(file.getParentFile());
dataSource = file.getAbsolutePath();
InputStream in = progessMonitoredInputStream(file, "Loading analysis");
try {
readXML(in, file);
} catch (IOException e) {
throw newIOException(file, e);
} catch (DocumentException e) {
throw new DocumentException("Failing reading " + file, e);
}
}
private static IOException newIOException(Object file, IOException e) {
IOException result = new IOException("Failing reading " + file);
result.initCause(e);
return result;
}
public void readXML(URL u) throws IOException, DocumentException {
InputStream in = progessMonitoredInputStream(u.openConnection(), "Loading analysis");
dataSource = u.toString();
try {
readXML(in);
} catch (IOException e) {
throw newIOException(u, e);
} catch (DocumentException e) {
throw new DocumentException("Failing reading " + u, e);
}
}
/**
* Read XML data from given input stream into this object, populating the
* Project as a side effect. An attempt will be made to close the input
* stream (even if an exception is thrown).
*
* @param in
* the InputStream
*/
public void readXML(@WillClose InputStream in, File base) throws IOException, DocumentException {
try {
doReadXML(in, base);
} finally {
in.close();
}
}
@Override
public void readXML(@WillClose InputStream in) throws IOException, DocumentException {
assert project != null;
assert in != null;
doReadXML(in, null);
}
@Override
public void readXML(@WillClose Reader reader) throws IOException, DocumentException {
assert project != null;
assert reader != null;
doReadXML(reader, null);
}
private void doReadXML(@WillClose InputStream in, @CheckForNull File base) throws IOException, DocumentException {
try {
checkInputStream(in);
Reader reader = Util.getReader(in);
doReadXML(reader, base);
} catch (RuntimeException e) {
in.close();
throw e;
} catch (IOException e) {
in.close();
throw e;
}
}
private void doReadXML(@WillClose Reader reader, @CheckForNull File base) throws IOException, DocumentException {
timeStartedLoading = System.currentTimeMillis();
SAXBugCollectionHandler handler = new SAXBugCollectionHandler(this, base);
Profiler profiler = getProjectStats().getProfiler();
profiler.start(handler.getClass());
try {
XMLReader xr;
try {
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();
xr = parser.getXMLReader();
} catch (SAXException | ParserConfigurationException e) {
AnalysisContext.logError("Couldn't create XMLReaderFactory", e);
throw new DocumentException("Sax error ", e);
}
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
xr.parse(new InputSource(reader));
} catch (SAXParseException e) {
if (base != null) {
throw new DocumentException("Parse error at line " + e.getLineNumber() + " : " + e.getColumnNumber() + " of "
+ base, e);
}
throw new DocumentException("Parse error at line " + e.getLineNumber() + " : " + e.getColumnNumber(), e);
} catch (SAXException e) {
// FIXME: throw SAXException from method?
if (base != null) {
throw new DocumentException("Sax error while parsing " + base, e);
}
throw new DocumentException("Sax error ", e);
} finally {
Util.closeSilently(reader);
profiler.end(handler.getClass());
}
timeFinishedLoading = System.currentTimeMillis();
bugsPopulated();
// Presumably, project is now up-to-date
project.setModified(false);
}
@Override
public void writeXML(OutputStream out) throws IOException {
writeXML(UTF8.writer(out));
}
/**
* Write this BugCollection to a file as XML.
*
* @param fileName
* the file to write to
*/
@Override
public void writeXML(String fileName) throws IOException {
OutputStream out = new FileOutputStream(fileName);
if (fileName.endsWith(".gz")) {
out = new GZIPOutputStream(out);
}
writeXML(out);
}
/**
* Write this BugCollection to a file as XML.
*
* @param file
* the file to write to
*/
public void writeXML(File file) throws IOException {
OutputStream out = new FileOutputStream(file);
if (file.getName().endsWith(".gz")) {
out = new GZIPOutputStream(out);
}
writeXML(out);
}
/**
* Convert the BugCollection into a dom4j Document object.
*
* @return the Document representing the BugCollection as a dom4j tree
*/
@Override
public Document toDocument() {
// if (project == null) throw new NullPointerException("No project");
assert project != null;
DocumentFactory docFactory = new DocumentFactory();
Document document = docFactory.createDocument();
Dom4JXMLOutput treeBuilder = new Dom4JXMLOutput(document);
try {
writeXML(treeBuilder);
} catch (IOException e) {
// Can't happen
}
return document;
}
/**
* Write the BugCollection to given output stream as XML. The output stream
* will be closed, even if an exception is thrown.
*
* @param out
* the OutputStream to write to
*/
@Override
public void writeXML(@WillClose Writer out) throws IOException {
assert project != null;
bugsPopulated();
XMLOutput xmlOutput;
// if (project == null) throw new NullPointerException("No project");
xmlOutput = new OutputStreamXMLOutput(out);
writeXML(xmlOutput);
}
@Override
public void writePrologue(XMLOutput xmlOutput) throws IOException {
xmlOutput.beginDocument();
xmlOutput.openTag(
ROOT_ELEMENT_NAME,
new XMLAttributeList().addAttribute("version", analysisVersion)
.addAttribute("sequence", String.valueOf(getSequenceNumber()))
.addAttribute("timestamp", String.valueOf(getTimestamp()))
.addAttribute("analysisTimestamp", String.valueOf(getAnalysisTimestamp()))
.addAttribute("release", getReleaseName()));
project.writeXML(xmlOutput, null, this);
}
public void computeBugHashes() {
if (preciseHashOccurrenceNumbersAvailable) {
return;
}
invalidateHashes();
HashMap seen = new HashMap<>();
for (BugInstance bugInstance : getCollection()) {
String hash = bugInstance.getInstanceHash();
Integer count = seen.get(hash);
if (count == null) {
bugInstance.setInstanceOccurrenceNum(0);
seen.put(hash, 0);
} else {
bugInstance.setInstanceOccurrenceNum(count + 1);
seen.put(hash, count + 1);
}
}
for (BugInstance bugInstance : getCollection()) {
bugInstance.setInstanceOccurrenceMax(seen.get(bugInstance.getInstanceHash()));
}
preciseHashOccurrenceNumbersAvailable = true;
}
/**
* Write the BugCollection to an XMLOutput object. The finish() method of
* the XMLOutput object is guaranteed to be called.
*
*
* To write the SummaryHTML element, set property
* findbugs.report.SummaryHTML to "true".
*
*
* @param xmlOutput
* the XMLOutput object
*/
@Override
public void writeXML(@WillClose XMLOutput xmlOutput) throws IOException {
assert project != null;
try {
writePrologue(xmlOutput);
if (withMessages) {
computeBugHashes();
getProjectStats().computeFileStats(this);
String commonBase = null;
for (String s : project.getSourceDirList()) {
if (commonBase == null) {
commonBase = s;
} else {
commonBase = commonBase.substring(0, commonPrefix(commonBase, s));
}
}
if (commonBase != null && commonBase.length() > 0) {
if (commonBase.indexOf("/./") > 0) {
commonBase = commonBase.substring(0, commonBase.indexOf("/."));
}
File base = new File(commonBase);
if (base.exists() && base.isDirectory() && base.canRead()) {
SourceLineAnnotation.generateRelativeSource(base, project);
}
}
}
if (earlyStats && !minimalXML) {
getProjectStats().writeXML(xmlOutput, withMessages);
}
// Write BugInstances
for (BugInstance bugInstance : getCollection()) {
if (!applySuppressions || !project.getSuppressionFilter().match(bugInstance)) {
bugInstance.writeXML(xmlOutput, this, withMessages);
}
}
writeEpilogue(xmlOutput);
} finally {
xmlOutput.finish();
SourceLineAnnotation.clearGenerateRelativeSource();
}
}
int commonPrefix(String s1, String s2) {
int pos = 0;
while (pos < s1.length() && pos < s2.length() && s1.charAt(pos) == s2.charAt(pos)) {
pos++;
}
return pos;
}
@Override
public void writeEpilogue(XMLOutput xmlOutput) throws IOException {
if (withMessages) {
writeBugCategories(xmlOutput);
writeBugPatterns(xmlOutput);
writeBugCodes(xmlOutput);
}
// Errors, missing classes
if (!minimalXML) {
emitErrors(xmlOutput);
}
if (!earlyStats && !minimalXML) {
// Statistics
getProjectStats().writeXML(xmlOutput, withMessages);
}
// // Class and method hashes
// xmlOutput.openTag(CLASS_HASHES_ELEMENT_NAME);
// for (Iterator i = classHashIterator(); i.hasNext();) {
// ClassHash classHash = i.next();
// classHash.writeXML(xmlOutput);
// }
// xmlOutput.closeTag(CLASS_HASHES_ELEMENT_NAME);
// Class features
xmlOutput.openTag("ClassFeatures");
for (Iterator i = classFeatureSetIterator(); i.hasNext();) {
ClassFeatureSet classFeatureSet = i.next();
classFeatureSet.writeXML(xmlOutput);
}
xmlOutput.closeTag("ClassFeatures");
// AppVersions
xmlOutput.openTag(HISTORY_ELEMENT_NAME);
for (Iterator i = appVersionIterator(); i.hasNext();) {
AppVersion appVersion = i.next();
appVersion.writeXML(xmlOutput);
}
xmlOutput.closeTag(HISTORY_ELEMENT_NAME);
// Summary HTML
if (REPORT_SUMMARY_HTML) {
String html = getSummaryHTML();
if (html != null && !html.isEmpty()) {
xmlOutput.openTag(SUMMARY_HTML_ELEMENT_NAME);
xmlOutput.writeCDATA(html);
xmlOutput.closeTag(SUMMARY_HTML_ELEMENT_NAME);
}
}
xmlOutput.closeTag(ROOT_ELEMENT_NAME);
}
private void writeBugPatterns(XMLOutput xmlOutput) throws IOException {
// Find bug types reported
Set bugTypeSet = new HashSet<>();
for (BugInstance bugInstance : this) {
BugPattern bugPattern = bugInstance.getBugPattern();
bugTypeSet.add(bugPattern.getType());
}
// Emit element describing each reported bug pattern
for (String bugType : bugTypeSet) {
BugPattern bugPattern = DetectorFactoryCollection.instance().lookupBugPattern(bugType);
if (bugPattern == null) {
continue;
}
XMLAttributeList attributeList = new XMLAttributeList();
attributeList.addAttribute("type", bugType);
attributeList.addAttribute("abbrev", bugPattern.getAbbrev());
attributeList.addAttribute("category", bugPattern.getCategory());
if (bugPattern.getCWEid() != 0) {
attributeList.addAttribute("cweid", Integer.toString(bugPattern.getCWEid()));
}
xmlOutput.openTag("BugPattern", attributeList);
xmlOutput.openTag("ShortDescription");
xmlOutput.writeText(bugPattern.getShortDescription());
xmlOutput.closeTag("ShortDescription");
xmlOutput.openTag("Details");
xmlOutput.writeCDATA(bugPattern.getDetailText());
xmlOutput.closeTag("Details");
xmlOutput.closeTag("BugPattern");
}
}
private void writeBugCodes(XMLOutput xmlOutput) throws IOException {
// Find bug codes reported
Set bugCodeSet = new HashSet<>();
for (BugInstance bugInstance : this) {
String bugCode = bugInstance.getAbbrev();
if (bugCode != null) {
bugCodeSet.add(bugCode);
}
}
// Emit element describing each reported bug code
for (String bugCodeAbbrev : bugCodeSet) {
BugCode bugCode = DetectorFactoryCollection.instance().getBugCode(bugCodeAbbrev);
String bugCodeDescription = bugCode.getDescription();
if (bugCodeDescription == null) {
continue;
}
XMLAttributeList attributeList = new XMLAttributeList();
attributeList.addAttribute("abbrev", bugCodeAbbrev);
if (bugCode.getCWEid() != 0) {
attributeList.addAttribute("cweid", Integer.toString(bugCode.getCWEid()));
}
xmlOutput.openTag("BugCode", attributeList);
xmlOutput.openTag("Description");
xmlOutput.writeText(bugCodeDescription);
xmlOutput.closeTag("Description");
xmlOutput.closeTag("BugCode");
}
}
private void writeBugCategories(XMLOutput xmlOutput) throws IOException {
// Find bug categories reported
Set bugCatSet = new HashSet<>();
for (BugInstance bugInstance : this) {
BugPattern bugPattern = bugInstance.getBugPattern();
bugCatSet.add(bugPattern.getCategory());
}
// Emit element describing each reported bug code
for (String bugCat : bugCatSet) {
String bugCatDescription = I18N.instance().getBugCategoryDescription(bugCat);
if (bugCatDescription == null) {
continue;
}
XMLAttributeList attributeList = new XMLAttributeList();
attributeList.addAttribute("category", bugCat);
xmlOutput.openTag("BugCategory", attributeList);
xmlOutput.openTag("Description");
xmlOutput.writeText(bugCatDescription);
xmlOutput.closeTag("Description");
xmlOutput.closeTag("BugCategory");
}
}
private void emitErrors(XMLOutput xmlOutput) throws IOException {
// System.err.println("Writing errors to XML output");
XMLAttributeList attributeList = new XMLAttributeList();
attributeList.addAttribute("errors", Integer.toString(errorList.size()));
attributeList.addAttribute("missingClasses", Integer.toString(missingClassSet.size()));
xmlOutput.openTag(ERRORS_ELEMENT_NAME, attributeList);
// Emit Error elements describing analysis errors
for (AnalysisError error : getErrors()) {
xmlOutput.openTag(ERROR_ELEMENT_NAME);
xmlOutput.openTag(ERROR_MESSAGE_ELEMENT_NAME);
xmlOutput.writeText(error.getMessage());
xmlOutput.closeTag(ERROR_MESSAGE_ELEMENT_NAME);
if (error.getExceptionMessage() != null) {
xmlOutput.openTag(ERROR_EXCEPTION_ELEMENT_NAME);
xmlOutput.writeText(error.getExceptionMessage());
xmlOutput.closeTag(ERROR_EXCEPTION_ELEMENT_NAME);
String stackTrace[] = error.getStackTrace();
if (stackTrace != null) {
for (String aStackTrace : stackTrace) {
xmlOutput.openTag(ERROR_STACK_TRACE_ELEMENT_NAME);
xmlOutput.writeText(aStackTrace);
xmlOutput.closeTag(ERROR_STACK_TRACE_ELEMENT_NAME);
}
}
// if (false && error.getNestedExceptionMessage() != null) {
// xmlOutput.openTag(ERROR_EXCEPTION_ELEMENT_NAME);
// xmlOutput.writeText(error.getNestedExceptionMessage());
// xmlOutput.closeTag(ERROR_EXCEPTION_ELEMENT_NAME);
//
// stackTrace = error.getNestedStackTrace();
// if (stackTrace != null) {
// for (String aStackTrace : stackTrace) {
// xmlOutput.openTag(ERROR_STACK_TRACE_ELEMENT_NAME);
// xmlOutput.writeText(aStackTrace);
// xmlOutput.closeTag(ERROR_STACK_TRACE_ELEMENT_NAME);
// }
// }
// }
}
xmlOutput.closeTag(ERROR_ELEMENT_NAME);
}
// Emit missing classes
XMLOutputUtil.writeElementList(xmlOutput, MISSING_CLASS_ELEMENT_NAME, missingClassIterator());
xmlOutput.closeTag(ERRORS_ELEMENT_NAME);
}
private static void checkInputStream(@WillNotClose InputStream in) throws IOException {
if (!in.markSupported()) {
return;
}
byte[] buf = new byte[200];
in.mark(buf.length);
int numRead = 0;
boolean isEOF = false;
while (numRead < buf.length && !isEOF) {
int n = in.read(buf, numRead, buf.length - numRead);
if (n < 0) {
isEOF = true;
} else {
numRead += n;
}
}
in.reset();
try (BufferedReader reader = new BufferedReader(Util.getReader(new ByteArrayInputStream(buf)))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith(" dest, Collection source) {
for (BugInstance obj : source) {
dest.add((BugInstance) obj.clone());
}
}
private static final class BoundedLinkedHashSet extends LinkedHashSet {
@Override
public boolean add(AnalysisError a) {
if (this.size() > 1000) {
return false;
}
return super.add(a);
}
}
public static class BugInstanceComparator implements Comparator {
private BugInstanceComparator() {
}
@Override
public int compare(BugInstance lhs, BugInstance rhs) {
ClassAnnotation lca = lhs.getPrimaryClass();
ClassAnnotation rca = rhs.getPrimaryClass();
if (lca == null || rca == null) {
throw new IllegalStateException("null class annotation: " + lca + "," + rca);
}
int cmp = lca.getClassName().compareTo(rca.getClassName());
if (cmp != 0) {
return cmp;
}
return lhs.compareTo(rhs);
}
public static final BugInstanceComparator instance = new BugInstanceComparator();
}
public static class MultiversionBugInstanceComparator extends BugInstanceComparator {
private MultiversionBugInstanceComparator() {
}
@Override
public int compare(BugInstance lhs, BugInstance rhs) {
int result = super.compare(lhs, rhs);
if (result != 0) {
return result;
}
long diff = lhs.getFirstVersion() - rhs.getFirstVersion();
if (diff == 0) {
diff = lhs.getLastVersion() - rhs.getLastVersion();
}
if (diff < 0) {
return -1;
}
if (diff > 0) {
return 1;
}
return 0;
}
public static final MultiversionBugInstanceComparator instance = new MultiversionBugInstanceComparator();
}
public SortedBugCollection(Project project) {
this(new ProjectStats(), MultiversionBugInstanceComparator.instance, project);
}
public SortedBugCollection(File f) throws IOException, DocumentException {
this();
this.readXML(f);
}
/**
* Constructor. Creates an empty object.
*/
public SortedBugCollection() {
this(new ProjectStats());
}
/**
* Constructor. Creates an empty object.
*/
public SortedBugCollection(Comparator comparator) {
this(new ProjectStats(), comparator);
}
/**
* Constructor. Creates an empty object given an existing ProjectStats.
*
* @param projectStats
* the ProjectStats
*/
public SortedBugCollection(ProjectStats projectStats) {
this(projectStats, MultiversionBugInstanceComparator.instance);
}
public SortedBugCollection(ProjectStats projectStats, Project project) {
this(projectStats, MultiversionBugInstanceComparator.instance, project);
}
/**
* Constructor. Creates an empty object given an existing ProjectStats.
*
* @param projectStats
* the ProjectStats
* @param comparator
* to use for sorting bug instances
*/
public SortedBugCollection(ProjectStats projectStats, Comparator comparator) {
this(projectStats, comparator, new Project());
}
public SortedBugCollection(ProjectStats projectStats, Comparator comparator, Project project) {
this.projectStats = projectStats;
this.comparator = comparator;
this.project = project;
bugSet = new TreeSet<>(comparator);
errorList = new BoundedLinkedHashSet();
missingClassSet = new TreeSet<>();
summaryHTML = null;
classFeatureSetMap = new TreeMap<>();
sequence = 0L;
appVersionList = new LinkedList<>();
releaseName = "";
timestamp = -1L;
}
@Override
public boolean add(BugInstance bugInstance, boolean updateActiveTime) {
if (bugsPopulated) {
AnalysisContext.logError("Bug collection marked as populated, but bugs added",
new RuntimeException("Bug collection marked as populated, but bugs added"));
bugsPopulated = false;
}
preciseHashOccurrenceNumbersAvailable = false;
if (updateActiveTime) {
bugInstance.setFirstVersion(sequence);
}
invalidateHashes();
if (!bugInstance.isDead()) {
projectStats.addBug(bugInstance);
}
return bugSet.add(bugInstance);
}
private void invalidateHashes() {
preciseHashOccurrenceNumbersAvailable = false;
}
public boolean remove(BugInstance bugInstance) {
invalidateHashes();
return bugSet.remove(bugInstance);
}
@Override
public Iterator iterator() {
return bugSet.iterator();
}
@Override
public Collection getCollection() {
return Collections.unmodifiableCollection(bugSet);
}
public void addError(String message, Throwable exception) {
if (exception instanceof MissingClassException) {
MissingClassException e = (MissingClassException) exception;
addMissingClass(AbstractBugReporter.getMissingClassName(e.getClassNotFoundException()));
return;
}
if (exception instanceof ClassNotFoundException) {
ClassNotFoundException e = (ClassNotFoundException) exception;
addMissingClass(AbstractBugReporter.getMissingClassName(e));
return;
}
if (exception instanceof edu.umd.cs.findbugs.classfile.MissingClassException) {
edu.umd.cs.findbugs.classfile.MissingClassException e = (edu.umd.cs.findbugs.classfile.MissingClassException) exception;
addMissingClass(AbstractBugReporter.getMissingClassName(e.toClassNotFoundException()));
return;
}
errorList.add(new AnalysisError(message, exception));
}
@Override
public void addError(AnalysisError error) {
errorList.add(error);
}
public void clearErrors() {
errorList.clear();
}
@Override
public void addMissingClass(String className) {
if (className == null || className.length() == 0) {
return;
}
if (className.startsWith("[")) {
assert false : "Bad class name " + className;
return;
}
if (className.endsWith(";")) {
addError("got signature rather than classname: " + className, new IllegalArgumentException());
} else {
missingClassSet.add(className);
}
}
public Collection extends AnalysisError> getErrors() {
return errorList;
}
public Iterator missingClassIterator() {
return missingClassSet.iterator();
}
public boolean contains(BugInstance bugInstance) {
return bugSet.contains(bugInstance);
}
public BugInstance getMatching(BugInstance bugInstance) {
SortedSet tailSet = bugSet.tailSet(bugInstance);
if (tailSet.isEmpty()) {
return null;
}
BugInstance first = tailSet.first();
return bugInstance.equals(first) ? first : null;
}
public String getSummaryHTML() throws IOException {
if (summaryHTML == null) {
try {
StringWriter writer = new StringWriter();
ProjectStats stats = getProjectStats();
stats.transformSummaryToHTML(writer);
summaryHTML = writer.toString();
} catch (final TransformerException e) {
IOException ioe = new IOException("Couldn't generate summary HTML");
ioe.initCause(e);
throw ioe;
}
}
return summaryHTML;
}
@Override
public ProjectStats getProjectStats() {
return projectStats;
}
@Override
@Deprecated
public BugInstance lookupFromUniqueId(String uniqueId) {
for (BugInstance bug : bugSet) {
if (bug.getInstanceHash().equals(uniqueId)) {
return bug;
}
}
return null;
}
/** Returns whether this bug collection contains results from multiple analysis runs,
* either of different version of the software or from different versions of FindBugs.
*/
@Override
public boolean isMultiversion() {
return sequence > 0;
}
@Override
public boolean hasDeadBugs() {
if (sequence == 0) {
return false;
}
for (BugInstance b : bugSet) {
if (b.isDead()) {
return true;
}
}
return false;
}
@Override
public long getSequenceNumber() {
return sequence;
}
@Override
public void setSequenceNumber(long sequence) {
this.sequence = sequence;
}
public SortedBugCollection duplicate() {
SortedBugCollection dup = createEmptyCollectionWithMetadata();
SortedBugCollection.cloneAll(dup.bugSet, this.bugSet);
return dup;
}
@Override
public SortedBugCollection createEmptyCollectionWithMetadata() {
SortedBugCollection dup = new SortedBugCollection(projectStats.clone(), comparator, project);
dup.projectStats.clearBugCounts();
dup.errorList.addAll(this.errorList);
dup.missingClassSet.addAll(this.missingClassSet);
dup.summaryHTML = this.summaryHTML;
dup.classFeatureSetMap.putAll(this.classFeatureSetMap);
dup.sequence = this.sequence;
dup.analysisVersion = this.analysisVersion;
dup.analysisTimestamp = this.analysisTimestamp;
dup.timestamp = this.timestamp;
dup.releaseName = this.releaseName;
for (AppVersion appVersion : appVersionList) {
dup.appVersionList.add((AppVersion) appVersion.clone());
}
return dup;
}
public void clearBugInstances() {
bugSet.clear();
invalidateHashes();
}
@Override
public void clearMissingClasses() {
missingClassSet.clear();
}
@Override
public String getReleaseName() {
if (releaseName == null) {
return "";
}
return releaseName;
}
@Override
public void setReleaseName(String releaseName) {
this.releaseName = releaseName;
}
@Override
public Iterator appVersionIterator() {
return appVersionList.iterator();
}
@Override
public void addAppVersion(AppVersion appVersion) {
appVersionList.add(appVersion);
}
@Override
public void clearAppVersions() {
appVersionList.clear();
sequence = 0;
}
public void trimAppVersions(long numberToRetain) {
while (appVersionList.size() > numberToRetain) {
appVersionList.remove(appVersionList.size() - 1);
}
sequence = appVersionList.size();
}
@Override
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
@Override
public long getTimestamp() {
return timestamp;
}
public ClassFeatureSet getClassFeatureSet(String className) {
return classFeatureSetMap.get(className);
}
@Override
public void setClassFeatureSet(ClassFeatureSet classFeatureSet) {
classFeatureSetMap.put(classFeatureSet.getClassName(), classFeatureSet);
}
public Iterator classFeatureSetIterator() {
return classFeatureSetMap.values().iterator();
}
@Override
public void clearClassFeatures() {
classFeatureSetMap.clear();
}
@Override
public void setWithMessages(boolean withMessages) {
this.withMessages = withMessages;
}
@Override
public boolean getWithMessages() {
return withMessages;
}
@Override
public AppVersion getAppVersionFromSequenceNumber(long target) {
for (AppVersion av : appVersionList) {
if (av.getSequenceNumber() == target) {
return av;
}
}
if (target == this.getSequenceNumber()) {
return this.getCurrentAppVersion();
}
return null;
}
@Override
public BugInstance findBug(String instanceHash, String bugType, int lineNumber) {
for (BugInstance bug : bugSet) {
if (bug.getInstanceHash().equals(instanceHash) && bug.getBugPattern().getType().equals(bugType)
&& bug.getPrimarySourceLineAnnotation().getStartLine() == lineNumber) {
return bug;
}
}
return null;
}
@Override
public void setAnalysisVersion(String version) {
this.analysisVersion = version;
}
public String getAnalysisVersion() {
return this.analysisVersion;
}
public InputStream progessMonitoredInputStream(File f, String msg) throws IOException {
long length = f.length();
if (length > Integer.MAX_VALUE) {
throw new IllegalArgumentException("File " + f + " is too big at " + length + " bytes");
}
InputStream in = new FileInputStream(f);
return wrapGzip(progressMonitoredInputStream(in, (int) length, msg), f);
}
public InputStream progessMonitoredInputStream(URLConnection c, String msg) throws IOException {
InputStream in = c.getInputStream();
int length = c.getContentLength();
return wrapGzip(progressMonitoredInputStream(in, length, msg), c.getURL());
}
public InputStream progressMonitoredInputStream(InputStream in, int length, String msg) {
if (GraphicsEnvironment.isHeadless()) {
return in;
}
IGuiCallback guiCallback = project.getGuiCallback();
return guiCallback.getProgressMonitorInputStream(in, length, msg);
}
public InputStream wrapGzip(InputStream in, Object source) {
try {
if (source instanceof File) {
File f = (File) source;
if (f.getName().endsWith(".gz")) {
return new GZIPInputStream(in);
}
} else if (source instanceof URL) {
URL u = (URL) source;
if (u.getPath().endsWith(".gz")) {
return new GZIPInputStream(in);
}
}
} catch (IOException e) {
assert true;
}
return in;
}
@Override
public void setMinimalXML(boolean minimalXML) {
this.minimalXML = minimalXML;
}
@Override
public void bugsPopulated() {
bugsPopulated = true;
}
@Override
public String toString() {
return bugSet.stream().map(Object::toString).collect(Collectors.joining(",", "[", "]"));
}
}