001package Torello.Java.Build;
002
003import Torello.HTML.HTMLPage; // needed for a JavaDoc '@link'
004
005import Torello.Java.ReadOnly.ReadOnlyList;
006import Torello.Java.ReadOnly.ReadOnlyArrayList;
007import Torello.Java.ReadOnly.ROArrayListBuilder;
008
009import Torello.Java.StrCSV;
010import Torello.Java.StringParse;
011import Torello.Java.StrCmpr;
012
013import static Torello.Java.C.*;
014
015import java.io.File;
016import java.util.TreeSet;
017import java.util.stream.Stream;
018
019/**
020 * This User-Data class is used to describe the Java-Packages included inside of a Java Project.
021 * 
022 * <BR /><BR /><B CLASS=JDDescLabel>Java-HTML JAR BuildPackage instances:</B>
023 * 
024 * <BR />You may view the list of packages that are used to build the Java-HTML JAR-Library
025 * here, in the link below:
026 * 
027 * <BR /><BR /><A HREF='hilite-files/MyPackages.java.html'>MyPackages.java</A>
028 */
029public class BuildPackage
030{
031    // ********************************************************************************************
032    // ********************************************************************************************
033    // Static Flag-Fields
034    // ********************************************************************************************
035    // ********************************************************************************************
036
037
038    /**
039     * This flag may be attached to one of your {@code BuildPackage} instances to signify that the
040     * package being flagged should not ever be re-compiled when Build-Stage 1 ({@code 'javac'}) is
041     * executing - <I>unless the package's nick-name has been explicity specified at the Command
042     * Line Interface</I>.
043     * 
044     * <BR /><BR />This flag can be an invaluable tool for speeding up build times be eliminating
045     * the compilation for Source-Files whose code is not changing and is not dependent on the
046     * Source-Files inside your project.
047     * 
048     * <BR /><BR /><B CLASS=JDDescLabel>For Instance, Java-HTML:</B>
049     * 
050     * <BR />In the Java-HTML {@code '.jar'} File, the external-imports that are included such as
051     * the Glass-Fish JSON Processor, and the Apache CLI Tools have Source-Files that do not ever
052     * need to be recompiled.  Those packages are included in this {@code '.jar'} file's
053     * documentation, but when this project is built, the Compiler Build-Stage will skip the
054     * compilation of the Source-Files in those Packages.
055     */
056    public static final byte DO_NOT_RECOMPILE = 1;
057
058    /**
059     * This flag may be attached to one of your {@code BuildPackage} instances to signify that the
060     * package being flagged should not ever be documented when Build-Stage 2 ({@code 'javadoc'})
061     * is executing.
062     * 
063     * <BR /><BR />When this flag is attached to a {@code BuildPackage}, there will simply be no
064     * documentation generated for that package.
065     * 
066     * <BR /><BR /><B CLASS=JDDescLabel>For Instance, Java-HTML:</B>
067     * 
068     * <BR />This Project's {@code '.jar'} File includes several classes that act as Internal
069     * Processors for the Java-Doc Upgrader Tool.  Those classes are all part of a root package
070     * known as {@code JDUInternal}.
071     * 
072     * <BR /><BR />Because the Source-Code in these classes serves no purpose whatsoever as a part
073     * of any kind of "External API", the methods and fields of those classes are not documented.
074     * The {@code BuildPackage} instance for the {@code JDUInternal} Package has been flagged with
075     * this flag {@code DO_NOT_DOCUMENT}
076     */
077    public static final byte DO_NOT_DOCUMENT = DO_NOT_RECOMPILE << 1;
078
079    /**
080     * This is a special flag that may be used to signify that a particular {@code BuildPackage}
081     * instance actually refers to a tree of packages, rather than just a single package.
082     * 
083     * <BR /><BR />This flag works very well with the {@link #DO_NOT_DOCUMENT} Flag.
084     * 
085     * <BR /><BR /><B CLASS=JDDescLabel>For Instance, Java-HTML:</B>
086     * 
087     * <BR />Again, the internal Java-Doc Upgrader Classes, rooted in package {@code JDUInternal},
088     * actually form a tree of packages that are ultimately included in the {@code '.jar'} File.
089     */
090    public static final byte HAS_SUB_PACKAGES = DO_NOT_DOCUMENT << 1;
091
092    /**
093     * This is a flag that can be used to boost Build-Times in a way quite similar to the 
094     * {@link #DO_NOT_RECOMPILE} flag.  When a {@code BuildPackage} instance is flagged with 
095     * {@code QUICKER_BUILD_SKIP}, if a user is performing a "Partial Build" using one of the
096     * Partial-Build Flags ({@code -pb1} and {@code -pb2}), then that package will simply be 
097     * eliminated from the Build.
098     * 
099     * <BR /><BR />For packages that are not necessarily external and independent packages, but
100     * are disjoint enough such that they do not need to be recompiled or documented in most 
101     * situations, then this flag can help boost Build-Speed during development.
102     * 
103     * <BR /><BR /><B CLASS=JDDescLabel>For Instance, Java-HTML:</B>
104     * 
105     * <BR />The Java-HTML {@code '.jar'} includes an Experimental Headless-Browser
106     * Package.  The {@code BuildPackage} instance that models that package is flagged with the
107     * {@code QUICKER_BUILD_SKIP}.  This means that during development, unless a Full Release
108     * Build has been invoked (Build-Switch {@code '-cb1'}), the Browser-Package is simply ignored.
109     * 
110     * <BR /><BR />It is not compiled by Stage 1; it is not documented by the {@code 'javadoc'}
111     * stage, and its documentation pages are not upgraded.  Because that package was generated by
112     * a Code-Generator, and is extremely lengthy and doesn't change, it doesn't need to be 
113     * included in the Build-Process at all unless a Full-Release Build is being invoked.
114     * 
115     * <BR /><BR />Note that the Build-Switch {@code '-NQB'} can override the elimination of 
116     * packages that have been flagged in this way.
117     */
118    public static final byte QUICKER_BUILD_SKIP = HAS_SUB_PACKAGES << 1;
119
120    /**
121     * This can be used to request that the class files for a Java Package not be included in the
122     * {@code '.jar'} File generated for that package.
123     * 
124     * <BR /><BR /><B CLASS=JDDescLabel>For Instance, Java-HTML:</B>
125     * 
126     * <BR />There is a proprietary, internal, Builder Package Hierarchy that is only used for 
127     * improving this particular package.  The classes in package {@code Torello.BuildJAR} are,
128     * therefore, flagged with the {@code 'DO_NOT_JAR'} flag.
129     * 
130     * <BR /><BR />A cursory inspection of the Java-HTML {@code '.jar'} File will reveal that
131     * there is not package named {@code Torello.BuildJAR} inside the {@code '.jar'} File.  This is
132     * because that package contains proprietary and internal classes that serve no purpose 
133     * whatsoever outside of the Build-Process.
134     */
135    public static final byte DO_NOT_JAR = QUICKER_BUILD_SKIP << 1;
136
137    /**
138     * Can be used to flag a package as "Under Development."  Packages flagged as such will not be
139     * included in the Build unless explicity requested.
140     * 
141     * <BR /><BR /><B CLASS=JDDescLabel>For Instance, Java-HTML:</B>
142     * 
143     * <BR />The most recent addition to Java-HTML has been the package {@code Torello.CSS}.
144     * It is currently under development and therefore wholly eliminated from the Build except
145     * when it has been explicity requested.
146     * 
147     * <BR /><BR />The elimination of packages flagged with the {@code 'EARLY_DEVELOPMENT'} flag 
148     * can be overriden using the Build Command-Line Switch {@code '-IEDP'}, which simply stands 
149     * for "Include Early Development Packages".
150     */
151    public static final byte EARLY_DEVELOPMENT = DO_NOT_JAR << 1;
152
153
154    // ********************************************************************************************
155    // ********************************************************************************************
156    // Static Fields
157    // ********************************************************************************************
158    // ********************************************************************************************
159
160
161    // This can be important
162    public static final boolean DEBUGGING = false;
163
164    // This is ubiquitous in Build-Stuff
165    private static final String FS = File.separator;
166
167    // For (most) packages, which do not have "Helper Packages"
168    private static final ReadOnlyList<String> EMPTY_LIST = ReadOnlyList.of();
169
170
171    // ********************************************************************************************
172    // ********************************************************************************************
173    // Instance Fields
174    // ********************************************************************************************
175    // ********************************************************************************************
176
177
178    /** This package's full-name. */
179    public final String fullName;
180
181    /** This package's class-path location within the File-System. */
182    public final String classPathLocation;
183
184    /** This package's root-directory within the File-System. */
185    public final String pkgRootDirectory;
186
187    /** A Nick-Name that may be used when trying to request this package at the CLI */
188    public final String nickName;
189
190    /**
191     * Indicates that this package's Source-Code Directory has an {@code 'upgrade-files/'}
192     * sub-directory.  The {@code 'upgrade-files/'} directory stores myriad configurations that are
193     * utilized by the Java-Doc Upgrader Tool, which is executed in Build-Stage 3.
194     * 
195     * <BR /><BR />This field is assigned as follows:
196     * 
197     * <BR /><DIV CLASS=SNIP>{@code
198     * File f = new File(this.pkgRootDirectory + "upgrade-files" + FS);
199     * 
200     * this.hasUpgradeFilesDir = f.exists() && f.isDirectory();
201     * }</DIV>
202     */
203    public final boolean hasUpgradeFilesDir;
204
205    /**
206     * Indicates that this package's Source-Code Directory has a {@code 'package-source/'}
207     * sub-directory containing {@code '.java'} Files.  The {@code 'package-source/'} directory
208     * can be used to organize and categorize {@code '.java'} Files within a single Java Package 
209     * so that they are not all lumped together in a single Java-Package Directory.
210     * 
211     * <BR /><BR />This field is assigned as follows:
212     * 
213     * <BR /><DIV CLASS=SNIP>{@code
214     * f = new File(this.pkgRootDirectory + "package-source" + FS);
215     * 
216     * this.hasPackageSourceDir = f.exists() && f.isDirectory();
217     * }</DIV>
218     */
219    public final boolean hasPackageSourceDir;
220
221    /**
222     * This field indicates whether or not the {@link #DO_NOT_RECOMPILE} flag was assigned to
223     * {@code 'this'} instance of {@code BuildPackage}.  This field's value is assigned, in this
224     * class' constructor, as below:
225     * 
226     * <BR /><DIV CLASS=SNIP>{@code
227     * this.mustReCompile = (flags & DO_NOT_RECOMPILE) == 0;
228     * }</DIV>
229     * 
230     * @see #DO_NOT_RECOMPILE
231     */
232    public final boolean mustReCompile;
233
234    /**
235     * This field simply indicates whether or not the {@link #DO_NOT_DOCUMENT} was assigned to
236     * {@code 'this'} instance of {@code BuildPackage}.  This field's value is assigned, in this
237     * class' constructor, as below:
238     * 
239     * <BR /><DIV CLASS=SNIP>{@code
240     * this.mustDocument = (flags & DO_NOT_DOCUMENT) == 0;
241     * }</DIV>
242     * 
243     * @see #DO_NOT_DOCUMENT
244     */
245    public final boolean mustDocument;
246
247    /**
248     * This field simply indicates whether or not the {@link #DO_NOT_JAR} was assigned to
249     * {@code 'this'} instance of {@code BuildPackage}.  This field's value is assigned, in this
250     * class' constructor, as below:
251     * 
252     * <BR /><DIV CLASS=SNIP>{@code
253     * this.doNotJAR = (flags & DO_NOT_JAR) >= 1;
254     * }</DIV>
255     * 
256     * @see #DO_NOT_JAR
257     */
258    public final boolean doNotJAR;
259
260    /**
261     * This field simply indicates whether or not the {@link #HAS_SUB_PACKAGES} was assigned to
262     * {@code 'this'} instance of {@code BuildPackage}.  This field's value is assigned, in this
263     * class' constructor, as below:
264     * 
265     * <BR /><DIV CLASS=SNIP>{@code
266     * this.hasSubPackages = (flags & HAS_SUB_PACKAGES) >= 1;
267     * }</DIV>
268     * 
269     * @see #HAS_SUB_PACKAGES
270     */
271    public final boolean hasSubPackages;
272
273    /**
274     * This field simply indicates whether or not the {@link #QUICKER_BUILD_SKIP} was assigned to
275     * {@code 'this'} instance of {@code BuildPackage}.  This field's value is assigned, in this
276     * class' constructor, as below:
277     * 
278     * <BR /><DIV CLASS=SNIP>{@code
279     * this.skipIfQuickerBuild = (flags & QUICKER_BUILD_SKIP) >= 1;
280     * }</DIV>
281     * 
282     * @see #QUICKER_BUILD_SKIP
283     */
284    public final boolean skipIfQuickerBuild;
285
286    /**
287     * This field simply indicates whether or not the {@link #EARLY_DEVELOPMENT} was assigned to
288     * {@code 'this'} instance of {@code BuildPackage}.  This field's value is assigned, in this
289     * class' constructor, as below:
290     * 
291     * <BR /><DIV CLASS=SNIP>{@code
292     * this.earlyDevelopment = (flags & EARLY_DEVELOPMENT) >= 1;
293     * }</DIV>
294     * 
295     * @see #EARLY_DEVELOPMENT
296     */
297    public final boolean earlyDevelopment;
298
299    /**
300     * Helper packages may be passed as parameters to the {@code BuildPackage} constructor.  All a
301     * "Helper Package" is is a sub-directory of the Package-Directory that contains additional
302     * classes that are germaine to the classes in this package.
303     * 
304     * <BR /><BR />The classes in a Helper-Package will not be documented by {@code 'javadoc'}, nor
305     * will they be (obviously) be upgrader by the Stage 3 Build-Class performing the JavaDoc
306     * Upgrade.
307     * 
308     * <BR /><BR />Instead, these classes will be compiled by the {@code 'javac'}, and included,
309     * quietly, in the final {@code '.jar'} File produced by this Build.  This can be a great way 
310     * to add simple classes that are required for an API Class to fully function, but do not 
311     * actually need to be documented and included in the API themselves.
312     * 
313     * <BR /><BR />A cursory inspection of the contents of the Java-HTML {@code '.jar'}
314     * Distribution should reveal, for instance the HTML Helper-Package
315     * {@code 'Torello.HTML.parse'}, which does the actual parsing for the HTML Parsing class 
316     * {@link HTMLPage}.  This class is fundamental and necessary to the operation of the HTML 
317     * Package, but is largely useless in the API that's exported to the end user.
318     * 
319     * <BR /><BR />This field's value is assigned by this class' constructor, as below:
320     * 
321     * <BR /><DIV CLASS=SNIP>{@code
322     * this.helperPackages = (helperPackages == null) || (helperPackages.length == 0)
323     *      ? EMPTY_LIST
324     *      : new ReadOnlyArrayList(
325     *          0,
326     *          (String subDirName) -> this.pkgRootDirectory +
327     *              (subDirName.endsWith(FS) ? subDirName : (subDirName + FS)),
328     *          helperPackages
329     *      );
330     * }</DIV>
331     */
332    public final ReadOnlyList<String> helperPackages;
333
334
335    // ********************************************************************************************
336    // ********************************************************************************************
337    // Lone Constructor
338    // ********************************************************************************************
339    // ********************************************************************************************
340
341
342    /**
343     * Constructs an instance of this class.
344     * 
345     * @param fullName The full name of the package.  This package, for instance, is named
346     * {@code Torello.Java.Build}.
347     * 
348     * @param classPathLocation The root class-path location
349     * 
350     * @param nickName A nick-name for this package that may be passed at the Command-Line
351     * Interface when it is necessary to specify that this package be included in the Build-Proces.
352     * 
353     * @param flags The <B>{@code Boolean-OR}</B> of all flags being assigned to this package.
354     * 
355     * @param helperPackages A list of any and all sub-directory packages to be included in this
356     * {@code '.jar'} upon which this package depends.  This list must contain {@code String's}
357     * that name actual sub-directories of the package-directory that contains the source-files
358     * for this package.
359     * 
360     * @throws IllegalArgumentException This exception throws under the following circumstances:
361     * 
362     * <BR /><BR /><UL CLASS=JDUL>
363     * <LI> If {@link #mustDocument} and {@link #hasSubPackages} are both, simultaneously, set</LI>
364     * <LI> If any of the "Helper Packages" do not name valid sub-directories of the primary
365     *      package-directory
366     *      </LI>
367     * </UL>
368     */
369    public BuildPackage(
370            final String      fullName,
371            final String      classPathLocation,
372            final String      nickName,
373            final int         flags,
374            final String...   helperPackages
375        )
376    {
377        this.fullName = fullName;
378
379        if (StrCmpr.equalsXOR(classPathLocation, ".", ""))
380            this.classPathLocation  = "";
381        else if (! classPathLocation.endsWith(FS))
382            this.classPathLocation = classPathLocation + FS;
383        else
384            this.classPathLocation = classPathLocation;
385
386        this.nickName           = nickName;
387        this.earlyDevelopment   = (flags & EARLY_DEVELOPMENT)   >= 1;
388        this.skipIfQuickerBuild = (flags & QUICKER_BUILD_SKIP)  >= 1;
389        this.mustReCompile      = (flags & DO_NOT_RECOMPILE)    == 0; // The "NOT" of the flag
390        this.mustDocument       = (flags & DO_NOT_DOCUMENT)     == 0; // The "NOT" of the flag
391        this.doNotJAR           = (flags & DO_NOT_JAR)          >= 1;
392        this.hasSubPackages     = (flags & HAS_SUB_PACKAGES)    >= 1;
393        this.pkgRootDirectory   = classPathLocation + fullName.replace(".", FS) + FS;
394
395        this.helperPackages = (helperPackages == null) || (helperPackages.length == 0)
396            ? EMPTY_LIST
397            : new ReadOnlyArrayList<>(
398                0,
399                (String subDirName) -> this.pkgRootDirectory +
400                    (subDirName.endsWith(FS) ? subDirName : (subDirName + FS)),
401                helperPackages
402            );
403
404        File f = null;
405
406        for (String pkgDirName : this.helperPackages)
407        {
408            f = new File(pkgDirName);
409
410            if ((! f.exists()) || (! f.isDirectory())) throw new IllegalArgumentException(
411                "Helper Package [" + pkgDirName + "] specifies a directory that either doesn't " +
412                "exist or is a file, rather than a directory:\n" + f.toString()
413            );
414        }
415
416        f = new File(this.pkgRootDirectory + "upgrade-files" + FS);
417
418        this.hasUpgradeFilesDir = f.exists() && f.isDirectory();
419
420        f = new File(this.pkgRootDirectory + "package-source" + FS);
421
422        this.hasPackageSourceDir = f.exists() && f.isDirectory();
423
424        if (this.mustDocument && this.hasSubPackages) throw new IllegalArgumentException
425            ("this.mustDocument AND this.hasSubPackages");
426    }
427
428
429    // ********************************************************************************************
430    // ********************************************************************************************
431    // toString
432    // ********************************************************************************************
433    // ********************************************************************************************
434
435
436    /**
437     * Stringify an instance of this class.
438     * @return a Java {@code String} representation of this class.
439     */
440    public String toString()
441    {
442        final int LEN = 22;
443
444        final String CPL = (classPathLocation.length() == 0)
445            ? BGREEN + "Current Working Directory" + RESET
446            : classPathLocation;
447
448        return 
449            StringParse.rightSpacePad("fullName:", LEN)             + fullName              + '\n' +
450            StringParse.rightSpacePad("classPathLocation:", LEN)    + CPL                   + '\n' +
451            StringParse.rightSpacePad("pkgRootDirectory:", LEN)     + pkgRootDirectory      + '\n' +
452            StringParse.rightSpacePad("nickName:", LEN)             + nickName              + '\n' +
453            StringParse.rightSpacePad("hasUpgradeFilesDir:", LEN)   + hasUpgradeFilesDir    + '\n' +
454            StringParse.rightSpacePad("hasPackageSourceDir:", LEN)  + hasPackageSourceDir   + '\n' +
455            StringParse.rightSpacePad("mustReCompile:", LEN)        + mustReCompile         + '\n' +
456            StringParse.rightSpacePad("mustDocument:", LEN)         + mustDocument          + '\n' +
457            StringParse.rightSpacePad("hasSubPackages:", LEN)       + hasSubPackages        + '\n' +
458            StringParse.rightSpacePad("skipIfQuickerBuild:", LEN)   + skipIfQuickerBuild    + '\n' +
459            StringParse.rightSpacePad("earlyDevelopment:", LEN)     + earlyDevelopment      + '\n' +
460            StringParse.rightSpacePad("helperPackages:", LEN) +
461                '[' + StrCSV.toCSV(helperPackages, true, true, null) + ']';
462    }
463
464
465    // ********************************************************************************************
466    // ********************************************************************************************
467    // Static Helper
468    // ********************************************************************************************
469    // ********************************************************************************************
470
471
472    static ReadOnlyList<BuildPackage> nickNameArgVPackages
473        (BuildPackage[] allPackages, ReadOnlyList<String> packageNickNames)
474    {
475        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
476        // Print it to terminal
477        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
478
479        if (DEBUGGING)
480        {
481            System.out.print("packageNickNames: ");
482            for (String pnn : packageNickNames) System.out.print(pnn + ", ");
483            System.out.println();
484        }
485
486
487        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
488        // Pkg Nick-Name Duplicate-Checker (typing the same name more than once - print & exit)
489        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
490
491        TreeSet<String> duplicateCheckerTS = new TreeSet<>();
492
493        for (String nickName : packageNickNames)
494
495            if (duplicateCheckerTS.contains(nickName))
496            {
497                System.err.println(
498                    "Duplicate Package Nick-Name Provided: " + BRED + nickName + RESET + '\n' +
499                    "Exiting..."
500                );
501
502                System.exit(1);
503            }
504
505            else duplicateCheckerTS.add(nickName);
506
507
508        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
509        // Convert to a list of "BuildPackage" instances
510        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***    
511
512        // Keeps a list of the packages that matched a nick-name
513        ROArrayListBuilder<BuildPackage> roab = new ROArrayListBuilder<>(packageNickNames.size());
514
515        // Keeps a list of any nick-names for which no matching package is found.  This is needed
516        // to ensure that if there is one un-matched, that the loop DOES NOT BREAK, and continues
517        // to check for matches... So that when there is an unmatched nick-name, the error-message
518        // that is ultimately printed to the user - CONTAINS THE LIST OF **ALL** unmatched
519        // nick-names.
520
521        Stream.Builder<String> unrecognized = Stream.builder();
522
523        // A flag indicating that there is at least one unmatched package
524        boolean atLeastOneMismatch = false;
525
526        TOP:
527        for (int i=0; i < packageNickNames.size(); i++)
528        {
529            for (BuildPackage pkg : allPackages)
530
531                if (pkg.nickName.equals(packageNickNames.get(i)))
532                {
533                    roab.add(pkg);
534
535                    if (DEBUGGING) System.out.println
536                        ("argv[" + i + "]: " + packageNickNames.get(i) + " ==> " + pkg.fullName);
537
538                    continue TOP;
539                }
540
541            atLeastOneMismatch = true;
542            unrecognized.accept(packageNickNames.get(i));
543        }
544
545        if (atLeastOneMismatch)
546        {
547            System.err.println(
548                "Unrecognized Package Nick-Name(s): " +
549                "[" +
550                BGREEN + String.join(", ", unrecognized.build().toArray(String[]::new)) + RESET +
551                "]\n" +
552                "This Name could not be mapped to any of your Java Packages to Compile\n" +
553                "Exiting...\n"
554            );
555
556            System.exit(1);
557        }
558
559
560        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
561        // DONE
562        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
563
564        return roab.build();
565    }
566}