Coverage Report - net.objectlab.qalab.parser.BuildStatForChartParser
 
Classes in this File Line Coverage Branch Coverage Complexity
BuildStatForChartParser
83%
237/285
90%
46/51
2.341
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 //
 3  
 //                  ObjectLab is sponsoring QALab
 4  
 // 
 5  
 // Based in London, we are world leaders in the design and development 
 6  
 // of bespoke applications for the Securities Financing markets.
 7  
 // 
 8  
 // <a href="http://www.objectlab.co.uk/open">Click here to learn more</a>
 9  
 //           ___  _     _           _   _          _
 10  
 //          / _ \| |__ (_) ___  ___| |_| |    __ _| |__
 11  
 //         | | | | '_ \| |/ _ \/ __| __| |   / _` | '_ \
 12  
 //         | |_| | |_) | |  __/ (__| |_| |__| (_| | |_) |
 13  
 //          \___/|_.__// |\___|\___|\__|_____\__,_|_.__/
 14  
 //                   |__/
 15  
 //
 16  
 //                   http://www.ObjectLab.co.uk
 17  
 // ---------------------------------------------------------------------------
 18  
 //
 19  
 //QALab is released under the GNU General Public License.
 20  
 //
 21  
 //QALab: Collects QA Statistics from your build over time.
 22  
 //2005+, ObjectLab Ltd
 23  
 //
 24  
 //This library is free software; you can redistribute it and/or
 25  
 //modify it under the terms of the GNU General Public
 26  
 //License as published by the Free Software Foundation; either
 27  
 //version 2.1 of the License, or (at your option) any later version.
 28  
 //
 29  
 //This library is distributed in the hope that it will be useful,
 30  
 //but WITHOUT ANY WARRANTY; without even the implied warranty of
 31  
 //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 32  
 //General Public License for more details.
 33  
 //
 34  
 //You should have received a copy of the GNU General Public
 35  
 //License along with this library; if not, write to the Free Software
 36  
 //Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 37  
 //
 38  
 ////////////////////////////////////////////////////////////////////////////////
 39  
 package net.objectlab.qalab.parser;
 40  
 
 41  
 import net.objectlab.qalab.util.QALabTags;
 42  
 import net.objectlab.qalab.util.TaskLogger;
 43  
 import net.objectlab.qalab.util.Util;
 44  
 
 45  
 import org.jfree.chart.ChartColor;
 46  
 import org.jfree.chart.ChartFactory;
 47  
 import org.jfree.chart.ChartUtilities;
 48  
 import org.jfree.chart.JFreeChart;
 49  
 import org.jfree.chart.axis.DateAxis;
 50  
 import org.jfree.chart.plot.XYPlot;
 51  
 import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
 52  
 import org.jfree.chart.renderer.xy.XYItemRenderer;
 53  
 import org.jfree.chart.title.TextTitle;
 54  
 import org.jfree.data.time.FixedMillisecond;
 55  
 import org.jfree.data.time.MovingAverage;
 56  
 import org.jfree.data.time.TimeSeries;
 57  
 import org.jfree.data.time.TimeSeriesCollection;
 58  
 import org.jfree.ui.Align;
 59  
 import org.jfree.ui.HorizontalAlignment;
 60  
 import org.jfree.ui.RectangleEdge;
 61  
 import org.xml.sax.Attributes;
 62  
 import org.xml.sax.SAXException;
 63  
 import org.xml.sax.SAXParseException;
 64  
 import org.xml.sax.helpers.DefaultHandler;
 65  
 
 66  
 import javax.swing.ImageIcon;
 67  
 
 68  
 import java.awt.BasicStroke;
 69  
 import java.awt.Color;
 70  
 import java.awt.Font;
 71  
 import java.awt.GradientPaint;
 72  
 import java.awt.Image;
 73  
 import java.awt.Paint;
 74  
 import java.awt.Stroke;
 75  
 
 76  
 import java.io.File;
 77  
 import java.io.IOException;
 78  
 
 79  
 import java.net.URL;
 80  
 
 81  
 import java.text.ParseException;
 82  
 
 83  
 import java.util.ArrayList;
 84  
 import java.util.Calendar;
 85  
 import java.util.Collections;
 86  
 import java.util.Date;
 87  
 import java.util.HashMap;
 88  
 import java.util.Iterator;
 89  
 import java.util.List;
 90  
 import java.util.Map;
 91  
 
 92  
 /**
 93  
  * This is the parser for generating the charts from qalab.xml.
 94  
  * 
 95  
  * Created by IntelliJ IDEA. User: xhensevalb Date: 09-Feb-2004 Time: 17:14:25
 96  
  * 
 97  
  * @version $Revision: 253 $
 98  
  */
 99  
 public class BuildStatForChartParser extends DefaultHandler {
 100  
     // ~ Static fields/initializers
 101  
     // ------------------------------------------------------------------------
 102  
 
 103  
     private static final int NUM_HOURS_BEFORE_LAST_DATE = -28;
 104  
 
 105  
     private static final long ONE_DAY_IN_MS = 24L * 60L * 60L * 1000L;
 106  
 
 107  
     /** Default Summary Types */
 108  
     private static final String DEFAULT_SUMMARY_TYPES = "checkstyle,pmd,findbugs,simian";
 109  
 
 110  
     /** Default Types */
 111  
     private static final String DEFAULT_TYPES = "checkstyle,pmd,findbugs,simian,cobertura-branch,cobertura-line";
 112  
 
 113  
     /** footer font size. */
 114  
     private static final int DEFAULT_FONT_SIZE = 9;
 115  
 
 116  
     /** Chart parameters for fading the logo in. */
 117  
     private static final float ALPHA = 0.70f;
 118  
 
 119  
     /** Max Widths for chart. */
 120  
     private static final int MAX_WIDTH = 1000;
 121  
 
 122  
     /** Colours per type. */
 123  3
     private static final Map COLOURS = new HashMap();
 124  
 
 125  
     /** Stroke per type. */
 126  3
     private static final Map STROKES = new HashMap();
 127  
 
 128  
     static {
 129  3
         COLOURS.put("findbugs", ChartColor.RED);
 130  3
         COLOURS.put("pmd", new Color(0x55, 0x55, 0xFF));
 131  3
         COLOURS.put("checkstyle", new Color(0x55, 0xFF, 0x55));
 132  3
         COLOURS.put("pmd-cpd", new Color(0xFF, 0x55, 0xFF));
 133  3
         COLOURS.put("simian", ChartColor.GRAY);
 134  3
         COLOURS.put("cobertura-line", ChartColor.PINK);
 135  3
         COLOURS.put("cobertura-branch", ChartColor.DARK_CYAN);
 136  
 
 137  3
         STROKES.put("checkstyle", new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1.0f, new float[] { 2.0f,
 138  
                 6.0f }, 0.0f));
 139  3
         STROKES.put("findbugs", new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1.0f, new float[] { 2.0f,
 140  
                 3.0f }, 1.0f));
 141  3
         STROKES.put("cobertura-branch", new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
 142  3
         STROKES.put("cobertura-line", new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
 143  3
         STROKES.put("simian", new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
 144  3
         STROKES.put("pmd-cpd", new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
 145  3
         STROKES.put("pmd", new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
 146  3
     }
 147  
 
 148  
     // ~ Instance fields
 149  
     // ------------------------------------------------------------------------
 150  
 
 151  
     /** the logger to use. */
 152  
     private TaskLogger parentTask;
 153  
 
 154  
     /** true if the current element is a summary one. */
 155  12
     private boolean currentElementisSummary = false;
 156  
 
 157  
     /** output directory. */
 158  12
     private String toDir = "";
 159  
 
 160  
     /** chart width in pixels. */
 161  
     private int chartWidth;
 162  
 
 163  
     /** chart height. */
 164  
     private int chartHeight;
 165  
 
 166  
     /** moving average for second line, none if <=0. */
 167  12
     private int movingAverage = 0;
 168  
 
 169  
     /** if true, generate only the summary chart. */
 170  12
     private boolean summaryOnly = true;
 171  
 
 172  
     /** current element name. */
 173  
     private String currentName;
 174  
 
 175  
     /** true if current statistics relates to file. */
 176  12
     private boolean fileStat = false;
 177  
 
 178  
     /** series of dates per types. */
 179  12
     private Map dates = new HashMap();
 180  
 
 181  
     /** series of statistics per types. */
 182  12
     private Map errors = new HashMap();
 183  
 
 184  
     /** the accepted styles as a List of Strings. */
 185  12
     private List types = Collections.EMPTY_LIST;
 186  
 
 187  
     /** the accepted styles as a List of Strings. */
 188  12
     private List summaryTypes = Collections.EMPTY_LIST;
 189  
 
 190  
     /** The logo to put in the bottom left corner of charts. */
 191  
     private Image logo;
 192  
 
 193  
     /** if true, no debug output. */
 194  12
     private boolean quiet = true;
 195  
 
 196  
     /** file prefix for the chart names. */
 197  12
     private String filePrefix = "";
 198  
 
 199  
     /** X Axis Title */
 200  12
     private String xAxisTitle = "Dates";
 201  
 
 202  
     /** Y Axis Title */
 203  12
     private String yAxisTitle = "Violations";
 204  
 
 205  
     /** X Axis Title for Summary */
 206  12
     private String xAxisSummaryTitle = "Dates";
 207  
 
 208  
     /** Y Axis Title for Summary */
 209  12
     private String yAxisSummaryTitle = "Violations";
 210  
 
 211  
     // ~ Constructors
 212  
     // ------------------------------------------------------------------------
 213  
 
 214  
     /**
 215  
      * constructor, accepts the logger.
 216  
      * 
 217  
      * @param logger
 218  
      *            the looger for this task, will vary if ant/maven/other.
 219  
      */
 220  12
     public BuildStatForChartParser(final TaskLogger logger) {
 221  12
         parentTask = logger;
 222  12
         setAcceptedStyle(DEFAULT_TYPES);
 223  12
         setSummaryStyle(DEFAULT_SUMMARY_TYPES);
 224  12
     }
 225  
 
 226  
     // ~ Methods
 227  
     // ------------------------------------------------------------------------
 228  
 
 229  
     /**
 230  
      * set the accepted styles for charts, each type will be a line/series on
 231  
      * the chart. Accepted as a comma separated string, eg checkstyle,pmd for
 232  
      * both PMD and Checkstyle.
 233  
      * 
 234  
      * @param acceptedStyleStr
 235  
      *            a comma separated string with styles.
 236  
      */
 237  
     public final void setAcceptedStyle(final String acceptedStyleStr) {
 238  24
         types = Util.listify(acceptedStyleStr, ",");
 239  24
     }
 240  
 
 241  
     /**
 242  
      * set the accepted styles for SUMMARY charts, each type will be a
 243  
      * line/series on the chart. Accepted as a comma separated string, eg
 244  
      * checkstyle,pmd for both PMD and Checkstyle.
 245  
      * 
 246  
      * @param acceptedStyleStr
 247  
      *            a comma separated string with styles.
 248  
      */
 249  
     public final void setSummaryStyle(final String acceptedStyleStr) {
 250  24
         summaryTypes = Util.listify(acceptedStyleStr, ",");
 251  24
         if (!quiet) {
 252  0
             parentTask.log("Summary Styles: " + summaryTypes);
 253  
         }
 254  24
     }
 255  
 
 256  
     //
 257  
     // ContentHandler methods
 258  
     //
 259  
 
 260  
     /**
 261  
      * Start element, called when each new tag is encountered.
 262  
      * 
 263  
      * @param ignoreUri
 264  
      *            ignore, this is due to sax interface.
 265  
      * @param local
 266  
      *            local name of the tag.
 267  
      * @param ignoreRaw
 268  
      *            ignore, this is due to sax interface.
 269  
      * @param attrs
 270  
      *            the element attributes.
 271  
      * @throws SAXException
 272  
      *             any SAX parser exception.
 273  
      */
 274  
     public final void startElement(final String ignoreNamespaceURI, // NOPMD
 275  
             final String localname, final String qualifiedname, final Attributes attrs) throws SAXException {
 276  1248
         String local = localname;
 277  
 
 278  1248
         if ("".equals(local)) {
 279  1248
             local = qualifiedname;
 280  
         }
 281  
 
 282  1248
         if (!currentElementisSummary && QALabTags.SUMMARY_TAG.equals(local)) {
 283  12
             currentElementisSummary = true;
 284  12
             currentName = QALabTags.SUMMARY_TAG;
 285  
         }
 286  
 
 287  1248
         if (!currentElementisSummary && !fileStat && QALabTags.FILE_TAG.equals(local)) {
 288  84
             fileStat = true;
 289  84
             currentName = getFileName(attrs);
 290  84
             dates.clear();
 291  84
             errors.clear();
 292  
         }
 293  
 
 294  1248
         if (currentElementisSummary) {
 295  624
             processSummaryResult(local, attrs);
 296  624
         } else if (!isSummaryOnly() && fileStat) {
 297  306
             processFileResult(local, attrs);
 298  
         }
 299  1248
     }
 300  
 
 301  
     /**
 302  
      * Process a file result.
 303  
      * 
 304  
      * @param local
 305  
      *            name of element.
 306  
      * @param attrs
 307  
      *            attributes of element.
 308  
      */
 309  
     private void processFileResult(final String local, final Attributes attrs) {
 310  306
         if (!QALabTags.RESULT_TAG.equals(local)) {
 311  78
             return;
 312  
         }
 313  
 
 314  228
         processResult(attrs);
 315  228
     }
 316  
 
 317  
     /**
 318  
      * Helper method that add a non-null value to an array in a map for the
 319  
      * given type. The map contains pairs string-array.
 320  
      * 
 321  
      * @param map
 322  
      *            where the item will be stored if val!=null
 323  
      * @param type
 324  
      *            the key in the map
 325  
      * @param val
 326  
      *            the value (to be added to an array).
 327  
      */
 328  
     private void add(final Map map, final String type, final Object val) {
 329  792
         if (val == null) {
 330  0
             return;
 331  
         }
 332  
 
 333  792
         List list = (List) map.get(type);
 334  
 
 335  792
         if (list == null) {
 336  108
             list = new ArrayList();
 337  108
             map.put(type, list);
 338  
         }
 339  
 
 340  792
         list.add(val);
 341  792
     }
 342  
 
 343  
     /**
 344  
      * Extract the file name (id) from an element.
 345  
      * 
 346  
      * @param attrs
 347  
      *            XML attributes
 348  
      * @return null if id attribute is not found.
 349  
      */
 350  
     private String getFileName(final Attributes attrs) {
 351  84
         String fileName = null;
 352  84
         final int attrCount = attrs.getLength();
 353  
 
 354  84
         for (int i = 0; i < attrCount; i++) {
 355  84
             final String attribute = attrs.getQName(i);
 356  
 
 357  84
             if (QALabTags.ID_ATTRIBUTE.equals(attribute)) {
 358  84
                 fileName = attrs.getValue(i).replace('/', '_');
 359  
 
 360  84
                 break;
 361  
             }
 362  
         }
 363  
 
 364  84
         return fileName;
 365  
     }
 366  
 
 367  
     /**
 368  
      * Process a summary result.
 369  
      * 
 370  
      * @param local
 371  
      *            name of element.
 372  
      * @param attrs
 373  
      *            attributes of element.
 374  
      */
 375  
     private void processSummaryResult(final String local, final Attributes attrs) {
 376  624
         if (!QALabTags.SUMMARY_RESULT_TAG.equals(local)) {
 377  12
             return;
 378  
         }
 379  
 
 380  612
         processResult(attrs);
 381  612
     }
 382  
 
 383  
     /**
 384  
      * Both summary and file results are actually quite similar with the
 385  
      * attributes 'type', 'date' and 'statvalue' being extracted for charting.
 386  
      * 
 387  
      * @param attrs
 388  
      *            list of attributes.
 389  
      */
 390  
     private void processResult(final Attributes attrs) {
 391  840
         final int attrCount = attrs.getLength();
 392  840
         Date date = null;
 393  840
         boolean accepted = false;
 394  840
         Integer error = null;
 395  840
         String type = null;
 396  
 
 397  
         // this loop goes through all attributes to extract
 398  
         // the attributes, there is no check that we get a full
 399  
         // set.
 400  3972
         for (int i = 0; i < attrCount; i++) {
 401  3132
             final String attribute = attrs.getQName(i);
 402  
 
 403  3132
             final String value = attrs.getValue(i);
 404  3132
             if (QALabTags.TYPE_ATTRIBUTE.equals(attribute)) {
 405  840
                 type = value;
 406  840
                 if (currentElementisSummary) {
 407  612
                     if (!quiet) {
 408  153
                         parentTask.log("Check that the summary " + value + " is accepted in " + summaryTypes);
 409  
                     }
 410  612
                     accepted = summaryTypes.contains(value);
 411  612
                 } else {
 412  228
                     accepted = types.contains(value);
 413  
                 }
 414  228
             } else if (QALabTags.STATVALUE_ATTRIBUTE.equals(attribute)) {
 415  840
                 error = Integer.valueOf(value);
 416  840
             } else if (QALabTags.DATE_ATTRIBUTE.equals(attribute)) {
 417  
                 try {
 418  840
                     if (value != null && value.length() > QALabTags.DATE_ONLY_SIZE) {
 419  840
                         date = QALabTags.DEFAULT_DATETIME_FORMAT.parse(value);
 420  840
                     } else if (value != null && value.length() == QALabTags.DATE_ONLY_SIZE) {
 421  0
                         date = QALabTags.DEFAULT_DATE_FORMAT.parse(value);
 422  0
                     } else {
 423  0
                         throw new IllegalArgumentException("OUCH -> date size = " + (value != null ? value.length() : 0) + " ["
 424  
                                 + value + "]");
 425  
                     }
 426  0
                 } catch (ParseException e) {
 427  0
                     parentTask.log(e.getMessage());
 428  840
                 }
 429  
             }
 430  
         }
 431  
 
 432  
         // we add only the accepted types.
 433  840
         if (accepted) {
 434  396
             add(dates, type, date);
 435  396
             add(errors, type, error);
 436  
         }
 437  840
     }
 438  
 
 439  
     //
 440  
     // ErrorHandler methods
 441  
     //
 442  
 
 443  
     /**
 444  
      * Warning.
 445  
      * 
 446  
      * @param ex
 447  
      *            the SAX Parser Exception.
 448  
      * @throws SAXException
 449  
      *             any SAX Parser Exception.
 450  
      */
 451  
     public final void warning(final SAXParseException ex) throws SAXException {
 452  0
         printError("Warning", ex);
 453  0
     }
 454  
 
 455  
     /**
 456  
      * Error.
 457  
      * 
 458  
      * @param ex
 459  
      *            the SAX Parser Exception.
 460  
      * @throws SAXException
 461  
      *             any SAX Parser Exception.
 462  
      */
 463  
     public final void error(final SAXParseException ex) throws SAXException {
 464  0
         printError("Error", ex);
 465  0
     }
 466  
 
 467  
     /**
 468  
      * Fatal error.
 469  
      * 
 470  
      * @param ex
 471  
      *            the SAX Parser Exception.
 472  
      * @throws SAXException
 473  
      *             any SAX Parser Exception.
 474  
      */
 475  
     public final void fatalError(final SAXParseException ex) throws SAXException {
 476  0
         printError("Fatal Error", ex);
 477  0
     }
 478  
 
 479  
     /**
 480  
      * Common handling of SAX errors & warnings, log it to the given logger.
 481  
      * 
 482  
      * @param errorType
 483  
      *            warning/error/fatal
 484  
      * @param ex
 485  
      *            the SAX exception
 486  
      */
 487  
     private void printError(final String errorType, final SAXParseException ex) {
 488  0
         final StringBuffer buf = new StringBuffer();
 489  
 
 490  0
         buf.append("[").append(errorType).append("] ");
 491  
 
 492  0
         if (ex == null) {
 493  0
             buf.append("!!!");
 494  
         }
 495  
 
 496  0
         if (ex != null) {
 497  0
             String systemId = ex.getSystemId();
 498  
 
 499  0
             if (systemId != null) {
 500  0
                 final int index = systemId.lastIndexOf('/');
 501  
 
 502  0
                 if (index != -1) {
 503  0
                     systemId = systemId.substring(index + 1);
 504  
                 }
 505  
 
 506  0
                 buf.append(systemId);
 507  
             }
 508  
 
 509  0
             buf.append(':');
 510  0
             buf.append(ex.getLineNumber());
 511  0
             buf.append(':');
 512  0
             buf.append(ex.getColumnNumber());
 513  0
             buf.append(": ");
 514  0
             buf.append(ex.getMessage());
 515  
         }
 516  
 
 517  0
         parentTask.log(buf.toString());
 518  0
     }
 519  
 
 520  
     /**
 521  
      * Called when a closing element tag is encountered, it generates the chart
 522  
      * each time it moves from a file or summary.
 523  
      * 
 524  
      * @param ignoreUri
 525  
      *            ignore, this is due to sax interface.
 526  
      * @param localName
 527  
      *            local name of the tag.
 528  
      * @param ignoreQName
 529  
      *            ignore, this is due to sax interface.
 530  
      * @throws SAXException
 531  
      *             any SAX exceptions.
 532  
      */
 533  
     public final void endElement(final String ignoreNamespaceURI, // NOPMD
 534  
             final String localName, final String qualifiedname) // NOPMD
 535  
             throws SAXException {
 536  1248
         String local = localName;
 537  1248
         if ("".equals(local)) {
 538  1248
             local = qualifiedname;
 539  
         }
 540  1248
         if (currentElementisSummary && QALabTags.SUMMARY_TAG.equals(local)) {
 541  12
             currentElementisSummary = false;
 542  
 
 543  12
             if (!quiet) {
 544  3
                 parentTask.log("Summary Found Dates:" + dates + " / errors " + errors);
 545  
             }
 546  
 
 547  12
             chart("summary", dates, errors, true, true);
 548  12
         } else if (!isSummaryOnly() && fileStat && QALabTags.FILE_TAG.equals(local)) {
 549  78
             fileStat = false;
 550  
 
 551  78
             if (!quiet) {
 552  39
                 parentTask.log("Found " + currentName + " Dates:" + dates + " / errors " + errors);
 553  
             }
 554  
 
 555  78
             chart(currentName, dates, errors, false, false);
 556  
         }
 557  1248
     }
 558  
 
 559  
     /**
 560  
      * Generates the chart using jFreeChart, each type will generate a series.  The chart
 561  
      * will check the LATEST date on record across all types, if the last day of a given type
 562  
      * is AT LEAST 28h before the latest date and there are NO records afterwards, we shall
 563  
      * assume that the statistics have fallen to 0 and draw a line down to 0 for the day after.
 564  
      * 
 565  
      * @param name
 566  
      *            name of the chart either summary or the file name.
 567  
      * @param theDates
 568  
      *            the map of dates available per type.
 569  
      * @param theErrors
 570  
      *            the map of error counts available per type.
 571  
      * @param withAverage
 572  
      *            if true, generate a second series showing. moving average.
 573  
      */
 574  
     private void chart(final String name, final Map theDates, final Map theErrors, final boolean withAverage,
 575  
             final boolean summaryChart) {
 576  
         try {
 577  90
             if (theDates.size() == 0) {
 578  36
                 return;
 579  
             }
 580  
 
 581  54
             List availableTypes = new ArrayList();
 582  54
             final TimeSeriesCollection series = new TimeSeriesCollection();
 583  54
             buildTimeSeries(theDates, theErrors, withAverage, availableTypes, series);
 584  
 
 585  54
             StringBuffer existingTypes = new StringBuffer();
 586  54
             for (Iterator it = availableTypes.iterator(); it.hasNext();) {
 587  54
                 existingTypes.append(it.next()).append(" ");
 588  54
             }
 589  
 
 590  54
             String title = existingTypes.toString() + " Stats " + name;
 591  
 
 592  54
             String yTitle = getYAxisTitle();
 593  54
             String xTitle = getXAxisTitle();
 594  54
             if (summaryChart) {
 595  9
                 yTitle = getYAxisSummaryTitle();
 596  9
                 xTitle = getXAxisSummaryTitle();
 597  
             }
 598  54
             final JFreeChart chart = ChartFactory.createTimeSeriesChart(title, xTitle, yTitle, series, true, false, false);
 599  
 
 600  54
             final TextTitle copyright = new TextTitle("QALab 2004+,ObjectLab.co.uk Generated "
 601  
                     + QALabTags.DEFAULT_DATETIME_FORMAT.format(new Date()), new Font("SansSerif", Font.PLAIN, DEFAULT_FONT_SIZE));
 602  
 
 603  54
             copyright.setPosition(RectangleEdge.BOTTOM);
 604  54
             copyright.setHorizontalAlignment(HorizontalAlignment.RIGHT);
 605  54
             chart.addSubtitle(copyright);
 606  
 
 607  54
             chart.setBackgroundImage(getLogo());
 608  54
             chart.setBackgroundImageAlpha(ALPHA);
 609  54
             chart.setBackgroundImageAlignment(Align.BOTTOM_LEFT);
 610  54
             chart.setBackgroundPaint(new GradientPaint(0, 0, Color.white, 0, MAX_WIDTH, Color.blue));
 611  
 
 612  54
             final XYPlot xyPlot = chart.getXYPlot();
 613  54
             final XYItemRenderer renderer = xyPlot.getRenderer();
 614  54
             if (!withAverage && renderer instanceof StandardXYItemRenderer) {
 615  0
                 final StandardXYItemRenderer rr = (StandardXYItemRenderer) renderer;
 616  
 
 617  0
                 rr.setPlotImages(true);
 618  0
                 rr.setShapesFilled(Boolean.TRUE);
 619  
             }
 620  
 
 621  54
             int index = 0;
 622  108
             for (Iterator it = availableTypes.iterator(); it.hasNext(); index++) {
 623  54
                 final String type = (String) it.next();
 624  
 
 625  54
                 renderer.setSeriesPaint(index, (Paint) COLOURS.get(type));
 626  54
                 Stroke stroke = (Stroke) STROKES.get(type);
 627  54
                 if (stroke != null) {
 628  54
                     renderer.setSeriesStroke(index, stroke);
 629  
                 }
 630  
             }
 631  
 
 632  54
             final DateAxis axis = (DateAxis) xyPlot.getDomainAxis();
 633  
 
 634  54
             axis.setVerticalTickLabels(true);
 635  
 
 636  54
             final File file2 = new File(getToDir() + getFilePrefix() + name + ".png");
 637  
 
 638  54
             ChartUtilities.saveChartAsPNG(file2, chart, getChartWidth(), getChartHeight());
 639  0
         } catch (IOException e) {
 640  0
             parentTask.log(e.toString());
 641  54
         }
 642  54
     }
 643  
 
 644  
     private void buildTimeSeries(final Map theDates, final Map theErrors, final boolean withAverage, 
 645  
             final List availableTypes, final TimeSeriesCollection series) {
 646  54
         final Iterator typeIter = theDates.entrySet().iterator();
 647  54
         final Date max = findMaxDate(theDates);
 648  54
         final Calendar cal = Calendar.getInstance();
 649  54
         cal.setTime(max);
 650  54
         cal.add(Calendar.HOUR_OF_DAY, NUM_HOURS_BEFORE_LAST_DATE); // no stats over the last 28h
 651  54
         final Date zeroDeadline = cal.getTime(); // no stats over the last 28h
 652  
 
 653  108
         while (typeIter.hasNext()) {
 654  54
             final Map.Entry pairTypeDates = (Map.Entry) typeIter.next();
 655  54
             final String type = (String) pairTypeDates.getKey();
 656  54
             availableTypes.add(type);
 657  60
             final TimeSeries ts = new TimeSeries(type, "bla", "bli", FixedMillisecond.class);
 658  
 
 659  54
             final List typeDates = (List) pairTypeDates.getValue();
 660  54
             final List typeErrors = (List) theErrors.get(type);
 661  
 
 662  54
             if ((typeDates != null) && (typeErrors != null)) {
 663  54
                 addDataToTimeSeries(zeroDeadline, ts, typeDates, typeErrors);
 664  
             }
 665  
 
 666  54
             series.addSeries(ts);
 667  
 
 668  54
             if (withAverage && (getMovingAverage() > 0)) {
 669  3
                 series.addSeries(MovingAverage.createPointMovingAverage(ts, type + " " + getMovingAverage()
 670  
                         + "-run Moving Average", getMovingAverage()));
 671  
             }
 672  54
         }
 673  54
     }
 674  
 
 675  
     private void addDataToTimeSeries(final Date zeroDeadline, final TimeSeries ts, final List typeDates, final List typeErrors) {
 676  54
         final Iterator dateIter = typeDates.iterator();
 677  54
         final Iterator errorsIter = typeErrors.iterator();
 678  
 
 679  54
         Date lastDateForThisType = null;
 680  450
         while (dateIter.hasNext() && errorsIter.hasNext()) {
 681  396
             lastDateForThisType = (Date) dateIter.next();
 682  396
             final Integer count = (Integer) errorsIter.next();
 683  
 
 684  396
             ts.addOrUpdate(new FixedMillisecond(lastDateForThisType), count);
 685  396
         }
 686  
 
 687  
         // if no statistics for 36h before the LAST Date, assume it fell to 0.
 688  54
         if (lastDateForThisType != null && zeroDeadline != null && zeroDeadline.after(lastDateForThisType)) {
 689  0
             ts.addOrUpdate(new FixedMillisecond(lastDateForThisType.getTime() + ONE_DAY_IN_MS), 0);
 690  
         }
 691  54
     }
 692  
 
 693  
     private Date findMaxDate(Map theDates) {
 694  54
         Date maxDate = null;
 695  54
         final Iterator typeIter = theDates.entrySet().iterator();
 696  108
         while (typeIter.hasNext()) {
 697  54
             final Map.Entry pairTypeDates = (Map.Entry) typeIter.next();
 698  54
             final List typeDates = (List) pairTypeDates.getValue();
 699  
 
 700  54
             if (typeDates != null) {
 701  54
                 for (Iterator it = typeDates.iterator(); it.hasNext();) {
 702  396
                     Date date = (Date) it.next();
 703  396
                     if (maxDate != null) {
 704  342
                         maxDate = (maxDate.before(date) ? date : maxDate);
 705  342
                     } else {
 706  54
                         maxDate = date;
 707  
                     }
 708  396
                 }
 709  
             }
 710  54
         }
 711  54
         return maxDate;
 712  
     }
 713  
 
 714  
     /**
 715  
      * @return Returns the yAxisSummaryTitle.
 716  
      */
 717  
     public String getYAxisSummaryTitle() {
 718  9
         return yAxisSummaryTitle;
 719  
     }
 720  
 
 721  
     /**
 722  
      * @param axisSummaryTitle
 723  
      *            The yAxisSummaryTitle to set.
 724  
      */
 725  
     public void setYAxisSummaryTitle(String axisSummaryTitle) {
 726  0
         yAxisSummaryTitle = axisSummaryTitle;
 727  0
     }
 728  
 
 729  
     /**
 730  
      * Include a logo for the bottom left corner.
 731  
      * 
 732  
      * @return Image, the logo to include.
 733  
      */
 734  
     private Image getLogo() {
 735  54
         if (logo == null) {
 736  9
             final URL url = BuildStatForChartParser.class.getResource("/net/objectlab/qalab/objectlab.gif");
 737  
 
 738  
             // use ImageIcon because it waits for the image to load...
 739  9
             final ImageIcon temp = new ImageIcon(url);
 740  
 
 741  9
             logo = temp.getImage();
 742  
         }
 743  
 
 744  54
         return logo;
 745  
     }
 746  
 
 747  
     /**
 748  
      * @return chart width in pixels.
 749  
      */
 750  
     public final int getChartWidth() {
 751  54
         return chartWidth;
 752  
     }
 753  
 
 754  
     /**
 755  
      * @param width
 756  
      *            in pixels.
 757  
      */
 758  
     public final void setChartWidth(final int width) {
 759  12
         this.chartWidth = width;
 760  12
     }
 761  
 
 762  
     /**
 763  
      * @return chart height in pixels.
 764  
      */
 765  
     public final int getChartHeight() {
 766  54
         return chartHeight;
 767  
     }
 768  
 
 769  
     /**
 770  
      * @param height
 771  
      *            in pixels.
 772  
      */
 773  
     public final void setChartHeight(final int height) {
 774  12
         this.chartHeight = height;
 775  12
     }
 776  
 
 777  
     /**
 778  
      * @return output directory.
 779  
      */
 780  
     public final String getToDir() {
 781  54
         return toDir;
 782  
     }
 783  
 
 784  
     /**
 785  
      * @param outputDirectory
 786  
      *            output directory.
 787  
      */
 788  
     public final void setToDir(final String outputDirectory) {
 789  12
         this.toDir = outputDirectory;
 790  12
     }
 791  
 
 792  
     /**
 793  
      * @return number of point to take into account for moving average.
 794  
      */
 795  
     public final int getMovingAverage() {
 796  15
         return movingAverage;
 797  
     }
 798  
 
 799  
     /**
 800  
      * @param average
 801  
      *            number of points to take into account in moving average, no
 802  
      *            moving average if <= 0.
 803  
      */
 804  
     public final void setMovingAverage(final int average) {
 805  15
         this.movingAverage = average;
 806  15
     }
 807  
 
 808  
     /**
 809  
      * @return Returns the xAxisSummaryTitle.
 810  
      */
 811  
     public String getXAxisSummaryTitle() {
 812  9
         return xAxisSummaryTitle;
 813  
     }
 814  
 
 815  
     /**
 816  
      * @param axisSummaryTitle
 817  
      *            The xAxisSummaryTitle to set.
 818  
      */
 819  
     public void setXAxisSummaryTitle(String axisSummaryTitle) {
 820  0
         xAxisSummaryTitle = axisSummaryTitle;
 821  0
     }
 822  
 
 823  
     /**
 824  
      * @return true if only summary chart required.
 825  
      */
 826  
     public final boolean isSummaryOnly() {
 827  1860
         return summaryOnly;
 828  
     }
 829  
 
 830  
     /**
 831  
      * @param summary
 832  
      *            true if we require only summary chart.
 833  
      */
 834  
     public final void setSummaryOnly(final boolean summary) {
 835  9
         this.summaryOnly = summary;
 836  9
     }
 837  
 
 838  
     /**
 839  
      * @return true if no debug log required.
 840  
      */
 841  
     public final boolean isQuiet() {
 842  0
         return quiet;
 843  
     }
 844  
 
 845  
     /**
 846  
      * @param noLog
 847  
      *            true if no debug log required.
 848  
      */
 849  
     public final void setQuiet(final boolean noLog) {
 850  15
         this.quiet = noLog;
 851  15
     }
 852  
 
 853  
     /**
 854  
      * @return Returns the filePrefix.
 855  
      */
 856  
     public String getFilePrefix() {
 857  54
         if (filePrefix == null) {
 858  0
             return "";
 859  
         } else {
 860  54
             return filePrefix;
 861  
         }
 862  
     }
 863  
 
 864  
     /**
 865  
      * @param filePrefix
 866  
      *            The filePrefix to set.
 867  
      */
 868  
     public void setFilePrefix(String filePrefix) {
 869  12
         this.filePrefix = filePrefix;
 870  12
     }
 871  
 
 872  
     /**
 873  
      * @return Returns the xAxisTitle.
 874  
      */
 875  
     public String getXAxisTitle() {
 876  54
         return xAxisTitle;
 877  
     }
 878  
 
 879  
     /**
 880  
      * @param axisTitle
 881  
      *            The xAxisTitle to set.
 882  
      */
 883  
     public void setXAxisTitle(String axisTitle) {
 884  0
         xAxisTitle = axisTitle;
 885  0
     }
 886  
 
 887  
     /**
 888  
      * @return Returns the yAxisTitle.
 889  
      */
 890  
     public String getYAxisTitle() {
 891  54
         return yAxisTitle;
 892  
     }
 893  
 
 894  
     /**
 895  
      * @param axisTitle
 896  
      *            The yAxisTitle to set.
 897  
      */
 898  
     public void setYAxisTitle(String axisTitle) {
 899  0
         yAxisTitle = axisTitle;
 900  0
     }
 901  
 }
 902  
 /*
 903  
  *                   ObjectLab is sponsoring QALab
 904  
  * 
 905  
  * Based in London, we are world leaders in the design and development 
 906  
  * of bespoke applications for the securities financing markets.
 907  
  * 
 908  
  * <a href="http://www.objectlab.co.uk/open">Click here to learn more about us</a>
 909  
  *           ___  _     _           _   _          _
 910  
  *          / _ \| |__ (_) ___  ___| |_| |    __ _| |__
 911  
  *         | | | | '_ \| |/ _ \/ __| __| |   / _` | '_ \
 912  
  *         | |_| | |_) | |  __/ (__| |_| |__| (_| | |_) |
 913  
  *          \___/|_.__// |\___|\___|\__|_____\__,_|_.__/
 914  
  *                   |__/
 915  
  *
 916  
  *                     www.ObjectLab.co.uk
 917  
  */