/*
* FindBugs - Find bugs in Java programs
* Copyright (C) 2004, 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.workflow;
import java.awt.GraphicsEnvironment;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import javax.annotation.CheckForNull;
import org.dom4j.DocumentException;
import edu.umd.cs.findbugs.BugCollection;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugRanker;
import edu.umd.cs.findbugs.CommandLineUiCallback;
import edu.umd.cs.findbugs.DetectorFactoryCollection;
import edu.umd.cs.findbugs.FindBugs;
import edu.umd.cs.findbugs.IGuiCallback;
import edu.umd.cs.findbugs.Project;
import edu.umd.cs.findbugs.ProjectStats;
import edu.umd.cs.findbugs.SortedBugCollection;
import edu.umd.cs.findbugs.cloud.BugCollectionStorageCloud;
import edu.umd.cs.findbugs.cloud.Cloud;
import edu.umd.cs.findbugs.config.CommandLine;
import edu.umd.cs.findbugs.launchGUI.LaunchGUI;
/**
* Compute the union of two sets of bug results, preserving annotations.
*/
public class MergeSummarizeAndView {
public static class MSVOptions {
public List<String> workingDirList = new ArrayList<String>();
public List<String> analysisFiles = new ArrayList<String>();
public List<String> srcDirList = new ArrayList<String>();
public int maxRank = 12;
public int maxConsideredRank = 14;
public int maxAge = 10000;
public boolean alwaysShowGui = false;
public @CheckForNull
Date baselineDate;
public String cloudId;
}
static class MSVCommandLine extends CommandLine {
final MSVOptions options;
public MSVCommandLine(MSVOptions options) {
this.options = options;
addOption("-workingDir", "filename",
"Comma separated list of current working directory paths, used to resolve relative paths (Jar, AuxClasspathEntry, SrcDir)");
addOption("-cloud", "id", "id of the cloud to use");
addOption("-srcDir", "filename", "Comma separated list of directory paths, used to resolve relative SourceFile paths");
addOption("-maxRank", "rank", "maximum rank of issues to show in summary (default 12)");
addOption("-maxConsideredRank", "rank", "maximum rank of issues to consider (default 14)");
addOption("-maxAge", "days", "maximum age of issues to show in summary");
addOption("-baseline", "date", "issues before this date are considered old (date format is MM/dd/yyyy)");
addSwitch("-gui", "display GUI for any warnings. Default: Displays GUI for warnings meeting filtering criteria");
}
/*
* (non-Javadoc)
*
* @see
* edu.umd.cs.findbugs.config.CommandLine#handleOption(java.lang.String,
* java.lang.String)
*/
@Override
protected void handleOption(String option, String optionExtraPart) throws IOException {
if (option.equals("-gui"))
options.alwaysShowGui = true;
else
throw new IllegalArgumentException("Unknown option : " + option);
}
/*
* (non-Javadoc)
*
* @see
* edu.umd.cs.findbugs.config.CommandLine#handleOptionWithArgument(java
* .lang.String, java.lang.String)
*/
@Override
protected void handleOptionWithArgument(String option, String argument) throws IOException {
if (option.equals("-workingDir"))
options.workingDirList = Arrays.asList(argument.split(","));
else if (option.equals("-srcDir"))
options.srcDirList = Arrays.asList(argument.split(","));
else if (option.equals("-maxRank"))
options.maxRank = Integer.parseInt(argument);
else if (option.equals("-maxAge"))
options.maxAge = Integer.parseInt(argument);
else if (option.equals("-cloud"))
options.cloudId = argument;
else if (option.equals("-baseline"))
try {
options.baselineDate = new SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH).parse(argument);
} catch (ParseException e) {
System.err.println("Date " + argument + " not in MM/dd/yyyy format (e.g., 05/13/2009)");
}
else
throw new IllegalArgumentException("Unknown option : " + option);
}
}
static {
DetectorFactoryCollection.instance(); // as a side effect, loads
// detector plugins
}
static public SortedBugCollection union(SortedBugCollection origCollection, SortedBugCollection newCollection) {
SortedBugCollection result = origCollection.duplicate();
for (Iterator<BugInstance> i = newCollection.iterator(); i.hasNext();) {
BugInstance bugInstance = i.next();
result.add(bugInstance);
}
ProjectStats stats = result.getProjectStats();
ProjectStats stats2 = newCollection.getProjectStats();
stats.addStats(stats2);
Project project = result.getProject();
project.add(newCollection.getProject());
return result;
}
public static void main(String[] argv) throws Exception {
FindBugs.setNoAnalysis();
final MSVOptions options = new MSVOptions();
final MSVCommandLine commandLine = new MSVCommandLine(options);
int argCount = commandLine.parse(argv, 1, Integer.MAX_VALUE, "Usage: " + MergeSummarizeAndView.class.getName()
+ " [options] [<results1> <results2> ... <resultsn>] ");
for (int i = argCount; i < argv.length; i++)
options.analysisFiles.add(argv[i]);
MergeSummarizeAndView msv = new MergeSummarizeAndView(options);
boolean isCloudManagedByGui = false;
try {
msv.load();
isCloudManagedByGui = msv.report();
} finally {
if (!isCloudManagedByGui) {
msv.shutdown();
}
}
}
SortedBugCollection results;
SortedBugCollection scaryBugs;
int numLowConfidence = 0;
int tooOld = 0;
int harmless = 0;
boolean isConnectedToCloud;
Cloud cloud;
Cloud.Mode originalMode;
final MSVOptions options;
public MergeSummarizeAndView(MSVOptions options) {
this.options = options;
}
public void execute() {
try {
load();
} finally {
shutdown();
}
}
public boolean isConnectedToCloud() {
return isConnectedToCloud;
}
/**
* @return Returns true if there were bugs that passed all of the cutoffs.
*/
public int numScaryBugs() {
return scaryBugs.getCollection().size();
}
/**
* @return Returns the bugs that passed all of the cutoffs
*/
public BugCollection getScaryBugs() {
return scaryBugs;
}
/**
* @return Returns all of the merged bugs
*/
public BugCollection getAllBugs() {
return scaryBugs;
}
/**
* @return Returns the number of issues classified as harmless
*/
public int getHarmless() {
return harmless;
}
/**
* @return Returns the number of issues that had a rank higher than the
* maxRank (but not marked as harmless)
*/
public int getLowConfidence() {
return numLowConfidence;
}
/**
* @return Returns the number of issues older than the age cutoff (but not
* ranked higher than the maxRank or marked as harmless).
*/
public int getTooOld() {
return tooOld;
}
private void shutdown() {
if (cloud != null) {
cloud.shutdown();
cloud = null;
}
}
private void load() {
if (options.workingDirList.isEmpty()) {
String userDir = System.getProperty("user.dir");
if (null != userDir && !"".equals(userDir)) {
options.workingDirList.add(userDir);
}
}
IGuiCallback cliUiCallback = new CommandLineUiCallback();
for (String analysisFile : options.analysisFiles) {
try {
SortedBugCollection more = createPreconfiguredBugCollection(options.workingDirList, options.srcDirList,
cliUiCallback);
more.readXML(analysisFile);
BugRanker.trimToMaxRank(more, options.maxConsideredRank);
if (results != null) {
results = union(results, more);
} else {
results = more;
}
} catch (IOException e) {
System.err.println("Trouble reading " + analysisFile);
} catch (DocumentException e) {
System.err.println("Trouble parsing " + analysisFile);
}
}
if (results == null) {
throw new RuntimeException("No files successfully read");
}
if (options.cloudId != null) {
results.getProject().setCloudId(options.cloudId);
results.reinitializeCloud();
}
cloud = results.getCloud();
cloud.waitUntilIssueDataDownloaded();
isConnectedToCloud = !(cloud instanceof BugCollectionStorageCloud);
Project project = results.getProject();
originalMode = cloud.getMode();
cloud.setMode(Cloud.Mode.COMMUNAL);
long old = System.currentTimeMillis() - options.maxAge * (24 * 3600 * 1000L);
if (options.baselineDate != null) {
long old2 = options.baselineDate.getTime();
if (old2 > old)
old = old2;
}
scaryBugs = results.createEmptyCollectionWithMetadata();
for (BugInstance warning : results.getCollection())
if (!project.getSuppressionFilter().match(warning)) {
int rank = BugRanker.findRank(warning);
if (rank > BugRanker.VISIBLE_RANK_MAX)
continue;
if (cloud.getConsensusDesignation(warning).score() < 0) {
harmless++;
continue;
}
long firstSeen = cloud.getFirstSeen(warning);
boolean isOld = FindBugs.validTimestamp(firstSeen) && firstSeen < old;
boolean highRank = rank > options.maxRank;
if (highRank)
numLowConfidence++;
else if (isOld)
tooOld++;
else
scaryBugs.add(warning);
}
}
private boolean report() {
assert cloud == results.getCloud();
boolean hasScaryBugs = !scaryBugs.getCollection().isEmpty();
if (hasScaryBugs) {
System.out.printf("%4s%n", "days");
System.out.printf("%4s %4s %s%n", "old", "rank", "issue");
for (BugInstance warning : scaryBugs) {
int rank = BugRanker.findRank(warning);
long firstSeen = cloud.getFirstSeen(warning);
System.out.printf("%4d %4d %s%n", ageInDays(firstSeen), rank, warning.getMessageWithoutPrefix());
}
}
if (numLowConfidence > 0 || tooOld > 0) {
if (hasScaryBugs) {
System.out.println();
System.out.print("plus ");
if (numLowConfidence > 0)
System.out.printf("%d less scary recent issues", numLowConfidence);
if (numLowConfidence > 0 && tooOld > 0)
System.out.printf(" and ");
if (tooOld > 0)
System.out.printf("%d older issues", tooOld);
System.out.println();
}
}
if (hasScaryBugs || (options.alwaysShowGui && results.getCollection().size() > 0)) {
if (GraphicsEnvironment.isHeadless()) {
System.out.println("Running in GUI headless mode, can't open GUI");
return false;
}
cloud.setMode(originalMode);
LaunchGUI.launchGUI(results);
return true;
}
return false;
}
static SortedBugCollection createPreconfiguredBugCollection(List<String> workingDirList, List<String> srcDirList,
IGuiCallback guiCallback) {
Project project = new Project();
for (String cwd : workingDirList) {
project.addWorkingDir(cwd);
}
for (String srcDir : srcDirList) {
project.addSourceDir(srcDir);
}
project.setGuiCallback(guiCallback);
return new SortedBugCollection(project);
}
static int ageInDays(long firstSeen) {
return (int) (NOW - firstSeen) / 24 / 3600 / 1000;
}
static final long NOW = System.currentTimeMillis();
}
// vim:ts=3