(Finally) reach the point where we have some useful, if basic, functionality.
[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 && m_locations.size() > 0) {
216                         return m_locations.get(0);
217                 }
218                 return null;
219         }
220         
221         private void computeLocations()
222         {
223                 assert(null != m_classes);
224                 assert(null != m_methods);
225                 assert(null != m_lines);
226                 
227                 m_locations.clear();
228                 
229                 /*
230                         Somewhat unfortunate special case.
231                         The primary "location" for a bug instance is split between tags.
232                         Most bugs have a pattern like this:
233                         <BugInstance>
234                                 ...
235                                 <Method>
236                                         <SourceLine .../>
237                                 </Method>
238                                 ...
239                                 <SourceLine .../>
240                         </BugInstance>
241                         
242                         The primary location for a bug is given by the <Method> with no role attribute, 
243                         but the <SourceLine/> inside that method describes the whole range of lines 
244                         covered by that Method, not the spot where the bug is located--that is given 
245                         by the <SourceLine/> that is a direct child fo the <BugInstance/>.
246                  */
247                 
248                 BugMethod primaryMethod = null;
249                 SourceLine primaryLine = null;
250                 
251                 for (BugMethod method : m_methods) {
252                         if (null != method.getRole()) {
253                                 primaryMethod = method;
254                                 break;
255                         }
256                 }
257                 if (m_lines.size() > 0) {
258                         primaryLine = m_lines.get(0);
259                 }
260                 
261                 if ((null != primaryMethod) && (null != primaryLine)) {
262                         m_locations.add(new Location(primaryMethod, primaryLine));
263                 }
264                 
265                 for (BugMethod method : m_methods) {
266                         if (primaryMethod != method) {
267                                 m_locations.add(new Location(method));                          
268                         }
269                 }
270                 for (SourceLine line : m_lines) {
271                         if (primaryLine != line) {
272                                 m_locations.add(new Location(line));
273                         }
274                 }
275                 for (BugClass clazz : m_classes) {
276                         m_locations.add(new Location(clazz));
277                 }
278         }
279 }