001: /*
002: * Hammurapi
003: * Automated Java code review system.
004: * Copyright (C) 2004 Hammurapi Group
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program 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
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * URL: http://www.hammurapi.org
021: * e-Mail: support@hammurapi.biz
022: */
023: package org.hammurapi.inspectors.history;
024:
025: import java.io.File;
026: import java.io.IOException;
027: import java.io.InputStream;
028: import java.sql.ResultSet;
029: import java.sql.SQLException;
030: import java.text.MessageFormat;
031: import java.util.ArrayList;
032: import java.util.Collection;
033: import java.util.Date;
034: import java.util.Iterator;
035: import java.util.Properties;
036:
037: import javax.xml.parsers.DocumentBuilderFactory;
038: import javax.xml.parsers.FactoryConfigurationError;
039: import javax.xml.parsers.ParserConfigurationException;
040: import javax.xml.transform.Transformer;
041: import javax.xml.transform.TransformerConfigurationException;
042: import javax.xml.transform.TransformerException;
043: import javax.xml.transform.TransformerFactory;
044: import javax.xml.transform.dom.DOMSource;
045: import javax.xml.transform.stream.StreamResult;
046: import javax.xml.transform.stream.StreamSource;
047:
048: import org.hammurapi.HammurapiException;
049: import org.hammurapi.HammurapiRuntimeException;
050: import org.hammurapi.PersistingInspectorBase;
051: import org.hammurapi.results.AnnotationContext;
052: import org.hammurapi.results.LinkedAnnotation;
053: import org.hammurapi.results.NamedResults;
054: import org.hammurapi.results.ResultsFactory;
055: import org.hammurapi.results.AnnotationContext.FileEntry;
056: import org.hammurapi.results.persistent.jdbc.sql.AggregatedResultsMetricData;
057: import org.hammurapi.results.persistent.jdbc.sql.BasicResultTotal;
058: import org.hammurapi.results.persistent.jdbc.sql.Report;
059: import org.hammurapi.results.persistent.jdbc.sql.ResultsEngine;
060: import org.hammurapi.results.simple.SimpleAggregatedResults;
061: import org.jfree.chart.ChartFactory;
062: import org.jfree.chart.ChartUtilities;
063: import org.jfree.chart.JFreeChart;
064: import org.jfree.chart.plot.PlotOrientation;
065: import org.jfree.data.time.Day;
066: import org.jfree.data.time.TimeSeries;
067: import org.jfree.data.time.TimeSeriesCollection;
068: import org.w3c.dom.Document;
069: import org.w3c.dom.Element;
070:
071: import com.pavelvlasov.config.ConfigurationException;
072: import com.pavelvlasov.config.XmlSource;
073: import com.pavelvlasov.convert.CompositeConverter;
074: import com.pavelvlasov.jsel.Repository;
075: import com.pavelvlasov.sql.DataAccessObject;
076: import com.pavelvlasov.sql.SQLProcessor;
077: import com.pavelvlasov.xml.dom.AbstractDomObject;
078: import com.pavelvlasov.xml.dom.DOMUtils;
079:
080: /**
081: * @author Pavel Vlasov
082: * @version $Revision: 1.8 $
083: */
084: public class HistoryInspector extends PersistingInspectorBase {
085:
086: private XmlSource styleConfig;
087:
088: public HistoryInspector() {
089: styleConfig = new XmlSource("style", this .getClass(), ".xsl");
090: addConfigurator(styleConfig);
091: }
092:
093: public static class JoinedHistoryImplEx extends JoinedHistoryImpl
094: implements DataAccessObject {
095: private SQLProcessor processor;
096:
097: public void toDom(Element holder) {
098: super .toDom(holder);
099: AbstractDomObject.addTextElement(holder, "DPMO", getDPMO());
100: AbstractDomObject.addTextElement(holder, "Sigma",
101: getSigma());
102:
103: try {
104: String description = new HistoryEngine(processor)
105: .getLastDescription(getReportDate());
106: if (description != null) {
107: AbstractDomObject.addTextElement(holder,
108: "Description", description);
109: }
110: } catch (SQLException e) {
111: throw new HammurapiRuntimeException(e);
112: }
113: }
114:
115: public String getDPMO() {
116: if (getReviews() == 0) {
117: return "Not available, no reviews";
118: }
119:
120: return String
121: .valueOf((int) (1000000 * getViolationLevel() / getReviews()))
122: + (getHasWarnings() > 0 ? "*" : "");
123: }
124:
125: public String getSigma() {
126: double p = 1.0 - getViolationLevel() / getReviews();
127: if (getReviews() == 0) {
128: return "No results";
129: } else if (p <= 0) {
130: return "Full incompliance";
131: } else if (p >= 1) {
132: return "Full compliance";
133: } else {
134: return MessageFormat
135: .format("{0,number,#.###}",
136: new Object[] { new Double(
137: SimpleAggregatedResults
138: .normsinv(p) + 1.5) })
139: + (getHasWarnings() > 0 ? "*" : "");
140: }
141: }
142:
143: public JoinedHistoryImplEx() {
144: super ();
145: }
146:
147: public JoinedHistoryImplEx(ResultSet rs) throws SQLException {
148: super (rs);
149: }
150:
151: public void setSQLProcessor(SQLProcessor sqlProcessor) {
152: this .processor = sqlProcessor;
153: }
154: }
155:
156: private static abstract class TimeChartGenerator {
157:
158: abstract Number getValue(JoinedHistoryImplEx jhie);
159:
160: TimeSeries createTimeSeries(Collection series, String title) {
161: TimeSeries timeSeries = new TimeSeries(title);
162: Iterator sit = series.iterator();
163: while (sit.hasNext()) {
164: JoinedHistoryImplEx jhie = (JoinedHistoryImplEx) sit
165: .next();
166: timeSeries.add(new Day(new Date(jhie.getReportDate()
167: .getTime())), getValue(jhie));
168: }
169: return timeSeries;
170: }
171:
172: void createPngChart(String title, TimeSeries timeSeries,
173: File out) throws IOException {
174: TimeSeriesCollection timeDataset = new TimeSeriesCollection(
175: timeSeries);
176:
177: JFreeChart chart = ChartFactory.createTimeSeriesChart(
178: title, // Title
179: "Time", // X-Axis label
180: timeSeries.getName(), // Y-Axis label
181: timeDataset, // Dataset
182: true, // Show legend
183: true, true);
184:
185: ChartUtilities.saveChartAsPNG(out, chart, 500, 300);
186: }
187:
188: void createPngBarChart(String title, TimeSeries timeSeries,
189: File out) throws IOException {
190: TimeSeriesCollection timeDataset = new TimeSeriesCollection(
191: timeSeries);
192:
193: JFreeChart chart = ChartFactory.createXYBarChart(title, // Title
194: "Time", // X-Axis label
195: true, timeSeries.getName(), // Y-Axis label
196: timeDataset, // Dataset
197: PlotOrientation.VERTICAL, true, // Show legend
198: true, true);
199:
200: //System.out.println(out);
201: ChartUtilities.saveChartAsPNG(out, chart, 500, 300);
202: }
203:
204: }
205:
206: public void leave(Repository repository) {
207: getContext().annotate(new LinkedAnnotation() {
208: NamedResults summary = ResultsFactory.getThreadResults();
209: FileEntry rootFile;
210:
211: public String getName() {
212: return "History";
213: }
214:
215: public void render(AnnotationContext context)
216: throws HammurapiException {
217: if (ResultsFactory.getInstance() instanceof org.hammurapi.results.persistent.jdbc.ResultsFactory) {
218: try {
219: SQLProcessor processor = getContext()
220: .getSession().getProcessor();
221: HistoryEngine historyEngine = new HistoryEngine(
222: processor);
223: ResultsEngine resultsEngine = new ResultsEngine(
224: processor);
225: Iterator it = historyEngine
226: .getReportWithoutHistory(
227: summary.getName(),
228: ((org.hammurapi.results.persistent.jdbc.ResultsFactory) ResultsFactory
229: .getInstance())
230: .getReportId())
231: .iterator();
232: while (it.hasNext()) {
233: Report report = (Report) it.next();
234: BasicResultTotal result = resultsEngine
235: .getBasicResultTotal(report
236: .getResultId().intValue());
237: AggregatedResultsMetricData cr = resultsEngine
238: .getAggregatedResultsMetricData(
239: report.getResultId()
240: .intValue(),
241: "Change ratio");
242:
243: historyEngine
244: .insertHistory(
245: report.getId(),
246: result.getCodebase(),
247: (Integer) CompositeConverter
248: .getDefaultConverter()
249: .convert(
250: result
251: .getMaxSeverity(),
252: Integer.class,
253: false),
254: result.getReviews(),
255: result.getViolationLevel(),
256: result.getViolations(),
257: result
258: .getWaivedViolations(),
259: (int) result
260: .getHasWarnings(),
261: cr == null ? -1
262: : cr
263: .getTotalValue()
264: / cr
265: .getMeasurements(),
266: result
267: .getCompilationUnits(),
268: result.getResultDate(),
269: report.getName(),
270: report.getDescription(),
271: new Long(
272: report
273: .getExecutionTime() == null ? 0
274: : report
275: .getExecutionTime()
276: .longValue()));
277: }
278:
279: Document doc = DocumentBuilderFactory
280: .newInstance().newDocumentBuilder()
281: .newDocument();
282: Element root = doc.createElement("history");
283: root.setAttribute("title", summary.getName());
284: doc.appendChild(root);
285: Collection series = new ArrayList();
286: DOMUtils.toDom(historyEngine.getJoinedHistory(
287: summary.getName(), series,
288: JoinedHistoryImplEx.class), root);
289:
290: // Generate charts here
291: if (series.size() > 1) {
292: generateNodesChart(context, root, series);
293: generateFilesChart(context, root, series);
294: generateActivityChart(context, root, series);
295: generateSigmaChart(context, root, series);
296: generateDpmoChart(context, root, series);
297: generateMaxSeverityChart(context, root,
298: series);
299: generateViolationsChart(context, root,
300: series);
301: generateReviewsChart(context, root, series);
302: //generatePerformanceChart(context, root, series);
303: }
304:
305: rootFile = context.getNextFile(context
306: .getExtension());
307:
308: if (context.getExtension().equalsIgnoreCase(
309: ".html")) {
310: TransformerFactory tFactory = TransformerFactory
311: .newInstance();
312: InputStream styleStream = styleConfig
313: .getStream();
314: if (styleStream == null) {
315: throw new HammurapiException(
316: "Stylesheet cannot be loaded");
317: }
318:
319: Transformer transformer = tFactory
320: .newTransformer(new StreamSource(
321: styleStream));
322:
323: DOMSource domSource = new DOMSource(doc);
324: transformer
325: .transform(domSource,
326: new StreamResult(rootFile
327: .getFile()));
328: } else {
329: DOMUtils.serialize(doc, rootFile.getFile());
330: }
331: } catch (ConfigurationException e) {
332: throw new HammurapiException(
333: "Cannot render history annotation, "
334: + e.getMessage(), e);
335: } catch (IOException e) {
336: throw new HammurapiException(
337: "Cannot render history annotation, "
338: + e.getMessage(), e);
339: } catch (SQLException e) {
340: throw new HammurapiException(
341: "Cannot process history annotation, "
342: + e.getMessage(), e);
343: } catch (ParserConfigurationException e) {
344: throw new HammurapiException(
345: "Cannot process history annotation, "
346: + e.getMessage(), e);
347: } catch (FactoryConfigurationError e) {
348: throw new HammurapiException(
349: "Cannot process history annotation, "
350: + e.getMessage(), e);
351: } catch (TransformerConfigurationException e) {
352: throw new HammurapiException(
353: "Cannot process history annotation, "
354: + e.getMessage(), e);
355: } catch (TransformerException e) {
356: throw new HammurapiException(
357: "Cannot process history annotation, "
358: + e.getMessage(), e);
359: }
360: }
361: }
362:
363: /**
364: * @param context
365: * @param root
366: * @param series
367: * @throws HammurapiException
368: * @throws IOException
369: */
370: private void generateNodesChart(AnnotationContext context,
371: Element root, Collection series)
372: throws HammurapiException, IOException {
373: TimeChartGenerator codeBaseChartGenerator = new TimeChartGenerator() {
374: Number getValue(JoinedHistoryImplEx jhie) {
375: return new Long(jhie.getCodebase());
376: }
377: };
378:
379: FileEntry codeBaseNodesChart = context
380: .getNextFile(".png");
381: TimeSeries cbtn = codeBaseChartGenerator
382: .createTimeSeries(series, "Nodes");
383: codeBaseChartGenerator.createPngChart(
384: "Codebase history (nodes)", cbtn,
385: codeBaseNodesChart.getFile());
386: root.setAttribute("nodes-chart", codeBaseNodesChart
387: .getPath());
388: }
389:
390: /**
391: * @param context
392: * @param root
393: * @param series
394: * @throws HammurapiException
395: * @throws IOException
396: */
397: private void generateFilesChart(AnnotationContext context,
398: Element root, Collection series)
399: throws HammurapiException, IOException {
400: TimeChartGenerator codeBaseChartGenerator = new TimeChartGenerator() {
401: Number getValue(JoinedHistoryImplEx jhie) {
402: return new Long(jhie.getCompilationUnits());
403: }
404: };
405:
406: FileEntry codeBaseChart = context.getNextFile(".png");
407: TimeSeries cbtn = codeBaseChartGenerator
408: .createTimeSeries(series, "Files");
409: codeBaseChartGenerator.createPngChart(
410: "Codebase history (files)", cbtn, codeBaseChart
411: .getFile());
412: root.setAttribute("files-chart", codeBaseChart
413: .getPath());
414: }
415:
416: /**
417: * @param context
418: * @param root
419: * @param series
420: * @throws HammurapiException
421: * @throws IOException
422: */
423: private void generateActivityChart(
424: AnnotationContext context, Element root,
425: Collection series) throws HammurapiException,
426: IOException {
427: TimeChartGenerator chartGenerator = new TimeChartGenerator() {
428: Number getValue(JoinedHistoryImplEx jhie) {
429: return new Integer(
430: (int) (jhie.getChangeRatio() * 100));
431: }
432: };
433:
434: FileEntry fileEntry = context.getNextFile(".png");
435: TimeSeries cbtn = chartGenerator.createTimeSeries(
436: series, "Activity (%)");
437: chartGenerator.createPngBarChart("Activity history",
438: cbtn, fileEntry.getFile());
439: root
440: .setAttribute("activity-chart", fileEntry
441: .getPath());
442: }
443:
444: /**
445: * @param context
446: * @param root
447: * @param series
448: * @throws HammurapiException
449: * @throws IOException
450: */
451: private void generateSigmaChart(AnnotationContext context,
452: Element root, Collection series)
453: throws HammurapiException, IOException {
454: TimeChartGenerator chartGenerator = new TimeChartGenerator() {
455: Number getValue(JoinedHistoryImplEx jhie) {
456: String sigma = jhie.getSigma();
457: int idx = sigma.indexOf(' ');
458: try {
459: return new Double(idx == -1 ? sigma : sigma
460: .substring(0, idx));
461: } catch (NumberFormatException e) {
462: return null;
463: }
464: }
465: };
466:
467: FileEntry fileEntry = context.getNextFile(".png");
468: TimeSeries cbtn = chartGenerator.createTimeSeries(
469: series, "Sigma");
470: chartGenerator.createPngChart("Sigma history", cbtn,
471: fileEntry.getFile());
472: root.setAttribute("sigma-chart", fileEntry.getPath());
473: }
474:
475: /**
476: * @param context
477: * @param root
478: * @param series
479: * @throws HammurapiException
480: * @throws IOException
481: */
482: private void generateDpmoChart(AnnotationContext context,
483: Element root, Collection series)
484: throws HammurapiException, IOException {
485: TimeChartGenerator chartGenerator = new TimeChartGenerator() {
486: Number getValue(JoinedHistoryImplEx jhie) {
487: String dpmo = jhie.getDPMO();
488: int idx = dpmo.indexOf(' ');
489: try {
490: return new Long(idx == -1 ? dpmo : dpmo
491: .substring(0, idx));
492: } catch (NumberFormatException e) {
493: return null;
494: }
495: }
496: };
497:
498: FileEntry fileEntry = context.getNextFile(".png");
499: TimeSeries cbtn = chartGenerator.createTimeSeries(
500: series, "DPMO");
501: chartGenerator.createPngChart("DPMO history", cbtn,
502: fileEntry.getFile());
503: root.setAttribute("dpmo-chart", fileEntry.getPath());
504: }
505:
506: /**
507: * @param context
508: * @param root
509: * @param series
510: * @throws HammurapiException
511: * @throws IOException
512: */
513: private void generateViolationsChart(
514: AnnotationContext context, Element root,
515: Collection series) throws HammurapiException,
516: IOException {
517: TimeChartGenerator chartGenerator = new TimeChartGenerator() {
518: Number getValue(JoinedHistoryImplEx jhie) {
519: return new Long(jhie.getViolations());
520: }
521: };
522:
523: FileEntry fileEntry = context.getNextFile(".png");
524: TimeSeries cbtn = chartGenerator.createTimeSeries(
525: series, "Violations");
526: chartGenerator.createPngChart("Violations history",
527: cbtn, fileEntry.getFile());
528: root.setAttribute("violations-chart", fileEntry
529: .getPath());
530: }
531:
532: /**
533: * @param context
534: * @param root
535: * @param series
536: * @throws HammurapiException
537: * @throws IOException
538: */
539: private void generateMaxSeverityChart(
540: AnnotationContext context, Element root,
541: Collection series) throws HammurapiException,
542: IOException {
543: TimeChartGenerator chartGenerator = new TimeChartGenerator() {
544: Number getValue(JoinedHistoryImplEx jhie) {
545: return new Integer(jhie.getMaxSeverity());
546: }
547: };
548:
549: FileEntry fileEntry = context.getNextFile(".png");
550: TimeSeries cbtn = chartGenerator.createTimeSeries(
551: series, "Max severity");
552: chartGenerator.createPngChart("Max severity history",
553: cbtn, fileEntry.getFile());
554: root.setAttribute("max-severity-chart", fileEntry
555: .getPath());
556: }
557:
558: /**
559: * @param context
560: * @param root
561: * @param series
562: * @throws HammurapiException
563: * @throws IOException
564: */
565: private void generateReviewsChart(
566: AnnotationContext context, Element root,
567: Collection series) throws HammurapiException,
568: IOException {
569: TimeChartGenerator chartGenerator = new TimeChartGenerator() {
570: Number getValue(JoinedHistoryImplEx jhie) {
571: return new Long(jhie.getReviews());
572: }
573: };
574:
575: FileEntry fileEntry = context.getNextFile(".png");
576: TimeSeries cbtn = chartGenerator.createTimeSeries(
577: series, "Reviews");
578: chartGenerator.createPngChart("Reviews history", cbtn,
579: fileEntry.getFile());
580: root.setAttribute("reviews-chart", fileEntry.getPath());
581: }
582:
583: public Properties getProperties() {
584: return null;
585: }
586:
587: public String getPath() {
588: return rootFile.getPath();
589: }
590:
591: });
592: }
593: }
|