001: /*
002: * FindBugs - Find bugs in Java programs
003: * Copyright (C) 2003-2007, University of Maryland
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019:
020: package edu.umd.cs.findbugs;
021:
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.util.Arrays;
025: import java.util.Comparator;
026: import java.util.HashSet;
027: import java.util.LinkedHashSet;
028: import java.util.LinkedList;
029: import java.util.List;
030: import java.util.Set;
031:
032: import org.dom4j.DocumentException;
033:
034: import edu.umd.cs.findbugs.ba.AnalysisContext;
035: import edu.umd.cs.findbugs.ba.ClassNotFoundExceptionParser;
036: import edu.umd.cs.findbugs.ba.MethodUnprofitableException;
037: import edu.umd.cs.findbugs.classfile.ClassDescriptor;
038: import edu.umd.cs.findbugs.classfile.MethodDescriptor;
039:
040: /**
041: * An abstract class which provides much of the functionality
042: * required of all BugReporter objects.
043: */
044: public abstract class AbstractBugReporter implements BugReporter {
045: private static final boolean DEBUG = SystemProperties
046: .getBoolean("abreporter.debug");
047: private static final boolean DEBUG_MISSING_CLASSES = SystemProperties
048: .getBoolean("findbugs.debug.missingclasses");
049:
050: private static class Error {
051: private int sequence;
052: private String message;
053: private Throwable cause;
054:
055: public Error(int sequence, String message) {
056: this (sequence, message, null);
057: }
058:
059: public Error(int sequence, String message, Throwable cause) {
060: this .sequence = sequence;
061: this .message = message;
062: this .cause = cause;
063: }
064:
065: public int getSequence() {
066: return sequence;
067: }
068:
069: public String getMessage() {
070: return message;
071: }
072:
073: public Throwable getCause() {
074: return cause;
075: }
076:
077: @Override
078: public int hashCode() {
079: int hashCode = message.hashCode();
080: if (cause != null) {
081: hashCode += 1009 * cause.hashCode();
082: }
083: return hashCode;
084: }
085:
086: @Override
087: public boolean equals(Object obj) {
088: if (obj == null || obj.getClass() != this .getClass())
089: return false;
090: Error other = (Error) obj;
091: if (!message.equals(other.message))
092: return false;
093: if (this .cause == other.cause)
094: return true;
095: if (this .cause == null || other.cause == null)
096: return false;
097: return this .cause.equals(other.cause);
098: }
099: }
100:
101: private int verbosityLevel = NORMAL;
102: private int priorityThreshold;
103: private boolean analysisUnderway, relaxed;
104: private LinkedHashSet<String> missingClassMessageList = new LinkedHashSet<String>();
105: private Set<Error> errorSet = new HashSet<Error>();
106: private List<BugReporterObserver> observerList = new LinkedList<BugReporterObserver>();
107: private ProjectStats projectStats = new ProjectStats();
108: private int errorCount;
109:
110: public void setErrorVerbosity(int level) {
111: this .verbosityLevel = level;
112: }
113:
114: public void setPriorityThreshold(int threshold) {
115: this .priorityThreshold = threshold;
116: }
117:
118: // Subclasses must override doReportBug(), not this method.
119: public final void reportBug(BugInstance bugInstance) {
120: if (priorityThreshold == 0)
121: throw new IllegalStateException(
122: "Priority threshold not set");
123: if (!analysisUnderway) {
124: if (FindBugsAnalysisFeatures.isRelaxedMode()) {
125: relaxed = true;
126: }
127:
128: analysisUnderway = true;
129: }
130: ClassAnnotation primaryClass = bugInstance.getPrimaryClass();
131: if (primaryClass != null
132: && !AnalysisContext
133: .currentAnalysisContext()
134: .isApplicationClass(primaryClass.getClassName())) {
135: if (DEBUG)
136: System.out
137: .println("AbstractBugReporter: Filtering due to non-primary class");
138: return;
139: }
140: if (bugInstance.getPriority() <= priorityThreshold || relaxed) {
141: doReportBug(bugInstance);
142: } else {
143: if (DEBUG) {
144: System.out
145: .println("AbstractBugReporter: Filtering due to priorityThreshold "
146: + bugInstance.getPriority()
147: + " > "
148: + priorityThreshold);
149: }
150: }
151: }
152:
153: public final void reportBugsFromXml(InputStream in,
154: Project theProject) throws IOException, DocumentException {
155: SortedBugCollection theCollection = new SortedBugCollection();
156: theCollection.readXML(in, theProject);
157: for (BugInstance bug : theCollection.getCollection())
158: doReportBug(bug);
159: }
160:
161: public static String getMissingClassName(ClassNotFoundException ex) {
162: String message = ex.getMessage();
163: if (message == null) {
164: message = "";
165: }
166:
167: // Try to decode the error message by extracting the class name.
168: String className = ClassNotFoundExceptionParser
169: .getMissingClassName(ex);
170: if (className != null) {
171: if (className.indexOf('/') >= 0) {
172: className = className.replace('/', '.');
173: }
174: return className;
175:
176: }
177:
178: // Just return the entire message.
179: // It hopefully will still make sense to the user.
180: return message;
181: }
182:
183: public void reportMissingClass(ClassNotFoundException ex) {
184: if (DEBUG_MISSING_CLASSES) {
185: System.out.println("Missing class: " + ex.toString());
186: ex.printStackTrace(System.out);
187: }
188:
189: if (verbosityLevel == SILENT)
190: return;
191:
192: String message = getMissingClassName(ex);
193:
194: if (message.startsWith("[")) {
195: // Sometimes we see methods called on array classes.
196: // Obviously, these don't exist as class files.
197: // So, we should just ignore the exception.
198: // Really, we should fix the class/method search interfaces
199: // to be much more intelligent in resolving method
200: // implementations.
201: return;
202: }
203:
204: if (message.trim().equals("")) {
205: // Subtypes2 throws ClassNotFoundExceptions with no message in
206: // some cases. Ignore them (the missing classes will already
207: // have been reported).
208: return;
209: }
210:
211: assert message.indexOf('<') == -1 : "message is " + message;
212: logMissingClass(message);
213: }
214:
215: /* (non-Javadoc)
216: * @see edu.umd.cs.findbugs.classfile.IErrorLogger#reportMissingClass(edu.umd.cs.findbugs.classfile.ClassDescriptor)
217: */
218: public void reportMissingClass(ClassDescriptor classDescriptor) {
219: if (DEBUG_MISSING_CLASSES) {
220: System.out.println("Missing class: " + classDescriptor);
221: new Throwable().printStackTrace(System.out);
222: }
223:
224: if (verbosityLevel == SILENT)
225: return;
226:
227: String dottedClassName = classDescriptor.toDottedClassName();
228: if (dottedClassName.charAt(0) == '[')
229: return;
230: if (dottedClassName.endsWith("package-info"))
231: return;
232: logMissingClass(dottedClassName);
233: }
234:
235: /**
236: * @param message
237: */
238: private void logMissingClass(String message) {
239:
240: if (message == null || message.length() == 0) {
241: throw new AssertionError("Bad missing class" + message);
242: }
243:
244: if (message.charAt(0) == '['
245: || message.endsWith(".package-info")) {
246: return;
247: }
248:
249: missingClassMessageList.add(message);
250: }
251:
252: /**
253: * Report that we skipped some analysis of a method
254: * @param method
255: */
256: public void reportSkippedAnalysis(MethodDescriptor method) {
257: // TODO: log this
258: }
259:
260: public void logError(String message) {
261: if (verbosityLevel == SILENT)
262: return;
263:
264: Error error = new Error(errorCount++, message);
265: if (!errorSet.contains(error))
266: errorSet.add(error);
267: }
268:
269: public void logError(String message, Throwable e) {
270:
271: if (e instanceof MethodUnprofitableException) {
272: // TODO: log this
273: return;
274: }
275: if (e instanceof edu.umd.cs.findbugs.classfile.MissingClassException) {
276: edu.umd.cs.findbugs.classfile.MissingClassException e2 = (edu.umd.cs.findbugs.classfile.MissingClassException) e;
277: reportMissingClass(e2.getClassDescriptor());
278: return;
279: }
280: if (e instanceof edu.umd.cs.findbugs.ba.MissingClassException) {
281: // Record the missing class, in case the exception thrower didn't.
282: edu.umd.cs.findbugs.ba.MissingClassException missingClassEx = (edu.umd.cs.findbugs.ba.MissingClassException) e;
283: ClassNotFoundException cnfe = missingClassEx
284: .getClassNotFoundException();
285:
286: reportMissingClass(cnfe);
287: // Don't report dataflow analysis exceptions due to missing classes.
288: // Too much noise.
289: return;
290: }
291:
292: if (verbosityLevel == SILENT)
293: return;
294:
295: Error error = new Error(errorCount++, message, e);
296: if (!errorSet.contains(error))
297: errorSet.add(error);
298: }
299:
300: public void reportQueuedErrors() {
301: // Report unique errors in order of their sequence
302: Error[] errorList = errorSet
303: .toArray(new Error[errorSet.size()]);
304: Arrays.sort(errorList, new Comparator<Error>() {
305: public int compare(Error o1, Error o2) {
306: return o1.getSequence() - o2.getSequence();
307: }
308: });
309: for (Error error : errorList) {
310: reportAnalysisError(new AnalysisError(error.getMessage(),
311: error.getCause()));
312: }
313:
314: for (String aMissingClassMessageList : missingClassMessageList) {
315: reportMissingClass(aMissingClassMessageList);
316: }
317: }
318:
319: public void addObserver(BugReporterObserver observer) {
320: observerList.add(observer);
321: }
322:
323: public ProjectStats getProjectStats() {
324: return projectStats;
325: }
326:
327: /**
328: * This should be called when a bug is reported by a subclass.
329: *
330: * @param bugInstance the bug to inform observers of
331: */
332: protected void notifyObservers(BugInstance bugInstance) {
333: for (BugReporterObserver aObserverList : observerList)
334: aObserverList.reportBug(bugInstance);
335: }
336:
337: /**
338: * Subclasses must override this.
339: * It will be called only for bugs which meet the priority threshold.
340: *
341: * @param bugInstance the bug to report
342: */
343: protected abstract void doReportBug(BugInstance bugInstance);
344:
345: /**
346: * Report a queued error.
347: *
348: * @param error the queued error
349: */
350: public abstract void reportAnalysisError(AnalysisError error);
351:
352: /**
353: * Report a missing class.
354: *
355: * @param string the name of the class
356: */
357: public abstract void reportMissingClass(String string);
358: }
359:
360: // vim:ts=4
|