1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package net.sourceforge.jhunters.pool.ui;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.StringWriter;
22 import java.nio.file.Files;
23 import java.nio.file.LinkOption;
24 import java.nio.file.Path;
25 import java.nio.file.StandardCopyOption;
26 import java.time.LocalDate;
27 import java.util.Formatter;
28 import java.util.Locale;
29
30 import javax.xml.parsers.DocumentBuilder;
31 import javax.xml.parsers.DocumentBuilderFactory;
32 import javax.xml.parsers.ParserConfigurationException;
33 import javax.xml.transform.Transformer;
34 import javax.xml.transform.TransformerConfigurationException;
35 import javax.xml.transform.TransformerException;
36 import javax.xml.transform.TransformerFactory;
37 import javax.xml.transform.dom.DOMSource;
38 import javax.xml.transform.stream.StreamResult;
39
40 import org.w3c.dom.Document;
41 import org.w3c.dom.Element;
42
43 import net.sourceforge.jhunters.pool.PoolException;
44 import net.sourceforge.jhunters.pool.data.DataParser;
45 import net.sourceforge.jhunters.pool.data.Fixture;
46 import net.sourceforge.jhunters.pool.data.Player;
47 import net.sourceforge.jhunters.pool.data.Result;
48
49
50
51
52 public class ReportBuilder {
53
54
55
56 private static final String JS_TABS = "jtabs.js";
57
58
59
60
61 private static final String CSS_STANDARD = "jhuntersPrint.css";
62
63
64
65
66 private static final String CSS_WEB = "jhuntersWeb.css";
67
68
69
70
71 private static final String ELEMENT_DIV = "div";
72
73
74
75
76 private static final String ATTR_ID = "id";
77
78
79
80
81 private static final String ATTR_CLASS = "class";
82
83
84
85
86 protected static final String ATTR_ROWSPAN = "rowspan";
87
88
89
90
91 protected static final String CLASS_SEPARATOR = "jhp-table-separator";
92
93
94
95
96 private final DocumentBuilder theBuilder;
97
98
99
100
101 private final Transformer theXformer;
102
103
104
105
106 private final Document theDocument;
107
108
109
110
111 private final StringBuilder theStringBuilder;
112
113
114
115
116 private final Formatter theFormatter;
117
118
119
120
121 private final Element theBody;
122
123
124
125
126 private final WebControl theWebControl;
127
128
129
130
131 private Element theSection;
132
133
134
135
136 private Element theTable;
137
138
139
140
141 private Element theTableHdrRow;
142
143
144
145
146 private Element theTableBody;
147
148
149
150
151 private Element theRow;
152
153
154
155
156 private int theRowCount;
157
158
159
160
161 private boolean isReset = true;
162
163
164
165
166
167
168 protected ReportBuilder(final boolean pWeb) throws PoolException {
169
170 try {
171
172 DocumentBuilderFactory myFactory = DocumentBuilderFactory.newInstance();
173 theBuilder = myFactory.newDocumentBuilder();
174 theDocument = theBuilder.newDocument();
175
176
177 TransformerFactory myXformFactory = TransformerFactory.newInstance();
178 theXformer = myXformFactory.newTransformer();
179
180
181 theStringBuilder = new StringBuilder();
182 theFormatter = new Formatter(theStringBuilder, Locale.getDefault());
183
184
185 Element myHTML = theDocument.createElement("html");
186 theDocument.appendChild(myHTML);
187 Element myHead = theDocument.createElement("head");
188 myHTML.appendChild(myHead);
189
190
191 theBody = theDocument.createElement("body");
192 myHTML.appendChild(theBody);
193
194
195 if (pWeb) {
196
197 theWebControl = initWebBuilder(myHead);
198 } else {
199
200 initReportBuilder(myHead);
201 theWebControl = null;
202 }
203
204 } catch (ParserConfigurationException | TransformerConfigurationException e) {
205 throw new PoolException("Failed to initialise report builder", e);
206 }
207 }
208
209
210
211
212
213 private void initReportBuilder(final Element pHeader) {
214
215 setTitle(pHeader, "Test");
216
217
218 setStyleSheet(pHeader, getClass().getResource(CSS_STANDARD).toString());
219
220
221 theSection = theBody;
222 }
223
224
225
226
227
228
229 private WebControl initWebBuilder(final Element pHeader) {
230
231 Element myTitle = setTitle(pHeader, null);
232
233
234 setStyleSheet(pHeader, CSS_STANDARD);
235 setStyleSheet(pHeader, CSS_WEB);
236
237
238 setScript(pHeader, "https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js");
239 setScript(pHeader, JS_TABS);
240
241 return new WebControl(myTitle);
242 }
243
244
245
246
247
248
249
250 private Element setTitle(final Element pHeader,
251 final String pTitle) {
252
253 Element myTitle = theDocument.createElement("title");
254 pHeader.appendChild(myTitle);
255 if (pTitle != null) {
256 myTitle.setTextContent(pTitle);
257 }
258 return myTitle;
259 }
260
261
262
263
264
265
266 private void setStyleSheet(final Element pHeader,
267 final String pStyleSheet) {
268
269 Element myLink = theDocument.createElement("link");
270 pHeader.appendChild(myLink);
271 myLink.setAttribute("REL", "StyleSheet");
272 myLink.setAttribute("HREF", pStyleSheet);
273 myLink.setAttribute("TYPE", "text/css");
274 myLink.setAttribute("MEDIA", "screen, print");
275 }
276
277
278
279
280
281
282 private void setScript(final Element pHeader,
283 final String pScript) {
284
285 Element myScript = theDocument.createElement("script");
286 pHeader.appendChild(myScript);
287 myScript.setAttribute("type", "text/javascript");
288 myScript.setAttribute("src", pScript);
289 }
290
291
292
293
294
295 private void clearElement(final Element pElement) {
296
297 while (pElement.hasChildNodes()) {
298 pElement.removeChild(pElement.getFirstChild());
299 }
300 }
301
302
303
304
305
306 protected boolean isReset() {
307 return isReset;
308 }
309
310
311
312
313 protected void resetDocument() {
314
315 clearElement(theBody);
316 isReset = true;
317 }
318
319
320
321
322
323 protected void setTheTitle(final String pTitle) {
324 theWebControl.setTheTitle(pTitle);
325 }
326
327
328
329
330
331 protected void newSection(final String pTitle) {
332 theWebControl.newSection(pTitle);
333 }
334
335
336
337
338 protected void setPageBreak() {
339
340 closeTable();
341
342
343 Element myDiv = theDocument.createElement(ELEMENT_DIV);
344 myDiv.setAttribute(ATTR_CLASS, "page-break");
345 theSection.appendChild(myDiv);
346 }
347
348
349
350
351 protected void setLineSpace() {
352
353 closeTable();
354
355
356 Element myBreak = theDocument.createElement(ELEMENT_DIV);
357 myBreak.setAttribute(ATTR_CLASS, "line-space");
358 theSection.appendChild(myBreak);
359 }
360
361
362
363
364
365
366
367 protected Element setHeading(final String pHeading,
368 final Object... pArgs) {
369
370 closeTable();
371 isReset = false;
372
373
374 theStringBuilder.setLength(0);
375 theFormatter.format(pHeading, pArgs);
376
377
378 Element myHeading = theDocument.createElement("h2");
379 myHeading.setTextContent(theStringBuilder.toString());
380 theSection.appendChild(myHeading);
381 return myHeading;
382 }
383
384
385
386
387 private void closeTable() {
388
389 theTable = null;
390 theTableHdrRow = null;
391 theTableBody = null;
392 theRow = null;
393 }
394
395
396
397
398
399 protected Element newTable() {
400 return newTable(null);
401 }
402
403
404
405
406
407 protected Element newSmallTable() {
408 return newTable("jhp-table-small");
409 }
410
411
412
413
414
415 protected Element newTinyTable() {
416 return newTable("jhp-table-tiny");
417 }
418
419
420
421
422
423
424 private Element newTable(final String pClass) {
425 isReset = false;
426 theTable = theDocument.createElement("table");
427 Element myHeader = theDocument.createElement("thead");
428 theTable.appendChild(myHeader);
429 theTableBody = theDocument.createElement("tbody");
430 theTable.appendChild(theTableBody);
431 theSection.appendChild(theTable);
432 if (pClass != null) {
433 theTable.setAttribute(ATTR_CLASS, pClass);
434 }
435
436
437 theTableHdrRow = theDocument.createElement("tr");
438 myHeader.appendChild(theTableHdrRow);
439 theTableHdrRow.setAttribute(ATTR_CLASS, "jhp-row-header");
440
441
442 theRow = null;
443 theRowCount = 0;
444 return theTable;
445 }
446
447
448
449
450
451
452 protected void setTableCaption(final String pCaption,
453 final Object... pArgs) {
454
455 theStringBuilder.setLength(0);
456 theFormatter.format(pCaption, pArgs);
457
458
459 Element myCaption = theDocument.createElement("caption");
460 theTable.insertBefore(myCaption, theTable.getFirstChild());
461 myCaption.setTextContent(theStringBuilder.toString());
462 }
463
464
465
466
467
468
469 protected Element setColumnHeading(final String pHeading) {
470 Element myCell = theDocument.createElement("th");
471 theTableHdrRow.appendChild(myCell);
472 myCell.setTextContent(pHeading);
473 return myCell;
474 }
475
476
477
478
479
480
481 protected Element setRotatedColumnHeading(final String pHeading) {
482 Element myCell = theDocument.createElement("th");
483 myCell.setAttribute(ATTR_CLASS, "rotate");
484 theTableHdrRow.appendChild(myCell);
485 Element myDiv = theDocument.createElement(ELEMENT_DIV);
486 myCell.appendChild(myDiv);
487 Element mySpan = theDocument.createElement("span");
488 myDiv.appendChild(mySpan);
489 mySpan.setTextContent(pHeading);
490 return myCell;
491 }
492
493
494
495
496
497 protected Element newRow() {
498 theRow = theDocument.createElement("tr");
499 theTableBody.appendChild(theRow);
500 theRow.setAttribute(ATTR_CLASS, (theRowCount % 2 == 0)
501 ? "jhp-row-even"
502 : "jhp-row-odd");
503 theRowCount++;
504 return theRow;
505 }
506
507
508
509
510
511
512
513 private Element setCellData(final String pData,
514 final String pClass) {
515 Element myCell = theDocument.createElement("td");
516 theRow.appendChild(myCell);
517 if (pData != null) {
518 myCell.setTextContent(pData);
519 myCell.setAttribute(ATTR_CLASS, pClass);
520 }
521 return myCell;
522 }
523
524
525
526
527
528
529 protected Element setCellObject(final Object pData) {
530 return setCellData(pData == null
531 ? null
532 : pData.toString(),
533 "jhp-cell-data");
534 }
535
536
537
538
539
540
541 protected Element setCellResult(final Result pData) {
542 return setCellData(pData == null
543 ? Fixture.MATCH_VERSUS
544 : pData.toString(),
545 "jhp-cell-result");
546 }
547
548
549
550
551
552
553 protected Element setCellNullResult(final Result pData) {
554 return setCellData(pData == null
555 ? "-"
556 : pData.toString(),
557 "jhp-cell-null-result");
558 }
559
560
561
562
563
564
565 protected Element setCellDate(final LocalDate pData) {
566 return setCellData(pData == null
567 ? null
568 : DataParser.formatShortDate(pData),
569 "jhp-cell-date");
570 }
571
572
573
574
575
576
577 protected Element setCellInteger(final Integer pData) {
578 return setCellData(pData == null
579 ? null
580 : Integer.toString(pData),
581 "jhp-cell-integer");
582 }
583
584
585
586
587
588
589 protected Element setCellId(final String pData) {
590 return setCellData(pData == null
591 ? null
592 : pData,
593 "jhp-cell-id");
594 }
595
596
597
598
599
600
601 protected Element setCellPlayerRight(final Player pData) {
602 return setCellData(pData == null
603 ? null
604 : pData.getName(),
605 "jhp-cell-name-right");
606 }
607
608
609
610
611
612
613 protected Element setCellPlayerLeft(final Player pData) {
614 return setCellData(pData == null
615 ? null
616 : pData.getName(),
617 "jhp-cell-name-left");
618 }
619
620
621
622
623
624
625 protected Element setCellContact(final Player pData) {
626 return setCellData(pData == null
627 ? null
628 : pData.getContact(),
629 "jhp-cell-contact");
630 }
631
632
633
634
635
636
637 protected void addClassAttribute(final Element pElement,
638 final String pAttr) {
639
640 String myAttr = pElement.getAttribute(ATTR_CLASS);
641
642
643 if (myAttr.length() == 0) {
644
645 pElement.setAttribute(ATTR_CLASS, pAttr);
646
647
648 } else {
649
650 theStringBuilder.setLength(0);
651 theStringBuilder.append(myAttr);
652 theStringBuilder.append(' ');
653 theStringBuilder.append(pAttr);
654 pElement.setAttribute(ATTR_CLASS, theStringBuilder.toString());
655 }
656 }
657
658
659
660
661
662
663 protected String formatReport() throws PoolException {
664
665 try {
666
667 StringWriter myWriter = new StringWriter();
668 theXformer.transform(new DOMSource(theDocument), new StreamResult(myWriter));
669
670
671 return myWriter.toString();
672
673 } catch (TransformerException e) {
674 throw new PoolException("Failed to format document", e);
675 }
676 }
677
678
679
680
681
682
683 protected void copyWebResources(final Path pDir) throws PoolException {
684
685
686 try {
687
688 if (!Files.exists(pDir, LinkOption.NOFOLLOW_LINKS)) {
689
690 Files.createDirectory(pDir);
691 }
692
693
694 } catch (IOException e) {
695 throw new PoolException("Failed to ensure directory", e);
696 }
697
698
699 copyResource(JS_TABS, pDir);
700 copyResource(CSS_STANDARD, pDir);
701 copyResource(CSS_WEB, pDir);
702 }
703
704
705
706
707
708
709
710 private void copyResource(final String pName,
711 final Path pDir) throws PoolException {
712
713 Path myFile = pDir.resolve(pName);
714 try (InputStream myStream = getClass().getResourceAsStream(pName)) {
715
716 Files.copy(myStream, myFile, StandardCopyOption.REPLACE_EXISTING);
717
718
719 } catch (IOException e) {
720 throw new PoolException("Failed to copy resource", e);
721 }
722 }
723
724
725
726
727 private final class WebControl {
728
729
730
731 private final Element theTitle;
732
733
734
735
736 private final Element theHeading;
737
738
739
740
741 private final Element theTabLinks;
742
743
744
745
746 private final Element theTabs;
747
748
749
750
751
752 private WebControl(final Element pTitle) {
753
754 theTitle = pTitle;
755
756
757 Element myDiv = theDocument.createElement(ELEMENT_DIV);
758 theBody.appendChild(myDiv);
759 myDiv.setAttribute(ATTR_ID, "header");
760 myDiv.setAttribute(ATTR_CLASS, "tabs");
761
762
763 theHeading = theDocument.createElement("h2");
764 myDiv.appendChild(theHeading);
765
766
767 theTabLinks = theDocument.createElement("ul");
768 myDiv.appendChild(theTabLinks);
769 theTabLinks.setAttribute(ATTR_CLASS, "tab-links");
770
771
772 theTabs = theDocument.createElement(ELEMENT_DIV);
773 theBody.appendChild(theTabs);
774 theTabs.setAttribute(ATTR_ID, "content");
775 theTabs.setAttribute(ATTR_CLASS, "tabs tab-content");
776 }
777
778
779
780
781
782 private void setTheTitle(final String pTitle) {
783
784 clearElement(theTabLinks);
785 clearElement(theTabs);
786
787
788 theTitle.setTextContent(pTitle);
789 theHeading.setTextContent(pTitle);
790 }
791
792
793
794
795
796 private void newSection(final String pSection) {
797
798 Element myItem = theDocument.createElement("li");
799 theTabLinks.appendChild(myItem);
800 Element myLink = theDocument.createElement("a");
801 myItem.appendChild(myLink);
802 myLink.setAttribute("href", "#" + pSection);
803 myLink.setTextContent(pSection);
804
805
806 Element mySection = theDocument.createElement(ELEMENT_DIV);
807 mySection.setAttribute(ATTR_ID, pSection);
808 mySection.setAttribute(ATTR_CLASS, "tab");
809 theTabs.appendChild(mySection);
810
811
812 if (isReset) {
813
814 myLink.setAttribute(ATTR_CLASS, "active");
815 addClassAttribute(mySection, "active");
816 }
817
818
819 theSection = mySection;
820 }
821 }
822 }