analysis.failed=Attempt to analyze source code failed. Will now stop.
+analyzed.at=Analyzed at {0}
cannot.connect.to.db=Unable to connect to, or to initialize, database {2} on {0}:{1} as user {3}.
cannot.exec=Got result code {1} when attempting to execute command-line: {0}
+cfb=Comparative FindBugs
+cfb.report=Comparative FindBugs Report
+fixed.bugs=Fixed Bugs
+new.bugs=New Bugs
+new.version=New Version
+num.bugs={0}
+num.bugs.old={0} (common to both versions)
+old.bugs=Old Bugs
+old.version=Old Version
stderr.was=-----8<------ Error (stderr) output was: ------8<-----
stdout.was=-----8<----- Console (stdout) output was: -----8<-----
+version.num={1} (analyzed at {0})
import net.jaekl.cfb.analyze.Analysis;
import net.jaekl.cfb.analyze.Analyzer;
import net.jaekl.cfb.analyze.Delta;
+import net.jaekl.cfb.analyze.HtmlReport;
import net.jaekl.cfb.analyze.MessageMap;
import net.jaekl.cfb.db.CfbSchema;
import net.jaekl.cfb.db.TypeMismatchException;
String m_pass; // db password
String m_buildNum; // build number (version)
boolean m_removeSchema; // purge DB schema
+ File m_output; // File to which we should write our output (report)
CFB(Locale locale) {
m_driver = new PostgresqlDriver();
m_user = "user";
m_buildNum = null;
m_removeSchema = false;
+ m_output = null;
}
Options createOptions() {
Options opt = new Options();
- opt.addOption("d", "dbname", true, "DB name");
- opt.addOption("f", "fbp", true, "FindBugsProject file");
- opt.addOption("h", "host", true, "DB hostname");
- opt.addOption("n", "number", true, "Build number (version)");
- opt.addOption("p", "pass", true, "DB password");
- opt.addOption("r", "remove", false, "Remove database schema (drop all data)");
- opt.addOption("t", "port", true, "DB port");
- opt.addOption("u", "user", true, "DB username");
+ opt.addOption("d", "dbname", true, "DB name");
+ opt.addOption(null, "drop-tables", false, "Remove database schema (drop all data)");
+ opt.addOption("f", "fbp", true, "FindBugsProject file");
+ opt.addOption("h", "host", true, "DB hostname");
+ opt.addOption("n", "number", true, "Build number (version)");
+ opt.addOption("o", "outfile", true, "Output report filename");
+ opt.addOption("p", "pass", true, "DB password");
+ opt.addOption("t", "port", true, "DB port");
+ opt.addOption("u", "user", true, "DB username");
return opt;
}
if (line.hasOption("n")) {
m_buildNum = line.getOptionValue("n");
}
+ if (line.hasOption("o")) {
+ m_output = new File(line.getOptionValue("o"));
+ }
if (line.hasOption("p")) {
m_pass = line.getOptionValue("p");
}
- m_removeSchema = line.hasOption("r");
+ m_removeSchema = line.hasOption("drop-tables");
if (line.hasOption("t")) {
m_port = Integer.parseInt(line.getOptionValue("t"));
}
store.put(analysis);
Analysis prior = store.getPrior(analysis);
Delta delta = new Delta(prior, analysis);
- delta.dump(pw);
+
+ HtmlReport report = new HtmlReport(m_bundle, messageMap.getColl());
+ report.write(m_output, delta);
}
catch (SQLException exc) {
reportUnableToConnect(pw, exc);
// Copyright (C) 2015 Christian Jaekl
+import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
public class CfbBundle {
public static final String ANALYSIS_FAILED = "analysis.failed";
+ public static final String ANALYZED_AT = "analyzed.at";
public static final String CANNOT_CONNECT = "cannot.connect.to.db";
public static final String CANNOT_EXEC = "cannot.exec";
+ public static final String CFB = "cfb";
+ public static final String CFB_REPORT = "cfb.report";
+ public static final String COMPARING_RUNS = "comparing.runs";
+ public static final String COMPARING_VERSIONS = "comparing.versions";
+ public static final String FIXED_BUGS = "fixed.bugs";
+ public static final String NEW_BUGS = "new.bugs";
+ public static final String NEW_VERSION = "new.version";
+ public static final String NUM_BUGS = "num.bugs";
+ public static final String NUM_BUGS_OLD = "num.bugs.old";
+ public static final String OLD_BUGS = "old.bugs";
+ public static final String OLD_VERSION = "old.version";
public static final String STDERR_WAS = "stderr.was";
public static final String STDOUT_WAS = "stdout.was";
+ public static final String VERSION_NUM = "version.num";
final static String BUNDLE_NAME = "cfb";
m_bundle = QDBundleFactory.getInst().getBundle(BUNDLE_NAME, locale);
}
- public String get(String key) {
+ public String get(String key, Object... arguments) {
try {
if (null != m_bundle) {
- return m_bundle.getString(key);
+ String pattern = m_bundle.getString(key);
+ return MessageFormat.format(pattern, arguments);
}
}
catch (MissingResourceException exc) {
exc.printStackTrace();
// Fall through to the fallback behaviour below
}
- return "[" + key + "]";
+
+ StringBuilder sb = new StringBuilder("[" + key + "]");
+ for (Object obj : arguments) {
+ sb.append("[" + obj + "]");
+ }
+ return sb.toString();
}
}
import java.util.Locale;
import java.util.Locale.Category;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-
import net.jaekl.cfb.CfbBundle;
import net.jaekl.cfb.util.Command;
import net.jaekl.qd.xml.XmlParseException;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
public class Analyzer {
MessageMap m_msgMap;
import java.io.PrintWriter;
import java.util.HashSet;
+import net.jaekl.cfb.store.Run;
import net.jaekl.cfb.xml.BugInstance;
// Compute and store the delta (difference) between two analyses
public class Delta {
+ Run m_earlier;
+ Run m_later;
+
HashSet<BugInstance> m_fixed; // bugs that have been fixed
HashSet<BugInstance> m_common; // bugs that are present in both versions
HashSet<BugInstance> m_new; // bugs introduced in the new version
public Delta(Analysis before, Analysis after)
{
+ m_earlier = new Run(before);
+ m_later = new Run(after);
+
m_fixed = new HashSet<BugInstance>();
m_common = new HashSet<BugInstance>();
m_new = new HashSet<BugInstance>();
public BugInstance[] getNew() { return m_new.toArray(new BugInstance[m_new.size()]); }
public int getNumNew() { return m_new.size(); }
+ public Run getEarlier() { return m_earlier; }
+ public Run getLater() { return m_later; }
+
public void dump(PrintWriter pw) {
pw.println("=========================");
pw.println(" NEW BUGS (" + m_new.size() + ")");
--- /dev/null
+package net.jaekl.cfb.analyze;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import net.jaekl.cfb.CfbBundle;
+import net.jaekl.cfb.store.Location;
+import net.jaekl.cfb.xml.BugInstance;
+import net.jaekl.cfb.xml.messages.BugPattern;
+import net.jaekl.cfb.xml.messages.MessageCollection;
+
+public class HtmlReport {
+ CfbBundle m_bundle;
+ MessageCollection m_msgColl;
+
+ public HtmlReport(CfbBundle bundle, MessageCollection msgColl)
+ {
+ m_bundle = bundle;
+ m_msgColl = msgColl;
+ }
+
+ public void write(File output, Delta delta) throws IOException
+ {
+ try ( FileOutputStream fos = new FileOutputStream(output);
+ PrintWriter pw = new PrintWriter(fos); )
+ {
+ write(pw, delta);
+ }
+ }
+
+ void write(PrintWriter pw, Delta delta)
+ {
+ startPage(pw, delta);
+ }
+
+ void startPage(PrintWriter pw, Delta delta)
+ {
+ writeHeader(pw, delta);
+ pw.println(" <BODY>");
+ writeBody(pw, delta);
+ pw.println(" </BODY>");
+ pw.println("</HTML>");
+ }
+
+ String trans(String key, Object... arguments) {
+ return m_bundle.get(key, arguments);
+ }
+
+ void writeBody(PrintWriter pw, Delta delta)
+ {
+ writeSummary(pw, delta);
+ writeBugs(pw, CfbBundle.NEW_BUGS, delta.getNew());
+ writeBugs(pw, CfbBundle.FIXED_BUGS, delta.getFixed());
+ writeBugs(pw, CfbBundle.OLD_BUGS, delta.getCommon());
+ }
+
+ void writeBugLocations(PrintWriter pw, BugInstance bug)
+ {
+ for (Location loc : bug.getLocations()) {
+ StringBuffer sb = new StringBuffer();
+
+ if (null != loc) {
+ if (null != loc.getClassName()) {
+ sb.append(loc.getClassName());
+ }
+ if ((null != loc.getMethodName()) && (loc.getMethodName().length() > 0)) {
+ sb.append(".").append(loc.getMethodName()).append("()");
+ }
+ int start = loc.getStart();
+ int end = loc.getEnd();
+ if (start > 0) {
+ sb.append(":").append("" + start);
+ if ((end > 0) && (end > start)) {
+ sb.append("-").append("" + end);
+ }
+ }
+
+ if (null != loc.getMethodRole()) {
+ sb.append(" (" + loc.getMethodRole() + ")");
+ }
+ }
+
+ pw.write(" <TR>");
+ pw.write(" <TD COLSPAN=\"2\" CLASS=\"Loc\">" + sb.toString() + "</TD>");
+ pw.write(" </TR>");
+ }
+ }
+
+ void writeBugs(PrintWriter pw, String key, BugInstance[] bugs)
+ {
+ if (null == bugs || bugs.length < 1) {
+ return;
+ }
+
+ writeSectionHeading(pw, trans(key));
+
+ for (BugInstance bug : bugs) {
+ BugPattern pattern = m_msgColl.getPattern(bug.getType());
+
+ pw.write(" <P>");
+ pw.write(" <TABLE CLASS=\"bug\">");
+ pw.write(" <TR>");
+ pw.write(" <TD WIDTH=\"20%\">" + bug.getCategory() + "</TD>");
+ pw.write(" <TD>" + bug.getType() + "</TD>");
+ pw.write(" </TR>");
+ writeBugLocations(pw, bug);
+ pw.write(" <TR>");
+ pw.write(" <TD COLSPAN=\"2\">" + pattern.getShort() + "</TD>");
+ pw.write(" </TR>");
+ pw.write(" <TR>");
+ pw.write(" <TD COLSPAN=\"2\">" + pattern.getDetails() + "</TD>");
+ pw.write(" </TR>");
+ pw.write(" </TABLE>");
+ pw.write(" </P>");
+ }
+ }
+
+ void writeHeader(PrintWriter pw, Delta delta)
+ {
+ String title = trans(CfbBundle.CFB_REPORT);
+
+ pw.println("<HTML>");
+ pw.println(" <HEAD>");
+ pw.println(" <META CHARSET=\"UTF-8\"/>");
+ pw.println(" <TITLE>" + title + "</TITLE>");
+ writeStyle(pw);
+ pw.println(" </HEAD>");
+ }
+
+ void writeSectionHeading(PrintWriter pw, String heading)
+ {
+ pw.println(" <P CLASS=\"SectionHead\">");
+ pw.println(" <TABLE WIDTH=\"100%\"><TR><TD>" + heading + "</TD></TR></TABLE>");
+ pw.println(" </P>");
+ }
+
+ void writeSummary(PrintWriter pw, Delta delta)
+ {
+ final String SEP = ": ";
+
+ writeSectionHeading(pw, trans(CfbBundle.CFB_REPORT));
+ pw.println(" <P>");
+ pw.println(" <TABLE>");
+ pw.println(" <TR>");
+ pw.println(" <TD CLASS=\"CategoryName\">" + trans(CfbBundle.NEW_VERSION) + SEP + "</TD>");
+ pw.println(" <TD CLASS=\"CategoryValue\">" + delta.getLater().constructVersionText(m_bundle) + "</TD>");
+ pw.println(" </TR>");
+ pw.println(" <TR>");
+ pw.println(" <TD CLASS=\"CategoryName\">" + trans(CfbBundle.OLD_VERSION) + SEP + "</TD>");
+ pw.println(" <TD CLASS=\"CategoryValue\">" + delta.getEarlier().constructVersionText(m_bundle) + "</TD>");
+ pw.println(" </TR>");
+ pw.println(" <TR><TD> </TD></TR>");
+ pw.println(" <TR>");
+ pw.println(" <TD CLASS=\"CategoryName\">" + trans(CfbBundle.NEW_BUGS) + SEP + "</TD>");
+ pw.println(" <TD CLASS=\"CategoryValue\">" + trans(CfbBundle.NUM_BUGS, delta.getNumNew()) + "</TD>");
+ pw.println(" </TR>");
+ pw.println(" <TR>");
+ pw.println(" <TD CLASS=\"CategoryName\">" + trans(CfbBundle.FIXED_BUGS) + SEP + "</TD>");
+ pw.println(" <TD CLASS=\"CategoryValue\">" + trans(CfbBundle.NUM_BUGS, delta.getNumFixed()) + "</TD>");
+ pw.println(" </TR>");
+ pw.println(" <TR>");
+ pw.println(" <TD CLASS=\"CategoryName\">" + trans(CfbBundle.OLD_BUGS) + SEP + "</TD>");
+ pw.println(" <TD CLASS=\"CategoryValue\">" + trans(CfbBundle.NUM_BUGS_OLD, delta.getNumCommon()) + "</TD>");
+ pw.println(" </TR>");
+ pw.println(" </TABLE>");
+ pw.println(" </P>");
+ }
+
+ void writeStyle(PrintWriter pw)
+ {
+ pw.println(" <STYLE>");
+ pw.println(" body { background-color: #F0F0FF; }");
+ pw.println(" .CategoryName { text-align: right; }");
+ pw.println(" .CategoryValue { text-align: left; }");
+ pw.println(" .Loc { font-family: monospace; }");
+ pw.println(" .SectionHead td { background-color: #0000FF; color: #FFFFFF; font-size: 1.25em; font-weight: bold; }");
+ pw.println(" </STYLE>");
+ }
+}
--- /dev/null
+package net.jaekl.cfb.store;
+
+import net.jaekl.cfb.CfbBundle;
+import net.jaekl.cfb.analyze.Analysis;
+
+public class Run {
+ java.util.Date m_analysisStart;
+ String m_buildNumber;
+
+ public Run(Analysis analysis)
+ {
+ m_analysisStart = analysis.getStart();
+ m_buildNumber = analysis.getBuildNumber();
+ }
+
+ public java.util.Date getAnalysisStart() { return new java.util.Date(m_analysisStart.getTime()); }
+ public String getBuildNumber() { return m_buildNumber; }
+
+ public String constructVersionText(CfbBundle bundle)
+ {
+ if (null == m_buildNumber) {
+ return bundle.get(CfbBundle.ANALYZED_AT, m_analysisStart);
+ }
+ return bundle.get(CfbBundle.VERSION_NUM, m_analysisStart, m_buildNumber);
+ }
+}