Add ability to load previously found bugs back out of the database.
[cfb.git] / prod / net / jaekl / cfb / xml / BugInstance.java
1 package net.jaekl.cfb.xml;
2
3 import java.io.PrintWriter;
4 import java.util.ArrayList;
5 import java.util.Arrays;
6 import java.util.Collections;
7 import java.util.List;
8
9 import org.xml.sax.Attributes;
10
11 import net.jaekl.cfb.store.Location;
12 import net.jaekl.cfb.util.Util;
13 import net.jaekl.qd.xml.MissingAttributeException;
14 import net.jaekl.qd.xml.ParseResult;
15 import net.jaekl.qd.xml.XmlParseException;
16
17 public class BugInstance extends ParseResult {
18
19         static final String TAG = "BugInstance";
20         static final String[] INTERNAL = {  };
21         static final Object[][] EXTERNAL = { { BugClass.TAG, BugClass.class},
22                                                  { BugMethod.TAG, BugMethod.class},
23                                                  { LocalVariable.TAG, LocalVariable.class},
24                                                  { SourceLine.TAG, SourceLine.class} };
25         static final String CATEGORY = "category";
26         static final String TYPE = "type";
27
28         Long m_id;
29         String m_category;
30         String m_type;
31         ArrayList<BugClass> m_classes;
32         ArrayList<BugMethod> m_methods;
33         ArrayList<LocalVariable> m_locals;
34         ArrayList<SourceLine> m_lines;
35         ArrayList<Location> m_locations;
36         
37         public BugInstance() {
38                 super(TAG, INTERNAL, EXTERNAL);
39                 
40                 m_id = null;
41                 m_category = m_type = null;
42                 m_classes = new ArrayList<BugClass>();
43                 m_methods = new ArrayList<BugMethod>();
44                 m_locals = new ArrayList<LocalVariable>();
45                 m_lines = new ArrayList<SourceLine>();
46                 m_locations = new ArrayList<Location>();
47         }
48         
49         public BugInstance(Long id,
50                                    String category, 
51                                    String type,
52                                    Location[] locations,
53                                    LocalVariable[] variables)
54         {
55                 super(TAG, INTERNAL, EXTERNAL);
56                 
57                 m_id = id;
58                 m_category = category;
59                 m_type = type;
60                 m_classes = null;
61                 m_methods = null;
62                 m_lines = null;
63                 m_locations = new ArrayList<Location>(Arrays.asList(locations));
64                 m_locals = new ArrayList<LocalVariable>(Arrays.asList(variables));
65         }
66         
67         public String getCategory() { return m_category; }
68         public String getType() { return m_type; }
69         public List<LocalVariable> getVariables() { return Collections.unmodifiableList(m_locals); }
70         public List<Location> getLocations() { return Collections.unmodifiableList(m_locations); }
71         
72         @Override
73         public void endContents(String uri, String localName, String qName, String chars) 
74                 throws XmlParseException 
75         {
76                 // no operation
77         }
78         
79         @Override
80         public void complete()
81         {
82                 computeLocations();
83         }
84
85         @Override
86         public void endExternal(String uri, String localName, String qName)
87                 throws XmlParseException 
88         {
89                 if (BugClass.TAG.equals(localName)) {
90                         ParseResult[] collected = collectParsedChildren(BugClass.class);
91                         for (ParseResult pr : collected) {
92                                 assert(pr instanceof BugClass);
93                                 m_classes.add((BugClass) pr);
94                         }
95                 }
96                 else if (BugMethod.TAG.equals(localName)) {
97                         ParseResult[] collected = collectParsedChildren(BugMethod.class);
98                         for (ParseResult pr : collected) {
99                                 assert(pr instanceof BugMethod);
100                                 m_methods.add((BugMethod)pr);
101                         }                       
102                 }
103                 else if (LocalVariable.TAG.equals(localName)) {
104                         ParseResult[] collected = collectParsedChildren(LocalVariable.class);
105                         for (ParseResult pr : collected) {
106                                 assert(pr instanceof LocalVariable);
107                                 m_locals.add((LocalVariable)pr);
108                         }                       
109                 }
110                 else if (SourceLine.TAG.equals(localName)) {
111                         ParseResult[] collected = collectParsedChildren(SourceLine.class);
112                         for (ParseResult pr : collected) {
113                                 assert(pr instanceof SourceLine);
114                                 m_lines.add((SourceLine)pr);
115                         }
116                 }
117         }
118         
119         @Override
120         public void handleMainAttributes(Attributes attr) throws MissingAttributeException
121         {
122                 m_type = this.getRequiredAttr(TAG, attr, TYPE);
123                 m_category = this.getRequiredAttr(TAG, attr, CATEGORY);
124         }
125
126         @Override 
127         public void dump(PrintWriter pw, int indent)
128         {
129                 int childIndent = indent + 2;
130                 String margin = String.format("%" + indent + "s", "");
131                 
132                 pw.println(margin + TAG + " (" + m_type + ")");
133                 pw.println(margin + CATEGORY + " (" + m_category + ")");
134                 for (BugClass bc : m_classes) {
135                         bc.dump(pw, childIndent);
136                 }
137                 for (BugMethod bm : m_methods) {
138                         bm.dump(pw, childIndent);
139                 }
140                 for (LocalVariable lv : m_locals) {
141                         lv.dump(pw, childIndent);
142                 }
143                 for (SourceLine sl : m_lines) {
144                         sl.dump(pw, childIndent);
145                 }
146         }
147         
148         // Note that this is a heuristic, "fuzzy", equals.
149         // Two BugInstances will be considered equal if:
150         //   - they refer to the same bug type
151         //   - they took place in the same class and method
152         //   - the variable names referred to (if any) match
153         // In particular, this equality test does not check 
154         // for line numbers being equal.  This is by design;
155         // we want to consider two bugs to be the "same bug" 
156         // even if other changes in the file have shifted 
157         // line numbers a bit.
158         @Override
159         public boolean equals(Object obj) 
160         {
161                 if (null == obj) { 
162                         return false;
163                 }
164                 if (obj instanceof BugInstance) {
165                         BugInstance that = (BugInstance)obj;
166                         
167                         if (! Util.objsAreEqual(this.m_type, that.m_type)) {
168                                 return false;
169                         }
170                         
171                         if (! Util.objsAreEqual(this.m_category, that.m_category)) {
172                                 return false;
173                         }
174
175                         Location thisLoc = this.getPrincipalLocation();
176                         Location thatLoc = that.getPrincipalLocation();
177                         if (null == thisLoc) {
178                                 if (null == thatLoc) {
179                                         return false;
180                                 }
181                         }
182                         else {
183                                 if (! Util.objsAreEqual(thisLoc.getClassName(), thatLoc.getClassName())) {
184                                         return false;
185                                 }
186                                 if (! Util.objsAreEqual(thisLoc.getMethodName(), thatLoc.getMethodName())) {
187                                         return false;
188                                 }
189                         }
190                         
191                         if (! Util.objsAreEqual(this.getVariables(), that.getVariables())) {
192                                 return false;
193                         }
194                         
195                         return true;
196                 }
197                 return false;
198         }
199         
200         @Override
201         public int hashCode() 
202         {
203                 int code = Util.objHashCode(m_type)
204                                  * Util.objHashCode(m_category)
205                                  * Util.objHashCode(getPrincipalLocation())
206                                  * Util.objHashCode(getVariables());
207                 return code;
208         }
209         
210         // Get the "principal" Location.
211         // This should be the place where the bug is reported.
212         Location getPrincipalLocation()
213         {
214                 if (null != m_locations && m_locations.size() > 0) {
215                         return m_locations.get(0);
216                 }
217                 return null;
218         }
219         
220         private void computeLocations()
221         {
222                 assert(null != m_classes);
223                 assert(null != m_methods);
224                 assert(null != m_lines);
225                 
226                 m_locations.clear();
227                 
228                 /*
229                         Somewhat unfortunate special case.
230                         The primary "location" for a bug instance is split between tags.
231                         Most bugs have a pattern like this:
232                         <BugInstance>
233                                 ...
234                                 <Method>
235                                         <SourceLine .../>
236                                 </Method>
237                                 ...
238                                 <SourceLine .../>
239                         </BugInstance>
240                         
241                         The primary location for a bug is given by the <Method> with no role attribute, 
242                         but the <SourceLine/> inside that method describes the whole range of lines 
243                         covered by that Method, not the spot where the bug is located--that is given 
244                         by the <SourceLine/> that is a direct child fo the <BugInstance/>.
245                  */
246                 
247                 BugMethod primaryMethod = null;
248                 SourceLine primaryLine = null;
249                 
250                 for (BugMethod method : m_methods) {
251                         if (null != method.getRole()) {
252                                 primaryMethod = method;
253                                 break;
254                         }
255                 }
256                 if (m_lines.size() > 0) {
257                         primaryLine = m_lines.get(0);
258                 }
259                 
260                 if ((null != primaryMethod) && (null != primaryLine)) {
261                         m_locations.add(new Location(primaryMethod, primaryLine));
262                 }
263                 
264                 for (BugMethod method : m_methods) {
265                         if (primaryMethod != method) {
266                                 m_locations.add(new Location(method));                          
267                         }
268                 }
269                 for (SourceLine line : m_lines) {
270                         if (primaryLine != line) {
271                                 m_locations.add(new Location(line));
272                         }
273                 }
274                 for (BugClass clazz : m_classes) {
275                         m_locations.add(new Location(clazz));
276                 }
277         }
278 }