X-Git-Url: http://jaekl.net/gitweb/?a=blobdiff_plain;f=prod%2Fnet%2Fjaekl%2Fcfb%2Fxml%2FBugInstance.java;h=b805096c67551c7f3a35847b12f6660bc35acca0;hb=4ba1c41e8a698ed67e8f262412e1fef7a09c5d17;hp=3386e3f70fd2d47da0d820e174c617f5592e73b5;hpb=9635991f7480e1b82f897948cf8adf56537c1818;p=cfb.git diff --git a/prod/net/jaekl/cfb/xml/BugInstance.java b/prod/net/jaekl/cfb/xml/BugInstance.java index 3386e3f..b805096 100644 --- a/prod/net/jaekl/cfb/xml/BugInstance.java +++ b/prod/net/jaekl/cfb/xml/BugInstance.java @@ -2,11 +2,14 @@ package net.jaekl.cfb.xml; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import org.xml.sax.Attributes; +import net.jaekl.cfb.store.Location; +import net.jaekl.cfb.util.Util; import net.jaekl.qd.xml.MissingAttributeException; import net.jaekl.qd.xml.ParseResult; import net.jaekl.qd.xml.XmlParseException; @@ -22,35 +25,63 @@ public class BugInstance extends ParseResult { static final String CATEGORY = "category"; static final String TYPE = "type"; + Long m_id; String m_category; String m_type; ArrayList m_classes; ArrayList m_methods; ArrayList m_locals; ArrayList m_lines; + ArrayList m_locations; public BugInstance() { super(TAG, INTERNAL, EXTERNAL); + m_id = null; m_category = m_type = null; m_classes = new ArrayList(); m_methods = new ArrayList(); m_locals = new ArrayList(); m_lines = new ArrayList(); + m_locations = new ArrayList(); + } + + public BugInstance(Long id, + String category, + String type, + Location[] locations, + LocalVariable[] variables) + { + super(TAG, INTERNAL, EXTERNAL); + + m_id = id; + m_category = category; + m_type = type; + + 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)); } public String getCategory() { return m_category; } public String getType() { return m_type; } - public List getClasses() { return Collections.unmodifiableList(m_classes); } - public List getMethods() { return Collections.unmodifiableList(m_methods); } - public List getLines() { return Collections.unmodifiableList(m_lines); } public List getVariables() { return Collections.unmodifiableList(m_locals); } + public List getLocations() { return Collections.unmodifiableList(m_locations); } @Override public void endContents(String uri, String localName, String qName, String chars) throws XmlParseException { - // no-op + // no operation + } + + @Override + public void complete() + { + computeLocations(); } @Override @@ -109,10 +140,152 @@ 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. + // Two BugInstances will be considered equal if: + // - they refer to the same bug type + // - they took place in the same class and method + // - the variable names referred to (if any) match + // In particular, this equality test does not check + // for line numbers being equal. This is by design; + // we want to consider two bugs to be the "same bug" + // even if other changes in the file have shifted + // line numbers a bit. + @Override + public boolean equals(Object obj) + { + if (null == obj) { + return false; + } + if (obj instanceof BugInstance) { + BugInstance that = (BugInstance)obj; + + if (! Util.objsAreEqual(this.m_type, that.m_type)) { + return false; + } + + if (! Util.objsAreEqual(this.m_category, that.m_category)) { + return false; + } + + Location thisLoc = this.getPrincipalLocation(); + Location thatLoc = that.getPrincipalLocation(); + if (null == thisLoc) { + if (null == thatLoc) { + return false; + } + } + else { + if (! thisLoc.fuzzyEquals(thatLoc)) { + return false; + } + } + + return true; + } + return false; + } + + @Override + public int hashCode() + { + int code = Util.objHashCode(m_type) + ^ Util.objHashCode(m_category) + ^ Util.objHashCode(getPrincipalLocation()); + return code; + } + + // Get the "principal" Location. + // This should be the place where the bug is reported. + Location getPrincipalLocation() + { + if (null == m_locations) { + return null; + } + + for (int idx = 0; idx < m_locations.size(); ++idx) { + Location loc = m_locations.get(idx); + if (Location.METHOD_CALLED.equals(loc.getMethodRole())) { + // METHOD_CALLED locations describe the method that is being called, + // but the bug is located in the caller, not in the callee. + // Thus, ignore this information about the callee. + continue; + } + return loc; + } + + return null; + } + + private void computeLocations() + { + assert(null != m_classes); + assert(null != m_methods); + assert(null != m_lines); + + m_locations.clear(); + + /* + Somewhat unfortunate special case. + The primary "location" for a bug instance is split between tags. + Most bugs have a pattern like this: + + ... + + + + ... + + + + The primary location for a bug is given by the with no role attribute, + but the inside that method describes the whole range of lines + covered by that Method, not the spot where the bug is located--that is given + by the that is a direct child fo the . + */ + + BugMethod primaryMethod = null; + SourceLine primaryLine = null; + + for (BugMethod method : m_methods) { + if (null != method.getRole()) { + primaryMethod = method; + break; + } + } + if (m_lines.size() > 0) { + primaryLine = m_lines.get(0); + } + + if ((null != primaryMethod) && (null != primaryLine)) { + m_locations.add(new Location(primaryMethod, primaryLine)); + } + + for (BugMethod method : m_methods) { + if (primaryMethod != method) { + m_locations.add(new Location(method)); + } + } + for (SourceLine line : m_lines) { + if (primaryLine != line) { + m_locations.add(new Location(line)); + } + } + for (BugClass clazz : m_classes) { + m_locations.add(new Location(clazz)); + } } }