1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
package Torello.Java.Build;

import Torello.Java.FileNodeFilter;
import Torello.Java.ReadOnly.ReadOnlyList;
import Torello.Java.ReadOnly.ROVectorBuilder;

import java.io.File;
import java.util.Objects;

/**
 * This is a very light-weight class, and has as its primary-operation / primary-feature the
 * ability to allow a user to request that additional files be automatically inserted into the
 * {@code '.jar'}-File that's generated by this Build-Tool.  There are several "goals" that are
 * acheived during a Build using this Tool, one of which includes creating a {@code '.jar'} file
 * containing the Java-Packages that have been specified to the class {@link Config}.
 * 
 * <BR /><BR />Specifying a list of Java-Packages (using the Configuration-Class 
 * {@link BuildPackage}) guarantees that all of the required {@code '.class'}-Files are properly
 * inserted into your {@code '.jar'}.  Often, however, there may be other files that need to be
 * inserted into the {@code 'META-INF/'} directory inside that {@code '.jar'}-File in order for
 * services and API's to work properly.  By instantiating an instance of class {@code JarInclude},
 * one may specify a list of as many non-class-file related files that also have to be inserted
 * into your {@code '.jar'}-File during the build.
 * 
 * <BR /><BR />The Build-Tool that you see here ({@link Torello.Java.Build}) is, indeed, the Tool
 * used to generate the Documentation, Tar's and Jar's for the Java-HTML Jar-Library.  This is
 * in fact a "Self-Building Build Tool".  The Source-Code Snippet included below will hopefully
 * clarify and specify exactly how the class {@code 'JarInclude'} is used to insert additional
 * files into a {@code '.jar'} so that exported API's properly work.
 * 
 * <BR /><BR />Here is a method from the (non-public, non-visible) build source for Java-HTML:
 * 
 * <DIV CLASS=EXAMPLE>{@code
 * public static JarInclude getJarIncludes()
 * {
 *     // RTF: Return-True-Filter
 *     final FileNodeFilter RTF = (FileNode fn) -> true;
 * 
 *     return new JarInclude()
 *          // The first one contains the list of annotation processors.  The file is named:
 *          // Torello/BuildJAR/IncludeFiles/META-INF/services/javax.annotation.processing.Processor
 * 
 *          .add("Torello/BuildJAR/IncludeFiles/", "META-INF/", true, RTF, RTF)
 * 
 *          // This one is provided by the Glass-Fish JSON Provider.  It is named:
 *          // Torello/etc/External/META-INF/services/javax.json.spi.JsonProvider
 * 
 *          .add("Torello/etc/External/", "META-INF/", true, RTF, RTF);
 * }
 * }</DIV>
 * 
 * <BR />The above files will be automatically added into the Archive that's created in the Stage 4
 * Build-Process / Build-Step.  The first file listed helps the Java-Doc Upgrader's Annotaton
 * Processors to work properly.  The second file listed is needed to ensure that the Glass-Fish
 * JSON-Processor included in this {@code '.jar'}-File works.
 * 
 * <BR /><BR />And that, by the way, is all folks!  That is all {@code 'JarInclude'} does.  If
 * you have such files which need to be inserted, then:
 * 
 * <BR /><BR /><UL CLASS=JDUL>
 * 
 * <LI> Create an instance of {@code 'JarInclude'} using this class' constructor.
 *      <BR /><BR /></LI>
 * 
 * <LI> Register your instance of {@code 'JarInclude'} with the Configuration-Class {@link Config}.
 *      You may do this, simply, by assigning the instance / reference of this class to the
 *      {@code 'Config'} class Configuration-Field, which is simply named:
 *      {@link Config#jarIncludes}.
 *      <BR /><BR /></LI>
 * 
 * <LI> When you obtain a {@link BuilderRecord} instance from {@link Config}, it wil automatically
 *      add your files as soon as your invoke {@link RunBuild#run(BuilderRecord)}.
 *      </LI>
 * 
 * </UL>
 * 
 * @see Config#jarIncludes
 * @see Config#createBuilder(String[])
 * @see RunBuild#run(BuilderRecord)
 */
public class JarInclude
{
    // This is a Package-Visible Inner-Class.  It is only usable inside Torello.Java.Build
    // Instances of this class are included in the ReadOnlyList returned by method
    // getAllDesriptors.  This is also a Package-Visible method

    static class Descriptor
    {
        final String workingDirectory, subDirectory;
        final boolean traverseTree;
        final FileNodeFilter fileFilter, dirFilter;

        Descriptor(
            String          workingDirectory,
            String          subDirectory,
            boolean         traverseTree,
            FileNodeFilter  fileFilter,
            FileNodeFilter  dirFilter
        )
        {
            this.workingDirectory   = workingDirectory;
            this.subDirectory       = subDirectory;
            this.traverseTree       = traverseTree;
            this.fileFilter         = fileFilter;
            this.dirFilter          = dirFilter;
        }

        public String toString() { return workingDirectory + subDirectory; }
    }

    /** Build an instance of this class */
    public JarInclude() { }

    private final ROVectorBuilder<JarInclude.Descriptor> descriptors =
        new ROVectorBuilder<>();

    // This is only invoked by class BuilderRecord
    ReadOnlyList<JarInclude.Descriptor> getAllDesriptors() { return descriptors.build(); }

    private static final String M1 = "Parameter '";
    private static final String M2 = "' was passed null, but this is not allowed";

    /**
     * Convenience Method.
     * <BR />Adds a Jar-Include Directive that <B>DOES NOT</B> recurse the directory-tree
     * <BR />Invokes: {@link #add(String, String, boolean, FileNodeFilter, FileNodeFilter)}
     */
    public JarInclude add(
            String          workingDirectory,
            String          subDirectory,
            FileNodeFilter  fileFilter
        )
    { return add(workingDirectory, subDirectory, false, fileFilter, null); }

