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