1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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.xml.sax.Attributes;
46 import org.xml.sax.InputSource;
47 import org.xml.sax.SAXException;
48 import org.xml.sax.helpers.DefaultHandler;
49
50 import javax.xml.parsers.ParserConfigurationException;
51 import javax.xml.parsers.SAXParser;
52 import javax.xml.parsers.SAXParserFactory;
53
54 import java.io.IOException;
55 import java.io.Writer;
56
57 import java.text.ParseException;
58
59 import java.util.Calendar;
60 import java.util.Date;
61 import java.util.HashMap;
62 import java.util.Iterator;
63 import java.util.LinkedList;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Stack;
67
68 /**
69 * A handler class which uses SAX method to parse the XML file from QALab and
70 * extract the files that have seen a change in statistics within the time
71 * window.
72 *
73 * @author Paramjit Rehinsi
74 * @version $Revision: 199 $
75 * @todo add a StartDate for the window
76 * @todo add an EndDate for the Window
77 * @todo simplify the constructor to remove File dependencies.
78 */
79 public class BuildStatMoverHandler extends DefaultHandler {
80 private static final int ONE_DAY = 24;
81
82 /**
83 * default 48h to deduct is weekendAdjustment.
84 */
85 private static final int WEEKEND_SKIP = 48;
86
87
88
89
90 /**
91 * The types to be handled checkstyle|findbugs|pmd|simian.
92 */
93 private List listOfTypes = new LinkedList();
94
95 /**
96 * The logger to use for exceptions and debug information.
97 */
98 private TaskLogger tasklogger;
99
100 /**
101 * The QALab input source.
102 */
103 private InputSource qalabSource;
104
105 /**
106 * the XML writer with the "movers".
107 */
108 private Writer outputWriter;
109
110 /**
111 * A map containing a Stack of items per type.
112 */
113 private Map mapOfTypes = new HashMap();
114
115 /**
116 * list of file results.
117 */
118 private List filebeans = new LinkedList();
119
120 /**
121 * the current file result.
122 */
123 private FileStats fileResult;
124
125 /**
126 * the current file name.
127 */
128 private String filename;
129
130 /**
131 * the current path to file.
132 */
133 private String path;
134
135 /**
136 * cutoff earliest date to consider in the stats.
137 */
138 private Date startDateCutoff;
139
140 /**
141 * cutoff latest date to consider in the stats.
142 */
143 private Date endDateCutoff;
144
145 /**
146 * StartTimeWindow.
147 */
148 private Date startTimeWindow = null;
149
150 /**
151 * End Time Window.
152 */
153 private Date endTimeWindow = null;
154
155 /**
156 * start time offset (from now or endTimeWindow).
157 */
158 private String offsetHours;
159
160 /**
161 * weekendAdjustment, true and an extra 48 will be subtracted to any
162 * datecutoff that fell on a Saturday or Sunday.
163 */
164 private boolean wkendadj = false;
165
166 /**
167 * quiet no debug if true (default).
168 */
169 private boolean quiet = true;
170
171 /** offset in hours to determine if the stat has fallen to 0 over the last n hours
172 * from the endDateCutoff.
173 */
174 private int hoursOffsetForLastRun = ONE_DAY;
175
176
177
178
179 /**
180 * Constructor used by ant Task/Maven.
181 *
182 * @param qaLabSource
183 * the original QALab file.
184 * @param types
185 * the types to extract.
186 * @param outputFile
187 * the output file.
188 * @param weekendAdjustment
189 * true if it should adjust for weekends.
190 * @param log
191 * the logger to use.
192 * @param noDebug
193 * if true, quiet output (no debug info).
194 */
195 public BuildStatMoverHandler(final InputSource qaLabSource,
196 final String types, final Writer outputFile,
197 final boolean weekendAdjustment, final TaskLogger log,
198 final boolean noDebug) {
199 setLogger(log);
200 quiet = noDebug;
201 wkendadj = weekendAdjustment;
202 listOfTypes = Util.listify(types, ",");
203 processTypes(listOfTypes);
204 this.outputWriter = outputFile;
205
206 qalabSource = qaLabSource;
207 }
208
209
210
211
212 /**
213 * Create an instance of the SAX Parser and process the qalab.xml file.
214 *
215 * @throws ParserConfigurationException
216 * cannot create an instance of parser.
217 * @throws SAXException
218 * any SAX issue.
219 * @throws java.io.IOException
220 * any IO/access to file issue.
221 */
222 public final void process() throws ParserConfigurationException,
223 SAXException, IOException {
224 calculateCutoffDateTime();
225
226 SAXParserFactory factory = SAXParserFactory.newInstance();
227 SAXParser saxParser = factory.newSAXParser();
228
229 saxParser.parse(getQalabSource(), this);
230 }
231
232 /**
233 * Process the types, assign a new stack to each type.
234 *
235 * @param listoftypes
236 * list of types to support.
237 */
238 private void processTypes(final List listoftypes) {
239 Iterator i = listoftypes.iterator();
240
241 while (i.hasNext()) {
242 mapOfTypes.put(i.next(), new Stack());
243 }
244 }
245
246 /**
247 * Gets the filename from the id attribute.
248 *
249 * @param attrs
250 * XML attributes.
251 */
252 public final void getFileName(final Attributes attrs) {
253 if (attrs != null) {
254 for (int i = 0; i < attrs.getLength(); i++) {
255 String name = attrs.getQName(i);
256 String value = attrs.getValue(i);
257
258 if (QALabTags.ID_ATTRIBUTE.equals(name)) {
259 filename = value;
260
261 if (!quiet) {
262 tasklogger.log("File:" + filename);
263 }
264 } else if (QALabTags.PATH_ATTRIBUTE.equals(name)) {
265 path = value;
266 }
267 }
268 }
269 }
270
271 /**
272 * Process the attributes of each result and return a SingleStat object
273 * built from the attributes date, type and statvalue.
274 *
275 * @param attrs
276 * XML attributes
277 * @return new SingleStat object built from the attributes.
278 */
279 private SingleStat buildResultBean(final Attributes attrs) {
280 SingleStat singleStat = new SingleStat();
281
282 if (attrs != null) {
283 for (int i = 0; i < attrs.getLength(); i++) {
284 String name = attrs.getQName(i);
285 String value = attrs.getValue(i);
286
287 if (QALabTags.TYPE_ATTRIBUTE.equals(name)) {
288 singleStat.setType(value);
289 } else if (QALabTags.STATVALUE_ATTRIBUTE.equals(name)) {
290 singleStat.setStatValue(Integer.parseInt(value));
291 } else if (QALabTags.DATE_ATTRIBUTE.equals(name)) {
292 try {
293 if (value != null
294 && value.length() > QALabTags.DATE_ONLY_SIZE) {
295 singleStat
296 .setDate(QALabTags.DEFAULT_DATETIME_FORMAT
297 .parse(value));
298 } else if (value != null
299 && value.length() == QALabTags.DATE_ONLY_SIZE) {
300 singleStat.setDate(QALabTags.DEFAULT_DATE_FORMAT
301 .parse(value));
302 }
303 } catch (ParseException e) {
304 tasklogger.log(e.toString());
305 }
306 }
307 }
308 }
309
310 return singleStat;
311 }
312
313
314
315
316
317 /**
318 * At the start of a new element, capture the filename and if the element is
319 * a result one, create a SingleStat to store for the given type.
320 *
321 * @param ignoreNamespaceURI
322 * ignore (present for interface implementation).
323 * @param localname
324 * name of the current element.
325 * @param qualifiedname
326 * element name.
327 * @param attrs
328 * the XML attribute of the current element.
329 * @throws SAXException
330 * any SAX issue
331 */
332 public final void startElement(final String ignoreNamespaceURI,
333 final String localname, final String qualifiedname,
334 final Attributes attrs) throws SAXException {
335 String local = localname;
336 if ("".equals(local)) {
337 local = qualifiedname;
338 }
339
340
341
342
343
344 if (QALabTags.FILE_TAG.equals(local)) {
345 getFileName(attrs);
346 }
347
348 if (QALabTags.RESULT_TAG.equals(local)) {
349 SingleStat resultbean = buildResultBean(attrs);
350 Stack s = (Stack) mapOfTypes.get(resultbean.getType());
351
352
353 if (s != null) {
354 s.push(resultbean);
355 }
356 }
357 }
358
359 /**
360 * At the end of an element, check if it is a file one and add the results
361 * found.
362 *
363 * @param ignoreNamespaceURI
364 * ignore (present for interface implementation).
365 * @param ignoreSimplename
366 * ignore (present for interface implementation).
367 * @param qualifiedname
368 * the name of the element.
369 * @throws SAXException
370 * any SAX issue
371 */
372 public final void endElement(final String ignoreNamespaceURI,
373 final String ignoreSimplename, final String qualifiedname)
374 throws SAXException {
375 if (QALabTags.FILE_TAG.equals(qualifiedname)) {
376 fileResult = new FileStats(mapOfTypes, filename, path,
377 startDateCutoff);
378 fileResult.setEndDateCutoff(endDateCutoff);
379 fileResult.setOffsetForLastRun(hoursOffsetForLastRun);
380 filebeans.add(fileResult);
381 mapOfTypes = new HashMap();
382 processTypes(listOfTypes);
383 }
384 }
385
386 /**
387 * Return the input source to the base stats.
388 *
389 * @return InputSource typically the source to qalab.xml
390 */
391 public final InputSource getQalabSource() {
392 return qalabSource;
393 }
394
395 /**
396 * At the end of parsing the qalab.xml document, generate the up/down
397 * report.
398 *
399 * @throws SAXException
400 * any SAX issue.
401 */
402 public final void endDocument() throws SAXException {
403 try {
404
405 outputWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
406 outputWriter.write(QALabTags.LINE_END);
407 outputWriter.write("<!DOCTYPE moversreport [");
408 outputWriter.write(QALabTags.LINE_END);
409 outputWriter.write("<!ELEMENT moversreport (types,datethreshold,"
410 + "enddatethreshold,daterun,up?,down?)>");
411 outputWriter.write(QALabTags.LINE_END);
412 outputWriter.write("<!ELEMENT types (#PCDATA)>");
413 outputWriter.write(QALabTags.LINE_END);
414 outputWriter.write("<!ELEMENT datethreshold (#PCDATA)>");
415 outputWriter.write(QALabTags.LINE_END);
416 outputWriter.write("<!ELEMENT enddatethreshold (#PCDATA)>");
417 outputWriter.write(QALabTags.LINE_END);
418 outputWriter.write("<!ELEMENT daterun (#PCDATA)>");
419 outputWriter.write(QALabTags.LINE_END);
420 outputWriter.write("<!ELEMENT up (file+)>");
421 outputWriter.write(QALabTags.LINE_END);
422 outputWriter.write("<!ELEMENT down (file+)>");
423 outputWriter.write(QALabTags.LINE_END);
424 outputWriter.write("<!ELEMENT file (diff+)>");
425 outputWriter.write(QALabTags.LINE_END);
426 outputWriter.write("<!ATTLIST file name CDATA #REQUIRED>");
427 outputWriter.write(QALabTags.LINE_END);
428 outputWriter.write("<!ATTLIST file path CDATA #REQUIRED>");
429 outputWriter.write(QALabTags.LINE_END);
430 outputWriter.write("<!ELEMENT diff EMPTY>");
431 outputWriter.write(QALabTags.LINE_END);
432 outputWriter.write("<!ATTLIST diff previousrun CDATA #REQUIRED>");
433 outputWriter.write(QALabTags.LINE_END);
434 outputWriter.write("<!ATTLIST diff currentrun CDATA #REQUIRED>");
435 outputWriter.write(QALabTags.LINE_END);
436 outputWriter.write("<!ATTLIST diff type CDATA #REQUIRED>");
437 outputWriter.write(QALabTags.LINE_END);
438 outputWriter.write("<!ATTLIST diff previouserrors "
439 + "CDATA #REQUIRED>");
440 outputWriter.write(QALabTags.LINE_END);
441 outputWriter.write("<!ATTLIST diff currenterrors CDATA "
442 + "#REQUIRED>");
443 outputWriter.write(QALabTags.LINE_END);
444 outputWriter.write("<!ATTLIST diff diff CDATA #REQUIRED>");
445 outputWriter.write(QALabTags.LINE_END);
446 outputWriter.write("]>");
447 outputWriter.write(QALabTags.LINE_END);
448 outputWriter.write("<moversreport>");
449 outputWriter.write(QALabTags.LINE_END);
450 outputWriter.write(" <types>");
451 outputWriter.write(Util.listToCSVString(listOfTypes));
452 outputWriter.write("</types>");
453 outputWriter.write(QALabTags.LINE_END);
454 outputWriter.write(" <datethreshold>");
455 outputWriter.write(QALabTags.DEFAULT_DATETIME_FORMAT
456 .format(startDateCutoff));
457 outputWriter.write("</datethreshold>");
458 outputWriter.write(QALabTags.LINE_END);
459 outputWriter.write(" <enddatethreshold>");
460 outputWriter.write(QALabTags.DEFAULT_DATETIME_FORMAT
461 .format(endDateCutoff));
462 outputWriter.write("</enddatethreshold>");
463 outputWriter.write(QALabTags.LINE_END);
464 outputWriter.write(" <daterun>");
465 outputWriter.write(QALabTags.DEFAULT_DATETIME_FORMAT
466 .format(new Date()));
467 outputWriter.write("</daterun>");
468 outputWriter.write(QALabTags.LINE_END);
469 outputWriter.write(" <up>");
470 outputWriter.write(QALabTags.LINE_END);
471
472 for (Iterator it = filebeans.iterator(); it.hasNext();) {
473 FileStats fb = (FileStats) it.next();
474
475 fb.constructXML();
476 outputWriter.write(fb.getUpXmlResult());
477 }
478
479 outputWriter.write(" </up>");
480 outputWriter.write(QALabTags.LINE_END);
481 outputWriter.write(" <down>");
482 outputWriter.write(QALabTags.LINE_END);
483
484 for (Iterator it = filebeans.iterator(); it.hasNext();) {
485 FileStats fb = (FileStats) it.next();
486
487 outputWriter.write(fb.getDownXmlResult());
488 }
489
490 outputWriter.write(" </down>");
491 outputWriter.write(QALabTags.LINE_END);
492 outputWriter.write("</moversreport>");
493
494 outputWriter.flush();
495 } catch (IOException e) {
496 tasklogger.log(e.toString());
497 } finally {
498 if (outputWriter != null) {
499 try {
500 outputWriter.close();
501 } catch (IOException e) {
502 tasklogger.log(e.toString());
503 }
504 }
505 }
506 }
507
508 /**
509 * calculates the datecutoff by adding the number of offset hours.
510 *
511 * @param offsetInHours
512 * number of hours from now.
513 */
514 public final void setDatecutoff(final int offsetInHours) {
515 Calendar cal = Calendar.getInstance();
516
517 cal.add(Calendar.HOUR, offsetInHours);
518 startDateCutoff = cal.getTime();
519 }
520
521 /**
522 * Calculate the cutoff date, ie the earliest date for the stat to be taken
523 * into account. extra 48h if original cutoff date falls on a Saturday or
524 * Sunday.
525 */
526 public final void calculateCutoffDateTime() {
527 if (startTimeWindow != null) {
528 startDateCutoff = startTimeWindow;
529 } else {
530
531 int offset = -Integer.parseInt(offsetHours);
532
533 setDatecutoff(offset);
534 }
535
536 if (wkendadj) {
537
538 Calendar cal = Calendar.getInstance();
539
540 cal.setTime(startDateCutoff);
541
542 final int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
543
544 if ((dayOfWeek == Calendar.SUNDAY)
545 || (dayOfWeek == Calendar.SATURDAY)) {
546 cal.add(Calendar.HOUR, -WEEKEND_SKIP);
547 startDateCutoff = cal.getTime();
548 }
549 }
550
551 if (!quiet) {
552 tasklogger.log("Cutoff = " + startDateCutoff);
553 }
554
555 if (endTimeWindow != null) {
556 endDateCutoff = endTimeWindow;
557 } else {
558 endDateCutoff = new Date();
559 }
560 }
561
562 /**
563 * @param log
564 * the logger to use.
565 */
566 private void setLogger(final TaskLogger log) {
567 this.tasklogger = log;
568 }
569
570 /**
571 * @param endTime
572 * The endTimeWindow to set.
573 */
574 public final void setEndTimeWindow(final Date endTime) {
575 this.endTimeWindow = new Date(endTime.getTime());
576 }
577
578 /**
579 * @param startTime
580 * The startTimeWindow to set.
581 */
582 public final void setStartTimeWindow(final Date startTime) {
583 this.startTimeWindow = new Date(startTime.getTime());
584 }
585
586 /**
587 * @param offset
588 * The offsetHours to set.
589 */
590 public final void setOffsetHours(final String offset) {
591 this.offsetHours = offset;
592 }
593
594 /**
595 * @return Returns the offsetHours.
596 */
597 public final String getOffsetHours() {
598 return offsetHours;
599 }
600
601 public int getHoursOffsetForLastRun() {
602 return hoursOffsetForLastRun;
603 }
604
605 public void setHoursOffsetForLastRun(int hoursOffsetForLastRun) {
606 this.hoursOffsetForLastRun = hoursOffsetForLastRun;
607 }
608 }
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624