View Javadoc
1   /*******************************************************************************
2    * jhunters: Pool League
3    * Copyright 2015 Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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   * Report Builder.
51   */
52  public class ReportBuilder {
53      /**
54       * jQuery functions.
55       */
56      private static final String JS_TABS = "jtabs.js";
57  
58      /**
59       * Standard CSS.
60       */
61      private static final String CSS_STANDARD = "jhuntersPrint.css";
62  
63      /**
64       * WebSite CSS.
65       */
66      private static final String CSS_WEB = "jhuntersWeb.css";
67  
68      /**
69       * division element.
70       */
71      private static final String ELEMENT_DIV = "div";
72  
73      /**
74       * Id attribute.
75       */
76      private static final String ATTR_ID = "id";
77  
78      /**
79       * Class attribute.
80       */
81      private static final String ATTR_CLASS = "class";
82  
83      /**
84       * RowSpan attribute.
85       */
86      protected static final String ATTR_ROWSPAN = "rowspan";
87  
88      /**
89       * Table separator attribute.
90       */
91      protected static final String CLASS_SEPARATOR = "jhp-table-separator";
92  
93      /**
94       * DocumentBuilder.
95       */
96      private final DocumentBuilder theBuilder;
97  
98      /**
99       * Transformer.
100      */
101     private final Transformer theXformer;
102 
103     /**
104      * The document.
105      */
106     private final Document theDocument;
107 
108     /**
109      * StringBuilder.
110      */
111     private final StringBuilder theStringBuilder;
112 
113     /**
114      * Message Formatter.
115      */
116     private final Formatter theFormatter;
117 
118     /**
119      * The Document body.
120      */
121     private final Element theBody;
122 
123     /**
124      * The WebControl.
125      */
126     private final WebControl theWebControl;
127 
128     /**
129      * The Current section.
130      */
131     private Element theSection;
132 
133     /**
134      * The Current table .
135      */
136     private Element theTable;
137 
138     /**
139      * The Table Header row.
140      */
141     private Element theTableHdrRow;
142 
143     /**
144      * The Table body.
145      */
146     private Element theTableBody;
147 
148     /**
149      * The Current row.
150      */
151     private Element theRow;
152 
153     /**
154      * The row count.
155      */
156     private int theRowCount;
157 
158     /**
159      * Has the document been reset?
160      */
161     private boolean isReset = true;
162 
163     /**
164      * Constructor.
165      * @param pWeb is this a web page builder?
166      * @throws PoolException on error
167      */
168     protected ReportBuilder(final boolean pWeb) throws PoolException {
169         /* protect against exceptions */
170         try {
171             /* Create a Document builder */
172             DocumentBuilderFactory myFactory = DocumentBuilderFactory.newInstance();
173             theBuilder = myFactory.newDocumentBuilder();
174             theDocument = theBuilder.newDocument();
175 
176             /* Create the transformer */
177             TransformerFactory myXformFactory = TransformerFactory.newInstance();
178             theXformer = myXformFactory.newTransformer();
179 
180             /* Create the formatter */
181             theStringBuilder = new StringBuilder();
182             theFormatter = new Formatter(theStringBuilder, Locale.getDefault());
183 
184             /* Create the HTML body */
185             Element myHTML = theDocument.createElement("html");
186             theDocument.appendChild(myHTML);
187             Element myHead = theDocument.createElement("head");
188             myHTML.appendChild(myHead);
189 
190             /* Create the body */
191             theBody = theDocument.createElement("body");
192             myHTML.appendChild(theBody);
193 
194             /* Switch on type of builder */
195             if (pWeb) {
196                 /* initialise the web builder */
197                 theWebControl = initWebBuilder(myHead);
198             } else {
199                 /* initialise the report builder */
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      * Initialise the ReportBuilder.
211      * @param pHeader the header
212      */
213     private void initReportBuilder(final Element pHeader) {
214         /* Create the title */
215         setTitle(pHeader, "Test");
216 
217         /* Link to the styleSheet */
218         setStyleSheet(pHeader, getClass().getResource(CSS_STANDARD).toString());
219 
220         /* Current section is the body */
221         theSection = theBody;
222     }
223 
224     /**
225      * Initialise the WebBuilder.
226      * @param pHeader the header
227      * @return the webControl
228      */
229     private WebControl initWebBuilder(final Element pHeader) {
230         /* Create the title element */
231         Element myTitle = setTitle(pHeader, null);
232 
233         /* Link to the styleSheets */
234         setStyleSheet(pHeader, CSS_STANDARD);
235         setStyleSheet(pHeader, CSS_WEB);
236 
237         /* Attach scripts */
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      * Set a title.
246      * @param pHeader the header
247      * @param pTitle the title
248      * @return the title element
249      */
250     private Element setTitle(final Element pHeader,
251                              final String pTitle) {
252         /* Create the title */
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      * Set a styleSheet.
263      * @param pHeader the header
264      * @param pStyleSheet the styleSheet
265      */
266     private void setStyleSheet(final Element pHeader,
267                                final String pStyleSheet) {
268         /* Create the link */
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      * Set a script.
279      * @param pHeader the header
280      * @param pScript the script
281      */
282     private void setScript(final Element pHeader,
283                            final String pScript) {
284         /* Create the script */
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      * Clear an element.
293      * @param pElement the element to clear
294      */
295     private void clearElement(final Element pElement) {
296         /* Clear the existing document */
297         while (pElement.hasChildNodes()) {
298             pElement.removeChild(pElement.getFirstChild());
299         }
300     }
301 
302     /**
303      * Is the document reset?
304      * @return true/false
305      */
306     protected boolean isReset() {
307         return isReset;
308     }
309 
310     /**
311      * Clear the document.
312      */
313     protected void resetDocument() {
314         /* Clear body */
315         clearElement(theBody);
316         isReset = true;
317     }
318 
319     /**
320      * Set the title.
321      * @param pTitle the title
322      */
323     protected void setTheTitle(final String pTitle) {
324         theWebControl.setTheTitle(pTitle);
325     }
326 
327     /**
328      * Create a new section.
329      * @param pTitle the title
330      */
331     protected void newSection(final String pTitle) {
332         theWebControl.newSection(pTitle);
333     }
334 
335     /**
336      * Set a page break.
337      */
338     protected void setPageBreak() {
339         /* Close any existing table */
340         closeTable();
341 
342         /* Create the page break */
343         Element myDiv = theDocument.createElement(ELEMENT_DIV);
344         myDiv.setAttribute(ATTR_CLASS, "page-break");
345         theSection.appendChild(myDiv);
346     }
347 
348     /**
349      * Set line space.
350      */
351     protected void setLineSpace() {
352         /* Close any existing table */
353         closeTable();
354 
355         /* Create the page break */
356         Element myBreak = theDocument.createElement(ELEMENT_DIV);
357         myBreak.setAttribute(ATTR_CLASS, "line-space");
358         theSection.appendChild(myBreak);
359     }
360 
361     /**
362      * Set the heading.
363      * @param pHeading the table heading
364      * @param pArgs the variable arguments
365      * @return the heading element
366      */
367     protected Element setHeading(final String pHeading,
368                                  final Object... pArgs) {
369         /* Close any existing table */
370         closeTable();
371         isReset = false;
372 
373         /* Format the message */
374         theStringBuilder.setLength(0);
375         theFormatter.format(pHeading, pArgs);
376 
377         /* Create the heading */
378         Element myHeading = theDocument.createElement("h2");
379         myHeading.setTextContent(theStringBuilder.toString());
380         theSection.appendChild(myHeading);
381         return myHeading;
382     }
383 
384     /**
385      * Close any table.
386      */
387     private void closeTable() {
388         /* Close any existing table */
389         theTable = null;
390         theTableHdrRow = null;
391         theTableBody = null;
392         theRow = null;
393     }
394 
395     /**
396      * Create standard table.
397      * @return the table element
398      */
399     protected Element newTable() {
400         return newTable(null);
401     }
402 
403     /**
404      * Create small table.
405      * @return the table element
406      */
407     protected Element newSmallTable() {
408         return newTable("jhp-table-small");
409     }
410 
411     /**
412      * Create tiny table.
413      * @return the table element
414      */
415     protected Element newTinyTable() {
416         return newTable("jhp-table-tiny");
417     }
418 
419     /**
420      * Create table.
421      * @param pClass the class of the table
422      * @return the table element
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         /* Create an active row for the headers */
437         theTableHdrRow = theDocument.createElement("tr");
438         myHeader.appendChild(theTableHdrRow);
439         theTableHdrRow.setAttribute(ATTR_CLASS, "jhp-row-header");
440 
441         /* Clear row details */
442         theRow = null;
443         theRowCount = 0;
444         return theTable;
445     }
446 
447     /**
448      * Set the table caption.
449      * @param pCaption the caption
450      * @param pArgs the variable arguments
451      */
452     protected void setTableCaption(final String pCaption,
453                                    final Object... pArgs) {
454         /* Format the message */
455         theStringBuilder.setLength(0);
456         theFormatter.format(pCaption, pArgs);
457 
458         /* Create the caption */
459         Element myCaption = theDocument.createElement("caption");
460         theTable.insertBefore(myCaption, theTable.getFirstChild());
461         myCaption.setTextContent(theStringBuilder.toString());
462     }
463 
464     /**
465      * Set the next column heading.
466      * @param pHeading the table heading
467      * @return the cell element
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      * Set the next rotated column heading.
478      * @param pHeading the table heading
479      * @return the cell element
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      * Set the next row.
495      * @return the row element
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      * Set the next cell.
509      * @param pData the cell contents
510      * @param pClass the class of the cell
511      * @return the cell element
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      * Set the next object cell.
526      * @param pData the cell contents
527      * @return the cell element
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      * Set the next fixture cell.
538      * @param pData the cell contents
539      * @return the cell element
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      * Set the next fixture cell.
550      * @param pData the cell contents
551      * @return the cell element
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      * Set the next date cell.
562      * @param pData the cell contents
563      * @return the cell element
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      * Set the next integer cell.
574      * @param pData the cell contents
575      * @return the cell element
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      * Set the next left-aligned player cell.
586      * @param pData the cell contents
587      * @return the cell element
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      * Set the next right-aligned player cell.
598      * @param pData the cell contents
599      * @return the cell element
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      * Set the next left-aligned player cell.
610      * @param pData the cell contents
611      * @return the cell element
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      * Set the next left-aligned contact cell.
622      * @param pData the cell contents
623      * @return the cell element
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      * Add class attribute.
634      * @param pElement the element to add the attribute to
635      * @param pAttr the attribute to add
636      */
637     protected void addClassAttribute(final Element pElement,
638                                      final String pAttr) {
639         /* determine whether we have an existing attribute */
640         String myAttr = pElement.getAttribute(ATTR_CLASS);
641 
642         /* New attribute */
643         if (myAttr.length() == 0) {
644             /* Just set the attribute */
645             pElement.setAttribute(ATTR_CLASS, pAttr);
646 
647             /* else existing attribute */
648         } else {
649             /* Build new attribute and set it */
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      * Format report.
660      * @return the formatted report
661      * @throws PoolException on error
662      */
663     protected String formatReport() throws PoolException {
664         /* protect against exceptions */
665         try {
666             /* Format the XML and write to stream */
667             StringWriter myWriter = new StringWriter();
668             theXformer.transform(new DOMSource(theDocument), new StreamResult(myWriter));
669 
670             /* Convert result to string */
671             return myWriter.toString();
672 
673         } catch (TransformerException e) {
674             throw new PoolException("Failed to format document", e);
675         }
676     }
677 
678     /**
679      * copy Web Resources.
680      * @param pDir the target directory
681      * @throws PoolException on error
682      */
683     protected void copyWebResources(final Path pDir) throws PoolException {
684 
685         /* Protect against exceptions */
686         try {
687             /* If the directory does not exist */
688             if (!Files.exists(pDir, LinkOption.NOFOLLOW_LINKS)) {
689                 /* Create the directory */
690                 Files.createDirectory(pDir);
691             }
692 
693             /* Catch Exceptions */
694         } catch (IOException e) {
695             throw new PoolException("Failed to ensure directory", e);
696         }
697 
698         /* Copy the various resources */
699         copyResource(JS_TABS, pDir);
700         copyResource(CSS_STANDARD, pDir);
701         copyResource(CSS_WEB, pDir);
702     }
703 
704     /**
705      * copy Resource to file.
706      * @param pName the resource to copy
707      * @param pDir the target directory
708      * @throws PoolException on error
709      */
710     private void copyResource(final String pName,
711                               final Path pDir) throws PoolException {
712         /* Protect against exceptions */
713         Path myFile = pDir.resolve(pName);
714         try (InputStream myStream = getClass().getResourceAsStream(pName)) {
715             /* Copy the resource to the directory */
716             Files.copy(myStream, myFile, StandardCopyOption.REPLACE_EXISTING);
717 
718             /* Catch Exceptions */
719         } catch (IOException e) {
720             throw new PoolException("Failed to copy resource", e);
721         }
722     }
723 
724     /**
725      * Web Control class.
726      */
727     private final class WebControl {
728         /**
729          * Header.
730          */
731         private final Element theTitle;
732 
733         /**
734          * Heading.
735          */
736         private final Element theHeading;
737 
738         /**
739          * Tab Links.
740          */
741         private final Element theTabLinks;
742 
743         /**
744          * Tabs.
745          */
746         private final Element theTabs;
747 
748         /**
749          * Constructor.
750          * @param pTitle the title element
751          */
752         private WebControl(final Element pTitle) {
753             /* Store the parameters */
754             theTitle = pTitle;
755 
756             /* Create the header division */
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             /* Create the title division */
763             theHeading = theDocument.createElement("h2");
764             myDiv.appendChild(theHeading);
765 
766             /* Create the tab links */
767             theTabLinks = theDocument.createElement("ul");
768             myDiv.appendChild(theTabLinks);
769             theTabLinks.setAttribute(ATTR_CLASS, "tab-links");
770 
771             /* Create the contents division */
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          * Set the document title.
780          * @param pTitle the title
781          */
782         private void setTheTitle(final String pTitle) {
783             /* Reset the links and tabs */
784             clearElement(theTabLinks);
785             clearElement(theTabs);
786 
787             /* Set the title */
788             theTitle.setTextContent(pTitle);
789             theHeading.setTextContent(pTitle);
790         }
791 
792         /**
793          * Create a new section.
794          * @param pSection the new section title
795          */
796         private void newSection(final String pSection) {
797             /* Create the new link */
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             /* Create the new section */
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             /* If this is the first element */
812             if (isReset) {
813                 /* Mark as active */
814                 myLink.setAttribute(ATTR_CLASS, "active");
815                 addClassAttribute(mySection, "active");
816             }
817 
818             /* record the section */
819             theSection = mySection;
820         }
821     }
822 }