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