Add code to load bug categories and patterns from the FindBugs messages.xml file.
authorChris Jaekl <chris@ringo.jaekl.net>
Wed, 23 Sep 2015 12:56:56 +0000 (21:56 +0900)
committerChris Jaekl <chris@ringo.jaekl.net>
Wed, 23 Sep 2015 12:56:56 +0000 (21:56 +0900)
22 files changed:
prod/cfb.properties
prod/net/jaekl/cfb/CFB.java
prod/net/jaekl/cfb/CfbBundle.java
prod/net/jaekl/cfb/analyze/Analysis.java
prod/net/jaekl/cfb/analyze/Analyzer.java
prod/net/jaekl/cfb/analyze/MessageMap.java [new file with mode: 0644]
prod/net/jaekl/cfb/db/CfbSchema.java
prod/net/jaekl/cfb/db/Condition.java [new file with mode: 0644]
prod/net/jaekl/cfb/db/Operation.java [new file with mode: 0644]
prod/net/jaekl/cfb/db/Row.java [new file with mode: 0644]
prod/net/jaekl/cfb/db/Schema.java
prod/net/jaekl/cfb/db/Sequence.java [new file with mode: 0644]
prod/net/jaekl/cfb/db/TypeMismatchException.java [new file with mode: 0644]
prod/net/jaekl/cfb/db/driver/DbDriver.java
prod/net/jaekl/cfb/db/driver/PostgresqlDriver.java
prod/net/jaekl/cfb/store/DbStore.java [new file with mode: 0644]
prod/net/jaekl/cfb/xml/messages/BugCategory.java [new file with mode: 0644]
prod/net/jaekl/cfb/xml/messages/BugPattern.java [new file with mode: 0644]
prod/net/jaekl/cfb/xml/messages/MessageCollection.java [new file with mode: 0644]
test/net/jaekl/cfb/analyze/AnalysisTest.java [new file with mode: 0644]
test/net/jaekl/cfb/analyze/AnalyzerTest.java
test/net/jaekl/cfb/analyze/MessageMapMock.java [new file with mode: 0644]

index d8979712ea1a84cec92cc48fe45dcd7979c66024..78c33520181377b2e20c1fd8a0352468f82e3800 100644 (file)
@@ -1,4 +1,5 @@
+analysis.failed=Attempt to analyze source code failed.  Will now stop.
 cannot.connect.to.db=Unable to connect to database {2} on {0}:{1} as user {3}.
 cannot.exec=Got result code {1} when attempting to execute command-line:  {0}
 stderr.was=-----8<------ Error (stderr) output was: ------8<-----
-stdout.was=-----8<----- Console (stdout) output was: -----8<-----
\ No newline at end of file
+stdout.was=-----8<----- Console (stdout) output was: -----8<-----
index d587c1878e882b0a0dfab3152669240149da5dd1..28d2b1c698dc437113d58e1899596f4882fc32b6 100644 (file)
@@ -14,12 +14,15 @@ import java.sql.Connection;
 import java.sql.SQLException;
 import java.text.MessageFormat;
 import java.util.Locale;
+import java.util.Locale.Category;
 
 import net.jaekl.cfb.analyze.Analysis;
 import net.jaekl.cfb.analyze.Analyzer;
+import net.jaekl.cfb.analyze.MessageMap;
 import net.jaekl.cfb.db.CfbSchema;
 import net.jaekl.cfb.db.driver.DbDriver;
 import net.jaekl.cfb.db.driver.PostgresqlDriver;
+import net.jaekl.cfb.store.DbStore;
 import net.jaekl.qd.xml.XmlParseException;
 
 import org.apache.commons.cli.CommandLine;
@@ -27,6 +30,7 @@ import org.apache.commons.cli.GnuParser;
 import org.apache.commons.cli.HelpFormatter;
 import org.apache.commons.cli.Options;
 import org.apache.commons.cli.ParseException;
