View Javadoc

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     private static final Map COLOURS = new HashMap();
124 
125     /** Stroke per type. */
126     private static final Map STROKES = new HashMap();
127 
128     static {
129         COLOURS.put("findbugs", ChartColor.RED);
130         COLOURS.put("pmd", new Color(0x55, 0x55, 0xFF));
131         COLOURS.put("checkstyle", new Color(0x55, 0xFF, 0x55));
132         COLOURS.put("pmd-cpd", new Color(0xFF, 0x55, 0xFF));
133         COLOURS.put("simian", ChartColor.GRAY);
134         COLOURS.put("cobertura-line", ChartColor.PINK);
135         COLOURS.put("cobertura-branch", ChartColor.DARK_CYAN);
136 
137         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         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         STROKES.put("cobertura-branch", new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
142         STROKES.put("cobertura-line", new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
143         STROKES.put("simian", new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
144         STROKES.put("pmd-cpd", new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
145         STROKES.put("pmd", new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
146     }
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     private boolean currentElementisSummary = false;
156 
157     /** output directory. */
158     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     private int movingAverage = 0;
168 
169     /** if true, generate only the summary chart. */
170     private boolean summaryOnly = true;
171 
172     /** current element name. */
173     private String currentName;
174 
175     /** true if current statistics relates to file. */
176     private boolean fileStat = false;
177 
178     /** series of dates per types. */
179     private Map dates = new HashMap();
180 
181     /** series of statistics per types. */
182     private Map errors = new HashMap();
183 
184     /** the accepted styles as a List of Strings. */
185     private List types = Collections.EMPTY_LIST;
186 
187     /** the accepted styles as a List of Strings. */
188     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     private boolean quiet = true;
195 
196     /** file prefix for the chart names. */
197     private String filePrefix = "";
198 
199     /** X Axis Title */
200     private String xAxisTitle = "Dates";
201 
202     /** Y Axis Title */
203     private String yAxisTitle = "Violations";
204 
205     /** X Axis Title for Summary */
206     private String xAxisSummaryTitle = "Dates";
207 
208     /** Y Axis Title for Summary */
209     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     public BuildStatForChartParser(final TaskLogger logger) {
221         parentTask = logger;
222         setAcceptedStyle(DEFAULT_TYPES);
223         setSummaryStyle(DEFAULT_SUMMARY_TYPES);
224     }
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         types = Util.listify(acceptedStyleStr, ",");
239     }
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         summaryTypes = Util.listify(acceptedStyleStr, ",");
251         if (!quiet) {
252             parentTask.log("Summary Styles: " + summaryTypes);
253         }
254     }
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         String local = localname;
277 
278         if ("".equals(local)) {
279             local = qualifiedname;
280         }
281 
282         if (!currentElementisSummary && QALabTags.SUMMARY_TAG.equals(local)) {
283             currentElementisSummary = true;
284             currentName = QALabTags.SUMMARY_TAG;
285         }
286 
287         if (!currentElementisSummary && !fileStat && QALabTags.FILE_TAG.equals(local)) {
288             fileStat = true;
289             currentName = getFileName(attrs);
290             dates.clear();
291             errors.clear();
292         }
293 
294         if (currentElementisSummary) {
295             processSummaryResult(local, attrs);
296         } else if (!isSummaryOnly() && fileStat) {
297             processFileResult(local, attrs);
298         }
299     }
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         if (!QALabTags.RESULT_TAG.equals(local)) {
311             return;
312         }
313 
314         processResult(attrs);
315     }
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         if (val == null) {
330             return;
331         }
332 
333         List list = (List) map.get(type);
334 
335         if (list == null) {
336             list = new ArrayList();
337             map.put(type, list);
338         }
339 
340         list.add(val);
341     }
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         String fileName = null;
352         final int attrCount = attrs.getLength();
353 
354         for (int i = 0; i < attrCount; i++) {
355             final String attribute = attrs.getQName(i);
356 
357             if (QALabTags.ID_ATTRIBUTE.equals(attribute)) {
358                 fileName = attrs.getValue(i).replace('/', '_');
359 
360                 break;
361             }
362         }
363 
364         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         if (!QALabTags.SUMMARY_RESULT_TAG.equals(local)) {
377             return;
378         }
379 
380         processResult(attrs);
381     }
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         final int attrCount = attrs.getLength();
392         Date date = null;
393         boolean accepted = false;
394         Integer error = null;
395         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         for (int i = 0; i < attrCount; i++) {
401             final String attribute = attrs.getQName(i);
402 
403             final String value = attrs.getValue(i);
404             if (QALabTags.TYPE_ATTRIBUTE.equals(attribute)) {
405                 type = value;
406                 if (currentElementisSummary) {
407                     if (!quiet) {
408                         parentTask.log("Check that the summary " + value + " is accepted in " + summaryTypes);
409                     }
410                     accepted = summaryTypes.contains(value);
411                 } else {
412                     accepted = types.contains(value);
413                 }
414             } else if (QALabTags.STATVALUE_ATTRIBUTE.equals(attribute)) {
415                 error = Integer.valueOf(value);
416             } else if (QALabTags.DATE_ATTRIBUTE.equals(attribute)) {
417                 try {
418                     if (value != null && value.length() > QALabTags.DATE_ONLY_SIZE) {
419                         date = QALabTags.DEFAULT_DATETIME_FORMAT.parse(value);
420                     } else if (value != null && value.length() == QALabTags.DATE_ONLY_SIZE) {
421                         date = QALabTags.DEFAULT_DATE_FORMAT.parse(value);
422                     } else {
423                         throw new IllegalArgumentException("OUCH -> date size = " + (value != null ? value.length() : 0) + " ["
424                                 + value + "]");
425                     }
426                 } catch (ParseException e) {
427                     parentTask.log(e.getMessage());
428                 }
429             }
430         }
431 
432         // we add only the accepted types.
433         if (accepted) {
434             add(dates, type, date);
435             add(errors, type, error);
436         }
437     }
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         printError("Warning", ex);
453     }
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         printError("Error", ex);
465     }
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         printError("Fatal Error", ex);
477     }
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         final StringBuffer buf = new StringBuffer();
489 
490         buf.append("[").append(errorType).append("] ");
491 
492         if (ex == null) {
493             buf.append("!!!");
494         }
495 
496         if (ex != null) {
497             String systemId = ex.getSystemId();
498 
499             if (systemId != null) {
500                 final int index = systemId.lastIndexOf('/');
501 
502                 if (index != -1) {
503                     systemId = systemId.substring(index + 1);
504                 }
505 
506                 buf.append(systemId);
507             }
508 
509             buf.append(':');
510             buf.append(ex.getLineNumber());
511             buf.append(':');
512             buf.append(ex.getColumnNumber());
513             buf.append(": ");
514             buf.append(ex.getMessage());
515         }
516 
517         parentTask.log(buf.toString());
518     }
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         String local = localName;
537         if ("".equals(local)) {
538             local = qualifiedname;
539         }
540         if (currentElementisSummary && QALabTags.SUMMARY_TAG.equals(local)) {
541             currentElementisSummary = false;
542 
543             if (!quiet) {
544                 parentTask.log("Summary Found Dates:" + dates + " / errors " + errors);
545             }
546 
547             chart("summary", dates, errors, true, true);
548         } else if (!isSummaryOnly() && fileStat && QALabTags.FILE_TAG.equals(local)) {
549             fileStat = false;
550 
551             if (!quiet) {
552                 parentTask.log("Found " + currentName + " Dates:" + dates + " / errors " + errors);
553             }
554 
555             chart(currentName, dates, errors, false, false);
556         }
557     }
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             if (theDates.size() == 0) {
578                 return;
579             }
580 
581             List availableTypes = new ArrayList();
582             final TimeSeriesCollection series = new TimeSeriesCollection();
583             buildTimeSeries(theDates, theErrors, withAverage, availableTypes, series);
584 
585             StringBuffer existingTypes = new StringBuffer();
586             for (Iterator it = availableTypes.iterator(); it.hasNext();) {
587                 existingTypes.append(it.next()).append(" ");
588             }
589 
590             String title = existingTypes.toString() + " Stats " + name;
591 
592             String yTitle = getYAxisTitle();
593             String xTitle = getXAxisTitle();
594             if (summaryChart) {
595                 yTitle = getYAxisSummaryTitle();
596                 xTitle = getXAxisSummaryTitle();
597             }
598             final JFreeChart chart = ChartFactory.createTimeSeriesChart(title, xTitle, yTitle, series, true, false, false);
599 
600             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             copyright.setPosition(RectangleEdge.BOTTOM);
604             copyright.setHorizontalAlignment(HorizontalAlignment.RIGHT);
605             chart.addSubtitle(copyright);
606 
607             chart.setBackgroundImage(getLogo());
608             chart.setBackgroundImageAlpha(ALPHA);
609             chart.setBackgroundImageAlignment(Align.BOTTOM_LEFT);
610             chart.setBackgroundPaint(new GradientPaint(0, 0, Color.white, 0, MAX_WIDTH, Color.blue));
611 
612             final XYPlot xyPlot = chart.getXYPlot();
613             final XYItemRenderer renderer = xyPlot.getRenderer();
614             if (!withAverage && renderer instanceof StandardXYItemRenderer) {
615                 final StandardXYItemRenderer rr = (StandardXYItemRenderer) renderer;
616 
617                 rr.setPlotImages(true);
618                 rr.setShapesFilled(Boolean.TRUE);
619             }
620 
621             int index = 0;
622             for (Iterator it = availableTypes.iterator(); it.hasNext(); index++) {
623                 final String type = (String) it.next();
624 
625                 renderer.setSeriesPaint(index, (Paint) COLOURS.get(type));
626                 Stroke stroke = (Stroke) STROKES.get(type);
627                 if (stroke != null) {
628                     renderer.setSeriesStroke(index, stroke);
629                 }
630             }
631 
632             final DateAxis axis = (DateAxis) xyPlot.getDomainAxis();
633 
634             axis.setVerticalTickLabels(true);
635 
636             final File file2 = new File(getToDir() + getFilePrefix() + name + ".png");
637 
638             ChartUtilities.saveChartAsPNG(file2, chart, getChartWidth(), getChartHeight());
639         } catch (IOException e) {
640             parentTask.log(e.toString());
641         }
642     }
643 
644     private void buildTimeSeries(final Map theDates, final Map theErrors, final boolean withAverage, 
645             final List availableTypes, final TimeSeriesCollection series) {
646         final Iterator typeIter = theDates.entrySet().iterator();
647         final Date max = findMaxDate(theDates);
648         final Calendar cal = Calendar.getInstance();
649         cal.setTime(max);
650         cal.add(Calendar.HOUR_OF_DAY, NUM_HOURS_BEFORE_LAST_DATE); // no stats over the last 28h
651         final Date zeroDeadline = cal.getTime(); // no stats over the last 28h
652 
653         while (typeIter.hasNext()) {
654             final Map.Entry pairTypeDates = (Map.Entry) typeIter.next();
655             final String type = (String) pairTypeDates.getKey();
656             availableTypes.add(type);
657             final TimeSeries ts = new TimeSeries(type, "bla", "bli", FixedMillisecond.class);
658 
659             final List typeDates = (List) pairTypeDates.getValue();
660             final List typeErrors = (List) theErrors.get(type);
661 
662             if ((typeDates != null) && (typeErrors != null)) {
663                 addDataToTimeSeries(zeroDeadline, ts, typeDates, typeErrors);
664             }
665 
666             series.addSeries(ts);
667 
668             if (withAverage && (getMovingAverage() > 0)) {
669                 series.addSeries(MovingAverage.createPointMovingAverage(ts, type + " " + getMovingAverage()
670                         + "-run Moving Average", getMovingAverage()));
671             }
672         }
673     }
674 
675     private void addDataToTimeSeries(final Date zeroDeadline, final TimeSeries ts, final List typeDates, final List typeErrors) {
676         final Iterator dateIter = typeDates.iterator();
677         final Iterator errorsIter = typeErrors.iterator();
678 
679         Date lastDateForThisType = null;
680         while (dateIter.hasNext() && errorsIter.hasNext()) {
681             lastDateForThisType = (Date) dateIter.next();
682             final Integer count = (Integer) errorsIter.next();
683 
684             ts.addOrUpdate(new FixedMillisecond(lastDateForThisType), count);
685         }
686 
687         // if no statistics for 36h before the LAST Date, assume it fell to 0.
688         if (lastDateForThisType != null && zeroDeadline != null && zeroDeadline.after(lastDateForThisType)) {
689             ts.addOrUpdate(new FixedMillisecond(lastDateForThisType.getTime() + ONE_DAY_IN_MS), 0);
690         }
691     }
692 
693     private Date findMaxDate(Map theDates) {
694         Date maxDate = null;
695         final Iterator typeIter = theDates.entrySet().iterator();
696         while (typeIter.hasNext()) {
697             final Map.Entry pairTypeDates = (Map.Entry) typeIter.next();
698             final List typeDates = (List) pairTypeDates.getValue();
699 
700             if (typeDates != null) {
701                 for (Iterator it = typeDates.iterator(); it.hasNext();) {
702                     Date date = (Date) it.next();
703                     if (maxDate != null) {
704                         maxDate = (maxDate.before(date) ? date : maxDate);
705                     } else {
706                         maxDate = date;
707                     }
708                 }
709             }
710         }
711         return maxDate;
712     }
713 
714     /**
715      * @return Returns the yAxisSummaryTitle.
716      */
717     public String getYAxisSummaryTitle() {
718         return yAxisSummaryTitle;
719     }
720 
721     /**
722      * @param axisSummaryTitle
723      *            The yAxisSummaryTitle to set.
724      */
725     public void setYAxisSummaryTitle(String axisSummaryTitle) {
726         yAxisSummaryTitle = axisSummaryTitle;
727     }
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         if (logo == null) {
736             final URL url = BuildStatForChartParser.class.getResource("/net/objectlab/qalab/objectlab.gif");
737 
738             // use ImageIcon because it waits for the image to load...
739             final ImageIcon temp = new ImageIcon(url);
740 
741             logo = temp.getImage();
742         }
743 
744         return logo;
745     }
746 
747     /**
748      * @return chart width in pixels.
749      */
750     public final int getChartWidth() {
751         return chartWidth;
752     }
753 
754     /**
755      * @param width
756      *            in pixels.
757      */
758     public final void setChartWidth(final int width) {
759         this.chartWidth = width;
760     }
761 
762     /**
763      * @return chart height in pixels.
764      */
765     public final int getChartHeight() {
766         return chartHeight;
767     }
768 
769     /**
770      * @param height
771      *            in pixels.
772      */
773     public final void setChartHeight(final int height) {
774         this.chartHeight = height;
775     }
776 
777     /**
778      * @return output directory.
779      */
780     public final String getToDir() {
781         return toDir;
782     }
783 
784     /**
785      * @param outputDirectory
786      *            output directory.
787      */
788     public final void setToDir(final String outputDirectory) {
789         this.toDir = outputDirectory;
790     }
791 
792     /**
793      * @return number of point to take into account for moving average.
794      */
795     public final int getMovingAverage() {
796         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         this.movingAverage = average;
806     }
807 
808     /**
809      * @return Returns the xAxisSummaryTitle.
810      */
811     public String getXAxisSummaryTitle() {
812         return xAxisSummaryTitle;
813     }
814 
815     /**
816      * @param axisSummaryTitle
817      *            The xAxisSummaryTitle to set.
818      */
819     public void setXAxisSummaryTitle(String axisSummaryTitle) {
820         xAxisSummaryTitle = axisSummaryTitle;
821     }
822 
823     /**
824      * @return true if only summary chart required.
825      */
826     public final boolean isSummaryOnly() {
827         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         this.summaryOnly = summary;
836     }
837 
838     /**
839      * @return true if no debug log required.
840      */
841     public final boolean isQuiet() {
842         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         this.quiet = noLog;
851     }
852 
853     /**
854      * @return Returns the filePrefix.
855      */
856     public String getFilePrefix() {
857         if (filePrefix == null) {
858             return "";
859         } else {
860             return filePrefix;
861         }
862     }
863 
864     /**
865      * @param filePrefix
866      *            The filePrefix to set.
867      */
868     public void setFilePrefix(String filePrefix) {
869         this.filePrefix = filePrefix;
870     }
871 
872     /**
873      * @return Returns the xAxisTitle.
874      */
875     public String getXAxisTitle() {
876         return xAxisTitle;
877     }
878 
879     /**
880      * @param axisTitle
881      *            The xAxisTitle to set.
882      */
883     public void setXAxisTitle(String axisTitle) {
884         xAxisTitle = axisTitle;
885     }
886 
887     /**
888      * @return Returns the yAxisTitle.
889      */
890     public String getYAxisTitle() {
891         return yAxisTitle;
892     }
893 
894     /**
895      * @param axisTitle
896      *            The yAxisTitle to set.
897      */
898     public void setYAxisTitle(String axisTitle) {
899         yAxisTitle = axisTitle;
900     }
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  */