001package Torello.HTML.Tools.JavaDoc;
002
003import Torello.Java.*;
004import Torello.HTML.*;
005import Torello.HTML.NodeSearch.*;
006
007import Torello.Java.Shell.C;
008import Torello.Java.Additional.Ret3;
009import Torello.Java.Additional.EffectivelyFinal;
010
011import java.util.*;
012import java.io.File;
013import java.io.IOException;
014
015/**
016 * <B STYLE='color:darkred;'>Process Java Doc Web-Page:</B>
017 * 
018 * Inserts link to a complete <CODE>'&#46;java'</CODE> HiLited Source-File Page into a
019 * Java Doc Page.
020 * 
021 * <EMBED CLASS="external-html" DATA-FILE-ID=PKG_PRIVATE_MSG>
022 * <EMBED CLASS="external-html" DATA-FILE-ID=HILITE_SCFILE>
023 */
024@StaticFunctional
025public class HiLiteSrcCodeFile
026{
027    private HiLiteSrcCodeFile() { }
028
029
030    // ********************************************************************************************
031    // ********************************************************************************************
032    // A PACKAGE-VISIBLE inner-class to hold a single Source Code HiLiting Link.
033    // ********************************************************************************************
034    // ********************************************************************************************
035
036
037    // This is a class for a single "LIST" filed 2-D String[][] Array Entry that contains a single
038    // Extra HiLited Source-Code Link.  Each time an "Extra Source Code Link" is added, it will
039    // have an actual File to be Hilited (javaSrcCodeFileName), a "Title-Bar" String, and also a
040    // "Class Name Label" String.  To view one of these things, look at any of the Search Classes
041    // in the Torello.HTML.NodeSearch Package.  Each of the Search-Loop Classes there have an
042    // extra "Search-Loops" link, and a link to "ARGCHECK.java" file.
043
044    static class ExtraSrcLink
045    {
046        public final String CIETName;
047        public final String javaSrcCodeFileName;    // A '.java' file to be sent to the HiLiter
048        public final String titleBarLabel;          // Title String explaining this extra file.
049        public final String classNameLabel;         // Text-String used - in place of the Java
050                                                    // Source FileName, because often that file
051                                                    // name is JUST TOO LONG!
052
053        ExtraSrcLink(String[] extraLinkArr)
054        {
055            this.CIETName               = extraLinkArr[0];
056            this.titleBarLabel          = extraLinkArr[1];
057            this.javaSrcCodeFileName    = extraLinkArr[2];
058            this.classNameLabel         = extraLinkArr[3];
059        }
060    }
061
062
063    // ********************************************************************************************
064    // ********************************************************************************************
065    // HiLited Source Code HTML-File Header & Footer (The LINKED HTML Page, NOT the Java-Doc Page)
066    // ********************************************************************************************
067    // ********************************************************************************************
068
069
070    private static final Vector<HTMLNode> PAGE_HEADER = HTMLPage.getPageTokens(
071        "<HTML>\n<HEAD>\n\n" + 
072        "<TITLE>CIET_NAME</TITLE>\n\n" +
073        "<!-- Java Documentation Upgrader, Torello.HTML.Tools.JavaDoc.Upgrader -->\n\n" +
074        "<META charset=\"UTF-8\">\n\n" +
075        "</HEAD>\n<BODY>\n",
076        false
077    );
078
079    private static final Vector<HTMLNode> PAGE_FOOTER = HTMLPage.getPageTokens
080        ("\n</BODY>\n</HTML>\n", false);
081
082    private static final int titlePos =
083        TextNodeFind.first(PAGE_HEADER, (String s) -> s.equals("CIET_NAME"));
084
085    /**
086     * Used in build library.
087     * 
088     * @param cietName The name of the class, interface or enumerated-type whose source-code file
089     * is being hilited.  This name will be placed in the title portion of the HTML
090     * {@code <HEAD>...</HEAD>} section.
091     * 
092     * @return A clone of the {@code Vector} which is placed at the top of a HiLited Source
093     * Code {@code '.html'} File.
094     */
095    @SuppressWarnings("unchecked")
096    public static Vector<HTMLNode> retrieveHEADER(String cietName)
097    {
098        Vector<HTMLNode> header = (Vector<HTMLNode>) PAGE_HEADER.clone();
099        header.setElementAt(new TextNode(cietName), titlePos);
100        return header;
101    }
102
103    /**
104     * Used in build library.  Must be declared {@code public.}
105     * 
106     * @return a clone of the hilited source-code HTML-{@code Vector} page footer.
107     */
108    @SuppressWarnings("unchecked")
109    public static Vector<HTMLNode> retrieveFOOTER()
110    { return (Vector<HTMLNode>) PAGE_FOOTER.clone(); }
111
112
113    // ********************************************************************************************
114    // ********************************************************************************************
115    // Page  URL Links (The LINKS inserted into the JAVA-DOC PAGE, **NOT** the linked-pages)
116    // ********************************************************************************************
117    // ********************************************************************************************
118
119
120    /** This is the HTML Inserted into the Page. */
121    protected static final Vector<HTMLNode> SRCCODE_ANCHOR = HTMLPage.getPageTokens(
122        "\n" +
123        "<BR />\n" +
124        "<DIV ID=SRCCODELINK>\n" +
125        "<SPAN CLASS=srcCodeLabel>Hi-Lited Source-Code:</SPAN>\n" +
126        "<DIV CLASS=JDTopIndent>\n" +
127        "<UL CLASS=JDUL>\n" +
128        "<LI><SPAN STYLE='white-space: pre; font-family: monospace'>" +
129        "View Here:              </SPAN><A>SRCCODE_FILE_NAME</A></LI>\n" +
130        "<LI><SPAN STYLE='white-space: pre; font-family: monospace'>" +
131        "Open New Browser-Tab:   </SPAN><A TARGET='_blank'>SRCCODE_FILE_NAME</A></LI>\n" +
132        "</UL>\n" +
133        "<SPAN STYLE='white-space: pre; font-family: monospace;'>" +
134        "File Size:     FS Bytes\n" +
135        "Line Count:    LC \'\\n\' Characters Found\n" +
136        "</SPAN>\n" +
137        "</DIV> <!-- DIV CLASS=JDTopIndent -->\n" +
138        "</DIV> <!-- DIV ID=SRCCODELINK -->\n" +
139        "<BR />\n\n",
140        false
141    );
142
143    private static final int[] ANCHOR_POS_ARR = 
144        TagNodeFind.all(SRCCODE_ANCHOR, TC.OpeningTags, "A");
145
146    private static final int[] FILENAME_POS_ARR =
147        TextNodeFind.all(SRCCODE_ANCHOR, TextComparitor.EQ, "SRCCODE_FILE_NAME");
148
149    private static final int TXTNODE_COUNT_POS =
150        TextNodeFind.first(SRCCODE_ANCHOR, TextComparitor.CN, "File Size");
151
152    private static final String[] replaceArr = { "FS", "LC" };
153
154
155    // ********************************************************************************************
156    // ********************************************************************************************
157    // EXTRA & ADDITIONAL HiLited-Source Links - By Request
158    // ********************************************************************************************
159    // ********************************************************************************************
160
161
162    private static final int INSERT_FIRST_DESCRIPTION_POS =
163        TagNodeFind.first(SRCCODE_ANCHOR, TC.OpeningTags, "ul");
164
165    private static final Vector<HTMLNode> FIRST_DESCRIPTION = HTMLPage.getPageTokens
166        ("<SPAN CLASS=SRCCODETITLE>DESCRIPTION</SPAN><BR />\n", false);
167
168    private static final int FIRST_DESCIPTION_TXTNODE_POS = TextNodeFind.first
169        (FIRST_DESCRIPTION, (String text) -> text.equals("DESCRIPTION"));
170
171    // It should be inserted directly at/before the following line:
172    // "</DIV> <!-- DIV CLASS=JDTopIndent -->\n"
173
174    private static final int EXTRA_LINK_INSERTION_POS =
175        TagNodeFind.first(SRCCODE_ANCHOR, TC.ClosingTags, "div");
176
177    /**
178     * Using a Lambda-Configuration with the Configuration-Class {@link Upgrade}, a user may
179     * request that additional HiLited Source Code Links be added to this section / area on
180     * Java Doc Class File Pages.
181     * 
182     * <BR /><BR />This is the HTML that is added to the page, upon request.
183     */
184    protected static final Vector<HTMLNode> EXTRA_SRCCODE_ANCHOR = HTMLPage.getPageTokens(
185        "\n" +
186        "<BR /><SPAN CLASS=SRCCODETITLE>DESCRIPTION</SPAN>\n" +
187        "<BR />\n" +
188        "<UL CLASS=JDUL>\n" +
189        "<LI><SPAN STYLE='white-space: pre; font-family: monospace'>" +
190        "View Here:              </SPAN><A>SRCCODE_FILE_NAME</A></LI>\n" +
191        "<LI><SPAN STYLE='white-space: pre; font-family: monospace'>" +
192        "Open New Browser-Tab:   </SPAN><A TARGET='_blank'>SRCCODE_FILE_NAME</A></LI>\n" +
193        "</UL>\n" +
194        "<SPAN STYLE='white-space: pre; font-family: monospace;'>" +
195        "File Size:     FS Bytes\n" +
196        "Line Count:    LC \'\\n\' Characters Found\n" +
197        "</SPAN>\n",
198        false
199    );
200
201    private static final int[] EXTRA_ANCHOR_POS_ARR = 
202        TagNodeFind.all(EXTRA_SRCCODE_ANCHOR, TC.OpeningTags, "A");
203
204    private static final int[] EXTRA_FILENAME_POS_ARR =
205        TextNodeFind.all(EXTRA_SRCCODE_ANCHOR, TextComparitor.EQ, "SRCCODE_FILE_NAME");
206
207    private static final int EXTRA_TXTNODE_COUNT_POS =
208        TextNodeFind.first(EXTRA_SRCCODE_ANCHOR, TextComparitor.CN, "File Size");
209
210    private static final int SECOND_DESCRIPTION_TXTNODE_POS = TextNodeFind.first
211        (EXTRA_SRCCODE_ANCHOR, (String text) -> text.equals("DESCRIPTION"));
212
213
214    // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
215    // FIX ME!!! THIS SHOULD BE MOVED TO CLASS Upgrade.  Static-Member Fields with State is
216    // cheating and ugly-to-look-at
217    // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
218
219    private static final HashSet<String> alreadyHiLitedList = new HashSet<>();
220
221
222    // ********************************************************************************************
223    // ********************************************************************************************
224    // Methods
225    // ********************************************************************************************
226    // ********************************************************************************************
227
228
229    // This is package-private.
230    //
231    // The inline comments may be read by clicking the "View HiLited Source Code Button"
232    // If there are errors, this method throws JDE
233    //
234    // MESSAGER:
235    //  1) SETS:        processorName to 'HiLiteSrcCodeFile' on entry
236    //  2) INVOKES:     'errorThrowJDE' for errors, also has 'errorContinue'
237    //  3) RETURNS:     boolean indicating whether or not the src-file was hilited
238    //  4) INVOKED-BY:  Only once in MainFilesProcessor, main-loop
239    //  5) EXPECTS:     FileName to already be set
240    //  6) THROWS:      JavaDocError
241
242    @SuppressWarnings({"unchecked", "rawtypes"})
243    static boolean hiLite(
244            Upgrade u, JavaDocHTMLFile jdhf, String srcCodeAsStr, String dotDots,
245            String srcCodeFileName, ArrayList<ExtraSrcLink> extraLinks
246        )
247    {
248        Messager.setProcessorName("HiLiteSrcCodeFile");
249
250        // Vector<HTMLNode> topOfHTMLPage = jdhf.pageTop();
251        Vector<HTMLNode> pageDLs = jdhf.headerFooter.defListsBelowDesc();
252
253        boolean hasExtra = (extraLinks != null) && (extraLinks.size() > 0);
254
255        int numExtra = (hasExtra) ? extraLinks.size() : 0;
256
257        // Since the addition of "hilited-files/" - this is now necessary
258        // Remember, for URL's (as opposed to files) ALWAYS USE A FOWARD-SLASH!
259
260        dotDots = dotDots + "../";
261
262        /*
263        System.out.println(
264            "Class [" + jdhf.fullyQualifiedName + "] " +
265            C.BGREEN + (hasExtra ? "*DOES*" : "**DOES NOT**") + C.RESET +
266            " have extra Hilite Links!"
267        );
268        */
269
270        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
271        // STEP A-ONE: Build '.html' Hi-Lited Source-Code File & Dir Names, Create Dir If Needed
272        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
273
274        // *** The Full-Path Diectory-Name of the output / save Directory used by this CIET
275        String hiLitedSrcCodeDirectory = StringParse.beforeLastFileSeparatorPos
276            (jdhf.fileName) + "hilite-files" + File.separator;
277
278        // *** The Simple File-Name of the HiLited Source-Code File: Used for <TITLE>...</TITLE>
279        //     jdhf.fileName is just that - the Full-Path Name of the Java Doc HTML File that is
280        //     currently being processed.
281
282        String simpleHiLitedFileName = StringParse.fromLastFileSeparatorPos(jdhf.fileName);
283
284        // REMEMBER: Inner-Classes reuse their container classes hilited html-files ... and there
285        //           is no need to hilite them twice!
286
287        if (jdhf.isInner) simpleHiLitedFileName =
288            simpleHiLitedFileName.substring(0, simpleHiLitedFileName.indexOf('.')) + ".java.html";
289        else
290            simpleHiLitedFileName = simpleHiLitedFileName.substring
291                (0, simpleHiLitedFileName.length() - ".html".length()) + ".java.html";
292
293        // *** The Full-Path File-Name of the HiLited Source-Code File: Used to write file to disk
294        String hiLitedSrcCodeFileName = hiLitedSrcCodeDirectory + simpleHiLitedFileName;
295
296        // *** The "Anchor Link HREF=..." URL
297        // NEW-AND-IMPROVED: This will insert the Line-Number into the Anchor-URL when the class
298        // that is being processed is an inner-class.  For "FileNode.RetTypeChoice" - for example -
299        // this will skip all the way to the bottom of the source-file (which is where 
300        // "RetTypeChoice" is defined).  It looks a lot better...
301
302        String anchorURL = "hilite-files" + File.separator + simpleHiLitedFileName +
303            (jdhf.isInner
304                ? ("#L" +
305                    ((jdhf.jdStartLineNumber != -1)
306                        ? jdhf.jdStartLineNumber
307                        : jdhf.startLineNumber))
308                : "");
309
310
311        // *** This will be the text used *BETWEEN* the Anchor-Elements <A HREF=...> TEXT-HERE </A>
312        // *** You could call this the 'LINK-TEXT' ... it is the File-Name as it is PRINTED inside
313        // *** the HTML Document itself!
314        //
315        // REMEMBER: Inner-Classes reuse their container classes hilited html-files ... and there
316        //           is no need to hilite them twice!
317
318        String printedFileName = jdhf.isInner
319
320            ? (jdhf.packageName.replace('.', '/') + '/' +
321                jdhf.simpleName.substring(0, jdhf.simpleName.indexOf('.')) + ".java")
322
323            : (jdhf.fullyQualifiedName.replace('.', '/') + ".java");
324
325        // *** Create the output directory, if it has not been created yet!
326        File hiLiteDir = new File(hiLitedSrcCodeDirectory);
327
328        try
329            { if (! hiLiteDir.exists()) hiLiteDir.mkdirs(); }
330
331        catch (Exception e)
332        {
333            Messager.assertFailGeneralPurpose(
334                e,
335                "An Exception was thrown while attempting to create the output directory for " +
336                "saving HiLited Source Code Files.",
337                null
338            );
339        }
340
341
342        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
343        // B-ONE: Build the "EXTRA" Hi-Lited Source-Code File & Dir Names.
344        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
345
346        String[] extraSrcCodeAsStrs             = null; // HiLite.ME Send-String
347        String[] extraSimpleHiLitedFileNames    = null; // Used for: <TITLE> ... </TITLE>
348        String[] extraHiLitedSrcCodeFileNames   = null; // Used for: Writing File(s) to Disk
349        String[] extraAnchorURLs                = null; // Used for: <A HREF=...> </A>
350
351        if (hasExtra)
352        {
353            extraSrcCodeAsStrs              = new String[numExtra];
354            extraSimpleHiLitedFileNames     = new String[numExtra];
355            extraHiLitedSrcCodeFileNames    = new String[numExtra];
356            extraAnchorURLs                 = new String[numExtra];
357
358            for (int i=0; i < numExtra; i++)
359            {
360                // The data inside the inner-class "ExtraSrcLink" is the actual list of Strings
361                // (a 2-D String[][] Array) the the user provided.  This includes three main
362                // String-Fields: The Java-FileName, the "Title-Bar Label", and "Class-Name Label"
363
364                ExtraSrcLink esl = extraLinks.get(i);
365
366                try
367                    { extraSrcCodeAsStrs[i] = FileRW.loadFileToString(esl.javaSrcCodeFileName); }
368
369                catch (Exception e)
370                {
371                    // When they provide a bogus XTRA-SRC-FILE to be hilited, this is going to be
372                    // called a non-fatal error....  The result will be that no hilited files will
373                    // be inserted ANYWHERE - INCLUDING THE NORMAL SRC-HILITE-AND-INSERT..
374                    Messager.errorContinue(
375                        e,
376                        "Inserting extra Hi-Lited Source-Code Files, User-Provided File-Name " +
377                        "failed to load from disk: " +
378                        "    [" + C.BCYAN + esl.javaSrcCodeFileName + C.RESET + "]\n" +
379                        "Not inserting extra hi-lited source-code file links..."
380                    );
381
382                    // CALCULATE THE NORMAL RETURN VALUE - AND EXIT THIS METHOD
383                    /// When an "Extra File" fails, just skip adding the HiLited Code, at all, for
384                    // this class
385                    //
386                    // 'false' is that the file was not successfully hilited.  "NavButtons" needs
387                    // to know
388
389                    return false;
390                }
391
392                // *** The Simple File-Name of the HiLited Source-Code File: Used for <TITLE>
393                // NOTE:    Slightly different than the main HiLited-Src File-Name Computation
394                // THERE:   jdhf.fileName   ==> The JavaDoc '.html' File
395                // HERE:    xtra.b[i]       ==> Some other '.java' Source-Code File
396                extraSimpleHiLitedFileNames[i] = StringParse.fromLastFileSeparatorPos
397                    (esl.javaSrcCodeFileName) + ".html";
398
399                // *** The Full-Path File-Name of the HiLited Source-Code File: Used to write the
400                //     file to disk
401                extraHiLitedSrcCodeFileNames[i] = hiLitedSrcCodeDirectory + 
402                    extraSimpleHiLitedFileNames[i];
403
404                // *** The "Anchor Link HREF=..." URL (for the extra file(s) - if any)
405                extraAnchorURLs[i] = "hilite-files" + File.separator +
406                    extraSimpleHiLitedFileNames[i];
407            }
408        }
409
410
411        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
412        // STEP TWO: Run the HiLiter
413        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
414
415        // This invokes the HiLiter, and saves the HTML File to Disk.  This now officially checks
416        // for inner-classes, and makes sure not to hilite them twice.
417        //
418        // ARG-1: Upgrade-Instance (for the Messager)
419        // ARG-2: <TITLE> ... <TITLE>
420        // ARG-3: Full-Path File-Save Name
421        // ARG-4: The '.java' File As a java.lang.String
422        // ARG-5: Relative Path String to use for the <META> Tags, Favicon, etc...
423        //
424        // NOTE: On error, this uses Messager.errorThrowJDE()
425
426        if (! alreadyHiLitedList.contains(hiLitedSrcCodeFileName))
427        {
428            hiLiteSrcCodeFile
429                (u, simpleHiLitedFileName, hiLitedSrcCodeFileName, srcCodeAsStr, dotDots);
430
431            alreadyHiLitedList.add(hiLitedSrcCodeFileName);
432        }
433
434        // Run the HiLiter for any / all "Extra HiLited Source Code Files"  This if-statement
435        // should rarely be invoked.  It is only used if the user has provided the 
436        // Configuration-Class File "ExtraHiLitedSrc.class" in the /upgrade-files/config-classes/
437        // directory.
438
439        if (hasExtra) for (int i=0; i < numExtra; i++)
440        {
441            // There is no need to continusously "Re-HiLite" and "Re-Save" any files, over and over
442            // The primary example is the use of "ARGCHECK.java" in Node-Search, which is linked
443            // in all of the HTML Files.
444            //
445            // This little HashMap "alreadyHiLitedList" just makes not of the on-disk file-name for
446            // the output HiLited-HTML-Files, and returns "TRUE" if it looks like the same exact
447            // file is about to be HiLited and written to disk again.
448
449            if (! alreadyHiLitedList.contains(extraHiLitedSrcCodeFileNames[i]))
450            {
451                // Uses: Messager.errorThrowJDE, if problems happen
452                hiLiteSrcCodeFile(
453                    u, extraSimpleHiLitedFileNames[i], extraHiLitedSrcCodeFileNames[i], 
454                    extraSrcCodeAsStrs[i], dotDots
455                );
456
457                alreadyHiLitedList.add(extraHiLitedSrcCodeFileNames[i]);
458            }
459        }
460
461
462        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
463        // STEP THREE: Clone the HTML Vectors to insert & Set the HREF=... in all Anchors <A>
464        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
465
466        // Vector<HTMLNode> SRCCODE_ANCHOR = HTMLPage.getPageTokens(...)
467        //
468        // @SuppressWarnings("unchecked")
469
470        Vector<HTMLNode> srcCodeAnchorHTMLVec = (Vector<HTMLNode>) SRCCODE_ANCHOR.clone();
471
472        Attributes.update
473            // int[] ANCHOR_POS_ARR = TagNodeFind.all(SRCCODE_ANCHOR, TC.OpeningTags, "A");
474            (srcCodeAnchorHTMLVec, AUM.Set, ANCHOR_POS_ARR, "href", anchorURL, SD.SingleQuotes);
475
476        // Vector<HTMLNode> EXTRA_SRCCODE_ANCHOR = HTMLPage.getPageTokens(...)
477        //
478        // @SuppressWarnings({"unchecked", "rawtypes"})
479        Vector<HTMLNode>[] extraSrcCodeAnchorHTMLVecs = null;
480
481        if (hasExtra)
482        {
483            extraSrcCodeAnchorHTMLVecs = new Vector[numExtra];
484
485            for (int i=0; i < numExtra; i++)
486            {
487                extraSrcCodeAnchorHTMLVecs[i] = (Vector<HTMLNode>) EXTRA_SRCCODE_ANCHOR.clone();
488
489                Attributes.update(
490                    // int[] EXTRA_ANCHOR_POS_ARR =
491                    //      TagNodeFind.all(EXTRA_SRCCODE_ANCHOR, TC.OpeningTags, "A");
492                    extraSrcCodeAnchorHTMLVecs[i], AUM.Set, EXTRA_ANCHOR_POS_ARR, "href",
493                    extraAnchorURLs[i], SD.SingleQuotes
494                );
495            }
496        }
497
498
499        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
500        // STEP FOUR: Insert / replace the correct the 'line-count' and 'file-size' TextNodes.
501        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
502
503        // int TXTNODE_COUNT_POS =
504        //      TextNodeFind.first(SRCCODE_ANCHOR, TextComparitor.CN, "File Size");
505        TextNode counts = (TextNode) srcCodeAnchorHTMLVec.elementAt(TXTNODE_COUNT_POS);
506
507        String[] countsTextArr = new String[]
508        { StringParse.commas(jdhf.typeSizeChars), StringParse.commas(jdhf.typeLineCount) };
509
510        counts = new TextNode(StrReplace.r(counts.str, replaceArr, countsTextArr));
511
512        // int TXTNODE_COUNT_POS =
513        //      TextNodeFind.first(SRCCODE_ANCHOR, TextComparitor.CN, "File Size");
514        srcCodeAnchorHTMLVec.setElementAt(counts, TXTNODE_COUNT_POS);
515
516        if (hasExtra) for (int i=0; i < numExtra; i++)
517        {
518            // int EXTRA_TXTNODE_COUNT_POS =
519            //      TextNodeFind.first(EXTRA_SRCCODE_ANCHOR, TextComparitor.CN, "File Size");
520            counts = (TextNode) extraSrcCodeAnchorHTMLVecs[i].elementAt(EXTRA_TXTNODE_COUNT_POS);
521
522            int fileSize = extraSrcCodeAsStrs[i].length();
523            int newLines = StringParse.countCharacters(extraSrcCodeAsStrs[i], '\n');
524    
525            countsTextArr = new String[]
526            { StringParse.commas(fileSize), StringParse.commas(newLines) };
527    
528            counts = new TextNode(StrReplace.r(counts.str, replaceArr, countsTextArr));
529
530            // int EXTRA_TXTNODE_COUNT_POS =
531            //      TextNodeFind.first(EXTRA_SRCCODE_ANCHOR, TextComparitor.CN, "File Size");
532            extraSrcCodeAnchorHTMLVecs[i].setElementAt(counts, EXTRA_TXTNODE_COUNT_POS);
533        }
534
535
536        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
537        // STEP FIVE: Insert The file-name(s) themselves (as a TextNode)
538        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
539
540        // NOTE: If you try to get rid of the "EffectivelyFinal" Cheat-Hack Class, it isn't the
541        //       "new TextNode" (since it is only one String, and only created twice) in the
542        //       ReplaceNodes.r internal-loop...
543        //
544        // BUT-RATHER: It is the 'i' in the loop below that also needs the "EffectivelyFinal" hack.
545
546        final EffectivelyFinal<TextNode> sourceFileNameTxN
547            = new EffectivelyFinal<>(new TextNode(printedFileName));
548
549        ReplaceNodes.r(
550            // int[] FILENAME_POS_ARR =
551            //      TextNodeFind.all(SRCCODE_ANCHOR, TextComparitor.EQ, "SRCCODE_FILE_NAME");
552            srcCodeAnchorHTMLVec, FILENAME_POS_ARR,
553            (HTMLNode n, int dummy1, int dummy2) -> sourceFileNameTxN.f
554        );
555
556        if (hasExtra) for (int i=0; i < numExtra; i++)
557        {
558            // The text that is used for between the <A HREF=...> TEXT </A> link is called the
559            // "classNameLabel".  It is a final field inside the ExtraSrcLink inner-class at the
560            // top of this class.  If this is hard to keep straight which label or text is being
561            // placed where ==> The 'classNameLabel' would be "searchLoops/TxNFind.java" (if you
562            // were talking about the extra-links in the NodeSearch Package).
563            //
564            // If you were talking about the GlassFish extra links from the java.json package, this
565            // would be the one that reads "org/glassfish/Json.java"  text-string.
566
567            sourceFileNameTxN.f = new TextNode(extraLinks.get(i).classNameLabel);
568
569            ReplaceNodes.r(
570                // int[] EXTRA_FILENAME_POS_ARR = TextNodeFind.all
571                //      (EXTRA_SRCCODE_ANCHOR, TextComparitor.EQ, "SRCCODE_FILE_NAME");
572                extraSrcCodeAnchorHTMLVecs[i], EXTRA_FILENAME_POS_ARR,
573                (HTMLNode n, int dummy1, int dummy2) -> sourceFileNameTxN.f
574            );
575        }
576
577
578        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
579        // STEP SIX: Add the "File-Title" <SPAN>'s.  This ONLY HAPPENS when there are Extra-Files
580        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
581
582        if (hasExtra)
583        {
584            // int EXTRA_LINK_INSERTION_POS =
585            //      TagNodeFind.last(SRCCODE_ANCHOR, TC.ClosingTags, "div");
586            int insertionPos = EXTRA_LINK_INSERTION_POS;
587
588            // NOTE: ORDER IS KIND OF IMPORTANT BELOW... THE POINTERS GO STALE!
589            // STALE VECTOR-INDICES: Always insert the stuff at the end *FIRST*.
590            for (int i=0; i < numExtra; i++)
591            {
592                // int SECOND_DESCRIPTION_TXTNODE_POS = TextNodeFind.first
593                //      (EXTRA_SRCCODE_ANCHOR, (String text) -> text.equals("DESCRIPTION"));
594                extraSrcCodeAnchorHTMLVecs[i].setElementAt(
595                    new TextNode(extraLinks.get(i).titleBarLabel),
596                    SECOND_DESCRIPTION_TXTNODE_POS
597                );
598
599                // Do this after previous line!
600                srcCodeAnchorHTMLVec.addAll(insertionPos, extraSrcCodeAnchorHTMLVecs[i]);
601
602                // NOTE: move this forward...
603                insertionPos += extraSrcCodeAnchorHTMLVecs[i].size();
604            }
605
606            // STALE-INDEX-MAIN-POINT: This is the part THAT HAS TO BE DONE LAST - because the
607            // index-pointer "INSERT_FIRST_DESCRIPTION_POS" points to the beginning of the
608            // "srcCodeAnchorHTMLVec" - and *THAT* Vector is the whole HiLited-Source-Code HTML
609            // Vector.
610
611            // Vector<HTMLNode> FIRST_DESCRIPTION = HTMLPage.getPageTokens
612            //      ("<BR /><SPAN CLASS=SRCCODETITLE>DESCRIPTION</SPAN><BR />\n", false);
613            @SuppressWarnings("unchecked")
614            Vector<HTMLNode> firstDescription = (Vector<HTMLNode>) FIRST_DESCRIPTION.clone();
615
616            // int FIRST_DESCIPTION_TXTNODE_POS = TextNodeFind.first
617            //      (FIRST_DESCRIPTION, (String text) -> text.equals("DESCRIPTION"));
618            firstDescription.setElementAt
619                (new TextNode("This File's Source Code:"), FIRST_DESCIPTION_TXTNODE_POS);
620
621            // int INSERT_FIRST_DESCRIPTION_POS =
622            //      TagNodeFind.first(SRCCODE_ANCHOR, TC.OpeningTags, "ul");
623            srcCodeAnchorHTMLVec.addAll(INSERT_FIRST_DESCRIPTION_POS, firstDescription);
624        }
625
626        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
627        // STEP SEVEN: INSERT (This used to be complicated, finding an insertion-point)
628        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
629
630        pageDLs.addAll(srcCodeAnchorHTMLVec);
631
632
633        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
634        // STEP EIGHT: Return Stats-Information
635        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
636
637        if (Messager.isVerbose()) Messager.println(
638            "    HiLited '.java' Source-Code File  successfully to disk: " +
639            C.BYELLOW + hiLitedSrcCodeFileName + C.RESET
640        );
641
642        // true is that the file was successfully hilited.  "NavButtons" needs to know
643        return true;
644    }
645
646
647    // ********************************************************************************************
648    // ********************************************************************************************
649    // HiLite a Source-Code File
650    // ********************************************************************************************
651    // ********************************************************************************************
652
653
654    // The CSS Class HLSCF => HiLited Source-Code File
655    private static final TagNode MAIN_DIV = new TagNode("<DIV CLASS=HLSCF>");
656
657    // This hilites a '.java' file, and saves the hilited file to disk.
658    // This is a separate method, because it might be done more than once - if there are extra
659    // files to hilite.
660    //
661    // NOTE: 'name' comes from jdhf.name - It *IS NOT* jdhf.fullyQualifiedName anymore!
662    //
663    // Since this is private, this method may only be called from this class.  This method 
664    // *DOES NOT* reset the Messager's 'processorName' because it is done at the top of the main
665    // method of this class.
666    //
667    // MESAGER ONLY USES: errorAndThrowJDE
668
669    private static void hiLiteSrcCodeFile(
670            Upgrade u, String name, String hiLitedSrcCodeFileName, String srcCodeAsStr,
671            String dotDots
672        )
673    {
674        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
675        // Invoke HiLite.ME on SourceCode
676        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
677
678        Vector<HTMLNode> hiLitedSource = null;
679
680        try
681            { hiLitedSource = u.hiLiter.hiLite(srcCodeAsStr, "java", false); }
682
683        catch (Exception e)
684        {
685            // NOTE: This error needs to be thought of as "more likely" my error.  Unfortunately,
686            //       for now, this method does not differentiate between the UNEXPECTED_ERROR and
687            //       ERROR.
688            //
689            // This used to be a 'return false', but that causes 'return null' which causes
690            // MainFilesProcessor to return false.  It is easier to read this as below... it has
691            // been switched to throw new JavaDocError()
692
693            Messager.assertFailGeneralPurpose(
694                e,
695                "There was a problem attempting to read either the Server 'http://HiLite.ME', " +
696                "or the File generated an Exception when trying to read from the Cache. " +
697                "Currently HiLiting File:\n" +
698                "    [" + C.BYELLOW + hiLitedSrcCodeFileName + C.RESET + "]\n",
699                null
700            );
701        }
702
703
704        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
705        // Insert all the "add-ons" to the hilited source-code '.java.html' file
706        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
707
708        try
709        {
710            // The First Element is a <DIV>, replace this <DIV> with the pre-instantiated and
711            // final static <DIV> TagNode that is listed directly above this method.
712            hiLitedSource.setElementAt(MAIN_DIV, 0);
713
714            // Insert Header and Footer
715            hiLitedSource.addAll(0, retrieveHEADER(name));
716            hiLitedSource.addAll(PAGE_FOOTER);
717
718            // Insert the CSS Relative-URL link into hilited-source-code HTML file vector
719            Features.insertCSSLink(hiLitedSource, dotDots + Upgrade.JAVA_DOC_CSS_FILE_NAME);
720            Messager.ifVPrintln("    Inserted CSS Link");
721
722            // Check for favicon request, and insert  into hilited-source-code HTML file vector
723            if (u.faviconImageFileName != null)
724            {
725                Features.insertFavicon(hiLitedSource, dotDots + u.faviconImageFileName);
726                Messager.ifVPrintln("\tInserted Favicon");
727            }
728
729            // Save the HiLited Source Code String to disk.
730            // System.out.println("Writing File: " + C.BYELLOW + hiLitedSrcCodeFileName + C.RESET);
731            FileRW.writeFile(Util.pageToString(hiLitedSource), hiLitedSrcCodeFileName);
732        }
733        catch (Exception e)
734        {
735            // Using "ERROR", rather than "UNEXPECTED_ERROR" because this is more likely the
736            // O/S or the User's Mistake.  The try=code just inserts HTML into a Vector, and
737            // writes the file to disk.
738            //
739            // Recently changed to Messager.errorThrowJDE
740
741            Messager.assertFailGeneralPurpose(
742                e,
743                "Failed to write HiLited Source-Code File:\n" +
744                "    [" + C.BYELLOW + hiLitedSrcCodeFileName + C.RESET + "]",
745                null
746            );
747        }
748    }
749
750
751    // ********************************************************************************************
752    // ********************************************************************************************
753    // Retrieve the CONFIGURATION-CLASS-FILE from the 'upgrade-files/' directory
754    // ********************************************************************************************
755    // ********************************************************************************************
756
757
758    // This is used internally the StrReplce -> Prepend Char Replacement Method.  This is used
759    // inside the method below ==> Strictly used for error-messages.
760
761    static final char[] TABS_NEWLINES = { '\t', '\n', '\r' };
762
763    // Helper for error message printing
764    static final String LIST_MSG(int i)
765    { return "static field LIST[" + C.BGREEN + i + C.RESET + "]"; }
766
767    // Helper for error message printing
768    static final String LIST_MSG(int i, int j)
769    { return "static field LIST[" + C.BGREEN + i + C.RESET + "][" + C.BGREEN + j + C.RESET + "]"; }
770
771    static final String LIST_MSG(int i, int j, String elem)
772    { return LIST_MSG(i, j) + ": [\"" + C.BCYAN + elem + C.RESET + "\"]\n"; }
773
774    // MESSAGER: 
775    //  1) SETS:        processorName & fileName
776    //  2) INVOKES:     errors are logged using either 'errorExitingNow' or 'errorContinue'.
777    //  3) RETURNS:     Any error's => return null.  Not Found/Present => return null.
778    //  4) INVOKED-BY:  MainFilesProcessor - part *BEFORE* Main Loop, called only ONCE.
779    //  5) EXPECTS:     NOTHING
780    //  6) THROWS:      No explicity throws
781    //
782    // RETURN: This method returns 'null' if there isn't any defined extra-hilite Config-Class
783    //         defined in the user's /upgrade-files/config-classes directory.
784    //
785    //         This method also returns 'null' if there were any errors that were sent to the
786    //         Messager.  If this happens, when MainFilesProcessor checks if errors occured, it
787    //         exits immediately.
788
789    static final TreeMap<String, ArrayList<ExtraSrcLink>> readConfigurationClassFromDisk
790        (String packageName, TreeSet<String> packageCIETList, String[] rootSourceFileDirectories)
791    {
792        Messager.setProcessorName("HiLiiteSrcCodeFile");
793
794
795        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
796        // FIND IF "upgrade-files/ExtraHiLitedSrc.class" EXISTS for PACKAGE anywhere in CLASSPATH
797        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
798
799        String extraLinksPartialClassFileName = packageName.replace(".", File.separator) +
800            File.separator + "upgrade-files" + File.separator + "config-classes" + File.separator +
801            "ExtraHiLitedSrc.class";
802
803        // Search the CLASS-PATH for:
804        // "full/package/name/upgrade-files/config-classes/ExtraHiLitedSrc.class"
805        //
806        // NOTE: This call doesn't cause any exceptions, nor does it use the messager
807        //
808        // PARAMETER: 'true' means this is a 'file' (not a directory)
809
810        String extraLinksClassFileName = HELPER.findFileNameInClassPath
811            (extraLinksPartialClassFileName, rootSourceFileDirectories, true);
812
813        if (extraLinksClassFileName != null) 
814        {
815            Messager.println(
816                "Found 'ExtraHiLitedSrc' Configuration-Class: " +
817                "[" + C.BYELLOW + extraLinksClassFileName + C.RESET + "]"
818            );
819
820            Messager.setCurrentFileName
821                (extraLinksClassFileName, "User-Provided Extra-Src-Hiliting Configuration-Class");
822        }
823        else
824        {
825            // Finish the "Opening Statement" from the 'm.print' before the previous loop started
826            if (Messager.isVerbose()) Messager.println(" ... Not Found");
827            return null;
828        }
829
830
831        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
832        // Load the 'ExtraHiLitedSrc' Class into instance of java.lang.Class - TWO POSSIBLE NAMES
833        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
834
835        Class<?> extraLinksClass = null;
836
837        // These are the two user options for the name of the package.
838        String[] cNamesArr = new String[] { packageName + ".ExtraHiLitedSrc", "ExtraHiLitedSrc" };
839
840        try 
841            { extraLinksClass = FileRW.readClass(extraLinksClassFileName, cNamesArr); }
842
843        catch (TypeNotPresentException e)
844        {
845            Messager.errorExitingNow(e, Messager.TNPE);
846            return null;
847        }
848
849        catch (Exception e)
850        {
851            Messager.errorExitingNow
852                (e, "Unexpected exception thrown attempting to read Configuration-Class");
853
854            return null;
855        }
856
857
858        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
859        // Retrieve the 2-D String-Array of User-Provided "Extra-Links", and error check.
860        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
861
862        java.lang.reflect.Field listAsField = HELPER.getStaticField
863            (extraLinksClass, "LIST", String[][].class, extraLinksClassFileName);
864
865        if (listAsField == null) return null;
866
867        String[][] list = (String[][]) HELPER.getStaticFieldEvaluation
868                (listAsField, extraLinksClassFileName);
869
870        if (list == null) return null;
871
872
873        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
874        // Check for User-Error in the User-Provided 2-D String[] Array
875        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
876
877        boolean errors = false;
878
879        for (int i=0; i < list.length; i++)
880        {
881            String[] extraLink = list[i];
882
883            if (extraLink == null)
884            {
885                Messager.errorContinue(LIST_MSG(i) + " is null");
886                errors=true;
887                continue;
888            }
889
890            if (extraLink.length != 4)
891            {
892                Messager.userErrorContinue(
893                    LIST_MSG(i) + " has length " +
894                    "[" + C.BRED + ("" + extraLink.length) + C.RESET + "], but this should be " +
895                    "precisely 4:\n    " +
896                    "arr[0] => CIET-Name, arr[1] => Java-Src-File-Name, arr[2] => Link-Title, " +
897                    "arr[3] => Link-Text"
898                );
899
900                errors=true;
901                continue;
902            }
903
904            // Iterate each of the four string's inside of the "Extra Link Configuration"
905            // Just make sure none are null, and none contain tabs or newlines.
906
907            for (int j=0; j < extraLink.length; j++)
908
909                if (extraLink[j] == null)
910                {
911                    Messager.errorContinue(LIST_MSG(i, j) + " is null.");
912                    errors=true;
913                    continue;
914                }
915                else if (StrCmpr.containsXOR(extraLink[j], "\n", "\r", "\t"))
916                {
917                    Messager.errorContinue(
918                        LIST_MSG(i, j, StrReplace.r(extraLink[j], TABS_NEWLINES, '\\')) +
919                        "contains a tab ('\\t') or a new-line character ('\\n', '\\r'), but " +
920                        "this is not allowed."
921                    );
922
923                    errors=true;
924                    continue;
925                }
926
927            // Check that the CIET/Type provided by the user is a valid java identifier
928            if (! StringParse.isJavaTypeStr(extraLink[0]))
929            {
930                Messager.errorContinue(
931                    LIST_MSG(i, 0, extraLink[0]) +
932                    "This String is not a valid Java Type Identifier."
933                );
934
935                errors=true;
936                continue;
937            }
938
939            // NEW ADDITION: Make sure the type is lited in the known types for this package.
940            if (! packageCIETList.contains(extraLink[0]))
941            {
942                Messager.errorContinue(
943                    LIST_MSG(i, 0, extraLink[0]) +
944                    "This is not one of the types listed in the 'package-summary.html' file for " +
945                    "this package"
946                );
947
948                errors=true;
949                continue;
950            }
951
952            // Check that the Title-Bar Label is not going to overflow the screen
953            // This is the descriptive title above the link!
954
955            if (extraLink[1].length() > 80)
956            {
957                Messager.errorContinue(
958                    LIST_MSG(i, 1, extraLink[1]) +
959                    "has a String-Length [" + C.BRED + extraLink[2].length() + C.RESET + "], " +
960                    "but it may not be over 80"
961                );
962
963                errors=true;
964                continue;
965            }
966
967            if (Messager.reachedMaxErrorsOrWarnings()) return null;
968        }
969
970        if (errors) return null;
971
972
973        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
974        // Convert 'LIST' 2D-String[][] Array Field into TreeMap<String, ArrayList<ExtraSrcLink>>
975        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
976
977        TreeMap<String, ArrayList<ExtraSrcLink>> ret = new TreeMap<>();
978
979        for (String[] extraLink : list)
980        {
981            // Since each CIET/Type may have more than "Extra HiLited Source Code Link", the
982            // TreeMap contains ArrayList<ExtraSrcLink> *RATHER THAN* just ExtraSrcLink.
983
984            ArrayList<ExtraSrcLink> linksAL = ret.get(extraLink[0]);
985
986            if (linksAL == null)
987                ret.put(extraLink[0], linksAL = new ArrayList<>());
988
989            linksAL.add(new ExtraSrcLink(extraLink));
990        }
991
992        return ret;
993    } 
994}