From 769f0f2e9b90516e68246b551a4c68f953018c72 Mon Sep 17 00:00:00 2001 From: Chris Jaekl Date: Sat, 3 Oct 2015 23:35:55 +0900 Subject: [PATCH] (Finally) reach the point where we have some useful, if basic, functionality. Delta now computes difference between previous run and the current one, and supports a text dump of the new, fixed, and old bugs. --- prod/net/jaekl/cfb/CFB.java | 4 ++ prod/net/jaekl/cfb/analyze/Analyzer.java | 2 +- prod/net/jaekl/cfb/analyze/Delta.java | 32 ++++++++- prod/net/jaekl/cfb/analyze/MessageMap.java | 4 +- prod/net/jaekl/cfb/db/Column.java | 20 ++++++ prod/net/jaekl/cfb/db/Row.java | 25 +++++-- prod/net/jaekl/cfb/db/driver/DbDriver.java | 28 +++----- prod/net/jaekl/cfb/store/DbStore.java | 36 ++++++---- prod/net/jaekl/cfb/store/Location.java | 78 ++++++++++++++++++++-- prod/net/jaekl/cfb/xml/BugCollection.java | 1 + prod/net/jaekl/cfb/xml/BugInstance.java | 31 ++++----- 11 files changed, 201 insertions(+), 60 deletions(-) diff --git a/prod/net/jaekl/cfb/CFB.java b/prod/net/jaekl/cfb/CFB.java index 81c30ea..d202d6f 100644 --- a/prod/net/jaekl/cfb/CFB.java +++ b/prod/net/jaekl/cfb/CFB.java @@ -18,6 +18,7 @@ import java.util.Locale.Category; import net.jaekl.cfb.analyze.Analysis; import net.jaekl.cfb.analyze.Analyzer; +import net.jaekl.cfb.analyze.Delta; import net.jaekl.cfb.analyze.MessageMap; import net.jaekl.cfb.db.CfbSchema; import net.jaekl.cfb.db.TypeMismatchException; @@ -189,6 +190,9 @@ public class CFB { DbStore store = new DbStore(con, m_driver, messageMap.getColl()); store.put(analysis); + Analysis prior = store.getPrior(analysis); + Delta delta = new Delta(prior, analysis); + delta.dump(pw); } catch (SQLException exc) { reportUnableToConnect(pw, exc); diff --git a/prod/net/jaekl/cfb/analyze/Analyzer.java b/prod/net/jaekl/cfb/analyze/Analyzer.java index 1ac5677..3f3c4cf 100644 --- a/prod/net/jaekl/cfb/analyze/Analyzer.java +++ b/prod/net/jaekl/cfb/analyze/Analyzer.java @@ -51,7 +51,7 @@ public class Analyzer { result.setEnd(new Date()); result.parse(new InputSource(fbOutput.getAbsolutePath())); - result.dump(pw); + // result.dump(pw); return result; } diff --git a/prod/net/jaekl/cfb/analyze/Delta.java b/prod/net/jaekl/cfb/analyze/Delta.java index e2853e0..51971e0 100644 --- a/prod/net/jaekl/cfb/analyze/Delta.java +++ b/prod/net/jaekl/cfb/analyze/Delta.java @@ -1,5 +1,6 @@ package net.jaekl.cfb.analyze; +import java.io.PrintWriter; import java.util.HashSet; import net.jaekl.cfb.xml.BugInstance; @@ -29,6 +30,29 @@ public class Delta { public BugInstance[] getNew() { return m_new.toArray(new BugInstance[m_new.size()]); } public int getNumNew() { return m_new.size(); } + public void dump(PrintWriter pw) { + pw.println("========================="); + pw.println(" NEW BUGS (" + m_new.size() + ")"); + pw.println("-------------------------"); + for (BugInstance bug : m_new) { + bug.dump(pw, 2); + } + + pw.println("========================="); + pw.println(" FIXED BUGS (" + m_fixed.size() + ")"); + pw.println("-------------------------"); + for (BugInstance bug : m_fixed) { + bug.dump(pw, 2); + } + + pw.println("========================="); + pw.println(" OLD BUGS (" + m_common.size() + ")"); + pw.println("-------------------------"); + for (BugInstance bug : m_common) { + bug.dump(pw, 2); + } + } + void computeDelta(Analysis before, Analysis after) { m_fixed.clear(); @@ -37,7 +61,9 @@ public class Delta { HashSet beforeBugs = new HashSet(); - beforeBugs.addAll(before.getBugCollection().getBugs()); + if (null != before) { + beforeBugs.addAll(before.getBugCollection().getBugs()); + } for (BugInstance bug : after.getBugCollection().getBugs()) { if (beforeBugs.contains(bug)) { @@ -48,6 +74,10 @@ public class Delta { } } + if (null == before) { + return; + } + for (BugInstance bug : before.getBugCollection().getBugs()) { if (! m_common.contains(bug)) { m_fixed.add(bug); diff --git a/prod/net/jaekl/cfb/analyze/MessageMap.java b/prod/net/jaekl/cfb/analyze/MessageMap.java index ae389ad..f4936b0 100644 --- a/prod/net/jaekl/cfb/analyze/MessageMap.java +++ b/prod/net/jaekl/cfb/analyze/MessageMap.java @@ -59,7 +59,7 @@ public class MessageMap { List rows = driver.select(con, columns, tables, conditions); for (Row row : rows) { - long catId = row.getLong(0); + Long catId = row.getLong(0); String catName = row.getString(1); BugCategory cat = getColl().getCategory(catName); @@ -80,7 +80,7 @@ public class MessageMap { List rows = driver.select(con, columns, tables, conditions); for (Row row: rows) { - long bugId = row.getLong(0); + Long bugId = row.getLong(0); String type = row.getString(1); BugPattern bug = getColl().getPattern(type); diff --git a/prod/net/jaekl/cfb/db/Column.java b/prod/net/jaekl/cfb/db/Column.java index 8fc723d..be9fb65 100644 --- a/prod/net/jaekl/cfb/db/Column.java +++ b/prod/net/jaekl/cfb/db/Column.java @@ -1,5 +1,9 @@ package net.jaekl.cfb.db; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Date; + import net.jaekl.cfb.util.Util; // Copyright (C) 2015 Christian Jaekl @@ -48,6 +52,22 @@ public class Column { return new Column(name, type, width.intValue(), canBeNull); } + // Wrapper around PreparedStatement.setObject(). + // Note that indices start at 1, not at zero. + public void setObject(PreparedStatement ps, int idx, Object obj) throws SQLException + { + if (this.getType().equals(Type.TIMESTAMPTZ)) { + // Special case: because there's no good way to read a TIMESTAMPTZ from + // the database using JDBC, we store it as an integer (milliseconds since + // the epoch, 01.01.1970 00:00:00.000 UTC). + Date date = (Date)obj; + ps.setLong(idx, date.getTime()); + } + else { + ps.setObject(idx, obj); + } + } + @Override public boolean equals(Object obj) { diff --git a/prod/net/jaekl/cfb/db/Row.java b/prod/net/jaekl/cfb/db/Row.java index fa3de99..35bf121 100644 --- a/prod/net/jaekl/cfb/db/Row.java +++ b/prod/net/jaekl/cfb/db/Row.java @@ -26,24 +26,37 @@ public class Row { return num.intValue(); } - public long getLong(int index) throws TypeMismatchException + public Long getLong(int index) throws TypeMismatchException { checkType(index, Column.Type.INTEGER); + if (null == m_values[index]) { + return null; + } + Number num = (Number)m_values[index]; return num.longValue(); } public java.util.Date getDate(int index) throws TypeMismatchException { - checkType(index, Column.Type.INTEGER); - long milliseconds = (Long)m_values[index]; - java.util.Date date = new java.util.Date(milliseconds); - return date; + checkType(index, Column.Type.TIMESTAMPTZ); + return (java.util.Date)m_values[index]; } protected void checkType(int index, Column.Type type) throws TypeMismatchException { Column column = m_columns[index]; - if (column.getType().equals(type)) { + Column.Type columnType = column.getType(); + + if (columnType.equals(Column.Type.TIMESTAMPTZ)) { + // Special case: TIMESTAMPTZ is stored as an INTEGER + if ( Column.Type.TIMESTAMPTZ.equals(type) + || Column.Type.INTEGER.equals(type) ) + { + return; + } + } + + if (columnType.equals(type)) { return; } diff --git a/prod/net/jaekl/cfb/db/driver/DbDriver.java b/prod/net/jaekl/cfb/db/driver/DbDriver.java index a756ab2..bbe38f8 100644 --- a/prod/net/jaekl/cfb/db/driver/DbDriver.java +++ b/prod/net/jaekl/cfb/db/driver/DbDriver.java @@ -9,7 +9,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Date; import java.util.List; import net.jaekl.cfb.db.Column; @@ -115,8 +114,9 @@ public abstract class DbDriver { int index = 0; for (Condition condition : conditions) { if (condition.getOperation().hasParam()) { + Column column = condition.getColumn(); index++; - ps.setObject(index, condition.getValue()); + column.setObject(ps, index, condition.getValue()); } } @@ -137,6 +137,9 @@ public abstract class DbDriver { } } } + catch (SQLException se) { + throw new SQLException("Error with SQL: " + sql, se); + } return result; } @@ -159,19 +162,10 @@ public abstract class DbDriver { assert(null != data); assert(data.length == table.getNumColumns()); - for (int col = 0; col < data.length; ++col) { - Object obj = data[col]; - Column column = table.getColumn(col); - if (column.getType().equals(Type.TIMESTAMPTZ)) { - // Special case: because there's no good way to read a TIMESTAMPTZ from - // the database using JDBC, we store it as an integer (milliseconds since - // the epoch, 01.01.1970 00:00:00.000 UTC). - Date date = (Date)obj; - ps.setLong(col + 1, date.getTime()); - } - else { - ps.setObject(col + 1, data[col]); - } + for (int idx = 0; idx < data.length; ++idx) { + Object obj = data[idx]; + Column column = table.getColumn(idx); + column.setObject(ps, idx + 1, obj); pendingValues++; } ps.addBatch(); @@ -300,10 +294,10 @@ public abstract class DbDriver { sb.append(sort.getColumn().getName()); if (sort.getDirection().equals(Sort.Direction.ASCENDING)) { - sb.append(" ASCENDING "); + sb.append(" ASC "); } else { - sb.append(" DESCENDING "); + sb.append(" DESC "); } } } diff --git a/prod/net/jaekl/cfb/store/DbStore.java b/prod/net/jaekl/cfb/store/DbStore.java index 69ea8bb..6c20979 100644 --- a/prod/net/jaekl/cfb/store/DbStore.java +++ b/prod/net/jaekl/cfb/store/DbStore.java @@ -119,6 +119,10 @@ public class DbStore { Location getLoc(Long locId) throws SQLException, TypeMismatchException { + if (null == locId) { + return null; + } + Column[] columns = { CfbSchema.CLASSNAME, CfbSchema.METHODNAME, CfbSchema.METHODROLE, CfbSchema.STARTLINE, CfbSchema.ENDLINE }; Table[] tables = { CfbSchema.LOCATIONS }; Condition[] conditions = { new Condition(CfbSchema.LOCID, locId, Operation.EQUAL) }; @@ -128,8 +132,8 @@ public class DbStore { String className = row.getString(0); String methodName = row.getString(1); String methodRole = row.getString(2); - long startLine = row.getLong(3); - long endLine = row.getLong(4); + Integer startLine = row.getInt(3); + Integer endLine = row.getInt(4); Location loc = new Location(locId, className, methodName, methodRole, startLine, endLine); return loc; @@ -206,6 +210,10 @@ public class DbStore { LocalVariable getVar(Long varId) throws SQLException, TypeMismatchException { + if (null == varId) { + return null; + } + Column[] columns = { CfbSchema.NAME, CfbSchema.VARROLE }; Table[] tables = { CfbSchema.VARIABLES }; Condition[] conditions = { new Condition(CfbSchema.VARID_PK, varId, Operation.EQUAL) }; @@ -290,11 +298,11 @@ public class DbStore { return rows.get(0).getLong(0); } - Analysis getAnalysis(Long priorId) throws SQLException, TypeMismatchException + Analysis getAnalysis(Long analysisId) throws SQLException, TypeMismatchException { Column[] columns = { CfbSchema.VERSION, CfbSchema.STARTTIME, CfbSchema.ENDTIME }; Table[] tables = { CfbSchema.RUNS }; - Condition[] conditions = { new Condition( CfbSchema.RUNID, priorId, Operation.EQUAL ) }; + Condition[] conditions = { new Condition( CfbSchema.RUNID, analysisId, Operation.EQUAL ) }; List rows = m_driver.select(m_con, columns, tables, conditions); if (rows.size() < 1) { @@ -308,11 +316,11 @@ public class DbStore { java.util.Date end = row.getDate(2); Analysis prior = new Analysis(version); - prior.setId(priorId.longValue()); + prior.setId(analysisId.longValue()); prior.setStart(start); prior.setEnd(end); - prior.setBugCollection(getBugCollection(priorId)); + prior.setBugCollection(getBugCollection(analysisId)); return prior; } @@ -341,21 +349,21 @@ public class DbStore { for (Row row : rows) { // long foundId = row.getLong(0); - long bugId = row.getLong(1); - long categoryId = row.getLong(2); - long firstLocId = row.getLong(3); - long secondLocId = row.getLong(4); - long thirdLocId = row.getLong(5); - long varId = row.getLong(6); + Long bugId = row.getLong(1); + Long categoryId = row.getLong(2); + Long firstLocId = row.getLong(3); + Long secondLocId = row.getLong(4); + Long thirdLocId = row.getLong(5); + Long varId = row.getLong(6); String bugType = getBugType(bugId); String category = getCategoryName(categoryId); Location[] locations = { getLoc(firstLocId), getLoc(secondLocId), getLoc(thirdLocId) }; - LocalVariable[] vars = { getVar(Long.valueOf(varId)) }; + LocalVariable[] vars = { getVar(varId) }; BugInstance bug = new BugInstance(bugId, category, bugType, locations, vars); - coll.getBugs().add(bug); + coll.add(bug); } return coll; diff --git a/prod/net/jaekl/cfb/store/Location.java b/prod/net/jaekl/cfb/store/Location.java index 8755fe6..51857eb 100644 --- a/prod/net/jaekl/cfb/store/Location.java +++ b/prod/net/jaekl/cfb/store/Location.java @@ -1,5 +1,8 @@ package net.jaekl.cfb.store; +import java.io.PrintWriter; + +import net.jaekl.cfb.util.Util; import net.jaekl.cfb.xml.BugClass; import net.jaekl.cfb.xml.BugMethod; import net.jaekl.cfb.xml.SourceLine; @@ -9,8 +12,8 @@ public class Location { String m_className; String m_methodName; String m_methodRole; - int m_startLine; - int m_endLine; + Integer m_startLine; + Integer m_endLine; public Location(SourceLine sourceLine) { @@ -39,9 +42,14 @@ public class Location { m_className = bugClass.getClassName(); } - public Location(Long id, String className, String methodName, String methodRole, long startLine, long endLine) + public Location(Long id, String className, String methodName, String methodRole, Integer startLine, Integer endLine) { - + m_id = id; + m_className = className; + m_methodName = methodName; + m_methodRole = methodRole; + m_startLine = startLine; + m_endLine = endLine; } public String getClassName() { return m_className; } @@ -50,6 +58,68 @@ public class Location { public int getStart() { return m_startLine; } public int getEnd() { return m_endLine; } + public void dump(PrintWriter pw, int indent) + { + String margin = String.format("%" + indent + "s", ""); + String tab = margin + " "; + pw.println(margin + "Location"); + if (null != m_className) { + pw.println(tab + "classname = " + m_className); + } + if (null != m_methodName) { + if (null != m_methodRole) { + pw.println(tab + "method = " + m_methodName + " (" + m_methodRole + ")"); + } + else { + pw.println(tab + "method = " + m_methodName); + } + } + if (null != m_startLine) { + pw.println(tab + "lines = " + m_startLine + " .. " + m_endLine); + } + } + + public boolean fuzzyEquals(Location that) + { + if (null == that) { + return false; + } + + if (! Util.objsAreEqual(this.m_className, that.m_className)) { + return false; + } + + if (! Util.objsAreEqual(this.m_methodName, that.m_methodName)) { + return false; + } + + if (! Util.objsAreEqual(this.m_methodRole, that.m_methodRole)) { + return false; + } + + return true; + } + + @Override + public boolean equals(Object other) + { + if (null == other) { + return false; + } + if (other instanceof Location) { + return fuzzyEquals((Location)other); + } + return false; + } + + @Override + public int hashCode() + { + return Util.objHashCode(m_className) + ^ Util.objHashCode(m_methodName) + ^ Util.objHashCode(m_methodRole); + } + private void init(SourceLine[] sourceLines) { if (sourceLines.length > 0) { diff --git a/prod/net/jaekl/cfb/xml/BugCollection.java b/prod/net/jaekl/cfb/xml/BugCollection.java index 6fbf446..98ea93b 100644 --- a/prod/net/jaekl/cfb/xml/BugCollection.java +++ b/prod/net/jaekl/cfb/xml/BugCollection.java @@ -22,6 +22,7 @@ public class BugCollection extends ParseResult { } public List getBugs() { return Collections.unmodifiableList(m_bugs); } + public void add(BugInstance bug) { m_bugs.add(bug); } @Override public void endContents(String uri, String localName, String qName, String chars) diff --git a/prod/net/jaekl/cfb/xml/BugInstance.java b/prod/net/jaekl/cfb/xml/BugInstance.java index 96a815e..0683a7d 100644 --- a/prod/net/jaekl/cfb/xml/BugInstance.java +++ b/prod/net/jaekl/cfb/xml/BugInstance.java @@ -57,9 +57,11 @@ public class BugInstance extends ParseResult { m_id = id; m_category = category; m_type = type; - m_classes = null; - m_methods = null; - m_lines = null; + + m_classes = new ArrayList(); + m_methods = new ArrayList(); + m_lines = new ArrayList(); + m_locations = new ArrayList(Arrays.asList(locations)); m_locals = new ArrayList(Arrays.asList(variables)); } @@ -138,11 +140,18 @@ public class BugInstance extends ParseResult { bm.dump(pw, childIndent); } for (LocalVariable lv : m_locals) { - lv.dump(pw, childIndent); + if (null != lv) { + lv.dump(pw, childIndent); + } } for (SourceLine sl : m_lines) { sl.dump(pw, childIndent); } + for (Location loc : m_locations) { + if (null != loc) { + loc.dump(pw, childIndent); + } + } } // Note that this is a heuristic, "fuzzy", equals. @@ -180,18 +189,11 @@ public class BugInstance extends ParseResult { } } else { - if (! Util.objsAreEqual(thisLoc.getClassName(), thatLoc.getClassName())) { - return false; - } - if (! Util.objsAreEqual(thisLoc.getMethodName(), thatLoc.getMethodName())) { + if (! thisLoc.fuzzyEquals(thatLoc)) { return false; } } - if (! Util.objsAreEqual(this.getVariables(), that.getVariables())) { - return false; - } - return true; } return false; @@ -201,9 +203,8 @@ public class BugInstance extends ParseResult { public int hashCode() { int code = Util.objHashCode(m_type) - * Util.objHashCode(m_category) - * Util.objHashCode(getPrincipalLocation()) - * Util.objHashCode(getVariables()); + ^ Util.objHashCode(m_category) + ^ Util.objHashCode(getPrincipalLocation()); return code; } -- 2.30.2