Add ability to output HTML report of differences found between two versions.
[cfb.git] / prod / net / jaekl / cfb / CFB.java
1 package net.jaekl.cfb;
2
3 // Comparative FindBugs
4 // 
5 // Tool to compare successive runs of FindBugs, 
6 // flagging the change from one run to the next.
7 // 
8 // Copyright (C) 2015 Christian Jaekl
9
10 import java.io.File;
11 import java.io.IOException;
12 import java.io.PrintWriter;
13 import java.sql.Connection;
14 import java.sql.SQLException;
15 import java.text.MessageFormat;
16 import java.util.Locale;
17 import java.util.Locale.Category;
18
19 import net.jaekl.cfb.analyze.Analysis;
20 import net.jaekl.cfb.analyze.Analyzer;
21 import net.jaekl.cfb.analyze.Delta;
22 import net.jaekl.cfb.analyze.HtmlReport;
23 import net.jaekl.cfb.analyze.MessageMap;
24 import net.jaekl.cfb.db.CfbSchema;
25 import net.jaekl.cfb.db.TypeMismatchException;
26 import net.jaekl.cfb.db.driver.DbDriver;
27 import net.jaekl.cfb.db.driver.PostgresqlDriver;
28 import net.jaekl.cfb.store.DbStore;
29 import net.jaekl.qd.xml.XmlParseException;
30
31 import org.apache.commons.cli.CommandLine;
32 import org.apache.commons.cli.GnuParser;
33 import org.apache.commons.cli.HelpFormatter;
34 import org.apache.commons.cli.Options;
35 import org.apache.commons.cli.ParseException;
36 import org.xml.sax.SAXException;
37
38 public class CFB {
39         DbDriver m_driver;
40         CfbSchema m_schema;
41         CfbBundle m_bundle;     
42         Locale m_locale;
43         
44         // Command-line parameters
45         String m_dbName; // db name
46         File m_fbp;             // FindBugsProject file
47         File m_fbDir;   // Directory where FindBugs is installed
48         String m_host;  // db host
49         int m_port;             // db port
50         String m_user;  // db user
51         String m_pass;  // db password
52         String m_buildNum; // build number (version)
53         boolean m_removeSchema; // purge DB schema
54         File m_output;  // File to which we should write our output (report)
55         
56         CFB(Locale locale) {
57                 m_driver = new PostgresqlDriver();
58                 m_schema = new CfbSchema(m_driver);
59                 m_locale = locale;
60                 m_bundle = CfbBundle.getInst(m_locale);
61                 
62                 m_dbName = "CFB";
63                 m_fbp    = null;
64                 m_fbDir  = null;
65                 m_host = "localhost";
66                 m_port = 5432;
67                 m_pass = "";
68                 m_user = "user";
69                 m_buildNum = null;
70                 m_removeSchema = false;
71                 m_output = null;
72         }
73         
74         Options createOptions() {
75                 Options opt = new Options();
76                 
77                 opt.addOption("d",  "dbname",      true,  "DB name");
78                 opt.addOption(null, "drop-tables", false, "Remove database schema (drop all data)");
79                 opt.addOption("f",  "fbp",         true,  "FindBugsProject file");
80                 opt.addOption("h",  "host",        true,  "DB hostname");
81                 opt.addOption("n",  "number",      true,  "Build number (version)");
82                 opt.addOption("o",  "outfile",     true,  "Output report filename");
83                 opt.addOption("p",  "pass",        true,  "DB password");
84                 opt.addOption("t",  "port",        true,  "DB port");
85                 opt.addOption("u",  "user",        true,  "DB username");
86                 
87                 return opt;
88         }
89         
90         boolean parseArgs(PrintWriter pw, String[] args) {
91                 Options opt = createOptions();
92                 
93                 try {
94                         CommandLine line = new GnuParser().parse(opt, args);
95                         if (line.hasOption("d")) {
96                                 m_dbName = line.getOptionValue("d");
97                         }
98                         if (line.hasOption("f")) {
99                                 m_fbp = new File(line.getOptionValue("f"));
100                         }
101                         if (line.hasOption("h")) {
102                                 m_host = line.getOptionValue("h");
103                         }
104                         if (line.hasOption("n")) {
105                                 m_buildNum = line.getOptionValue("n");
106                         }
107                         if (line.hasOption("o")) {
108                                 m_output = new File(line.getOptionValue("o"));
109                         }
110                         if (line.hasOption("p")) {
111                                 m_pass = line.getOptionValue("p");
112                         }
113                         m_removeSchema = line.hasOption("drop-tables");
114                         if (line.hasOption("t")) {
115                                 m_port = Integer.parseInt(line.getOptionValue("t"));
116                         }
117                         if (line.hasOption("u")) {
118                                 m_user = line.getOptionValue("u");
119                         }
120                 } 
121                 catch (ParseException exc) {
122                         usage(pw, opt);
123                         return false;
124                 }
125                 
126                 return true;
127         }
128         
129         void usage(PrintWriter pw, Options opt) {
130                 HelpFormatter help = new HelpFormatter();
131                 help.printHelp(pw, 80, getClass().getName(), "", opt, 0, 0, "", true);
132         }
133         
134         String trans(String key) {
135                 return m_bundle.get(key);
136         }
137         
138         String getenv(String varName) {
139                 // This is a separate function so that we can override it at unit test time
140                 return System.getenv(varName);
141         }
142         
143         String getProperty(String propName) {
144                 // This is a separate function so that we can override it at unit test time
145                 return System.getProperty(propName);
146         }
147         
148         File getFindBugsDir() {
149                 return (null != m_fbDir) ? m_fbDir : new File(".");
150         }
151         
152         void initArgs() {
153                 String findBugsDir = getenv("FINDBUGS_HOME");
154                 if (null != findBugsDir) {
155                         m_fbDir = new File(findBugsDir);
156                 }
157                 findBugsDir = getProperty("findbugs.home");
158                 if (null != findBugsDir) {
159                         m_fbDir = new File(findBugsDir);
160                 }
161         } 
162         
163         void doMain(PrintWriter pw, String[] args) throws SQLException, IOException, XmlParseException, SAXException, TypeMismatchException {
164                 initArgs();     // read environment and system properties
165                 if ( ! parseArgs(pw, args) ) {
166                         return;
167                 }
168
169                 File findBugsDir = getFindBugsDir();
170                 File workDir = new File(".");
171                 MessageMap messageMap = new MessageMap();
172                 messageMap.load(findBugsDir, Locale.getDefault(Category.DISPLAY));
173                 
174                 try (Connection con = m_driver.connect(m_host, m_port, m_dbName, m_user, m_pass)) {
175                         m_schema.setMessageMap(messageMap);
176                         
177                         if (m_removeSchema) {
178                                 m_schema.purge(con);
179                                 return;
180                         }
181                         m_schema.ensureDbInitialized(con);
182                         messageMap.loadIds(con, m_driver);
183                 }
184                 catch (SQLException exc) {
185                         reportUnableToConnect(pw, exc);
186                         return;
187                 }
188                 
189                 Analyzer analyzer = new Analyzer(messageMap);
190                 Analysis analysis = analyzer.analyze(pw, workDir, m_fbp, m_buildNum);
191                 if (null == analysis) {
192                         pw.println(trans(CfbBundle.ANALYSIS_FAILED));
193                         return;
194                 }
195                 
196                 try (Connection con = m_driver.connect(m_host, m_port, m_dbName, m_user, m_pass)) {
197                         DbStore store = new DbStore(con, m_driver, messageMap.getColl());
198                         
199                         store.put(analysis);
200                         Analysis prior = store.getPrior(analysis);
201                         Delta delta = new Delta(prior, analysis);
202
203                         HtmlReport report = new HtmlReport(m_bundle, messageMap.getColl());
204                         report.write(m_output, delta);
205                 }
206                 catch (SQLException exc) {
207                         reportUnableToConnect(pw, exc);
208                         return;
209                 }
210         }
211
212         private void reportUnableToConnect(PrintWriter pw, SQLException exc) {
213                 String cannotConnectFormat = trans(CfbBundle.CANNOT_CONNECT);
214                 String cannotConnect = MessageFormat.format(cannotConnectFormat, m_host, ""+m_port, m_dbName, m_user);
215                 exc.printStackTrace(pw);
216                 SQLException next = exc.getNextException();
217                 while (null != next) {
218                         next.printStackTrace(pw);
219                         next = next.getNextException();
220                 }
221                 pw.println(cannotConnect);
222         }
223         
224         public static void main(String[] args) {
225                 CFB cfb = new CFB(Locale.getDefault());
226                 
227                 try (PrintWriter pw = new PrintWriter(System.out)){
228                         cfb.doMain(pw, args);
229                         pw.flush();
230                 } catch (SQLException | IOException | XmlParseException | SAXException | TypeMismatchException exc) {
231                         exc.printStackTrace();
232                 }
233         }
234
235 }