    /**
     * Inserts a request for files to be included in the Tar-Jar Build Stage (Stage 4).
     * 
     * @param workingDirectory When files are added to a {@code '.jar'}-File, the "Working
     * Directory" part of the File-System Path <B>is not included</B> in the name of the files that
     * are inserted.
     * 
     * @param subDirectory The "Sub-Directory" part of the File-System Path <B>is included</B> into
     * the names of any and all files that are inserted in the {@code '.jar'}.
     * 
     * @param traverseTree Indicates whether the the {@code String}-Parameter
     * {@code 'subDirectory'} should be interpreted as a directory-name - <I>or as an entire tree
     * branch</I> whose own sub-directories should be traversed by the file-scanner.
     * 
     * @param fileFilter A filter / "chooser" / specifier for deciding which files residing on the
     * File-System inside {@code 'subDirectory'} (or {@code 'subDirectory'}, and its own
     * sub-directories - in the case that {@code 'traverseTree'} was passed {@code TRUE}), are to
     * be included in the {@code '.jar'}.
     * 
     * <BR /><BR />This filter must return {@code TRUE} if a file this filter is testing
     * <B><I>should</I></B> be inserted into the {@code '.jar'}, and {@code FALSE}, if the file
     * <B><I>should not</I></B> be.
     * 
     * <BR /><BR />This parameter may be passed null, and if it is it will be quietly ignored.
     * When this filter is null, all files that reside within {@code 'subDirectory'} will be 
     * inserted into the {@code '.jar'}-File.
     * 
     * <BR /><BR />If this parameter were passed null, and {@code 'traverseTree'} were passed
     * {@code TRUE}, then all files inside of {@code 'subDirectory'} would be inserted into the
     * {@code '.jar'} - <I>and furthermore, all files in all sub-directories of
     * {@code 'subDirectory'} would also be inserted</I>.
     * 
     * @param dirFilter This filter can only be employed if {@code 'traverseTree'} has been passed
     * {@code TRUE}.
     * 
     * <BR /><BR />When {@code 'traverseTree'} is {@code TRUE} as the directory tree rooted at
     * {@code workingDirectory/subDirectory/} is traversed, each sub-directory that is encountered
     * will be passed to this filter.  When this test is performed, the filter should return 
     * {@code TRUE} to indicate that it would like a particular sub-directory searched, and 
     * {@code FALSE} to indicate that it must be skipped.
     * 
     * <BR /><BR />This parameter may be passed null, and if it is it will be silently ignored.
     * If this parameter is null, and {@code 'traverseTree'} is {@code TRUE}, all sub-directories
     * of {@code workingDirectory/subDirectory/} will be entered / traversed.
     * 
     * <BR /><BR ><B>NOTE:</B> If this parameter is passed a non-null filter, but
     * {@code 'traverseTree'} has been passed {@code FALSE}, then an
     * {@code IllegalArgumentException} will throw.  Parameter {@code 'dirFilter'} has no use or
     * application if the named directory-tree is not going to be traversed!
     * 
     * @return {@code 'this'} instance, for convenience and invocation-chaining.
     * 
     * @throws NullPointerException If either {@code 'workingDirectory'} or {@code 'subDirectory'}
     * is passed null.
     * 
     * @throws IllegalArgumentException If either {@code 'workingDirectory'} or
     * {@code 'subDirectory'} do not name real directories that actually exist on the File-System.
     * 
     * <BR /><BR />This exception will also throw if {@code 'traverseTree'} is passed {@code FALSE}
     * but {@code 'dirFilter'} is non-null.
     */
    public JarInclude add(
            String          workingDirectory,
            String          subDirectory,
            boolean         traverseTree,
            FileNodeFilter  fileFilter,
            FileNodeFilter  dirFilter
        )
    {
        File f;

        Objects.requireNonNull(workingDirectory, M1 + "workingDirectory" + M2);
        Objects.requireNonNull(subDirectory, M1 + "subDirectory" + M2);

        if (workingDirectory.length() > 0)
        {
            f = new File(workingDirectory);

            if (! f.exists()) throw new IllegalArgumentException(
                "The directory-name provided to parameter 'workingDirectory' does not exist on " +
                "the File-System:\n[" + workingDirectory + ']'
            );

            if (! f.isDirectory()) throw new IllegalArgumentException(
                "The directory-name provided to parameter 'workingDirectory' is not the name of " +
                "an actual File-System directory:\n[" + workingDirectory + ']'
            );
        }

        if (! workingDirectory.endsWith(File.separator))
            if (workingDirectory.length() > 0)
                workingDirectory = workingDirectory + File.separator;

        String subDir = workingDirectory + subDirectory;

        if (subDir.length() > 0)
        {
            f = new File(subDir);

            if ((! f.exists()) || (! f.isDirectory())) throw new IllegalArgumentException(
                "The directory-name provided to parameter 'subDirectory' does not exist on the " +
                "File-System as a Sub-Directory of 'workingDirectory':\n" +
                "[" + subDirectory + ']'
            );
        }

        if (! subDirectory.endsWith(File.separator))
            if (subDirectory.length() > 0)
                subDirectory = subDirectory + File.separator;

        if ((! traverseTree) && (dirFilter != null)) throw new IllegalArgumentException(
            "You have passed FALSE to 'traverseTree', but a non-null filter to parameter " +
            "'dirFilter'.  This is not allowed."
        );
        
        this.descriptors.add
            (new Descriptor(workingDirectory, subDirectory, traverseTree, fileFilter, dirFilter));

        return this;
    }

}