001: /*
002: * FindBugs - Find bugs in Java programs
003: * Copyright (C) 2003, Mike Fagan <mfagan@tde.com>
004: * Copyright (C) 2003,2004 University of Maryland
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020:
021: package edu.umd.cs.findbugs;
022:
023: import java.io.ByteArrayInputStream;
024: import java.io.ByteArrayOutputStream;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.io.OutputStream;
028: import java.io.Reader;
029: import java.io.Writer;
030: import java.text.NumberFormat;
031: import java.text.ParseException;
032: import java.text.SimpleDateFormat;
033: import java.util.Collection;
034: import java.util.Date;
035: import java.util.Locale;
036: import java.util.Map;
037: import java.util.SortedMap;
038: import java.util.TreeMap;
039:
040: import javax.xml.transform.Transformer;
041: import javax.xml.transform.TransformerException;
042: import javax.xml.transform.TransformerFactory;
043: import javax.xml.transform.stream.StreamResult;
044: import javax.xml.transform.stream.StreamSource;
045:
046: import edu.umd.cs.findbugs.PackageStats.ClassStats;
047: import edu.umd.cs.findbugs.annotations.CheckForNull;
048: import edu.umd.cs.findbugs.xml.OutputStreamXMLOutput;
049: import edu.umd.cs.findbugs.xml.XMLOutput;
050: import edu.umd.cs.findbugs.xml.XMLWriteable;
051:
052: /**
053: * Statistics resulting from analyzing a project.
054: */
055: public class ProjectStats implements XMLWriteable, Cloneable {
056: private static final String TIMESTAMP_FORMAT = "EEE, d MMM yyyy HH:mm:ss Z";
057: private SortedMap<String, PackageStats> packageStatsMap;
058: private int[] totalErrors = new int[] { 0, 0, 0, 0, 0 };
059: private int totalClasses;
060: private int totalSize;
061: private Date timestamp;
062: private Footprint baseFootprint;
063:
064: @Override
065: public String toString() {
066: StringBuffer buf = new StringBuffer();
067: buf.append(totalClasses).append(" classes: ");
068: for (PackageStats pStats : getPackageStats())
069: for (ClassStats cStats : pStats.getClassStats())
070: buf.append(cStats.getName()).append(" ");
071: return buf.toString();
072: }
073:
074: /**
075: * Constructor. Creates an empty object.
076: */
077: public ProjectStats() {
078: this .packageStatsMap = new TreeMap<String, PackageStats>();
079: this .totalClasses = 0;
080: this .timestamp = new Date();
081: this .baseFootprint = new Footprint();
082: }
083:
084: @Override
085: public Object clone() {
086: try {
087: return super .clone();
088: } catch (CloneNotSupportedException e) {
089: // can't happen
090: throw new AssertionError(e);
091: }
092: }
093:
094: public int getCodeSize() {
095: return totalSize;
096: }
097:
098: public int getTotalBugs() {
099: return totalErrors[0];
100: }
101:
102: public int getBugsOfPriority(int priority) {
103: return totalErrors[priority];
104: }
105:
106: /**
107: * Set the timestamp for this analysis run.
108: *
109: * @param timestamp the time of the analysis run this
110: * ProjectStats represents, as previously
111: * reported by writeXML.
112: */
113: public void setTimestamp(String timestamp) throws ParseException {
114: this .timestamp = new SimpleDateFormat(TIMESTAMP_FORMAT,
115: Locale.ENGLISH).parse(timestamp);
116: }
117:
118: public void setTimestamp(long timestamp) {
119: this .timestamp = new Date(timestamp);
120: }
121:
122: /**
123: * Get the number of classes analyzed.
124: */
125: public int getNumClasses() {
126: return totalClasses;
127: }
128:
129: /**
130: * Report that a class has been analyzed.
131: *
132: * @param className the full name of the class
133: * @param isInterface true if the class is an interface
134: * @param size a normalized class size value;
135: * see detect/FindBugsSummaryStats.
136: * @deprecated Use {@link #addClass(String,String,boolean,int)} instead
137: */
138: public void addClass(String className, boolean isInterface, int size) {
139: addClass(className, null, isInterface, size);
140: }
141:
142: /**
143: * Report that a class has been analyzed.
144: *
145: * @param className the full name of the class
146: * @param sourceFile TODO
147: * @param isInterface true if the class is an interface
148: * @param size a normalized class size value;
149: * see detect/FindBugsSummaryStats.
150: */
151: public void addClass(String className, @CheckForNull
152: String sourceFile, boolean isInterface, int size) {
153: String packageName;
154: int lastDot = className.lastIndexOf('.');
155: if (lastDot < 0)
156: packageName = "";
157: else
158: packageName = className.substring(0, lastDot);
159: PackageStats stat = getPackageStats(packageName);
160: stat.addClass(className, sourceFile, isInterface, size);
161: totalClasses++;
162: totalSize += size;
163: }
164:
165: /**
166: * Report that a class has been analyzed.
167: *
168: * @param className the full name of the class
169: */
170: public ClassStats getClassStats(String className) {
171: String packageName;
172: int lastDot = className.lastIndexOf('.');
173: if (lastDot < 0)
174: packageName = "";
175: else
176: packageName = className.substring(0, lastDot);
177: PackageStats stat = getPackageStats(packageName);
178: return stat.getClassStatsOrNull(className);
179: }
180:
181: /**
182: * Called when a bug is reported.
183: */
184: public void addBug(BugInstance bug) {
185: PackageStats stat = getPackageStats(bug.getPrimaryClass()
186: .getPackageName());
187: stat.addError(bug);
188: ++totalErrors[0];
189: int priority = bug.getPriority();
190: if (priority >= 1) {
191: ++totalErrors[Math.min(priority, totalErrors.length - 1)];
192: }
193: }
194:
195: /**
196: * Clear bug counts
197: */
198: public void clearBugCounts() {
199: for (int i = 0; i < totalErrors.length; i++)
200: totalErrors[i] = 0;
201: for (PackageStats stats : packageStatsMap.values()) {
202: stats.clearBugCounts();
203: }
204: }
205:
206: public void recomputeFromClassStats() {
207: for (int i = 0; i < totalErrors.length; i++)
208: totalErrors[i] = 0;
209: totalSize = 0;
210: for (PackageStats stats : packageStatsMap.values()) {
211: stats.recomputeFromClassStats();
212: totalSize += stats.size();
213: for (int i = 0; i < totalErrors.length; i++)
214: totalErrors[i] += stats.getBugsAtPriority(i);
215: }
216:
217: }
218:
219: /**
220: * Output as XML.
221: */
222: public void writeXML(XMLOutput xmlOutput) throws IOException {
223: xmlOutput.startTag("FindBugsSummary");
224:
225: xmlOutput.addAttribute("timestamp", new SimpleDateFormat(
226: TIMESTAMP_FORMAT, Locale.ENGLISH).format(timestamp));
227: xmlOutput.addAttribute("total_classes", String
228: .valueOf(totalClasses));
229: xmlOutput.addAttribute("total_bugs", String
230: .valueOf(totalErrors[0]));
231: xmlOutput.addAttribute("total_size", String.valueOf(totalSize));
232: xmlOutput.addAttribute("num_packages", String
233: .valueOf(packageStatsMap.size()));
234:
235: Footprint delta = new Footprint(baseFootprint);
236: NumberFormat twoPlaces = NumberFormat
237: .getInstance(Locale.ENGLISH);
238: twoPlaces.setMinimumFractionDigits(2);
239: twoPlaces.setMaximumFractionDigits(2);
240: twoPlaces.setGroupingUsed(false);
241: long cpuTime = delta.getCpuTime(); // nanoseconds
242: if (cpuTime >= 0) {
243: xmlOutput.addAttribute("cpu_seconds", twoPlaces
244: .format(cpuTime / 1000000000.0));
245: }
246: long clockTime = delta.getClockTime(); // milliseconds
247: if (clockTime >= 0) {
248: xmlOutput.addAttribute("clock_seconds", twoPlaces
249: .format(clockTime / 1000.0));
250: }
251: long peakMemory = delta.getPeakMemory(); // bytes
252: if (peakMemory >= 0) {
253: xmlOutput.addAttribute("peak_mbytes", twoPlaces
254: .format(peakMemory / (1024.0 * 1024)));
255: }
256: xmlOutput.addAttribute("alloc_mbytes", twoPlaces.format(Runtime
257: .getRuntime().maxMemory()
258: / (1024.0 * 1024)));
259: long gcTime = delta.getCollectionTime(); // milliseconds
260: if (gcTime >= 0) {
261: xmlOutput.addAttribute("gc_seconds", twoPlaces
262: .format(gcTime / 1000.0));
263: }
264:
265: PackageStats.writeBugPriorities(xmlOutput, totalErrors);
266:
267: xmlOutput.stopTag(false);
268:
269: for (PackageStats stats : packageStatsMap.values()) {
270: stats.writeXML(xmlOutput);
271: }
272:
273: xmlOutput.closeTag("FindBugsSummary");
274: }
275:
276: /**
277: * Report statistics as an XML document to given output stream.
278: */
279: public void reportSummary(OutputStream out) throws IOException {
280: XMLOutput xmlOutput = new OutputStreamXMLOutput(out);
281: writeXML(xmlOutput);
282: xmlOutput.finish();
283: }
284:
285: /**
286: * Transform summary information to HTML.
287: *
288: * @param htmlWriter the Writer to write the HTML output to
289: */
290: public void transformSummaryToHTML(Writer htmlWriter)
291: throws IOException, TransformerException {
292:
293: ByteArrayOutputStream summaryOut = new ByteArrayOutputStream(
294: 8096);
295: reportSummary(summaryOut);
296:
297: StreamSource in = new StreamSource(new ByteArrayInputStream(
298: summaryOut.toByteArray()));
299: StreamResult out = new StreamResult(htmlWriter);
300: InputStream xslInputStream = this .getClass().getClassLoader()
301: .getResourceAsStream("summary.xsl");
302: if (xslInputStream == null)
303: throw new IOException("Could not load summary stylesheet");
304: StreamSource xsl = new StreamSource(xslInputStream);
305:
306: TransformerFactory tf = TransformerFactory.newInstance();
307: Transformer transformer = tf.newTransformer(xsl);
308: transformer.transform(in, out);
309:
310: Reader rdr = in.getReader();
311: if (rdr != null)
312: rdr.close();
313: htmlWriter.close();
314: InputStream is = xsl.getInputStream();
315: if (is != null)
316: is.close();
317: }
318:
319: public Collection<PackageStats> getPackageStats() {
320: return packageStatsMap.values();
321: }
322:
323: private PackageStats getPackageStats(String packageName) {
324: PackageStats stat = packageStatsMap.get(packageName);
325: if (stat == null) {
326: stat = new PackageStats(packageName);
327: packageStatsMap.put(packageName, stat);
328: }
329: return stat;
330: }
331:
332: /**
333: * @param stats2
334: */
335: public void addStats(ProjectStats stats2) {
336: totalSize += stats2.totalSize;
337: totalClasses += stats2.totalClasses;
338: for (int i = 0; i < totalErrors.length; i++)
339: totalErrors[i] += stats2.totalErrors[i];
340:
341: for (Map.Entry<String, PackageStats> entry : stats2.packageStatsMap
342: .entrySet()) {
343: String key = entry.getKey();
344: PackageStats pkgStats2 = entry.getValue();
345: if (packageStatsMap.containsKey(key)) {
346: PackageStats pkgStats = packageStatsMap.get(key);
347: for (ClassStats classStats : pkgStats2.getClassStats()) {
348: pkgStats.addClass(classStats);
349: }
350: } else {
351: packageStatsMap.put(key, pkgStats2);
352: }
353: }
354: }
355: }
|