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