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