001: package tide.exttools.findbugs;
002:
003: import tide.editor.MainEditorFrame;
004: import tide.editor.linemessages.*;
005: import java.util.*;
006: import java.io.*;
007: import org.xml.sax.*;
008: import snow.Basics;
009: import tide.utils.SyntaxUtils;
010:
011: /** Initialized by each parsing of an starting BugInstance XML tag
012: Example 1:
013:
014: <BugInstance type="NM_METHOD_NAMING_CONVENTION" priority="2" abbrev="Nm" category="STYLE">
015: <Class classname="SnowMailClient.FileEncryptor.SimpleFileEncryptor"/>
016: <Method classname="SnowMailClient.FileEncryptor.SimpleFileEncryptor" name="SimpleDecryptFileUI" signature="()V">
017: <SourceLine classname="SnowMailClient.FileEncryptor.SimpleFileEncryptor" start="186" end="252" startBytecode="0" endBytecode="364" sourcefile="SimpleFileEncryptor.java"/>
018: </Method>
019: </BugInstance>
020:
021: <BugInstance type="URF_UNREAD_FIELD" priority="2" abbrev="UrF" category="PERFORMANCE">
022: <Class classname="SnowMailClient.GnuPG.model.SignatureVerificationResult"/>
023: <Field classname="SnowMailClient.GnuPG.model.SignatureVerificationResult" name="keyID" signature="Ljava/lang/String;" isStatic="false"/>
024: </BugInstance>
025:
026: */
027: public class BugInstance extends FindBugsTag {
028: public final static boolean debug = false;
029:
030: String priority = "";
031: String category = "";
032: String type = "";
033: String abbrev = ""; // UNREAD !
034: String shortMessage = "";
035: String longMessage = "";
036:
037: private final StringBuilder readChars = new StringBuilder();
038:
039: FindBugsTag.Method actualMethod = null;
040: List<FindBugsTag.Method> methods = new ArrayList<FindBugsTag.Method>();
041:
042: FindBugsTag.Class actualClass = null;
043: List<FindBugsTag.Class> classes = new ArrayList<FindBugsTag.Class>();
044:
045: FindBugsTag.Field actualField = null;
046: List<FindBugsTag.Field> fields = new ArrayList<FindBugsTag.Field>();
047:
048: // Flat, not parsed from the class or fields.
049: // collects here all instances, even within class or methods !
050: // but each sourceline has a reference on the enclosing tag (field, class or method, or buginstance !)
051: FindBugsTag.SourceLine actualSourceLine = null;
052: List<FindBugsTag.SourceLine> sourceLines = new ArrayList<FindBugsTag.SourceLine>();
053:
054: FindBugsTag.Int intRead = null;
055:
056: public BugInstance(String qname, Attributes attrs) {
057: readAttributes(qname, attrs);
058: }
059:
060: /** Style, ...*/
061: public String getCategory() {
062: return category;
063: }
064:
065: /** Full bug name */
066: public String getType() {
067: return type;
068: }
069:
070: public int getPriority() {
071: return Integer.parseInt(priority);
072: }
073:
074: // parser
075: //
076: @Override
077: public void startElement(String namespaceURI, String lName, // local name
078: String qName, // qualified name
079: Attributes attrs) throws SAXException {
080: this .readChars.setLength(0);
081:
082: if (qName.equals("SourceLine")) {
083: actualSourceLine = new FindBugsTag.SourceLine(attrs);
084: if (actualField != null) {
085: actualSourceLine.enclosingTag = actualField;
086: } else if (actualMethod != null) {
087: actualSourceLine.enclosingTag = actualMethod;
088: } else if (actualClass != null) {
089: actualSourceLine.enclosingTag = actualClass;
090: } else {
091: actualSourceLine.enclosingTag = this ;
092: }
093: } else if (qName.equals("Method")) {
094: actualMethod = new FindBugsTag.Method(attrs);
095: } else if (qName.equals("Class")) {
096: actualClass = new FindBugsTag.Class(attrs);
097: } else if (qName.equals("Field")) {
098: actualField = new FindBugsTag.Field(attrs);
099: } else if (qName.equals("Int")) {
100: intRead = new FindBugsTag.Int(attrs);
101: } else if (qName.equals("ShortMessage")) {
102: this .readChars.setLength(0);
103: } else if (qName.equals("LongMessage")) {
104: this .readChars.setLength(0);
105: } else if (qName.equals("LocalVariable")) {
106: } else if (qName.equals("String")) {
107: } else // delegate
108: {
109: if (actualMethod != null) {
110: actualMethod.startElement(namespaceURI, lName, qName,
111: attrs);
112: } else if (actualClass != null) {
113: actualClass.startElement(namespaceURI, lName, qName,
114: attrs);
115: } else if (actualField != null) {
116: actualField.startElement(namespaceURI, lName, qName,
117: attrs);
118: } else {
119: if (debug) {
120: System.out.println("BugInstance.start ? " + qName);
121: }
122: }
123: }
124:
125: //readAttributes(qName, attrs);
126: }
127:
128: public void endElement(String namespaceURI, String sName, // simple name
129: String qName // qualified name
130: ) throws SAXException {
131: if (qName.equals("Method")) {
132: methods.add(actualMethod);
133: actualMethod = null;
134: } else if (qName.equals("Class")) {
135: classes.add(actualClass);
136: actualClass = null;
137: } else if (qName.equals("Field")) {
138: fields.add(actualField);
139: actualField = null;
140: } else if (qName.equals("SourceLine")) {
141: // end of source line... ok
142: sourceLines.add(actualSourceLine);
143: this .actualSourceLine = null;
144: } else if (qName.equals("ShortMessage")) {
145: this .shortMessage = this .getLastReadChars();
146: } else if (qName.equals("LongMessage")) {
147: this .longMessage = this .getLastReadChars();
148: } else if (qName.equals("Int")) {
149: // end of int ok
150: } else if (qName.equals("LocalVariable")) {
151: } else if (qName.equals("String")) {
152: } else {
153: if (debug)
154: System.out.println("BugInstance end ? " + qName);
155: }
156: }
157:
158: public void characters(char[] buf, int offset, int len)
159: throws SAXException {
160: String s = new String(buf, offset, len);
161: readChars.append(s);
162: }
163:
164: private void readAttributes(String parentQname, Attributes attrs) {
165: for (int i = 0; i < attrs.getLength(); i++) {
166: String qname = attrs.getQName(i);
167: String value = attrs.getValue(qname);
168:
169: if (qname.equals("type"))
170: type = value;
171: else if (qname.equals("abbrev"))
172: abbrev = value;
173: else if (qname.equals("priority"))
174: priority = value;
175: else if (qname.equals("category"))
176: category = value;
177: else if (qname.equals("active")) {
178: // ignore
179: } else if (qname.equals("uid")) {
180: }
181: // instanceOccurrenceNum, instanceOccurrenceMax, instanceHash
182: else {
183: if (debug) {
184: System.out
185: .println("BugInstance attrib ? "
186: + parentQname + ": " + qname + "= "
187: + value);
188: }
189: }
190: }
191: }
192:
193: public String getClassName() {
194: for (FindBugsTag.SourceLine sourceLine : sourceLines) {
195: if (sourceLine.className.length() > 0) {
196: return sourceLine.className;
197: }
198: }
199:
200: for (FindBugsTag.Class c : classes) {
201: if (c.className.length() > 0)
202: return c.className;
203: }
204: for (FindBugsTag.Method m : methods) {
205: if (m.classname.length() > 0)
206: return m.classname;
207: }
208: for (FindBugsTag.Field f : fields) {
209: if (f.classname.length() > 0)
210: return f.classname;
211: }
212:
213: return "?";
214: }
215:
216: /** Creates and register a new linemessage for this bug (one instance per source line encountered !)
217: * @param onlyAnalyse "xxx.yyy.C" => only add if the name match !
218: * @return {added, ignored_irrelevant, ignored_because_subanalysis}
219: */
220: public int[] createAndAddLineMessage(
221: final boolean ignoreIrrelevant, final String onlyAnalyse) {
222: int nadded = 0;
223: int ignor_irr = 0;
224: int ignor_sub = 0;
225:
226: String fullBugString = toString().trim();
227: // yes, we use the long message as unique short representation
228: String shortDescr = simplifyLongMessage(longMessage);
229: if (shortDescr.length() == 0)
230: shortDescr = this .shortMessage;
231: shortDescr = SyntaxUtils
232: .makeAllJavaNamesSimpleInText(shortDescr);
233:
234: int prior = 1;
235: try {
236: prior = Integer.parseInt(priority);
237: } catch (NumberFormatException nfe) {
238: Basics.ignore(nfe);
239: }
240:
241: List<FindBugsTag.SourceLine> toShow = getSourceLinesToAssociateWithThisBug();
242: //System.out.println(""+category+" :: "+type);
243: fl: for (FindBugsTag.SourceLine sourceLine : toShow) {
244: FBLineMessage lm = new FBLineMessage(sourceLine.className,
245: fullBugString, sourceLine.startLine, shortDescr,
246: prior, category, type, System.currentTimeMillis());
247:
248: if (ignoreIrrelevant
249: && LineMessagesManager.getInstance()
250: .getIrrelevantCategories().contains(
251: lm.getCategory())) {
252: ignor_irr += toShow.size();
253: break fl; // ignores also the others...
254: }
255:
256: if (onlyAnalyse != null) {
257: if (onlyAnalyse.endsWith(".*")) {
258: // accept only in package TODO
259: if (!sourceLine.className.startsWith(onlyAnalyse
260: .substring(0, onlyAnalyse.length() - 2))) {
261: ignor_sub++;
262: continue fl;
263: }
264: } else if (onlyAnalyse.endsWith(".-")) {
265: // accept recurse
266: if (!sourceLine.className.startsWith(onlyAnalyse
267: .substring(0, onlyAnalyse.length() - 2))) {
268: ignor_sub++;
269: continue;
270: }
271: } else {
272: // accept only exactely this one
273: if (!onlyAnalyse.equals(sourceLine.className)
274: && (!sourceLine.className
275: .startsWith(onlyAnalyse + "$"))
276: && (!sourceLine.className
277: .startsWith(onlyAnalyse + "."))) // and maybe "." ?? for inner classes
278: {
279: //MainEditorFrame.debugOut("Ignore "+sourceLine.className+" != "+onlyAnalyse);
280: ignor_sub++;
281: continue; // NOT return, the other may be ok !
282: }
283: }
284: }
285:
286: LineMessagesManager.getInstance().add(lm);
287: nadded++;
288: }
289:
290: return new int[] { nadded, ignor_irr, ignor_sub };
291: }
292:
293: /** Gives the full message representation, do not parse !
294: */
295: @Override
296: public String toString() {
297: StringBuilder sb = new StringBuilder("\r\n");//"\r\n================\r\n");
298: sb.append("Bug " + type + ", priority=" + priority + ", cat="
299: + category); // +", abb="+abbrev);
300:
301: if (shortMessage.length() > 0
302: && longMessage.indexOf(shortMessage) != -1) // [Nov2006]: don't repeat if contained!
303: {
304: sb.append("\n" + shortMessage);
305: }
306:
307: if (longMessage.length() > 0) {
308: // [nov2006]: simplifies
309: sb.append("\n" + simplifyLongMessage(longMessage));
310: }
311: //sb.append("\r\n");
312:
313: // int value (percent of unsync methods)
314:
315: if (intRead != null) {
316: sb.append("\r\n" + intRead.toString());
317: }
318:
319: sb.append(toStringLinesPositions());
320: return sb.toString();
321:
322: }
323:
324: private String toStringLinesPositions() {
325:
326: StringBuilder sb = new StringBuilder();
327:
328: // exact position in source
329: boolean hasLine = false;
330: boolean hasASourceLineDirectlyAssociatedWithThisBugInstance = false; // a line in this tag parent
331: Set<String> shownLines = new HashSet<String>();
332: for (FindBugsTag.SourceLine sourceLine : sourceLines) {
333: if (sourceLine.startLine >= 0) {
334: hasLine = true;
335: if (sourceLine.enclosingTag instanceof BugInstance) {
336: hasASourceLineDirectlyAssociatedWithThisBugInstance = true;
337: sb.append(sourceLine.toString());
338: shownLines.add(sourceLine.toString());
339: }
340: }
341: }
342:
343: if (hasLine) {
344: if (hasASourceLineDirectlyAssociatedWithThisBugInstance) {
345: // no, don't show other lines.
346: } else {
347: // show only the ones with real useful positions (not -1)
348: for (FindBugsTag.SourceLine sourceLine : sourceLines) {
349: String line = sourceLine.toString();
350: if (sourceLine.startLine >= 0
351: && !shownLines.contains(line)) {
352: sb.append(line);
353: shownLines.add(sourceLine.toString());
354: }
355: }
356: }
357: } else {
358: // show all source lines
359: for (FindBugsTag.SourceLine sourceLine : sourceLines) {
360: String line = sourceLine.toString();
361: if (!shownLines.contains(line)) {
362: sb.append(line);
363: shownLines.add(sourceLine.toString());
364: }
365: }
366:
367: // only show if no source line given.
368: // fields and method are now without line position :-( [April2006]
369: if (sourceLines.isEmpty()) {
370: for (FindBugsTag.Field f : fields) {
371: sb.append("\r\n" + f.toString());
372: }
373:
374: if (fields.isEmpty()) {
375: for (FindBugsTag.Class c : classes) {
376: sb.append("\r\n" + c.toString());
377: }
378: for (FindBugsTag.Method m : methods) {
379: sb.append("\r\n" + m.toString());
380: }
381: }
382: }
383:
384: }
385:
386: return sb.toString();
387: }
388:
389: /** when the bug has an associated source file, named "A.B", remove the "A.B." occurences
390: * to generate smaller messages.
391: */
392: private String simplifyLongMessage(String mess) {
393: String cn = getClassName();
394: if (cn == null || cn.length() == 0)
395: return mess;
396: return mess.replace(cn + ".", "");
397: }
398:
399: /** @return if any, (non -1), return it. null otherwise
400: */
401: private FindBugsTag.SourceLine getExactUniqueSourceLineIfExist() {
402: for (FindBugsTag.SourceLine sourceLine : sourceLines) {
403: if (sourceLine.startLine >= 0) {
404: if (sourceLine.enclosingTag instanceof BugInstance) {
405: return sourceLine;
406: }
407: }
408: }
409:
410: return null;
411: }
412:
413: private List<FindBugsTag.SourceLine> getSourceLinesToAssociateWithThisBug() {
414: List<FindBugsTag.SourceLine> linesToAssociate = new ArrayList<FindBugsTag.SourceLine>();
415:
416: boolean hasLine = false;
417: boolean hasASourceLineDirectlyAssociatedWithThisBugInstance = false; // a line in this tag parent
418: Set<String> shownLines = new HashSet<String>();
419: for (FindBugsTag.SourceLine sourceLine : sourceLines) {
420: if (sourceLine.startLine >= 0) {
421: hasLine = true;
422: if (sourceLine.enclosingTag instanceof BugInstance) {
423: hasASourceLineDirectlyAssociatedWithThisBugInstance = true;
424: shownLines.add(sourceLine.toString());
425: linesToAssociate.add(sourceLine);
426:
427: }
428: }
429: }
430:
431: // we are happy with this.
432: if (hasASourceLineDirectlyAssociatedWithThisBugInstance)
433: return linesToAssociate;
434:
435: if (hasLine) {
436: // show only the ones with real useful positions (not -1)
437: for (FindBugsTag.SourceLine sourceLine : sourceLines) {
438: String line = sourceLine.toString();
439: if (sourceLine.startLine >= 0
440: && !shownLines.contains(line)) {
441: linesToAssociate.add(sourceLine);
442: shownLines.add(sourceLine.toString());
443: }
444: }
445:
446: } else {
447: // show all source lines
448: for (FindBugsTag.SourceLine sourceLine : sourceLines) {
449: String line = sourceLine.toString();
450: if (!shownLines.contains(line)) {
451: linesToAssociate.add(sourceLine);
452: shownLines.add(sourceLine.toString());
453: }
454: }
455: /*
456: // only show if no source line given.
457: // fields and method are now without line position :-( [April2006]
458: if(sourceLines.isEmpty())
459: {
460: for(FindBugsTag.Field f: fields)
461: {
462: sb.append("\r\n"+f.toString());
463: }
464:
465: if(fields.isEmpty())
466: {
467: for(FindBugsTag.Class c: classes)
468: {
469: sb.append("\r\n"+c.toString());
470: }
471: for(FindBugsTag.Method m: methods)
472: {
473: sb.append("\r\n"+m.toString());
474: }
475: }
476: }*/
477:
478: }
479:
480: return linesToAssociate;
481: }
482:
483: /** fetch last read chars and empty the cache.
484: */
485: private String getLastReadChars() {
486: String read = this .readChars.toString();
487: this .readChars.delete(0, readChars.length()); // erase
488: return read;
489: }
490:
491: }
|