001package Torello.Java.Build;
002
003import Torello.Java.FileNodeFilter;
004import Torello.Java.ReadOnly.ReadOnlyList;
005import Torello.Java.ReadOnly.ROVectorBuilder;
006
007import java.io.File;
008import java.util.Objects;
009
010/**
011 * This is a very light-weight class, and has as its primary-operation / primary-feature the
012 * ability to allow a user to request that additional files be automatically inserted into the
013 * {@code '.jar'}-File that's generated by this Build-Tool.  There are several "goals" that are
014 * acheived during a Build using this Tool, one of which includes creating a {@code '.jar'} file
015 * containing the Java-Packages that have been specified to the class {@link Config}.
016 * 
017 * <BR /><BR />Specifying a list of Java-Packages (using the Configuration-Class 
018 * {@link BuildPackage}) guarantees that all of the required {@code '.class'}-Files are properly
019 * inserted into your {@code '.jar'}.  Often, however, there may be other files that need to be
020 * inserted into the {@code 'META-INF/'} directory inside that {@code '.jar'}-File in order for
021 * services and API's to work properly.  By instantiating an instance of class {@code JarInclude},
022 * one may specify a list of as many non-class-file related files that also have to be inserted
023 * into your {@code '.jar'}-File during the build.
024 * 
025 * <BR /><BR />The Build-Tool that you see here ({@link Torello.Java.Build}) is, indeed, the Tool
026 * used to generate the Documentation, Tar's and Jar's for the Java-HTML Jar-Library.  This is
027 * in fact a "Self-Building Build Tool".  The Source-Code Snippet included below will hopefully
028 * clarify and specify exactly how the class {@code 'JarInclude'} is used to insert additional
029 * files into a {@code '.jar'} so that exported API's properly work.
030 * 
031 * <BR /><BR />Here is a method from the (non-public, non-visible) build source for Java-HTML:
032 * 
033 * <DIV CLASS=EXAMPLE>{@code
034 * public static JarInclude getJarIncludes()
035 * {
036 *     // RTF: Return-True-Filter
037 *     final FileNodeFilter RTF = (FileNode fn) -> true;
038 * 
039 *     return new JarInclude()
040 *          // The first one contains the list of annotation processors.  The file is named:
041 *          // Torello/BuildJAR/IncludeFiles/META-INF/services/javax.annotation.processing.Processor
042 * 
043 *          .add("Torello/BuildJAR/IncludeFiles/", "META-INF/", true, RTF, RTF)
044 * 
045 *          // This one is provided by the Glass-Fish JSON Provider.  It is named:
046 *          // Torello/etc/External/META-INF/services/javax.json.spi.JsonProvider
047 * 
048 *          .add("Torello/etc/External/", "META-INF/", true, RTF, RTF);
049 * }
050 * }</DIV>
051 * 
052 * <BR />The above files will be automatically added into the Archive that's created in the Stage 4
053 * Build-Process / Build-Step.  The first file listed helps the Java-Doc Upgrader's Annotaton
054 * Processors to work properly.  The second file listed is needed to ensure that the Glass-Fish
055 * JSON-Processor included in this {@code '.jar'}-File works.
056 * 
057 * <BR /><BR />And that, by the way, is all folks!  That is all {@code 'JarInclude'} does.  If
058 * you have such files which need to be inserted, then:
059 * 
060 * <BR /><BR /><UL CLASS=JDUL>
061 * 
062 * <LI> Create an instance of {@code 'JarInclude'} using this class' constructor.
063 *      <BR /><BR /></LI>
064 * 
065 * <LI> Register your instance of {@code 'JarInclude'} with the Configuration-Class {@link Config}.
066 *      You may do this, simply, by assigning the instance / reference of this class to the
067 *      {@code 'Config'} class Configuration-Field, which is simply named:
068 *      {@link Config#jarIncludes}.
069 *      <BR /><BR /></LI>
070 * 
071 * <LI> When you obtain a {@link BuilderRecord} instance from {@link Config}, it wil automatically
072 *      add your files as soon as your invoke {@link RunBuild#run(BuilderRecord)}.
073 *      </LI>
074 * 
075 * </UL>
076 * 
077 * @see Config#jarIncludes
078 * @see Config#createBuilder(String[])
079 * @see RunBuild#run(BuilderRecord)
080 */
081public class JarInclude
082{
083    // This is a Package-Visible Inner-Class.  It is only usable inside Torello.Java.Build
084    // Instances of this class are included in the ReadOnlyList returned by method
085    // getAllDesriptors.  This is also a Package-Visible method
086
087    static class Descriptor
088    {
089        final String workingDirectory, subDirectory;
090        final boolean traverseTree;
091        final FileNodeFilter fileFilter, dirFilter;
092
093        Descriptor(
094            String          workingDirectory,
095            String          subDirectory,
096            boolean         traverseTree,
097            FileNodeFilter  fileFilter,
098            FileNodeFilter  dirFilter
099        )
100        {
101            this.workingDirectory   = workingDirectory;
102            this.subDirectory       = subDirectory;
103            this.traverseTree       = traverseTree;
104            this.fileFilter         = fileFilter;
105            this.dirFilter          = dirFilter;
106        }
107
108        public String toString() { return workingDirectory + subDirectory; }
109    }
110
111    /** Build an instance of this class */
112    public JarInclude() { }
113
114    private final ROVectorBuilder<JarInclude.Descriptor> descriptors =
115        new ROVectorBuilder<>();
116
117    // This is only invoked by class BuilderRecord
118    ReadOnlyList<JarInclude.Descriptor> getAllDesriptors() { return descriptors.build(); }
119
120    private static final String M1 = "Parameter '";
121    private static final String M2 = "' was passed null, but this is not allowed";
122
123    /**
124     * Convenience Method.
125     * <BR />Adds a Jar-Include Directive that <B>DOES NOT</B> recurse the directory-tree
126     * <BR />Invokes: {@link #add(String, String, boolean, FileNodeFilter, FileNodeFilter)}
127     */
128    public JarInclude add(
129            String          workingDirectory,
130            String          subDirectory,
131            FileNodeFilter  fileFilter
132        )
133    { return add(workingDirectory, subDirectory, false, fileFilter, null); }
134
135    /**
136     * Inserts a request for files to be included in the Tar-Jar Build Stage (Stage 4).
137     * 
138     * @param workingDirectory When files are added to a {@code '.jar'}-File, the "Working
139     * Directory" part of the File-System Path <B>is not included</B> in the name of the files that
140     * are inserted.
141     * 
142     * @param subDirectory The "Sub-Directory" part of the File-System Path <B>is included</B> into
143     * the names of any and all files that are inserted in the {@code '.jar'}.
144     * 
145     * @param traverseTree Indicates whether the the {@code String}-Parameter
146     * {@code 'subDirectory'} should be interpreted as a directory-name - <I>or as an entire tree
147     * branch</I> whose own sub-directories should be traversed by the file-scanner.
148     * 
149     * @param fileFilter A filter / "chooser" / specifier for deciding which files residing on the
150     * File-System inside {@code 'subDirectory'} (or {@code 'subDirectory'}, and its own
151     * sub-directories - in the case that {@code 'traverseTree'} was passed {@code TRUE}), are to
152     * be included in the {@code '.jar'}.
153     * 
154     * <BR /><BR />This filter must return {@code TRUE} if a file this filter is testing
155     * <B><I>should</I></B> be inserted into the {@code '.jar'}, and {@code FALSE}, if the file
156     * <B><I>should not</I></B> be.
157     * 
158     * <BR /><BR />This parameter may be passed null, and if it is it will be quietly ignored.
159     * When this filter is null, all files that reside within {@code 'subDirectory'} will be 
160     * inserted into the {@code '.jar'}-File.
161     * 
162     * <BR /><BR />If this parameter were passed null, and {@code 'traverseTree'} were passed
163     * {@code TRUE}, then all files inside of {@code 'subDirectory'} would be inserted into the
164     * {@code '.jar'} - <I>and furthermore, all files in all sub-directories of
165     * {@code 'subDirectory'} would also be inserted</I>.
166     * 
167     * @param dirFilter This filter can only be employed if {@code 'traverseTree'} has been passed
168     * {@code TRUE}.
169     * 
170     * <BR /><BR />When {@code 'traverseTree'} is {@code TRUE} as the directory tree rooted at
171     * {@code workingDirectory/subDirectory/} is traversed, each sub-directory that is encountered
172     * will be passed to this filter.  When this test is performed, the filter should return 
173     * {@code TRUE} to indicate that it would like a particular sub-directory searched, and 
174     * {@code FALSE} to indicate that it must be skipped.
175     * 
176     * <BR /><BR />This parameter may be passed null, and if it is it will be silently ignored.
177     * If this parameter is null, and {@code 'traverseTree'} is {@code TRUE}, all sub-directories
178     * of {@code workingDirectory/subDirectory/} will be entered / traversed.
179     * 
180     * <BR /><BR ><B>NOTE:</B> If this parameter is passed a non-null filter, but
181     * {@code 'traverseTree'} has been passed {@code FALSE}, then an
182     * {@code IllegalArgumentException} will throw.  Parameter {@code 'dirFilter'} has no use or
183     * application if the named directory-tree is not going to be traversed!
184     * 
185     * @return {@code 'this'} instance, for convenience and invocation-chaining.
186     * 
187     * @throws NullPointerException If either {@code 'workingDirectory'} or {@code 'subDirectory'}
188     * is passed null.
189     * 
190     * @throws IllegalArgumentException If either {@code 'workingDirectory'} or
191     * {@code 'subDirectory'} do not name real directories that actually exist on the File-System.
192     * 
193     * <BR /><BR />This exception will also throw if {@code 'traverseTree'} is passed {@code FALSE}
194     * but {@code 'dirFilter'} is non-null.
195     */
196    public JarInclude add(
197            String          workingDirectory,
198            String          subDirectory,
199            boolean         traverseTree,
200            FileNodeFilter  fileFilter,
201            FileNodeFilter  dirFilter
202        )
203    {
204        File f;
205
206        Objects.requireNonNull(workingDirectory, M1 + "workingDirectory" + M2);
207        Objects.requireNonNull(subDirectory, M1 + "subDirectory" + M2);
208
209        if (workingDirectory.length() > 0)
210        {
211            f = new File(workingDirectory);
212
213            if (! f.exists()) throw new IllegalArgumentException(
214                "The directory-name provided to parameter 'workingDirectory' does not exist on " +
215                "the File-System:\n[" + workingDirectory + ']'
216            );
217
218            if (! f.isDirectory()) throw new IllegalArgumentException(
219                "The directory-name provided to parameter 'workingDirectory' is not the name of " +
220                "an actual File-System directory:\n[" + workingDirectory + ']'
221            );
222        }
223
224        if (! workingDirectory.endsWith(File.separator))
225            if (workingDirectory.length() > 0)
226                workingDirectory = workingDirectory + File.separator;
227
228        String subDir = workingDirectory + subDirectory;
229
230        if (subDir.length() > 0)
231        {
232            f = new File(subDir);
233
234            if ((! f.exists()) || (! f.isDirectory())) throw new IllegalArgumentException(
235                "The directory-name provided to parameter 'subDirectory' does not exist on the " +
236                "File-System as a Sub-Directory of 'workingDirectory':\n" +
237                "[" + subDirectory + ']'
238            );
239        }
240
241        if (! subDirectory.endsWith(File.separator))
242            if (subDirectory.length() > 0)
243                subDirectory = subDirectory + File.separator;
244
245        if ((! traverseTree) && (dirFilter != null)) throw new IllegalArgumentException(
246            "You have passed FALSE to 'traverseTree', but a non-null filter to parameter " +
247            "'dirFilter'.  This is not allowed."
248        );
249        
250        this.descriptors.add
251            (new Descriptor(workingDirectory, subDirectory, traverseTree, fileFilter, dirFilter));
252
253        return this;
254    }
255
256}