+import org.xml.sax.SAXException;
 
 public class CFB {
        DbDriver m_driver;
@@ -42,6 +46,7 @@ public class CFB {
        int m_port;             // db port
        String m_user;  // db user
        String m_pass;  // db password
+       String m_buildNum; // build number (version)
        
        CFB(Locale locale) {
                m_driver = new PostgresqlDriver();
@@ -56,6 +61,7 @@ public class CFB {
                m_port = 5432;
                m_pass = "";
                m_user = "user";
+               m_buildNum = null;
        }
        
        Options createOptions() {
@@ -64,6 +70,7 @@ public class CFB {
                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("t", "port",   true, "DB port");
                opt.addOption("u", "user",   true, "DB username");
@@ -85,6 +92,9 @@ public class CFB {
                        if (line.hasOption("h")) {
                                m_host = line.getOptionValue("h");
                        }
+                       if (line.hasOption("n")) {
+                               m_buildNum = line.getOptionValue("n");
+                       }
                        if (line.hasOption("p")) {
                                m_pass = line.getOptionValue("p");
                        }
@@ -122,6 +132,10 @@ public class CFB {
                return System.getProperty(propName);
        }
        
+       File getFindBugsDir() {
+               return (null != m_fbDir) ? m_fbDir : new File(".");
+       }
+       
        void initArgs() {
                String findBugsDir = getenv("FINDBUGS_HOME");
                if (null != findBugsDir) {
@@ -133,7 +147,7 @@ public class CFB {
                }
        } 
        
-       void doMain(PrintWriter pw, String[] args) throws SQLException, IOException, XmlParseException {
+       void doMain(PrintWriter pw, String[] args) throws SQLException, IOException, XmlParseException, SAXException {
                initArgs();     // read environment and system properties
                if ( ! parseArgs(pw, args) ) {
                        return;
@@ -143,21 +157,38 @@ public class CFB {
                        m_schema.ensureDbInitialized(con);                      
                }
                catch (SQLException exc) {
-                       String cannotConnectFormat = trans(CfbBundle.CANNOT_CONNECT);
-                       String cannotConnect = MessageFormat.format(cannotConnectFormat, m_host, ""+m_port, m_dbName, m_user);
-                       exc.printStackTrace(pw);
-                       pw.println(cannotConnect);
+                       reportUnableToConnect(pw, exc);
                        return;
                }
                
-               File findBugsDir = (null != m_fbDir) ? m_fbDir : new File(".");
+               File findBugsDir = getFindBugsDir();
                File workDir = new File(".");
-               Analyzer analyzer = new Analyzer(findBugsDir);
-               Analysis analysis = analyzer.analyze(pw, workDir, m_fbp);
-               if (null != analysis) {
-                       // TODO
+               MessageMap messageMap = new MessageMap();
+               messageMap.load(findBugsDir, Locale.getDefault(Category.DISPLAY));
+               Analyzer analyzer = new Analyzer(messageMap);
+               Analysis analysis = analyzer.analyze(pw, workDir, m_fbp, m_buildNum);
+               if (null == analysis) {
+                       pw.println(trans(CfbBundle.ANALYSIS_FAILED));
+                       return;
+               }
+               
+               try (Connection con = m_driver.connect(m_host, m_port, m_dbName, m_user, m_pass)) {
+                       DbStore store = new DbStore(con);
+                       
+                       store.put(analysis);
+               }
+               catch (SQLException exc) {
+                       reportUnableToConnect(pw, exc);
+                       return;
                }
        }
+
+       private void reportUnableToConnect(PrintWriter pw, SQLException exc) {
+               String cannotConnectFormat = trans(CfbBundle.CANNOT_CONNECT);
+               String cannotConnect = MessageFormat.format(cannotConnectFormat, m_host, ""+m_port, m_dbName, m_user);
+               exc.printStackTrace(pw);
+               pw.println(cannotConnect);
+       }
        
        public static void main(String[] args) {
                CFB cfb = new CFB(Locale.getDefault());
@@ -165,7 +196,7 @@ public class CFB {
                try (PrintWriter pw = new PrintWriter(System.out)){
                        cfb.doMain(pw, args);
                        pw.flush();
-               } catch (SQLException | IOException | XmlParseException exc) {
+               } catch (SQLException | IOException | XmlParseException | SAXException exc) {
                        exc.printStackTrace();
                }
        }
index c83182c346338b0776e78ba0e50c2c71f15000a5..335a68dfbbb761c914b5523fee10115bba5e75e9 100644 (file)
@@ -10,6 +10,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import net.jaekl.qd.QDBundleFactory;
 
 public class CfbBundle {
+       public static final String ANALYSIS_FAILED = "analysis.failed";
        public static final String CANNOT_CONNECT = "cannot.connect.to.db";
        public static final String CANNOT_EXEC = "cannot.exec";
        public static final String STDERR_WAS = "stderr.was";
index ab9fffbe798e90b8d23d52a3347008ec931316e3..e2f090f32b0245c701532c27606d2c7b01c0d979 100644 (file)
@@ -5,6 +5,7 @@ package net.jaekl.cfb.analyze;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.Date;
 
 import net.jaekl.cfb.xml.BugCollection;
 import net.jaekl.qd.xml.ParseErrorHandler;
@@ -17,9 +18,13 @@ import org.xml.sax.helpers.XMLReaderFactory;
 
 public class Analysis {
        BugCollection m_bugCollection;
+       String m_buildNumber;
+       Date m_date;    // Date/time when analysis was started
        
-       public Analysis() {
+       public Analysis(String buildNumber) {
                m_bugCollection = null;
+               m_buildNumber = buildNumber;
+               m_date = new Date();
        }
        
        public BugCollection getBugCollection() { return m_bugCollection; }
index 5e72fe0499db0b7d2f50c3b67ec09f13eb8b7da8..03aeb1c6a4db718198e2eca9da9b9538829eab39 100644 (file)
@@ -17,14 +17,15 @@ import net.jaekl.cfb.util.Command;
 import net.jaekl.qd.xml.XmlParseException;
 
 public class Analyzer {
-       File m_findbugsDir;
+       MessageMap m_msgMap;
        
-       public Analyzer(File findbugsDir) {
-               m_findbugsDir = findbugsDir;
+       public Analyzer(MessageMap msgMap) {
+               m_msgMap = msgMap;
        }
        
-       public Analysis analyze(PrintWriter pw, File workDir, File fbp) throws IOException, XmlParseException {
-               Analysis result = new Analysis();
+       public Analysis analyze(PrintWriter pw, File workDir, File fbp, String buildNumber) throws IOException, XmlParseException, SAXException 
+       {
+               Analysis result = new Analysis(buildNumber);
        
                File fbOutput = outputWorkFile(workDir, fbp);
                
@@ -45,7 +46,7 @@ public class Analyzer {
                        return null;
                }
                
-               result = parseFbOutput(new InputSource(fbOutput.getAbsolutePath()));
+               result.parse(new InputSource(fbOutput.getAbsolutePath()));
                result.dump(pw);                        
                return result;
        }
@@ -62,7 +63,7 @@ public class Analyzer {
                
                StringBuilder sb = new StringBuilder();
                
-               sb.append(m_findbugsDir.getAbsolutePath())
+               sb.append(m_msgMap.getFindBugsDir().getAbsolutePath())
                  .append(File.separator)
                  .append("bin")
                  .append(File.separator)
@@ -95,17 +96,4 @@ public class Analyzer {
                
                return new File(workPath + File.separator + projName + ".xml");
        }
-       
-       // Parse the output.xml that resulted from a FindBugs run,
-       // and store its findings into an Analysis object.
-       Analysis parseFbOutput(InputSource fbOutput) throws XmlParseException 
-       {
-               Analysis result = new Analysis();
-               try {
-                       result.parse(fbOutput);
-               } catch (IOException | SAXException exc) {
-                       throw new XmlParseException(exc);
-               }
-               return result;
-       }
 }
diff --git a/prod/net/jaekl/cfb/analyze/MessageMap.java b/prod/net/jaekl/cfb/analyze/MessageMap.java
new file mode 100644 (file)
index 0000000..ac5288f
--- /dev/null
@@ -0,0 +1,63 @@
+package net.jaekl.cfb.analyze;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Locale;
+
+import net.jaekl.cfb.xml.messages.MessageCollection;
+import net.jaekl.qd.xml.ParseErrorHandler;
+import net.jaekl.qd.xml.ParseHandler;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+public class MessageMap {
+       static final String MESSAGES = "messages";
+       static final String XML = "xml";
+       
+       MessageCollection m_msgColl;
+       File m_findBugsDir;
+       
+       public MessageMap() {
+               m_msgColl = null;
+               m_findBugsDir = null;
+       }
+       
+       public MessageCollection getColl() { return m_msgColl; }
+       public File getFindBugsDir() { return m_findBugsDir; }
+
+       public void load(File findBugsDir, Locale locale) throws FileNotFoundException, IOException, SAXException
+       {
+               m_findBugsDir = findBugsDir;
+               
+               String langName = locale.getLanguage();
+               
+               File msgXml = new File(findBugsDir.getAbsolutePath() + File.separator + MESSAGES + "_" + langName + "." + XML);
+               if (! msgXml.canRead()) {
+                       msgXml = new File(findBugsDir.getAbsolutePath() + File.separator + MESSAGES + "." + XML);
+               }
+               
+               if (! msgXml.canRead()) {
+                       throw new FileNotFoundException(msgXml.getAbsolutePath());
+               }
+               
+               parse(new InputSource(new FileInputStream(msgXml)));
+               
+       }
+       
+       void parse(InputSource xml) throws FileNotFoundException, IOException, SAXException 
+       {
+               m_msgColl = new MessageCollection();
+               
+           XMLReader reader = XMLReaderFactory.createXMLReader();
+           ParseHandler ph = new ParseHandler(m_msgColl);
+           ParseErrorHandler peh = new ParseErrorHandler();
+           reader.setContentHandler(ph);
+           reader.setErrorHandler(peh);
+           reader.parse(xml);
+       }
+}
index d2333fab8f932d3fc6bdb11605dd72915497c8b6..e214b1bc59172dda99363b7b4865266dce20ec74 100644 (file)
@@ -15,21 +15,19 @@ public class CfbSchema extends Schema {
        // }
        private static final Object[][][] TABLES = {
                {
-                       { "BUGS" },
+                       // Description of each possible bug
+                       { "BUG" },
                        { "BUGID", INTEGER, -1, NOT_NULL },
-                       { "TYPE", VARCHAR, 80, NOT_NULL },
-                       { "SHORTDESCR", VARCHAR, 128, NOT_NULL },
-                       { "LONGDESCR", VARCHAR, 128, NOT_NULL },
-                       { "DETAILS", VARCHAR, 4096, NOT_NULL }
+                       { "TYPE", VARCHAR, 80, NOT_NULL }
                },
                {
-                       { "CATEGORIES" },
+                       // Description of each possible bug category
+                       { "CATEGORY" },
                        { "CATEGORYID", INTEGER, -1, NOT_NULL },
-                       { "DESCRIPTION", VARCHAR, 128, NOT_NULL },
-                       { "ABBREVIATION", CHAR, 1, NOT_NULL },
-                       { "DETAILS", VARCHAR, 4096, NOT_NULL }
+                       { "CATEGORY", VARCHAR, 80, NOT_NULL }
                },
                { 
+                       // One BugInstance, found during an analysis
                        { "FOUND" },
                        { "FOUNDID", INTEGER, -1, NOT_NULL },
                        { "BUGID", INTEGER, -1, NOT_NULL },
@@ -39,6 +37,7 @@ public class CfbSchema extends Schema {
                        { "THIRDLOCID", INTEGER, -1, NULL }
                },
                {
+                       // Location in the source code referenced by a BugInstance
                        { "LOCATION" },
                        { "LOCID", INTEGER, -1, NOT_NULL },
                        { "CLASSNAME", VARCHAR, 256, NOT_NULL },
@@ -47,7 +46,7 @@ public class CfbSchema extends Schema {
                },
                {
                        // Runs of FindBugs, normally one per build version
-                       { "RUNS" },
+                       { "RUN" },
                        { "RUNID", INTEGER, -1, NOT_NULL },
                        { "VERSION", VARCHAR, 32, NULL },
                        { "STARTTIME", TIMESTAMPTZ, -1, NOT_NULL },
diff --git a/prod/net/jaekl/cfb/db/Condition.java b/prod/net/jaekl/cfb/db/Condition.java
new file mode 100644 (file)
index 0000000..b221551
--- /dev/null
@@ -0,0 +1,23 @@
+package net.jaekl.cfb.db;
+
+public class Condition {
+
+       Column m_column;
+       Object m_value;
+       Operation m_operation;
+       
+       public Condition(Column column, Object value, Operation operation)
+       {
+               m_column = column;
+               m_value = value;
+               m_operation = operation;
+       }
+       
+       public Condition(Column column, Object value) {
+               this(column, value, Operation.EQUAL);
+       }
+       
+       public Column getColumn() { return m_column; }
+       public Object getValue() { return m_value; }
+       public Operation getOperation() { return m_operation; }
+}
diff --git a/prod/net/jaekl/cfb/db/Operation.java b/prod/net/jaekl/cfb/db/Operation.java
new file mode 100644 (file)
index 0000000..1751c31
--- /dev/null
@@ -0,0 +1,20 @@
+package net.jaekl.cfb.db;
+
+public enum Operation {
+       EQUAL(" = ? ", true), 
+       LESS_THAN(" < ? ", true), 
+       GREATER_THAN(" > ? ", true), 
+       NULL(" is null ", false), 
+       NOT_NULL(" is not null ", false);
+       
+       String m_sql;
+       boolean m_hasParam;
+       
+       Operation(String sql, boolean hasParam) {
+               m_sql = sql;
+               m_hasParam = hasParam;
+       }
+       
+       public String getSql() { return m_sql; }
+       public boolean hasParam() { return m_hasParam; }
+}
diff --git a/prod/net/jaekl/cfb/db/Row.java b/prod/net/jaekl/cfb/db/Row.java
new file mode 100644 (file)
index 0000000..c5d5478
--- /dev/null
@@ -0,0 +1,46 @@
+package net.jaekl.cfb.db;
+
+
+public class Row {
+       Column[] m_columns;
+       Object[] m_values;
+       
+       public Row(Column[] columns, Object[] values)
+       {
+               m_columns = columns.clone();
+               m_values = values.clone();
+       }
+       
+       public int getNumColumns() { return m_columns.length; }
+       
+       public String getString(int index) throws TypeMismatchException {
+               checkType(index, Column.Type.VARCHAR);
+               return (String)m_values[index];
+       }
+       
+       public int getInt(int index) throws TypeMismatchException
+       {
+               checkType(index, Column.Type.INTEGER);
+               Number num = (Number)m_values[index];
+               return num.intValue();
+       }
+       
+       public long getLong(int index) throws TypeMismatchException
+       {
+               checkType(index, Column.Type.INTEGER);
+               Number num = (Number)m_values[index];
+               return num.longValue();
+       }
+       
+       protected void checkType(int index, Column.Type type) throws TypeMismatchException {
+               Column column = m_columns[index];
+               if (column.getType().equals(type)) {
+                       return;
+               }
+               
+               String msg = "Column " + column.getName() 
+                                  + " is of type " + column.getType().name()
+                                  + " which cannot be coerced to type " + type.name() + ".";
+               throw new TypeMismatchException(msg);
+       }
+}
index b6dc6012fc6d720ded5ecae5383c7943e4558afe..f802b58ca3cf71f89f7b5ef6c8b0f57c95382c66 100644 (file)
@@ -16,11 +16,13 @@ public class Schema {
        String m_name;
        DbDriver m_driver;
        ArrayList<Table> m_tables;
+       ArrayList<Sequence> m_sequences;
        
        public Schema(String name, DbDriver driver) {
                m_name = name;
                m_driver = driver;
                m_tables = new ArrayList<Table>();
+               m_sequences = new ArrayList<Sequence>();
        }
        
        public boolean ensureDbInitialized(Connection con) throws SQLException {
@@ -34,6 +36,10 @@ public class Schema {
                        return false;
                }
                
+               if (!createAllSequences(con)) { 
+                       return false;
+               }
+               
                return true;
        }
        
@@ -73,6 +79,15 @@ public class Schema {
                return true;
        }
        
+       boolean createAllSequences(Connection con) throws SQLException {
+               for (Sequence seq : m_sequences) {
+                       if (!m_driver.createSequence(con, seq)) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+       
        void addTable(Table table) {
                m_tables.add(table);
        }
@@ -88,5 +103,9 @@ public class Schema {
                for (Object[][] table : tables) {
                        addTable(Table.construct(table));
                }
-       }       
+       }
+       
+       void addSequence(Sequence seq) {
+               m_sequences.add(seq);
+       }
 }
diff --git a/prod/net/jaekl/cfb/db/Sequence.java b/prod/net/jaekl/cfb/db/Sequence.java
new file mode 100644 (file)
index 0000000..55c3449
--- /dev/null
@@ -0,0 +1,11 @@
+package net.jaekl.cfb.db;
+
+public class Sequence {
+       String m_name;
+       
+       public Sequence(String name) {
+               m_name = name;
+       }
+       
+       public String getName() { return m_name; }
+}
diff --git a/prod/net/jaekl/cfb/db/TypeMismatchException.java b/prod/net/jaekl/cfb/db/TypeMismatchException.java
new file mode 100644 (file)
index 0000000..db7a303
--- /dev/null
@@ -0,0 +1,9 @@
+package net.jaekl.cfb.db;
+
+public class TypeMismatchException extends Exception {
+       private static final long serialVersionUID = 1L;
+       
+       public TypeMismatchException(String msg) {
+               super(msg);
+       }
+}
index c8649579685d12a9d38f80950e35fb55fb0c595f..525ca7049d3444545a717a9788e51595b29697f3 100644 (file)
@@ -8,9 +8,14 @@ import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
 
 import net.jaekl.cfb.db.Column;
 import net.jaekl.cfb.db.Column.Type;
+import net.jaekl.cfb.db.Condition;
+import net.jaekl.cfb.db.Row;
+import net.jaekl.cfb.db.Sequence;
 import net.jaekl.cfb.db.Table;
 
 public abstract class DbDriver {
@@ -35,7 +40,97 @@ public abstract class DbDriver {
                return true;
        }
        
-       public abstract ResultSet selectColumnsFromWhere(Column[] columns, Table[] tables, String where);
+       public boolean createSequence(Connection con, Sequence seq) throws SQLException 
+       {
+               String sql = createSequenceSql(seq);
+               try (PreparedStatement ps = con.prepareStatement(sql)) {
+                       ps.executeUpdate();
+               }
+               catch (SQLException exc) {
+                       throw new SQLException("Failed to executeUpdate:  " + sql, exc);
+               }
+               
+               return true;
+       }
+       
+       public List<Row> select(Connection con, Column[] columns, Table[] tables, Condition[] conditions)
+               throws SQLException
+       {
+               String sql = selectSql(columns, tables, conditions);
+               ArrayList<Row> result = new ArrayList<Row>();
+               
+               try (PreparedStatement ps = con.prepareStatement(sql)) {
+                       int index = 0;
+                       for (Condition condition : conditions) {
+                               if (condition.getOperation().hasParam()) {
+                                       index++;
+                                       ps.setObject(index, condition.getValue());
+                               }
+                       }
+                       
+                       try (ResultSet rs = ps.executeQuery()) {
+                               while (rs.next()) {
+                                       Object[] values = new Object[columns.length];
+                                       for (index = 0; index < columns.length; ++index) {
+                                               values[index] = rs.getObject(index);
+                                       }
+                                       Row row = new Row(columns, values);
+                                       result.add(row);
+                               }
+                       }
+               }
+               
+               return result;
+       }
+       
+       protected String selectSql(Column[] columns, Table[] tables, Condition[] conditions) 
+       {
+               StringBuilder sb = new StringBuilder("SELECT ");
+               
+               boolean firstColumn = true;
+               for (Column column : columns) {
+                       if (firstColumn) {
+                               firstColumn = false;
+                       }
+                       else {
+                               sb.append(", ");
+                       }
+                       sb.append(column.getName());
+               }
+               
+               sb.append(" FROM ");
+               
+               boolean firstTable = true;
+               for (Table table : tables) {
+                       if (firstTable) {
+                               firstTable = false;
+                       }
+                       else {
+                               sb.append(", ");
+                       }
+                       sb.append(table.getName());
+               }
+               
+               if (null != conditions && conditions.length > 0) {                      
+                       sb.append(" WHERE ");
+                       
+                       boolean firstCondition = true;
+                       
+                       for (Condition condition : conditions) {
+                               if (firstCondition) {
+                                       firstCondition = false;
+                               }
+                               else {
+                                       sb.append(" AND ");
+                               }
+                               
+                               sb.append(condition.getColumn().getName())
+                                 .append(condition.getOperation().getSql());
+                       }
+               }
+               
+               return sb.toString();
+       }
        
        protected String typeName(Type type) {
                return type.toString();
@@ -81,4 +176,11 @@ public abstract class DbDriver {
                
                return sb.toString();
        }       
+       
+       protected String createSequenceSql(Sequence seq) {
+               assert(null != seq);
+               assert(null != seq.getName());
+               
+               return "CREATE SEQUENCE " + seq.getName();
+       }
 }
index 0fe1f0809b129f812ea3e46819be0e834e1c2715..a8d28f9bb0a3b6700a92953bb00f3589cf01bb29 100644 (file)
@@ -4,13 +4,9 @@ package net.jaekl.cfb.db.driver;
 
 import java.sql.Connection;
 import java.sql.DriverManager;
-import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.Properties;
 
-import net.jaekl.cfb.db.Column;
-import net.jaekl.cfb.db.Table;
-
 public class PostgresqlDriver extends DbDriver {
 
        @Override
@@ -30,11 +26,4 @@ public class PostgresqlDriver extends DbDriver {
                //props.setProperty("ssl", "true");
                return DriverManager.getConnection(url, props);
        }
-
-       @Override
-       public ResultSet selectColumnsFromWhere(Column[] columns, Table[] tables, String where) {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
 }
diff --git a/prod/net/jaekl/cfb/store/DbStore.java b/prod/net/jaekl/cfb/store/DbStore.java
new file mode 100644 (file)
index 0000000..7a1e610
--- /dev/null
@@ -0,0 +1,23 @@
+package net.jaekl.cfb.store;
+
+import java.sql.Connection;
+
+import net.jaekl.cfb.analyze.Analysis;
+
+public class DbStore {
+       Connection m_conn;
+       
+       public DbStore(Connection conn) {
+               m_conn = conn;
+       }
+       
+       public boolean put(Analysis analysis) {
+               if (null == analysis) {
+                       return false;
+               }
+               
+               
+               
+               return true;
+       }
+}
diff --git a/prod/net/jaekl/cfb/xml/messages/BugCategory.java b/prod/net/jaekl/cfb/xml/messages/BugCategory.java
new file mode 100644 (file)
index 0000000..6b5b492
--- /dev/null
@@ -0,0 +1,62 @@
+package net.jaekl.cfb.xml.messages;
+
+import org.xml.sax.Attributes;
+
+import net.jaekl.qd.xml.MissingAttributeException;
+import net.jaekl.qd.xml.ParseResult;
+import net.jaekl.qd.xml.XmlParseException;
+
+public class BugCategory extends ParseResult {
+       static final String CATEGORY = "category";      // attribute name
+       static final String DESCRIPTION = "Description";
+       static final String ABBREVIATION = "Abbreviation";
+       static final String DETAILS = "Details";
+
+       static final String TAG = "BugCategory";
+       static final String[] INTERNAL = { DESCRIPTION, ABBREVIATION, DETAILS };
+       static final Object[][] EXTERNAL = { };
+
+       String m_category;
+       String m_descr;
+       String m_abbrev;
+       String m_details;
+       
+       public BugCategory(String tagName, String[] internalMemberTags, Object[][] externalParserTags) 
+       {
+               super(tagName, internalMemberTags, externalParserTags);
+               m_category = m_descr = m_abbrev = m_details = "";
+       }
+       
+       public String getCategory() { return m_category; }
+       public String getDescr() { return m_descr; }
+       public String getAbbrev() { return m_abbrev; }
+       public String getDetails() { return m_details; }
+
+       @Override
+       public void endContents(String uri, String localName, String qName, String chars) throws XmlParseException 
+       {
+               if (DESCRIPTION.equals(localName)) {
+                       m_descr = chars;
+               }
+               else if (ABBREVIATION.equals(localName)) {
+                       m_abbrev = chars;
+               }
+               else if (DETAILS.equals(localName)) {
+                       m_details = chars;
+               }
+       }
+
+       @Override
+       public void endExternal(String uri, String localName, String qName)
+               throws XmlParseException 
+       {
+               // nothing to do
+       }
+       
+       // Called once for this tag itself
+       @Override 
+       public void handleMainAttributes(Attributes attr) throws MissingAttributeException {
+               m_category = this.getRequiredAttr(TAG, attr, CATEGORY);
+       }
+       
+}
diff --git a/prod/net/jaekl/cfb/xml/messages/BugPattern.java b/prod/net/jaekl/cfb/xml/messages/BugPattern.java
new file mode 100644 (file)
index 0000000..82ada92
--- /dev/null
@@ -0,0 +1,60 @@
+package net.jaekl.cfb.xml.messages;
+
+import net.jaekl.qd.xml.MissingAttributeException;
+import net.jaekl.qd.xml.ParseResult;
+import net.jaekl.qd.xml.XmlParseException;
+
+import org.xml.sax.Attributes;
+
+public class BugPattern extends ParseResult {
+       static final String TYPE = "type";
+       static final String SHORT = "ShortDescription";
+       static final String LONG = "LongDescription";
+       static final String DETAILS = "Details";
+
+       static final String TAG = "BugPattern";
+       static final String[] INTERNAL = { SHORT, LONG, DETAILS };
+       static final Object[][] EXTERNAL = {  };
+       
+       String m_type;
+       String m_short;
+       String m_long;
+       String m_details;
+       
+       public BugPattern(String tagName, String[] internalMemberTags, Object[][] externalParserTags) 
+       {
+               super(tagName, internalMemberTags, externalParserTags);
+               m_type = m_short = m_long = m_details = "";
+       }
+       
+       public String getType() { return m_type; }
+       public String getShort() { return m_short; }
+       public String getLong() { return m_long; }
+       public String getDetails() { return m_details; }
+
+       @Override
+       public void endContents(String uri, String localName, String qName, String chars) throws XmlParseException 
+       {
+               if (SHORT.equals(localName)) {
+                       m_short = chars;
+               }
+               else if (LONG.equals(localName)) {
+                       m_long = chars;
+               }
+               else if (DETAILS.equals(localName)) {
+                       m_details = chars;
+               }
+       }
+
+       @Override
+       public void endExternal(String uri, String localName, String qName) throws XmlParseException 
+       {
+               // nothing to do
+       }
+
+       // Called once for this tag itself
+       @Override
+       public void handleMainAttributes(Attributes attr) throws MissingAttributeException {
+               m_type = this.getRequiredAttr(TAG, attr, TYPE);
+       }
+}
diff --git a/prod/net/jaekl/cfb/xml/messages/MessageCollection.java b/prod/net/jaekl/cfb/xml/messages/MessageCollection.java
new file mode 100644 (file)
index 0000000..cf16e23
--- /dev/null
@@ -0,0 +1,55 @@
+package net.jaekl.cfb.xml.messages;
+
+import java.util.HashMap;
+
+import net.jaekl.qd.xml.ParseResult;
+import net.jaekl.qd.xml.XmlParseException;
+
+public class MessageCollection extends ParseResult {
+       static final String TAG = "MessageCollection";
+       static final String[] INTERNAL = {  };
+       static final Object[][] EXTERNAL = { { BugCategory.TAG, BugCategory.class },
+                                                { BugPattern.TAG,  BugPattern.class }   };
+
+       HashMap<String, BugCategory> m_categories;
+       HashMap<String, BugPattern> m_patterns;
+       
+       public MessageCollection() 
+       {
+               super(TAG, INTERNAL, EXTERNAL);
+               m_categories = new HashMap<String, BugCategory>();
+               m_patterns   = new HashMap<String, BugPattern>();
+       }
+
+       @Override
+       public void endContents(String uri, String localName, String qName, String chars) 
+               throws XmlParseException 
+       {
+               // Nothing to do
+       }
+
+       @Override
+       public void endExternal(String uri, String localName, String qName) throws XmlParseException 
+       {
+               ParseResult[] prs;
+               
+               prs = collectParsedChildren(BugCategory.class);
+               if (null != prs && prs.length > 0) {
+                       for (ParseResult pr : prs) {
+                               assert(pr instanceof BugCategory);
+                               BugCategory bc = (BugCategory)pr;
+                               m_categories.put(bc.getCategory(), bc);
+                       }
+               }
+               
+               prs = collectParsedChildren(BugPattern.class);
+               if (null != prs && prs.length > 0) {
+                       for (ParseResult pr : prs) {
+                               assert(pr instanceof BugPattern);
+                               BugPattern bp = (BugPattern) pr;
+                               m_patterns.put(bp.getType(), bp);
+                       }
+               }
+       }
+
+}
diff --git a/test/net/jaekl/cfb/analyze/AnalysisTest.java b/test/net/jaekl/cfb/analyze/AnalysisTest.java
new file mode 100644 (file)
index 0000000..f0b28c7
--- /dev/null
@@ -0,0 +1,86 @@
+package net.jaekl.cfb.analyze;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+
+import net.jaekl.cfb.xml.BugCollection;
+import net.jaekl.cfb.xml.BugInstance;
+import net.jaekl.qd.xml.XmlParseException;
+
+import org.junit.Test;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class AnalysisTest {
+       private static final String UTF8 = "utf-8";
+       
+       private static final String SAMPLE1_XML = ""
+                       + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+                       + "<BugCollection version=\"3.0.1\" sequence=\"0\" timestamp=\"1440757060000\" analysisTimestamp=\"1440761315847\" release=\"\">\n"
+                       + "<Project projectName=\"CFB\">\n"
+                       + "<Jar>/home/chris/prog/././cfb/bin</Jar>\n"
+                       + "<SrcDir>/home/chris/prog/././cfb/src</SrcDir>\n"
+                       + "</Project>\n"
+                       + "<BugInstance type=\"DM_DEFAULT_ENCODING\" priority=\"1\" rank=\"19\" abbrev=\"Dm\" category=\"I18N\">\n"
+                       + "<Class classname=\"net.jaekl.cfb.CFB\">\n"
+                       + "<SourceLine classname=\"net.jaekl.cfb.CFB\" start=\"32\" end=\"119\" sourcefile=\"CFB.java\" sourcepath=\"net/jaekl/cfb/CFB.java\"/>\n"
+                       + "</Class>\n"
+                       + "<Method classname=\"net.jaekl.cfb.CFB\" name=\"main\" signature=\"([Ljava/lang/String;)V\" isStatic=\"true\">\n"
+                       + "<SourceLine classname=\"net.jaekl.cfb.CFB\" start=\"112\" end=\"119\" startBytecode=\"0\" endBytecode=\"266\" sourcefile=\"CFB.java\" sourcepath=\"net/jaekl/cfb/CFB.java\"/>\n"
+                       + "</Method>\n"
+                       + "<Method classname=\"java.io.PrintWriter\" name=\"&lt;init&gt;\" signature=\"(Ljava/io/OutputStream;)V\" isStatic=\"false\" role=\"METHOD_CALLED\">\n"
+                       + "<SourceLine classname=\"java.io.PrintWriter\" start=\"131\" end=\"132\" startBytecode=\"0\" endBytecode=\"62\" sourcefile=\"PrintWriter.java\" sourcepath=\"java/io/PrintWriter.java\"/>\n"
+                       + "</Method>\n"
+                       + "<SourceLine classname=\"net.jaekl.cfb.CFB\" start=\"114\" end=\"114\" startBytecode=\"22\" endBytecode=\"22\" sourcefile=\"CFB.java\" sourcepath=\"net/jaekl/cfb/CFB.java\"/>\n"
+                       + "</BugInstance>\n"
+                       + "<BugInstance type=\"RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE\" priority=\"2\" rank=\"18\" abbrev=\"RCN\" category=\"STYLE\">\n"
+                       + "<Class classname=\"net.jaekl.cfb.CFB\">\n"
+                       + "<SourceLine classname=\"net.jaekl.cfb.CFB\" start=\"32\" end=\"119\" sourcefile=\"CFB.java\" sourcepath=\"net/jaekl/cfb/CFB.java\"/>\n"
+                       + "</Class>\n"
+                       + "<Method classname=\"net.jaekl.cfb.CFB\" name=\"doMain\" signature=\"(Ljava/io/PrintWriter;[Ljava/lang/String;)V\" isStatic=\"false\">\n"
+                       + "<SourceLine classname=\"net.jaekl.cfb.CFB\" start=\"96\" end=\"109\" startBytecode=\"0\" endBytecode=\"407\" sourcefile=\"CFB.java\" sourcepath=\"net/jaekl/cfb/CFB.java\"/>\n"
+                       + "</Method>\n"
+                       + "<LocalVariable name=\"con\" register=\"5\" pc=\"44\" role=\"LOCAL_VARIABLE_VALUE_OF\"/>\n"
+                       + "<Method classname=\"net.jaekl.cfb.db.driver.DbDriver\" name=\"connect\" signature=\"(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/sql/Connection;\" isStatic=\"false\" role=\"METHOD_RETURN_VALUE_OF\">\n"
+                       + "<SourceLine classname=\"net.jaekl.cfb.db.driver.DbDriver\" sourcefile=\"DbDriver.java\" sourcepath=\"net/jaekl/cfb/db/driver/DbDriver.java\"/>\n"
+                       + "</Method>\n"
+                       + "<SourceLine classname=\"net.jaekl.cfb.CFB\" start=\"101\" end=\"101\" startBytecode=\"46\" endBytecode=\"46\" sourcefile=\"CFB.java\" sourcepath=\"net/jaekl/cfb/CFB.java\" role=\"SOURCE_REDUNDANT_NULL_CHECK\"/>\n"
+                       + "</BugInstance>\n"
+                       + "</BugCollection>\n";
+
+       @Test
+       public void testParseSample1() throws IOException, XmlParseException, SAXException {
+               Charset utf8 = Charset.forName(UTF8);
+               BugInstance inst = null;
+               
+               try ( ByteArrayInputStream bais = new ByteArrayInputStream(SAMPLE1_XML.getBytes(utf8)))
+               {
+                       InputSource inputSource = new InputSource(bais); 
+                       Analysis analysis = new Analysis(null);
+                       analysis.parse(inputSource);
+                       
+                       assertNotNull(analysis);
+                       
+                       BugCollection bugColl = analysis.getBugCollection();
+                       
+                       assertNotNull(bugColl);
+                       assertEquals(2, bugColl.size());
+                       
+                       HashMap<String, BugInstance> typeMap = new HashMap<String, BugInstance>();
+                       for (int idx = 0; idx < bugColl.size(); ++idx) {
+                               inst = bugColl.get(idx);
+                               typeMap.put(inst.getType(), inst);
+                       }
+                       
+                       inst = typeMap.get("DM_DEFAULT_ENCODING");
+                       assertNotNull(inst);
+                       
+                       inst = typeMap.get("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE");
+                       assertNotNull(inst);
+               }
+       }
+}
index 9691efa9b2196c0df43c418ccd58a0772ae0abd3..85bd3d2950dcc26044d77c3e1989ae5e29a98910 100644 (file)
@@ -1,57 +1,12 @@
 package net.jaekl.cfb.analyze;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
 
-import java.io.ByteArrayInputStream;
 import java.io.File;
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.util.HashMap;
-
-import net.jaekl.cfb.xml.BugCollection;
-import net.jaekl.cfb.xml.BugInstance;
-import net.jaekl.qd.xml.XmlParseException;
 
 import org.junit.Test;
-import org.xml.sax.InputSource;
 
 public class AnalyzerTest {
-       private static final String UTF8 = "utf-8";
-       
-       private static final String SAMPLE1_XML = ""
-                       + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
-                       + "<BugCollection version=\"3.0.1\" sequence=\"0\" timestamp=\"1440757060000\" analysisTimestamp=\"1440761315847\" release=\"\">\n"
-                       + "<Project projectName=\"CFB\">\n"
-                       + "<Jar>/home/chris/prog/././cfb/bin</Jar>\n"
-                       + "<SrcDir>/home/chris/prog/././cfb/src</SrcDir>\n"
-                       + "</Project>\n"
-                       + "<BugInstance type=\"DM_DEFAULT_ENCODING\" priority=\"1\" rank=\"19\" abbrev=\"Dm\" category=\"I18N\">\n"
-                       + "<Class classname=\"net.jaekl.cfb.CFB\">\n"
-                       + "<SourceLine classname=\"net.jaekl.cfb.CFB\" start=\"32\" end=\"119\" sourcefile=\"CFB.java\" sourcepath=\"net/jaekl/cfb/CFB.java\"/>\n"
-                       + "</Class>\n"
-                       + "<Method classname=\"net.jaekl.cfb.CFB\" name=\"main\" signature=\"([Ljava/lang/String;)V\" isStatic=\"true\">\n"
-                       + "<SourceLine classname=\"net.jaekl.cfb.CFB\" start=\"112\" end=\"119\" startBytecode=\"0\" endBytecode=\"266\" sourcefile=\"CFB.java\" sourcepath=\"net/jaekl/cfb/CFB.java\"/>\n"
-                       + "</Method>\n"
-                       + "<Method classname=\"java.io.PrintWriter\" name=\"&lt;init&gt;\" signature=\"(Ljava/io/OutputStream;)V\" isStatic=\"false\" role=\"METHOD_CALLED\">\n"
-                       + "<SourceLine classname=\"java.io.PrintWriter\" start=\"131\" end=\"132\" startBytecode=\"0\" endBytecode=\"62\" sourcefile=\"PrintWriter.java\" sourcepath=\"java/io/PrintWriter.java\"/>\n"
-                       + "</Method>\n"
-                       + "<SourceLine classname=\"net.jaekl.cfb.CFB\" start=\"114\" end=\"114\" startBytecode=\"22\" endBytecode=\"22\" sourcefile=\"CFB.java\" sourcepath=\"net/jaekl/cfb/CFB.java\"/>\n"
-                       + "</BugInstance>\n"
-                       + "<BugInstance type=\"RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE\" priority=\"2\" rank=\"18\" abbrev=\"RCN\" category=\"STYLE\">\n"
-                       + "<Class classname=\"net.jaekl.cfb.CFB\">\n"
-                       + "<SourceLine classname=\"net.jaekl.cfb.CFB\" start=\"32\" end=\"119\" sourcefile=\"CFB.java\" sourcepath=\"net/jaekl/cfb/CFB.java\"/>\n"
-                       + "</Class>\n"
-                       + "<Method classname=\"net.jaekl.cfb.CFB\" name=\"doMain\" signature=\"(Ljava/io/PrintWriter;[Ljava/lang/String;)V\" isStatic=\"false\">\n"
-                       + "<SourceLine classname=\"net.jaekl.cfb.CFB\" start=\"96\" end=\"109\" startBytecode=\"0\" endBytecode=\"407\" sourcefile=\"CFB.java\" sourcepath=\"net/jaekl/cfb/CFB.java\"/>\n"
-                       + "</Method>\n"
-                       + "<LocalVariable name=\"con\" register=\"5\" pc=\"44\" role=\"LOCAL_VARIABLE_VALUE_OF\"/>\n"
-                       + "<Method classname=\"net.jaekl.cfb.db.driver.DbDriver\" name=\"connect\" signature=\"(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/sql/Connection;\" isStatic=\"false\" role=\"METHOD_RETURN_VALUE_OF\">\n"
-                       + "<SourceLine classname=\"net.jaekl.cfb.db.driver.DbDriver\" sourcefile=\"DbDriver.java\" sourcepath=\"net/jaekl/cfb/db/driver/DbDriver.java\"/>\n"
-                       + "</Method>\n"
-                       + "<SourceLine classname=\"net.jaekl.cfb.CFB\" start=\"101\" end=\"101\" startBytecode=\"46\" endBytecode=\"46\" sourcefile=\"CFB.java\" sourcepath=\"net/jaekl/cfb/CFB.java\" role=\"SOURCE_REDUNDANT_NULL_CHECK\"/>\n"
-                       + "</BugInstance>\n"
-                       + "</BugCollection>\n";
-
        @Test
        public void testOutputWorkFile() {
                final String[][] DATA = {
@@ -71,41 +26,13 @@ public class AnalyzerTest {
                        File fbp         = new File(datum[2]);
                        File expected    = new File(datum[3]);
 
-                       Analyzer analyzer = new Analyzer(findBugsDir);
+                       MessageMapMock mmm = new MessageMapMock();
+                       mmm.mock_setFindBugsDir(findBugsDir);
+
+                       Analyzer analyzer = new Analyzer(mmm);
                        File actual = analyzer.outputWorkFile(workDir, fbp);
                        assertEquals(expected.getAbsolutePath(), actual.getAbsolutePath());
                }
        }
 
-       @Test
-       public void testParseSample1() throws IOException, XmlParseException {
-               Charset utf8 = Charset.forName(UTF8);
-               BugInstance inst = null;
-               
-               try ( ByteArrayInputStream bais = new ByteArrayInputStream(SAMPLE1_XML.getBytes(utf8)))
-               {
-                       InputSource inputSource = new InputSource(bais); 
-                       Analyzer analyzer = new Analyzer(new File("."));
-                       Analysis analysis = analyzer.parseFbOutput(inputSource);
-                       
-                       assertNotNull(analysis);
-                       
-                       BugCollection bugColl = analysis.getBugCollection();
-                       
-                       assertNotNull(bugColl);
-                       assertEquals(2, bugColl.size());
-                       
-                       HashMap<String, BugInstance> typeMap = new HashMap<String, BugInstance>();
-                       for (int idx = 0; idx < bugColl.size(); ++idx) {
-                               inst = bugColl.get(idx);
-                               typeMap.put(inst.getType(), inst);
-                       }
-                       
-                       inst = typeMap.get("DM_DEFAULT_ENCODING");
-                       assertNotNull(inst);
-                       
-                       inst = typeMap.get("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE");
-                       assertNotNull(inst);
-               }
-       }
 }
diff --git a/test/net/jaekl/cfb/analyze/MessageMapMock.java b/test/net/jaekl/cfb/analyze/MessageMapMock.java
new file mode 100644 (file)
index 0000000..41956c2
--- /dev/null
@@ -0,0 +1,9 @@
+package net.jaekl.cfb.analyze;
+
+import java.io.File;
+
+public class MessageMapMock extends MessageMap {
+       void mock_setFindBugsDir(File fbd) {
+               this.m_findBugsDir = fbd;
+       }
+}