001package Torello.JavaDoc;
002
003import Torello.Java.*;
004import Torello.HTML.*;
005import Torello.JDUInternal.ClassUpgradeData.*;
006
007import Torello.JDUInternal.Features.STATS.StatsInternal;
008
009import Torello.JDUInternal.Throwables.HiLiteError;
010import Torello.JDUInternal.Throwables.JavaDocError;
011import Torello.JDUInternal.Throwables.ParseException;
012import Torello.JDUInternal.Throwables.ReachedMaxErrors;
013
014import static Torello.Java.C.*;
015
016import Torello.JDUInternal.Messager.Messager;
017import Torello.JDUInternal.Messager.MsgControl;
018import Torello.JDUInternal.Messager.MsgVerbose;
019
020import Torello.JDUInternal.SimpleFeatures.LinksChecker;
021
022import Torello.HTML.Tools.SyntaxHiLite.HiLiteCache;
023import Torello.HTML.Tools.Images.IF;
024
025import Torello.Java.ReadOnly.ReadOnlyMap;
026import Torello.Java.ReadOnly.ReadOnlyList;
027import Torello.Java.ReadOnly.ReadOnlyArrayList;
028
029import Torello.Java.Build.BuildPackage;
030
031import java.util.*;
032import java.io.*;
033import java.util.stream.*;
034import java.util.function.*;
035
036
037/**
038 * The primary builder and configuration class for the Java Doc Upgrade Process, having many
039 * customizations that may be requested using the customize-settings methods available here.
040 * 
041 * <EMBED CLASS='external-html' DATA-FILE-ID=UPGRADE>
042 */
043public class Upgrade
044{
045    // ********************************************************************************************
046    // ********************************************************************************************
047    // Public-Static Constant/Final Fields
048    // ********************************************************************************************
049    // ********************************************************************************************
050
051
052    /** The name of the favicon-file (without extension).  This filename may not be changed. */
053    public static final String FAVICON_FILE_NAME = "favicon";
054
055    /** The name of the (very brief) {@code '.js'} file. */
056    public static final String JAVA_SCRIPT_FILE_NAME = "JDU.js";
057
058
059    // ********************************************************************************************
060    // ********************************************************************************************
061    // Private Fields (all are final/Constants - except "Stats")
062    // ********************************************************************************************
063    // ********************************************************************************************
064
065
066    // These are acctually for Internal-Use, and shouldn't be public.  Unfortunately due to Java's
067    // leaving out the 'friend' key-word from C/C++, these have to be public in order to share them
068    // with the Internal-Only Packages in JavaDoc-Upgrader.
069
070    private final UpgradePredicates.Builder  predicatesBuilder  = new UpgradePredicates.Builder();
071    private final UpgradeSettings.Builder    settingsBuilder    = new UpgradeSettings.Builder();
072    private final PathsAndTypes.Builder      pathsTypesBuilder  = new PathsAndTypes.Builder();
073
074    // Used by the log-file header string below
075    private final String dateTimeStr =
076        StringParse.dateStr('-') + " " + StringParse.timeStr(':');
077
078    // Prepended to the log-file that may (or may not) be saved to disk, or an Appendable
079    private final String logFileHeader = 
080        "<HTML>\n<HEAD>\n" +
081        "<TITLE>Log " + dateTimeStr + "</TITLE>\n" +
082        "<STYLE type='text/css'>\n" + C.getCSSDefinitions() + "\n</STYLE>\n" +
083        "</HEAD>\n" +
084        "<BODY STYLE='margin: 1em; background: black; color: white;'>\n\n" +
085        "<H1>JavaDoc Upgrader Log</H1>\n" +
086        "<CODE>" + dateTimeStr + "</CODE>\n" +
087        "<PRE>\n\n";
088
089    // used internally
090    private static final TextNode NEWLINE = new TextNode("\n");
091
092    // NOTE: This isn't final....  It can be re-constructed in this class....
093    private StatsInternal stats = new StatsInternal();
094
095
096    // ********************************************************************************************
097    // ********************************************************************************************
098    // Building / Constructor, Running the Upgrader
099    // ********************************************************************************************
100    // ********************************************************************************************
101
102
103    /**
104     * This returns a new instance of this class.  It will have all empty and null settings, except
105     * the root-directory descriptors.  It must be initialized with the various builder methods.
106     * 
107     * <BR /><BR />
108     * This constructor must tell the Upgrader (Builder) which directory contains {@code '.java'}
109     * Source-Files, and which directory shall contain Java-Doc Generated HTML Documentation Pages.
110     * 
111     * @param rootJavaDocDirectory This is the output directory that was used for the last call to
112     * the JavaDoc Utility.  The Upgrade Logic should expect to find all class, interface and
113     * enumerated types to be hilited in this directory.  This parameter may not be null.
114     * 
115     * @param rootSourceFileDirectories This is the location where the {@code '.java'} source files
116     * for the classes, interfaces and enumerated types named by your list files are stored.  This
117     * parameter may not be null; at least one directory must be passed.  If you have multiple
118     * source-code directories, then pass all of them, and whenever a JavaDoc {@code '.html'} file
119     * is loaded from disk, all source-code directories will be searched until the source-code is
120     * found.
121     * 
122     * @throws UpgradeException This exception will throw if either of these directories cannot be
123     * found, or may not be accessed.  The {@code 'getCause()'} method of the exception will
124     * provide more details of the specific error that occurred.
125     */
126    public Upgrade(String rootJavaDocDirectory, String... rootSourceFileDirectories)
127    {
128        // System.out.println("rootJavaDocDirectory = [" + rootJavaDocDirectory + "]\n" +
129        //                    "rootSourceFileDirectory = [" + rootSourceFileDirectory + "]");
130
131        Objects.requireNonNull(
132            rootJavaDocDirectory,
133            "You have passed 'null' to parameter 'rootJavaDocDirectory'"
134        );
135
136        Objects.requireNonNull(
137            rootSourceFileDirectories,
138            "You have passed 'null' to parameter 'rootSourceFileDirectories'"
139        );
140
141        // NOTE: Java seems to have no problem with '.' and '..' inside of a File-Name
142        if (StrCmpr.containsOR(rootJavaDocDirectory, "*", "?"))
143
144            throw new UpgradeException(
145                "The Root JavaDoc Directory String you have passed contains either the '*' " +
146                "character, or the '?', but these is not allowed."
147            );
148
149        // Check for these errors inside the Root Source Directories too.
150        for (String s : rootSourceFileDirectories) if (StrCmpr.containsOR(s, "*", "?"))
151
152            throw new UpgradeException(
153                "One of the Root Source Directory Strings that you have passed contains either " +
154                "the '*' character, or the '?', but these is not allowed."
155            );
156
157        if (rootSourceFileDirectories.length == 0) throw new UpgradeException(
158            "You have not passed any source-code directories to parameter " +
159            "'rootSourceFileDirectories'"
160        );
161
162        /*
163        // THIS HAS TO BE MOVED / COPIED
164        if (rootJavaDocDirectory.length() > 0)
165            UpgradeException.checkFileExistsAndCanAccess
166                (rootJavaDocDirectory, "Root '.html' Java-Doc Documentation Page Directory");
167        */
168
169        for (String rootSourceDir : rootSourceFileDirectories)
170            if (rootSourceDir.length() > 0)
171                UpgradeException.checkFileExistsAndCanAccess
172                    (rootSourceDir, "Root '.java' Source File Directory");
173
174        // Easier to type "rsd" in the code below
175        String[] rsd = new String[rootSourceFileDirectories.length];
176
177
178        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
179        // Ensure File.separator is at the end of each of these directory-names - except dir ""
180        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
181        //
182        // If the directory is the "Current Working Directory" - which is specified by the
183        // Zero-Length-String (a.k.a ""), then a trailing File.separator should **NOT** be added.
184        // All other directory names must end with the File.separator
185        //
186        // NOTE: For checking that a directory exists and can be accessed (the previous lines of
187        //       code), the class java.io.File doesn't require that there be a trailing File
188        //       Separator.  However, in this package (the JD-Upgrader Package), appending file
189        //       names to these root-directories mandates that the File-Separator be present at the
190        //       end of each of these directory-names - except, of course, the "" directory - which
191        //       is the "Current Working Directory."
192        //
193        // SPECIFICALLY: Later on when these Upgrade-Fields are actually used by the class
194        //               "MainFilesProcessor" - making sure these directory-names end with '/' is
195        //               where this stuff actually comes into play
196
197        for (int i=0; i < rootSourceFileDirectories.length; i++)
198
199            // DON'T FORGET: This *DOES NOT* end with File.separator - but it doesn't have to!
200            if (rootSourceFileDirectories[i].length() == 0)
201                rsd[i] = "";
202
203            else if (rootSourceFileDirectories[i].endsWith(File.separator))
204                rsd[i] = rootSourceFileDirectories[i];
205
206            else
207                rsd[i] = rootSourceFileDirectories[i] + File.separator;
208
209        // if (rsd == null) System.out.println("rsd is null!!!");
210        // else System.out.println(Arrays.toString(rsd));
211        this.pathsTypesBuilder.rootSourceFileDirectories = ReadOnlyList.of(rsd);
212
213
214        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
215        // Make sure the File.separator is here too, unless Root-JD-Dir is the CWD (the "" dir)
216        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
217
218        // This *DOES NOT* end with File.separator - but it doesn't have to!
219        if (rootJavaDocDirectory.length() == 0)
220            this.pathsTypesBuilder.rootJavaDocDirectory = "";
221
222        else if (rootJavaDocDirectory.endsWith(File.separator))
223            this.pathsTypesBuilder.rootJavaDocDirectory = rootJavaDocDirectory;
224
225        else
226            this.pathsTypesBuilder.rootJavaDocDirectory = rootJavaDocDirectory + File.separator;
227    }
228
229    /**
230     * <EMBED CLASS='external-html' DATA-FILE-ID=U_UPGRADE>
231     * 
232     * @return This returns the statistics computed for the upgrade process.  See class
233     * {@link Stats} for more information.  A complete listing of the information contained by
234     * the tables in a {@code 'Stats'} instance may be viewed by clicking the {@code 'Stats'}
235     * link at the top-right of this page.
236     * 
237     * <BR /><BR />If the Upgrade-Process had unrecoverable errors, null is returned.
238     */
239    public Stats upgrade()
240    {
241        if (this.pathsTypesBuilder.rootJavaDocDirectory.length() > 0)
242
243            UpgradeException.checkFileExistsAndCanAccess(
244                this.pathsTypesBuilder.rootJavaDocDirectory,
245                "Root '.html' Java-Doc Documentation Page Directory"
246            );
247
248        final UpgradePredicates   predicates  = this.predicatesBuilder.build();
249        final UpgradeSettings     settings    = this.settingsBuilder.build();
250
251        try
252        {
253            final ReadOnlyMap
254            <
255                String,
256                Torello.JDUInternal.MainJDU.Preliminary.D1_PkgFrameAndSummFiles
257            >
258            parsedPkgSummaryFiles = Torello.JDUInternal.MainJDU.Preliminary.API_Bootstrap
259                .run(settings, pathsTypesBuilder);
260
261            if (MsgControl.hadErrors()) return null;
262
263            final PathsAndTypes pathsTypes = this.pathsTypesBuilder.build();
264
265            if (MsgControl.hadErrors()) return null;
266
267            Torello.JDUInternal.MainJDU.API_LoopJavaPackages
268                .run(predicates, settings, pathsTypes, stats, parsedPkgSummaryFiles);
269
270            if (MsgControl.hadErrors()) return null;
271
272            Torello.JDUInternal.Features.STATS.S_ToHTML_Part1
273                .saveStatsHTMLFile(pathsTypes.rootJavaDocDirectory, this.stats);
274        }
275
276        catch (ReachedMaxErrors rme)
277        { System.out.println("Reached Maximum Number of Errors.  Exiting"); return null; }
278
279        // Currently Ignoring: HiLiteException
280        catch (JavaDocError | HiLiteError | ParseException | UpgradeException e)
281        {
282            System.out.println(e.getClass().getSimpleName() + " thrown.  Exiting.\n");
283            return null;
284        }
285
286        catch (Throwable t)
287        {
288            System.out.println(
289                '\n' +
290                "****************************************************************\n" +
291                EXCC.toString(t) +
292                "****************************************************************\n" +
293                "Unexpected Throw, Exiting."
294            );
295
296            return null;
297        }
298
299        finally
300        {
301            if (this.settingsBuilder.hlCache != null)
302                this.settingsBuilder.hlCache.persistMasterHashToDisk();
303        }
304
305        if (settings.linksChecker != null) settings.linksChecker.runCheck();
306
307        return new Stats(this.stats, null);
308    }
309
310    /**
311     * <EMBED CLASS='external-html' DATA-FILE-ID=U_MAIN>
312     * @param argv This is the argument received from the command line.
313     */
314    public static void main(String[] argv) throws Exception
315    {
316        Upgrade upgrader = new Upgrade(argv[0], "");
317
318        if (argv.length == 1) upgrader.upgrade();
319
320        else if (argv.length == 2)
321        {
322            java.io.File f = new java.io.File(argv[1]);
323
324            if (! f.exists())
325            {
326                f.mkdirs();
327                HiLiteCache.initializeOrClear(argv[1], null);
328            }
329
330            HiLiteCache CACHE = new HiLiteCache(argv[1]);
331            upgrader.useHiLiterCache(CACHE).upgrade();
332            CACHE.persistMasterHashToDisk();
333        }
334
335        else System.out.println("Failed, expected one or two arguments");
336    }
337
338
339    // ********************************************************************************************
340    // ********************************************************************************************
341    // Filter-Predicates
342    // ********************************************************************************************
343    // ********************************************************************************************
344
345
346    /**  Write an explanation*/
347    @SuppressWarnings("unchecked")
348    public Upgrade setRemoveAllDetailsFilter(Predicate<? super String> cietCanonicalNameFilter)
349    {
350        this.predicatesBuilder.removeAllDetailsFilter =
351            (Predicate<String>) cietCanonicalNameFilter;
352
353        return this;
354    }
355
356    /**
357     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_REM_ALL_DET_F>
358     * @param entity Specifies which HTML Detail-Section to remove.
359     * @param cietCanonicalNameFilter <EMBED CLASS='external-html' DATA-FILE-ID=U_CCNF>
360     */
361    public Upgrade setRemoveAllDetailsFilter
362        (Entity entity, Predicate<? super String> cietCanonicalNameFilter)
363    {
364        @SuppressWarnings("unchecked")
365        Predicate<String> p = (Predicate<String>) cietCanonicalNameFilter;
366
367        switch (Objects.requireNonNull(entity))
368        {
369            case METHOD:
370                this.predicatesBuilder.removeAllMethodDetailsFilter = p;
371                return this;
372
373            case CONSTRUCTOR:
374                this.predicatesBuilder.removeAllConstructorDetailsFilter = p;
375                return this;
376
377            case FIELD:
378                this.predicatesBuilder.removeAllFieldDetailsFilter = p;
379                return this;
380
381            case ENUM_CONSTANT:
382                this.predicatesBuilder.removeAllECDetailsFilter = p;
383                return this;
384
385            case ANNOTATION_ELEM:
386                this.predicatesBuilder.removeAllAEDetailsFilter = p;
387                return this;
388
389            case INNER_CLASS:
390                throw new IllegalArgumentException(
391                    "You have passed Entity.INNER_CLASS to parameter 'entity', but JavaDoc HTML " +
392                    "Web-Pages do not have a Nested-Type / Inner-Class Details-Section to remove " +
393                    "in the first place."
394                );
395
396            default: throw new UnreachableError();
397        }
398    }
399
400    /**
401     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_HL_ALL_DET_F>
402     * @param cietCanonicalNameFilter <EMBED CLASS='external-html' DATA-FILE-ID=U_CCNF>
403     */
404    public Upgrade setHiLiteAllDetailsFilter
405        (Entity entity, Predicate<? super String> cietCanonicalNameFilter)
406    {
407        @SuppressWarnings("unchecked")
408        Predicate<String> p = (Predicate<String>) cietCanonicalNameFilter;
409
410        switch(Objects.requireNonNull(entity))
411        {
412            case METHOD:
413                this.predicatesBuilder.hiLiteAllMethodsFilter = p;
414                return this;
415
416            case CONSTRUCTOR:
417                this.predicatesBuilder.hiLiteAllConstructorsFilter = p;
418                return this;
419
420            case FIELD:
421                this.predicatesBuilder.hiLiteAllFieldsFilter = p;
422                return this;
423
424            case ENUM_CONSTANT:
425                this.predicatesBuilder.hiLiteAllECsFilter = p;
426                return this;
427
428            case ANNOTATION_ELEM:
429                this.predicatesBuilder.hiLiteAllAEsFilter = p;
430                return this;
431
432            case INNER_CLASS:
433                throw new IllegalArgumentException();
434
435            default: throw new UnreachableError();
436        }
437    }
438
439    /**
440     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_SUMM_REM_F>
441     * @param cietCanonicalNameFilter <EMBED CLASS='external-html' DATA-FILE-ID=U_CCNF>
442     */
443    @SuppressWarnings("unchecked")
444    public Upgrade setSummaryRemoveFilter(Predicate<? super String> cietCanonicalNameFilter)
445    {
446        this.predicatesBuilder.summaryRemoveFilter = (Predicate<String>) cietCanonicalNameFilter;
447        return this;
448    }
449
450    /**
451     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_HL_SCF_F>
452     * @param cietCanonicalNameFilter <EMBED CLASS='external-html' DATA-FILE-ID=U_CCNF>
453     */
454    @SuppressWarnings("unchecked")
455    public Upgrade setHiLiteSourceCodeFileFilter(Predicate<? super String> cietCanonicalNameFilter)
456    {
457        this.predicatesBuilder.hiLiteSourceCodeFileFilter =
458            (Predicate<String>) cietCanonicalNameFilter;
459
460        return this;
461    }
462
463    /**
464     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_CSS_TAGS_F>
465     * @param cietCanonicalNameFilter <EMBED CLASS='external-html' DATA-FILE-ID=U_CCNF>
466     */
467    @SuppressWarnings("unchecked")
468    public Upgrade setCSSTagsFilter(Predicate<? super String> cietCanonicalNameFilter)
469    {
470        this.predicatesBuilder.cssTagsFilter = (Predicate<String>) cietCanonicalNameFilter;
471        return this;
472    }
473
474    /**
475     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_VAL_HTML_F>
476     * @param cietCanonicalNameFilter <EMBED CLASS='external-html' DATA-FILE-ID=U_CCNF>
477     */
478    @SuppressWarnings("unchecked")
479    public Upgrade setValidateHTMLFilter(Predicate<? super String> cietCanonicalNameFilter)
480    {
481        this.predicatesBuilder.validateHTMLFilter = (Predicate<String>) cietCanonicalNameFilter;
482        return this;
483    }
484
485
486    // ********************************************************************************************
487    // ********************************************************************************************
488    // Setting Features: Output Printing and Log-File
489    // ********************************************************************************************
490    // ********************************************************************************************
491
492
493    /**
494     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_VERBOSITY_LVL>
495     * @param verbosity One of the four available {@link Verbosity} constants.
496     */
497    public Upgrade setVerbosityLevel(Verbosity verbosity)
498    {
499        this.settingsBuilder.verbosityLevel = verbosity.level;
500        MsgControl.setVerbosityLevel(verbosity.level);
501        if (verbosity.level == 3) MsgVerbose.setVerbose();
502        return this;
503    }
504
505    /**
506     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_LOG_FILE_APND>
507     * @param logFile An {@code Appendable} to be used for backing-up / saving Log-Writes.
508     */
509    public Upgrade setLogFile(Appendable logFile)
510    {
511        this.settingsBuilder.logFile = logFile;
512        MsgControl.setLogAppendable(logFile);
513
514        return this;
515    }
516
517    /**
518     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_LOG_FILE_FN>
519     * @param logFileName File-Name of a writeable File, to be used for backuping-up Log-Writes
520     * @throws UpgradeException If the File provided cannot be written to.
521     * @see UpgradeException#checkFileIsWriteable(String, String, String)
522     */
523    public Upgrade setLogFile(String logFileName)
524    {
525        // This is just used / passed to the "Exception Checker" (below) to build a more
526        // readable Exception-Message.
527
528        String fileDescription = "Disk / File-System Upgrader Log-Dump File";
529
530        // Write log-file header.  Check that the log-file is accessible and writable.
531        UpgradeException.checkFileIsWriteable(logFileName, fileDescription, logFileHeader);
532
533        // Build a java.util.function.Consumer<String> 
534        // This consumer will function as the log-file write-mechanism.
535
536        this.settingsBuilder.logFile = new Appendable()
537        {
538            // This method is never actually used by the log-writes in JD-Upgrader.  Realize that
539            // writing to the log, and actually check-pointing the log to disk are not the same
540            // thing.  This appendable is used for actually writing out the log contents to a
541            // flat-file (or any user-provided output/storing mechanism that the user can think of)
542            //
543            // The user has the option of writing the log-contents to some other, user-specified,
544            // appendable that does whatever it wants with the log-contents.
545            //
546            // But whatever it is! - Check-pointing the log to it's output is only done in the
547            // class Messager - using the method: Messager.checkPointLog();
548            //
549            // FURTHERMORE: The method "Messager.checkPointLog()" is only invoked twice!  Once by
550            //              the class "ExtraFilesProcessor" and once by "MainFilesProcessor"
551
552            public Appendable append(char c) // AGAIN: Not used
553            { throw new UnreachableError(); }
554
555            public Appendable append(CharSequence s, int start, int end) // NOT USED
556            { throw new UnreachableError(); }
557
558            // Invoked only once: In Messager.checkPointLog()
559            public Appendable append(CharSequence s) 
560            {
561                try
562                    { FileRW.appendToFile(s, logFileName); }
563
564                catch (IOException ioe)
565                {
566                    throw new UpgradeException
567                        ("Cannot write to log-file: [" + logFileName + "]", ioe);
568                }
569
570                return this;
571            }
572        };
573
574        MsgControl.setLogAppendable(this.settingsBuilder.logFile);
575
576        return this;
577    }
578
579
580    // ********************************************************************************************
581    // ********************************************************************************************
582    // The HTML <EMBED> tag.  These can be used to provide more processing to the user.
583    // ********************************************************************************************
584    // ********************************************************************************************
585
586
587    /**
588     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_EMBED_TAG_F>
589     * @param tagIDMapFileName A Java {@code '.properties'} File-Name mapping File-ID's to Files.
590     * @throws UpgradeException If there are any problems that occur while loading the file
591     */
592    public Upgrade setProjectGlobalEmbedTagsMapFile(String tagIDMapFileName)
593    {
594        if (tagIDMapFileName == null) throw new UpgradeException
595            ("The parameter 'tagIDMapFileName' (as a String) was passed NULL.");
596
597        File tagIDMapFile = new File(tagIDMapFileName);
598
599        if (! tagIDMapFile.exists()) throw new UpgradeException
600            ("The <EMBED> Tag ID Map File Provided doesn't exist:\n[" + tagIDMapFileName + "]");
601
602        if (! tagIDMapFile.isFile()) throw new UpgradeException
603            ("The <EMBED> Tag ID Map File Provided isn't a file:\n[" + tagIDMapFileName + "]");
604
605        // MESSAGER:
606        //  1) INVOKES:     println, assertFailGeneralPurpose
607        //  2) INVOKED-BY:  MainFiesProcessor (main-loop, once), Upgrade (once)
608        //  3) RETURNS:     Map<String, String>
609        //  4) THROWS:      UpgradeException
610
611        ReadOnlyMap<String, String> tagIDMap =
612            Torello.JDUInternal.Features.EXTERNAL_HTML_FILES.API_EHF_Dispatch
613                .readPropertiesFile(tagIDMapFile);
614
615        if (tagIDMap == null) throw new UpgradeException
616            ("tagIDMapFileName: Could not Load.\n[" + tagIDMapFileName + "]");
617 
618        // NOTE: For "Project Global Tags Map" the "relative-directory" string is the empty
619        //       String.  (That is the empty-string parameter in the method call below)
620        //
621        // MESSAGER:
622        //  1) INVOKES:     println, userErrorContinue (only non-throwing Messager methods)
623        //  2) INVOKED BY:  Upgrade (twice), MainFilesProcessor (once)
624        //  3) RETURNS:     TRUE ==> no errors, FALSE if there were any errors
625        //  4) THROWS:      NO EXPLICIT THROWS STATEMENTS
626
627        MsgControl.setCurrentFileName(tagIDMapFileName, "<EMBED> Tag ID Map Properties File");
628
629        boolean res =
630            Torello.JDUInternal.Features.EXTERNAL_HTML_FILES.API_EHF_Dispatch
631            .checkMap(tagIDMap, this.settingsBuilder.checkBalance, "");
632
633        if (! res) throw new UpgradeException(
634            "There were errors when checking the HTML Validity of the External-HTML Global " +
635            "<EMBED> Tag Map '.properties' File:\n" +
636            "    [" + tagIDMapFileName + "]"
637        );
638
639        // Copy the Embed-Tag Map, (ID ==> FileName Map) into 'this' (internally-stored) map.
640        // this.settingsBuilder.projectGlobalEmbedTagsMap.clear();
641        // this.settingsBuilder.projectGlobalEmbedTagsMap.putAll(tagIDMap);
642
643        this.settingsBuilder.projectGlobalEmbedTagsMap = tagIDMap;
644
645
646        // Register this with the 'Stats' class.  This keeps a count of the use of each of these
647        // tags in Java Doc Pages, and outputs a 'Stats.html' file at the end.
648
649        this.stats = new StatsInternal(this.settingsBuilder.projectGlobalEmbedTagsMap);
650
651        return this;
652    }
653
654    /**
655     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_EMBED_TAG_MAP>
656     * @param tagIDMap This should map a {@code 'DATA-FILE-ID'} to an {@code '.html'} File-Name
657     */
658    public Upgrade setProjectGlobalEmbedTagsMap(ReadOnlyMap<String, String> tagIDMap)
659    {
660        if (tagIDMap == null) throw new UpgradeException
661            ("The parameter 'tagIDMap' (a java.util.Map<String, String>) was passed NULL.");
662
663        // This will check each line on the map, and log errors if there are errors
664        // 
665        // NOTE: For "Project Global Tags Map" the "relative-directory" string is the empty
666        //       String.  (That is the empty-string parameter in the method call below)
667        // 
668        // MESSAGER:
669        //  2) INVOKED BY:  Upgrade (twice), RetrieveEmbedTagMapPropFiles (once)
670        //  3) RETURNS: TRUE ==> no errors, FALSE if there were any errors
671
672        MsgControl.setCurrentFileName("User Provided Map Instance", "Instance, Not File");
673
674        // This map is the "Project-Global" Tag-Map
675        boolean res =
676            Torello.JDUInternal.Features.EXTERNAL_HTML_FILES.API_EHF_Dispatch
677            .checkMap(tagIDMap, this.settingsBuilder.checkBalance, "");
678
679        if (! res) throw new UpgradeException(
680            "There were errors when checking the HTML Validity of the External-HTML Global " +
681            "EMBED TAG FILES."
682        );
683
684        // Copy the Embed-Tag Map, (ID ==> FileName Map) into 'this' (internally-stored) map.
685        // this.settingsBuilder.projectGlobalEmbedTagsMap.clear();
686        // this.settingsBuilder.projectGlobalEmbedTagsMap.putAll(tagIDMap);
687
688        this.settingsBuilder.projectGlobalEmbedTagsMap = tagIDMap;
689
690        // Register this with the 'Stats' class.  This keeps a count of the use of each of these
691        // tags in Java Doc Pages, and outputs a 'Stats.html' file at the end.
692
693        this.stats = new StatsInternal(this.settingsBuilder.projectGlobalEmbedTagsMap);
694
695        // invocation chaining
696        return this;
697    }
698
699
700    // ********************************************************************************************
701    // ********************************************************************************************
702    // Using Features: The HiLiter & HiLiter-Cache
703    // ********************************************************************************************
704    // ********************************************************************************************
705
706
707    /**
708     * Convenience Method.
709     * <BR />Invokes: {@link HiLiter#getDefault(HiLiteCache, String, String)}
710     * <BR />And-Then: {@link #setHiLiter(HiLiter)}
711     */
712    public Upgrade useHiLiterCache(HiLiteCache cache)
713    {
714        setHiLiter(HiLiter.getDefault(cache, "vim", "native"));
715        this.settingsBuilder.hlCache = cache;
716        return this;
717    }
718
719    /**
720     * Configures the HiLiter to use the Default HiLiter, and use the provided Cache-Directory as
721     * the location for Caching HiLited HTML-Files.
722     * 
723     * @param hiLiterCacheDirectoryName The name of the File-System Directory which has 
724     * previously saved HiLited HTML-Files.
725     * 
726     * <BR /><BR /><B STYLE='color: red;'>NOTE:</B> If this is the first time using this Cache
727     * Directory, and it doesn't exists yet, this directory will be created, and future
728     * {@code Upgrade} instances will save &amp; cache HiLited-Source HTML to this directory.
729     */
730    public Upgrade useHiLiterCache(String hiLiterCacheDirectoryName)
731    {
732        // These four lines allow the Upgrade Tool to cache results for documentation web-pages
733        // as they are hilited so that future builds will not have to "re-poll" the server when
734        // hiliting source-code files that have not changed.  Use as depicted below.
735    
736        final File f = new File(hiLiterCacheDirectoryName);
737    
738        if (! f.exists())
739        {
740            f.mkdirs();
741            HiLiteCache.initializeOrClear(hiLiterCacheDirectoryName, null);
742        }
743
744        HiLiteCache cache = new HiLiteCache(hiLiterCacheDirectoryName);
745
746        useHiLiterCache(cache);
747
748        return this;
749    }
750
751    /**
752     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_HILITER>
753     * @param hiLiter Any valid implementation of the {@link HiLiter} interface
754     */
755    public Upgrade setHiLiter(HiLiter hiLiter)
756    {
757        this.settingsBuilder.hiLiter = hiLiter;
758        return this;
759    }
760
761
762    // ********************************************************************************************
763    // ********************************************************************************************
764    // The Big Kahuna Burger.  That is a tasty burger.  Brad - did I break your concentration?
765    // ********************************************************************************************
766    // ********************************************************************************************
767
768
769    /**
770     * <EMBED CLASS='external-html' DATA-FILE-ID=U_GET_DEF_CSS_FILES>
771     * @return All Java-Doc Upgrader {@code '.css'} Files
772     * @see #setCustomCSSFiles(CSSFiles)
773     */
774    public static CSSFiles retrieveDefaultCSSFilesFromJAR()
775    {
776        return LFEC.readObjectFromFile_JAR
777            (Upgrade.class, "data-files/AllCSSFiles.objdat", true, CSSFiles.class);
778    }
779
780    /**
781     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_CSS_FILES>
782     * @param cssFiles All Java-Doc Upgrader {@code '.css'} Files
783     * @see #retrieveDefaultCSSFilesFromJAR()
784     */
785    public Upgrade setCustomCSSFiles(CSSFiles cssFiles)
786    {
787        this.settingsBuilder.cssFiles = cssFiles;
788        return this;
789    }
790
791    /**
792     * <EMBED CLASS='external-html' DATA-FILE-ID=U_GET_DEF_JS_FILE>
793     * @return The <B STYLE='color:red'><I>entire contents</I></B> of the {@code '.js'} File (read
794     * from disk), returned as a {@code String}.
795     */
796    public static String retrieveDefaultJSFileFromJAR()
797    {
798        return LFEC.readObjectFromFile_JAR
799            (Upgrade.class, "data-files/JDU-JS.sdat", true, String.class);
800    }
801
802    /**
803     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_FAVICON_IF>
804     * @param faviconFileFormat An instance of {@link IF} - the Image-Format of the Favicon-File.
805     */
806    public Upgrade setFaviconFileFormat(IF faviconFileFormat)
807    {
808        this.settingsBuilder.faviconImageFileName =
809            FAVICON_FILE_NAME + '.' + faviconFileFormat.toString();
810
811        return this;
812    }
813
814    /**
815     * <EMBED CLASS='external-html' DATA-FILE-ID=U_ADD_HEADER_TAGS>
816     * @param headerTags HTML-Tags to be inserted into a page's {@code <HEAD>...</HEAD>} Section
817     */
818    public Upgrade addHeaderTags(Iterable<TagNode> headerTags)
819    {
820        Vector<HTMLNode> ht = this.settingsBuilder.headerTags;
821        for (TagNode tn : headerTags) { ht.add(tn); ht.add(NEWLINE); }
822        return this;
823    }
824
825    /**
826     * <EMBED CLASS='external-html' DATA-FILE-ID=U_ADD_HEADER_BLOCK>
827     * @param headerStuff HTML-Block to be inserted into a page's {@code <HEAD>...</HEAD>} Section
828     */
829    public Upgrade addHeaderBlock(Vector<HTMLNode> headerStuff)
830    { this.settingsBuilder.headerTags.addAll(headerStuff);  return this; }
831
832    /** <EMBED CLASS='external-html' DATA-FILE-ID=U_RUN_LINKS_CHECKER> */
833    public Upgrade runLinksChecker()
834    {
835        this.settingsBuilder.linksChecker = new LinksChecker();
836        return this;
837    }
838
839    /**
840     * Sets a tab-replacement policy for code-hilited HTML.
841     * 
842     * @param spacesPerTab The number of spaces that should be used as a substitue for a
843     * tab-character ({@code '\t'}) when hiliting source-code.
844     * 
845     * @param relativeOrAbsolute When this parameter receives {@code TRUE}, a tab-character is
846     * used to symbolize however many spaces are needed to place the cursor at the next 
847     * <B STYLE='color: red;'>rounded-integral</B> number-of-spaces - modulo the value in
848     * {@code 'spacesPerTab'}.
849     * 
850     * <BR /><BR />If a tab-charcter is found at index {@code 13} in a line-of-code, and the value
851     * passed to {@code 'spacesPerTab'} were {@code 4}, then the number of spaces inserted would be
852     * {@code 3}.  This is because precisely {@code 3} spaces would skip to index {@code 16}, which
853     * happens to be the next-highest <B STYLE='color: red;'>rounded-multiple</B> of {@code 4}. 
854     * 
855     * <BR /><BR />When this parameter receives {@code FALSE}, a tab-character is always 
856     * replaced by the exact number of space-characters specified by {@code spacesPerTab}.
857     * 
858     * @throws IllegalArgumentException If a number less than {@code 1} or greater than {@code 20}
859     * is passed to parameter {@code 'spacesPerTab'} 
860     * 
861     * @see StrIndent#setCodeIndent_WithTabsPolicyRelative(String, int, int)
862     * @see StrIndent#setCodeIndent_WithTabsPolicyAbsolute(String, int, String)
863     */
864    public Upgrade setTabsPolicy(int spacesPerTab, boolean relativeOrAbsolute)
865    {
866        if ((spacesPerTab < 1) || (spacesPerTab > 20)) throw new IllegalArgumentException(
867            "A tab-character ('\t') cannot represent less than one or more than twenty " +
868            "spaces.  You have passed [" + spacesPerTab + "]"
869        );
870
871        final String SPACES = StringParse.nChars(' ', spacesPerTab);
872
873        this.settingsBuilder.indentor = (relativeOrAbsolute)
874            ? (String s) -> StrIndent.setCodeIndent_WithTabsPolicyRelative(s, 1, spacesPerTab)
875            : (String s) -> StrIndent.setCodeIndent_WithTabsPolicyAbsolute(s, 1, SPACES);
876
877        return this;
878    }
879
880    /**
881     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_CHECK_BALANCE>
882     * @param checkBalance The value of this parameter can turn the balance checker on or off.
883     */
884    public Upgrade setCheckBalance(boolean checkBalance)
885    {
886        this.settingsBuilder.checkBalance = checkBalance;
887        return this;
888    }
889
890    /**
891     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_EXTRA_TASKS>
892     * 
893     * @param extraTasks This function-pointer may be used to, sort-of, do extra processing on a
894     * JavaDoc HTML Documentation File while the vectorized-html file is already loaded into
895     * memory - and parsed.
896     * 
897     * <BR /><BR />The class {@link JavaDocHTMLFile} provides many accessor methods to retrieve
898     * the Summary Tables, and the HTML Details - <I>along with reflection-classes about the
899     * {@link Method}'s, {@link Field}'s, etc... that they describe</I>
900     */
901    public Upgrade setExtraTasks(Consumer<JavaDocHTMLFile> extraTasks)
902    {
903        this.settingsBuilder.extraTasks = extraTasks;
904        return this;
905    }
906
907    /**
908     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_PKGSUMM_CLEAN>
909     * @param packageSummaryCleaner Java Lambda for modifying Vectorized-HTML.
910     */
911    public Upgrade setPackageSummaryCleaner(Consumer<Vector<HTMLNode>> packageSummaryCleaner)
912    {
913        this.settingsBuilder.packageSummaryCleaner = packageSummaryCleaner;
914        return this;
915    }
916
917    /** <EMBED CLASS='external-html' DATA-FILE-ID=U_USE_DEFAULT_PSC> */
918    public Upgrade useDefaultPackageSummaryCleaner()
919    {
920        this.settingsBuilder.packageSummaryCleaner = PackageSummaryHTML::defaultCleaner;
921        return this;
922    }
923
924    /**
925     * Often, for the purposes of "development speed" during the development phase of a project,
926     * it can be of tremendous benefit to skip, altogether, the Upgrade Process of some of the
927     * Packages in a Project.
928     * 
929     * <BR /><BR /><B CLASS=JDDescLabel>Filter Behavior:</B>
930     * 
931     * <BR />Packages which are explicity named by the input parameter list {@code 'packageList'}
932     * are <B STYLE='color: red;'><I>INCLUDED</I></B> not
933     * <B STYLE='color: red;'><I>EXCLUDED</I></B> by the Internal Upgrder Logic.
934     * 
935     * <BR /><BR /><B CLASS=JDDescLabel>Default Setting:</B>
936     * 
937     * <BR />By default, initiating an {@code upgrade} will cause this Package's internal 
938     * Upgrade Logic to iterate <B STYLE='color: red;'><I>ALL</I></B> Packages found / present in
939     * the {@code 'javadoc/'} output directory.
940     * 
941     * <BR /><BR /><B CLASS=JDDescLabel>Input-Parameter Type:</B>
942     * 
943     * <BR />If Java-HTML Package "{@link Torello.Java.Build Build}" is not actually being utilized
944     * by your Project's Build-System, there is a nearly identical variant of this method that
945     * accept's Package-Names as Java {@code String's}, instead of instances of class
946     * "{@link BuildPackage}".
947     * 
948     * <BR /><BR />(Class {@link BuildPackage} is one of the Primary Data-Classes for the Build
949     * Tool {@link Torello.Java.Build}).
950     * 
951     * @param packageList A list of instances of the {@link Torello.Java.Build Build} Package's
952     * class {@link BuildPackage}.
953     * 
954     * @return {@code 'this'} instance, for method-invocation chaining.
955     * @see #setPackageList(String[])
956     */
957    public Upgrade setPackageList(Iterable<BuildPackage> packageList)
958    {
959        TreeSet<String> ts = new TreeSet<>();
960        for (BuildPackage bp : packageList) ts.add(bp.fullName);
961        this.predicatesBuilder.packagesToUpgradeFilter = StrFilter.strListKEEP(ts, false)::test;
962        return this;
963    }
964
965    /**
966     * Often, for the purposes of "development speed" during the development phase of a project,
967     * it can be of tremendous benefit to skip, altogether, the Upgrade Process of some of the
968     * Packages in a Project.
969     * 
970     * <BR /><BR /><B CLASS=JDDescLabel>Filter Behavior:</B>
971     * 
972     * <BR />Packages which are explicity named by the input parameter list {@code 'packageList'}
973     * are <B STYLE='color: red;'><I>INCLUDED</I></B> not
974     * <B STYLE='color: red;'><I>EXCLUDED</I></B> by the Internal Upgrder Logic.
975     * 
976     * <BR /><BR /><B CLASS=JDDescLabel>Default Setting:</B>
977     * 
978     * <BR />By default, initiating an {@code upgrade} will cause this Package's internal 
979     * Upgrade Logic to iterate <B STYLE='color: red;'><I>ALL</I></B> Packages found / present in
980     * the {@code 'javadoc/'} output directory.
981     * 
982     * @param packageList A list of Package-Name's as a {@code String}.
983     * @return {@code 'this'} instance, for method-invocation chaining.
984     * @see #setPackageList(Iterable)
985     */
986    public Upgrade setPackageList(String... packageList)
987    {
988        this.predicatesBuilder.packagesToUpgradeFilter =
989            StrFilter.strListKEEP(false, packageList)::test;
990
991        return this;
992    }
993
994    /**
995     * A Configuration-Setting for requesting that the Upgrader Auto-Generate the original Java
996     * {@code 'package-frame.html'} &amp; {@code 'overview-frame.html'} Files.
997     * 
998     * <EMBED CLASS='external-html' DATA-FILE-ID=U_SET_GEN_FRAMES>
999     * 
1000     * @param generateFrames The {@code boolean}-Setting for this Configuration-Field
1001     * @return {@code 'this'} instance, for method-invocation chaining.
1002     * 
1003     * @throws UpgradeException If {@code 'generateFrames'} is passed {@code FALSE}, but you have
1004     * earlier configured / applied an {@code 'overview-frame.html'} sorter.
1005     * 
1006     * @see PackageSummaryHTML#JD_FRAMES_WARNING_MESSAGE
1007     */
1008    public Upgrade setGenerateFrames(boolean generateFrames)
1009    {
1010        this.settingsBuilder.generateFrames = generateFrames;
1011
1012        if ((! generateFrames) && (this.settingsBuilder.overviewFrameSections != null))
1013
1014            throw new UpgradeException(
1015                "Generate-Frames cannot be turned off if an Overview-Frame Sorter has already " +
1016                "been applied"
1017            );
1018
1019        return this;
1020    }    
1021
1022    /**
1023     * Generate and sort an {@code 'overview-frame.html'} File.
1024     * @param sectionNames List of "Categories" or "Sections" for the packages
1025     * @param sectionContents List of Packages for each Category / Section.
1026     * @return {@code 'this'} instance, for method-invocation chaining.
1027     */
1028    public Upgrade setOverviewFrameSorter(String[] sectionNames, String[][] sectionContents)
1029    {
1030        // This can only be sorted if it is first created / turned-on.
1031        this.settingsBuilder.generateFrames = true;
1032
1033        this.settingsBuilder.overviewFrameSections = new ReadOnlyArrayList<String>(sectionNames);
1034
1035        // Generics & Arrays do not always look so nice...  In the code below:
1036        //
1037        // String[][] sectionContents is cast to Object[], to "help" the javac Generics-Processor
1038        // Object o is then cast back to String[], also to "help" the javac Generics-Processor
1039        //
1040        // Note that this.settingsBuilder.overviewFramePackages is declared using type:
1041        // public ReadOnlyList<ReadOnlyList<String>> overviewFramePackages
1042
1043        this.settingsBuilder.overviewFramePackages = new ReadOnlyArrayList<ReadOnlyList<String>>(
1044            (Object o) -> new ReadOnlyArrayList<String>((String []) o),
1045            (Object[]) sectionContents
1046        );
1047
1048        return this;
1049    }
1050
1051    // This is only used to prevent the "Overview Frame Sorter" from causing a Messager
1052    // Exception-Throw when the Build has Opted for a "Quick-Build" - and the Overview
1053    // Frame Sorter hasn't removed the Quick-Build Packages from its sort!
1054    // 
1055    // Later this will also be used for the (upcoming - soon) "UNDER_DEVELOPMENT" BuildPackage 
1056    // flag.
1057    // 
1058    // This is an Internal-Method that is largely completely useless for the end user.  Hence it is
1059    // Package-Visible (available through the "EXPORT_PORTAL")
1060
1061    void registerEliminatedBuildPackages(ReadOnlyList<BuildPackage> eliminatedBuildPackages)
1062    { this.settingsBuilder.eliminatedBuildPackages = eliminatedBuildPackages; }
1063}