001package Torello.Java;
002
003import java.util.*;
004import java.util.function.*;
005import java.util.stream.*;
006import java.io.*;
007
008/**
009 * One of the <I>flagship classes</I> of Java HTML, {@code FileNode} loads a directory or
010 * directory-tree from a File-System into memory, and provides a thorough API for listing and
011 * analyzing its contents.
012 * 
013 * <EMBED CLASS='external-html' DATA-FILE-ID=FN>
014 */
015public final class FileNode
016    implements CharSequence, Comparable<FileNode>, java.io.Serializable, Cloneable
017{
018    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */
019    public static final long serialVersionUID = 1;
020
021    /**
022     * When this variable is {@code TRUE} debug and status information will be sent to 
023     * Standard-Output.
024     */
025    public static boolean VERBOSE = false;
026
027    /** The name of the file or directory is saved/stored here */
028    public final String name;
029
030    /**
031     * If {@code 'this'} class-instance represents a directory in the BASH/UNIX or MS-DOS file
032     * system, this variable will be {@code TRUE}.  When this field is set to {@code FALSE}, it
033     * means that {@code 'this'} instance is a file.
034     */
035    public final boolean isDirectory;
036
037    /** 
038     * The parent/container {@code 'FileNode'} directory is saved here - like a <I>pointer 
039     * "tree"</I>.
040     */
041    protected FileNode parent;
042
043    /**
044     * When a tree is loaded into memory, the size of each file is saved in this variable.  It can
045     * be retrieved (and even changed).
046     */
047    public final long fileSize;
048
049    /**
050     * When a tree is loaded into memory, the file-date of the file is saved in this variable.  It
051     * can be retrieved.  If the {@code SecurityManager.checkRead(fileName)} denies read access to
052     * the file, this field will equal zero.
053     *
054     * <BR /><BR /><B CLASS=JDDescLabel>Time in Milli-Seconds:</B>
055     * 
056     * <BR />This field is a Java simple-type {@code 'long'} which represents the time the file was
057     * last modified, measured in Milli-Seconds since the epoch
058     * {@code (00:00:00 GMT, January 1, 1970)}
059     */
060    public final long lastModified;
061
062    /**
063     * This variable remains null for all instances of this class which represent 'files' on the
064     * underlying Operating-System, rather than 'directories.'
065     * 
066     * <BR /><BR />On the other hand, if {@code 'this'} instance of {@code FileNode} represents an
067     * MS-DOS, Unix, or Apple File-System directory, then this field will be constructed /
068     * instantiated and hold all file and sub-directory {@code FileNode}-instances which contained
069     * by this directory.
070     */
071    protected final Vector<FileNode> children;
072
073
074    // ********************************************************************************************
075    // ********************************************************************************************
076    // java.io.FilenameFilter static-final helpers
077    // ********************************************************************************************
078    // ********************************************************************************************
079
080
081    /**
082     * Implements the interface {@code java.io.FilenameFilte}, and can therefore be used within the
083     * {@link #loadTree(int, FilenameFilter, FileFilter) loadTree} method.
084     * 
085     * <BR /><BR />Selects for files whose name ends with {@code '.html'}, and is
086     * <B STYLE='color: red;'>case-insensitive</B>.
087     */
088    public static final FilenameFilter HTML_FILES = (File f, String name) ->
089        StrCmpr.endsWithIgnoreCase(name, ".html");
090
091    /**
092     * Implements the interface {@code java.io.FilenameFilte}, and can therefore be used within the
093     * {@link #loadTree(int, FilenameFilter, FileFilter) loadTree} method.
094     * 
095     * <BR /><BR />Selects for files whose name ends with {@code '.css'}, and is
096     * <B STYLE='color: red;'>case-insensitive</B>.
097     */
098    public static final FilenameFilter CSS_FILES = (File f, String name) ->
099        StrCmpr.endsWithIgnoreCase(name, ".css");
100
101    /**
102     * Implements the interface {@code java.io.FilenameFilte}, and can therefore be used within the
103     * {@link #loadTree(int, FilenameFilter, FileFilter) loadTree} method.
104     * 
105     * <BR /><BR />Selects for files whose name ends with {@code '.java'}.  This particular filter
106     * is <B STYLE='color: red;'>case-sensitive</B>.
107     */
108    public static final FilenameFilter JAVA_FILES = (File f, String name) ->
109        name.endsWith(".java");
110
111    /**
112     * Implements the interface {@code java.io.FilenameFilte}, and can therefore be used within the
113     * {@link #loadTree(int, FilenameFilter, FileFilter) loadTree} method.
114     * 
115     * <BR /><BR />Selects for files whose name ends with {@code '.class'}.  This particular filter
116     * is <B STYLE='color: red;'>case-sensitive</B>.
117     */
118    public static final FilenameFilter CLASS_FILES = (File f, String name) ->
119        name.endsWith(".class");
120
121
122    // ********************************************************************************************
123    // ********************************************************************************************
124    // FileNode Constructors
125    // ********************************************************************************************
126    // ********************************************************************************************
127
128
129    /**
130     * This constructor builds a {@code FileNode} object - which <I>must be a
131     * {@code FileNode}-Directory instance</I> and may not be a {@code FileNode}-File instance.
132     *
133     * <BR /><BR /><B CLASS=JDDescLabel>{@code FileNode}-Directory:</B>
134     * 
135     * <BR />This instance will have a {@link #fileSize} field whose value equals {@code '0'}, and
136     * an {@link #isDirectory} value set to {@code FALSE}.
137     *
138     * <BR /><BR />Directory-Name validity checks are not performed here.  This constructor has a
139     * {@code 'protected'} access level, and is only called internally when a directory has been
140     * found by getter-calls to {@code java.io.File} (and therefore are extremely unlikely to be
141     * invalid).
142     * 
143     * @param name The name of {@code 'this' FileNode}.
144     *
145     * @param parent This is the parent or "container-directory" of {@code 'this' FileNode}.  If a
146     * {@code FileNode} that is not a directory itself is passed as the parent, then an exception
147     * will be thrown.
148     *
149     * @param lastModified This must be a {@code long} value indicating when the file was last
150     * modified - according to the Operating-System.  This value may be {@code '0'}, and if so, it
151     * indicates that this information was either not available, or a read of the value was not
152     * allowed by the Operating-System Security-Manager.
153     *
154     * <DIV CLASS=COMPLETE>{@code
155     * this.name            = name;  
156     * this.parent          = parent;
157     * this.isDirectory     = true;
158     * this.fileSize        = 0;
159     * this.lastModified    = lastModified;
160     * this.children        = new Vector<>();
161     * }</DIV>
162     */
163    protected FileNode(String name, FileNode parent, long lastModified)
164    {
165        this.name           = name;  
166        this.parent         = parent;
167        this.isDirectory    = true;
168        this.fileSize       = 0;
169        this.lastModified   = lastModified;
170        this.children       = new Vector<>();
171    }
172
173    /**
174     * This constructor builds a {@code FileNode} object which <I>must be a {@code FileNode}-File
175     * instance</I> - and may not be a {@code FileNode}-Directory instance.
176     *
177     * <BR /><BR /><B CLASS=JDDescLabel>{@code FileNode}-File:</B>
178     * 
179     * <BR /><BR /><B>SPECIFICALLY:</B> The node that is instantiated will have a
180     * {@link #isDirectory} value of {@code FALSE}.
181     * 
182     * <BR /><BR />File-Name validity checks are not performed here.  This constructor has a
183     * {@code 'protected'} access level, and is only called internally when a file has been found
184     * by getter-calls to {@code java.io.File} (and therefore are extremely unlikely to be
185     * invalid).
186     * 
187     * @param name The name of {@code 'this' FileNode}
188     *
189     * @param parent This is the parent or "container-directory" of {@code 'this' FileNode}.
190     * If a {@code FileNode} that is not a directory itself is passed as the parent, then an
191     * exception will be thrown.
192     *
193     * @param fileSize The size of this file - in bytes.
194     *
195     * @param lastModified This must be a long value indicating when the file was last modified -
196     * according to the Operating-System.  This value may be {@code '0'}, and if so, it indicates
197     * that this information was either not available, or a read of the value was not allowed by
198     * the Operating-System security manager.
199     *
200     * <DIV CLASS="COMPLETE">{@code
201     * this.name            = name;  
202     * this.parent          = parent;
203     * this.isDirectory     = false;
204     * this.fileSize        = fileSize;
205     * this.lastModified    = lastModified;
206     * this.children        = null;
207     * }</DIV>
208     */
209    protected FileNode(String name, FileNode parent, long fileSize, long lastModified)
210    {
211        this.name           = name;  
212        this.parent         = parent;
213        this.isDirectory    = false;
214        this.fileSize       = fileSize;
215        this.lastModified   = lastModified;
216        this.children       = null;
217    }
218
219    /**
220     * This constructor builds a {@code 'ROOT' FileNode} instance.  These instances are 
221     * {@code FileNode}-Directories, but they do not have a parent / container {@code FileNode}.
222     * 
223     * <BR /><BR />They function indentically to Directory-{@code FileNode's} in all other aspects.
224     * 
225     * @param name The name of {@code 'this' FileNode}
226     */
227    protected FileNode(String name)
228    {
229        if (name.contains("\n")) throw new IllegalArgumentException
230            ("File & Directory names may not contain newlines:\n" + name);
231
232        // NOTE: The first if-statement below is a newer (May, 2022) issue that happened.
233        //       The Google Cloud Server UNIX Shell Root Directory is named "/"
234        //       That haddn't been tested before.
235
236        if (! name.equals(File.separator))
237
238            // All other directories on the File-System are stored with their 'name' field saved
239            // with the ending / trailing File.Separator
240
241            if (name.endsWith(File.separator) || name.endsWith("\'"))
242                name = name.substring(0, name.length() - 1);
243
244        long lastModified = 0;
245
246        // Make sure this is a directory.  If not, this exception throws since this constructor is
247        // the one used by the "createRoot(String)" method.  A "Root FileNode" must always be a 
248        // directory
249
250        try
251        {
252            File f = new File(name);
253
254            if (! f.isDirectory()) throw new IllegalArgumentException(
255                "You have attempted to create a new Root Directory - but the filename passed " +
256                "isn't a valid Directory Name: [" + name + ']'
257            );
258
259            lastModified = f.lastModified();
260        }
261
262        catch (SecurityException se)
263        { 
264            throw new IllegalArgumentException(
265                "You have attempted to create a new Root FileNode instance - but a Security " +
266                "Exception is preventing this.  See this exception's Throwable.getCause() for " +
267                "more details.",
268                se
269            );
270        }
271
272        this.name           = name;  
273        this.parent         = null;
274        this.isDirectory    = true;
275        this.fileSize       = 0;
276        this.lastModified   = lastModified;
277        this.children       = new Vector<>();
278    }
279
280    /**
281     * This is the <B>"Factory Method"</B> for this class.  The {@code String name} parameter is
282     * intended to be the root directory-name from which the Complete {@code FileNode}-Tree should
283     * be built / constructed.
284     * 
285     * <BR /><BR />The {@code 'name'} parameter passed to this method must be the actual name of
286     * an actual directory on the File-System.
287     * 
288     * <BR /><BR /><B CLASS=JDDescLabel>Load-Tree Methods:</B>
289     * 
290     * <BR />Once this Root-Node for a tree has been built (by invoking this method), the next 
291     * thing to do is read any / all files &amp; directories that reside on the File-System inside
292     * the directory into memory.  This class provides several methods for both total and partial
293     * reads of a directory's contents.
294     * 
295     * <BR /><BR />In the example below, the standard Tree-Loading method {@link #loadTree()} is
296     * invoked in order to to read the entire contents of the specified File-System Directory into
297     * a {@code FileNode}-Tree in Java Memory.
298     *
299     * <DIV CLASS="EXAMPLE">{@code
300     * FileNode fn = FileNode
301     *      .createRoot("etc/MyDataFiles/user123/")
302     *      .loadTree();
303     * 
304     * }</DIV>
305     *
306     * @return An instance of this class from which a {@code FileNode} tree may be instantiated.
307     */
308    public static FileNode createRoot(String name)
309    { return new FileNode(name); }
310
311
312    // ********************************************************************************************
313    // ********************************************************************************************
314    // Load the contents of the MS-DOS or UNIX File-System into this tree-data-structure
315    // ********************************************************************************************
316    // ********************************************************************************************
317
318
319    /**
320     * Convenience Method.
321     * <BR />Invokes: {@link #loadTree(int, FilenameFilter, FileFilter)}
322     * <BR />Passes: All Tree-Branches requested ({@code '-1'})
323     * <BR />And-Passes: null-filters (Requests no filtering be applied).
324     */
325    public FileNode loadTree() { return loadTree(-1, null, null); }
326
327    /**
328     * Convenience Method.
329     * <BR />Invokes: {@link #loadTree(int, FilenameFilter, FileFilter)}
330     * <BR />Passes: {@code 'includeFiles'} as a {@code Predicate} to parameter
331     * {@code 'fileFilter'}
332     * <BR />Passes: {@code 'includeDirectories'} (as {@code Predicate}) to
333     * {@code 'directoryFilter'}
334     * <BR />Throws: {@code IllegalArgumentException} If both boolean parameters are {@code FALSE}
335     */
336    public FileNode loadTree(final boolean includeFiles, final boolean includeDirectories)
337    {
338        if ((! includeFiles) && (! includeDirectories)) throw new IllegalArgumentException(
339            "loadTree(boolean, boolean) has been invoked with both search criteria booleans set " +
340            "to FALSE.  This means that there is nothing for the method to do."
341        );
342
343        return loadTree
344            (-1, (File dir, String name) -> includeFiles, (File file) -> includeDirectories);
345    }
346
347    /**
348     * Convenience Method.
349     * <BR />Invokes: {@link #loadTree(int, FilenameFilter, FileFilter)}
350     * <BR />Passes: <B>'Always False'</B> {@code Predicate} to parameter {@code 'fileFilter'}
351     * <BR />Accepts: A {@code 'directoryFilter'} and {@code 'maxTreeDepth'}
352     */
353    public FileNode loadTreeJustDirs(int maxTreeDepth, FileFilter directoryFilter)
354    {
355        // Set the return value of the 'fileFilter' predicate to ALWAYS return FALSE.
356        return loadTree(maxTreeDepth, (File dir, String name) -> false, directoryFilter);
357    }
358
359    /**
360     * This populates {@code 'this' FileNode} tree with the contents of the File-System
361     * directory represented by {@code 'this'}.
362     * 
363     * <BR /><BR /><B CLASS=JDDescLabel>Directory-FileNode:</B>
364     * 
365     * <BR />This method can only be invoked on an instance of {@code 'FileNode'} which represents
366     * a directory on the UNIX or MS-DOS File-System.  A {@code DirExpectedException} shall throw
367     * if this method is invoked on a {@code FileNode} instance that represents a file.
368     * 
369     * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=FN_LOAD_TREE>
370     * 
371     * @param maxTreeDepth <EMBED CLASS='external-html' DATA-FILE-ID=FN_MAX_TREE_DEPTH>
372     * @param fileFilter <EMBED CLASS='external-html' DATA-FILE-ID=FN_LOAD_T_FILE_FILT>
373     * @param directoryFilter <EMBED CLASS='external-html' DATA-FILE-ID=FN_LOAD_T_DIR_FILT>
374     *
375     * @return a reference to {@code 'this' FileNode}, for convenience only.  It's tree branches
376     * (directories) and leaf nodes (files) will be populated, as per the above parameter
377     * specification-criteria.
378     *
379     * @throws DirExpectedException This method will only execute if the instance of {@code 'this'}
380     * is a directory.  Files on the File-System are leaves, not branches - so they do not
381     * have contents to load.
382     * 
383     * @throws SecurityException The method <B>{@code java.io.File.listFiles()}</B> is used to
384     * retrieve the list of files for each directory.  That method asserts that the Java
385     * Security Managaer {@code java.lang.SecurityManager} may throw this exception if a 
386     * restricted directory is accessed by {@code 'listFiles()'}.
387     * 
388     * <BR /><BR /><B STYLE='color: red;'>BY-PASS NOTE:</B> Those most common behavior for
389     * restricted directories has been for the {@code listFiles()} to simply return null, which
390     * is handled easily by this code.  If this exception is throwing, one may use the internal
391     * <B><I>({@code static} flag)</I></B> {@link #SKIP_DIR_ON_SECURITY_EXCEPTION}.  When this
392     * <B><I>{@code static-flag}</I></B> is used, {@code SecurityExceptions} are caught, and the
393     * contents of those directories will simply be ignored and eliminated from the tree.
394     *
395     * @see #loadTree()
396     * @see DirExpectedException#check(FileNode)
397     * @see #SKIP_DIR_ON_SECURITY_EXCEPTION
398     */
399    public FileNode loadTree
400        (int maxTreeDepth, FilenameFilter fileFilter, FileFilter directoryFilter)
401    {
402        DirExpectedException.check(this);
403
404        loadTreeINTERNAL(maxTreeDepth, fileFilter, directoryFilter);
405
406        return this;
407    }
408
409    /**
410     * Directories on a UNIX platform that were inaccessible didn't seem to throw a
411     * {@code SecurityException}, instead, a null-array was returned.  However, in the case that
412     * Java's {@code java.lang.SecurityManager} is catching attempts to access restricted
413     * dirtectories and throwing exceptions (which is likely a rare case) - this {@code boolean}
414     * flag can inform the internal directory-scanner logic to catch these
415     * {@code SecurityException's}.
416     * 
417     * <BR /><BR />If "catching and skipping" exceptions has been choosen, then any directory
418     * that is scanned and would throw an exception, instead is left empty by this class'
419     * tree-loading logic.
420     * 
421     * <BR /><BR /><B CLASS=JDDescLabel>Thread-Safety:</B>
422     * 
423     * <BR />This flag is a non-{@code Thread}-Safe feature, because it is a
424     * <B>{@code static}-Field Flag</B> that is applied to <I>all instances</I> of class
425     * {@code FileNode}
426     */
427    public static boolean SKIP_DIR_ON_SECURITY_EXCEPTION = false;
428
429    // NOTE: 'this' instance of FileNode will always be a Directory, never File
430    private void loadTreeINTERNAL
431        (int maxTreeDepth, FilenameFilter fileFilter, FileFilter directoryFilter)
432    {
433        File f = getJavaIOFile();
434
435        if (VERBOSE) System.out.println(f.getPath());
436
437        // TRICKY! Added: 2019.05.16 - if we are "re-loading" the tree, this line is imperative
438        this.children.removeAllElements(); 
439
440        File[] subFilesAndDirs = null;
441
442        // ADDED: 2022.05.18 - The SecurityManager didn't seem to throw a SecurityException for 
443        // UNIX directories that could not be accessed.  Instead, it just returned a null-pointer,
444        // and this code just threw a NullPointerException.
445        // 
446        // NOW: This checks for the "SecurityManager" case (which didn't seem to catch it anyway),
447        //      and allows the user whether to skip the directory completely, or throw an exception
448        //      when "null" is unceremoniously returned, below.
449
450        try
451            { subFilesAndDirs = f.listFiles(); }
452
453        catch (SecurityException e)
454        {
455            if (SKIP_DIR_ON_SECURITY_EXCEPTION) return;
456            else                                throw e;
457        }
458
459        // RECENT-OCCURENCE: (Never Needed the Google-Cloud-Shell Root Directory)
460        // A directory that is denied access, seems to return null.  The Java-Doc for it says it
461        // should be throwing a java.lang.SecurityException
462
463        if (subFilesAndDirs == null)
464        {
465            if (VERBOSE) System.out.println("DIR IS RESTRICTED: " + f.getAbsolutePath());
466            return;
467        }
468
469        for (File sub : subFilesAndDirs)
470
471            if (sub.isDirectory())
472            {
473                if (VERBOSE) System.out.println("TESTING DIR: " + sub.getAbsolutePath());
474
475                if (directoryFilter != null) if (! directoryFilter.accept(sub)) continue;
476
477                long lastModified = 0;
478
479                try { lastModified = sub.lastModified(); } catch (SecurityException se) { }
480
481                FileNode newSubDir = new FileNode(sub.getName(), this, lastModified);
482
483                children.addElement(newSubDir);
484
485                if (VERBOSE) System.out.println("ADDED DIR: " + newSubDir.getFullPathName());
486
487                if (maxTreeDepth != 0)
488                    newSubDir.loadTreeINTERNAL(maxTreeDepth - 1, fileFilter, directoryFilter);
489
490            }
491
492            else /* sub is a file, not a directory */
493            {
494                if (fileFilter != null)
495                    if (! fileFilter.accept(sub.getParentFile(), sub.getName()))
496                        continue;
497
498                long lastModified = 0;
499
500                try { lastModified = sub.lastModified(); } catch (SecurityException se) { }
501
502                children.addElement(new FileNode(sub.getName(), this, sub.length(), lastModified));
503
504                if (VERBOSE) System.out.println
505                    ("ADDED FILE: " + sub.getPath() + "\t\t[" + sub.length() + "]");
506            }
507    }
508
509
510    // ********************************************************************************************
511    // ********************************************************************************************
512    // Returns information about the contents of the "children Vector<FileNode>"
513    // ********************************************************************************************
514    // ********************************************************************************************
515
516
517    /**
518     * This returns the number of Child-Nodes in {@code 'this'} instance of {@code FileNode}.
519     *
520     * <BR /><BR /><B CLASS=JDDescLabel>Non-Recursive Check:</B>
521     * 
522     * <BR />This method is not 'recursive', which means that the integer returned by this method
523     * is only a count of the number of <B><I>direct-descendants</I></B> of {@code 'this'}
524     * instance.
525     * 
526     * <BR /><BR />Another way of saying this is that all it returns is the size of the internal
527     * {@link #children} {@code Vector}.  It doesn't actually enter any sub-directories to perform
528     * this count.
529     *
530     * @see #numDirChildren()
531     * @see #numFileChildren()
532     * @see #children
533     *
534     * @throws DirExpectedException If {@code 'this'} instance of {@code FileNode} is not a
535     * directory, but rather a file, then this exception is thrown.  (Files <I>may not</I> have
536     * child-nodes, only directories may have them).
537     */
538    public int numChildren() { DirExpectedException.check(this); return children.size(); }
539
540    /**
541     * This returns the exact number of Child-Nodes of {@code 'this'} instance of {@code FileNode}
542     * which are <B>directories</B>.
543     *
544     * <BR /><BR /><B CLASS=JDDescLabel>Non-Recursive Check:</B>
545     * 
546     * <BR />This method is not 'recursive', which means that the integer returned by this method
547     * is only a count of the number of <B><I>direct-descendants</I></B> of {@code 'this'}
548     * instance.
549     * 
550     * <BR /><BR />This method performs a count on the elements of the internal {@link #children}
551     * {@code Vector} to see how many elements have an {@link #isDirectory} field set to
552     * {@code TRUE}.
553     * 
554     * @see #numFileChildren()
555     * @see #numChildren()
556     * @see #children
557     *
558     * @throws DirExpectedException If {@code 'this'} instance of {@code FileNode} is not a
559     * directory, but rather a file, then this exception is thrown.  (Files <I>may not</I> have
560     * child-nodes, only directories may have them).
561     */
562    public int numDirChildren()
563    {
564        DirExpectedException.check(this);
565
566        int dirCount = 0;
567
568        for (int i=0; i < children.size(); i++) if (children.elementAt(i).isDirectory) dirCount++;
569
570        return dirCount;
571    }
572
573    /**
574     * This returns the exact number of Child-Nodes of {@code 'this'} instance of {@code FileNode}
575     * which are <B>files</B>.
576     *
577     * <BR /><BR /><B CLASS=JDDescLabel>Non-Recursive Check:</B>
578     * 
579     * <BR />This method is not 'recursive', which means that the integer returned by this method
580     * is only a count of the number of <B><I>direct-descendants</I></B> of {@code 'this'}
581     * instance.
582     * 
583     * <BR /><BR />This method performs a count on the elements of the internal {@link #children}
584     * {@code Vector} to see how many elements have an {@link #isDirectory} field set to
585     * {@code FALSE}.
586     *
587     * @see #numDirChildren()
588     * @see #numChildren()
589     * @see #isDirectory
590     * @see #children
591     *
592     * @throws DirExpectedException If {@code 'this'} instance of {@code FileNode} is not a
593     * directory, but rather a file, then this exception is thrown.  (Files <I>may not</I> have
594     * child-nodes, only directories may have them).
595     */
596    public int numFileChildren()
597    {
598        DirExpectedException.check(this);
599
600        int fileCount = 0;
601
602        for (int i=0; i < children.size(); i++)
603            if (! children.elementAt(i).isDirectory)
604                fileCount++;
605
606        return fileCount;
607    }
608
609
610    // ********************************************************************************************
611    // ********************************************************************************************
612    // retrieve operations
613    // ********************************************************************************************
614    // ********************************************************************************************
615
616
617    /** 
618     * Convenience Method.
619     * <BR />Invokes: {@link #dir(String, boolean)}
620     * <BR />Does <B>NOT</B> ignore case
621     */
622    public FileNode dir(String dirName) { return dir(dirName, false); }
623
624    /**
625     * Retrieves the sub-directory {@code FileNode} instance named by parameter {@code 'dirName'}
626     * if there is a {@code FileNode} that is a <I>direct descendant</I> of {@code 'this'} instance
627     * of {@code FileNode}.
628     * 
629     * @param dirName This is the name of any directory.
630     * 
631     * <BR /><BR /><B STYLE="color: red">IMPORTANT:</B> This must be the <I><B>name-only</I></B>
632     * leaving out all parent-directory or drive-letter text.
633     * 
634     * <BR /><BR /><B STYLE="color: red">FURTHERMORE:</B> The forward slash ({@code '/'}) or the
635     * back-slash ({@code '\'}) character that sometimes is appended to a directory-name
636     * <B><I>may not</I></B> be included in this name (unless a forward-slash or back-slash is
637     * a part of the name of the directory).
638     * 
639     * @param ignoreCase For some files and directories, on some operating systems (Microsoft
640     * Windows, for instance) File-System name case is not considered relevant when matching
641     * directory names.  If this parameter is passed {@code TRUE}, then name comparisons will use
642     * a case-insensitive comparison mechanism.
643     * 
644     * @return The child {@code FileNode} (sub-directory) of this directory whose name matches
645     * the name provided by parameter {@code 'dirName'}.
646     * 
647     * <BR /><BR />If no matching directory is found, then this method shall return null.
648     * 
649     * @throws DirExpectedException If {@code 'this'} instance of {@code FileNode} is a file,
650     * not a directory, then this exception shall throw.  Only directories can contain other
651     * instances of {@code FileNode}.
652     * 
653     * @see #children
654     * @see #isDirectory
655     * @see #name
656     */
657    public FileNode dir(String dirName, boolean ignoreCase)
658    {
659        // Only directories may contain other instances of FileNode
660        DirExpectedException.check(this);
661
662        // We are looking for a directory named 'dirName'
663        //
664        // IMPORTANT: The outer squiqgly-braces are MANDATORY.  Without them, there is 
665        //            "deceptive indentation," because the 'else' is paired with the second-if,
666        //            not the first!
667
668        if (ignoreCase)
669        {
670            for (FileNode fn : children)
671                if (fn.isDirectory && fn.name.equalsIgnoreCase(dirName)) return fn;
672        }
673
674        else
675        {
676            for (FileNode fn2 : children)
677                if (fn2.isDirectory && fn2.name.equals(dirName)) return fn2;
678        }
679
680        // Not found, return null.
681        return null;
682    }
683
684    /** 
685     * Convenience Method.
686     * <BR />Invokes: {@link #file(String, boolean)}
687     * <BR />Does <B>NOT</B> ignore case
688     */
689    public FileNode file(String fileName) { return file(fileName, false); }
690
691    /**
692     * Retrieves a {@code FileNode} named by parameter {@code 'fileName'} if there is a
693     * {@code FileNode} instance that is a <I>direct descendant</I> of {@code 'this' FileNode}
694     * that is, itself, a file and not a directory.
695     * 
696     * @param fileName This is the name of any file.
697     * 
698     * <BR /><BR /><B STYLE="color: red">IMPORTANT:</B> This must be the <I><B>name-only</I></B>
699     * leaving out all parent-directory or drive-letter text.
700     * 
701     * @param ignoreCase For some files and directories, on some operating systems (Microsoft
702     * Windows, for instance) file-name case is not considered relevant when matching file
703     * names.  If this parameter is passed {@code TRUE}, then file-name comparisons will use
704     * a case-insensitive comparison mechanism.
705     * 
706     * @return An instance of {@code FileNode} that is a <I>direct-descendant</I> of
707     * {@code 'this'} directory - and whose name matches the name provided by parameter
708     * {@code 'fileName'}.
709     * 
710     * <BR /><BR />If no matching file is found, then this method shall return null.
711     * 
712     * @throws DirExpectedException If {@code 'this'} instance of {@code FileNode} is a file,
713     * not a directory, then this exception shall throw.  Only directories can contain other
714     * instances of {@code FileNode}.
715     * 
716     * @see #children
717     * @see #isDirectory
718     * @see #name
719     */
720    public FileNode file(String fileName, boolean ignoreCase)
721    {
722        // Only directories may contain other instances of FileNode
723        DirExpectedException.check(this);
724
725        // We are looking for a file named 'fileName'
726        //
727        // IMPORTANT: The outer squiqly-braces are MANDATORY.  Without them, there is 
728        //            "deceptive indentation," because the 'else' is paired with the second-if,
729        //            not the first!
730
731        if (ignoreCase)
732        {
733            for (FileNode fn : children)
734                if ((! fn.isDirectory) && fn.name.equalsIgnoreCase(fileName)) return fn;
735        }
736
737        else
738        {
739            for (FileNode fn2 : children)
740                if ((! fn2.isDirectory) && fn2.name.equals(fileName)) return fn2;
741        }
742
743        // Not found, return null.
744        return null;
745    }
746
747
748    // ********************************************************************************************
749    // ********************************************************************************************
750    // Search and Retrieve Operations, Search the Entire Directory-Tree
751    // ********************************************************************************************
752    // ********************************************************************************************
753
754
755    /**
756     * Searches a {@code FileNode}, looking for any branch (directory) or leaf-node (file) that
757     * positively matches the provided filter parameter {@code 'f'}.  Exits and returns immediately
758     * upon finding such a match.
759     * 
760     * <BR /><BR />Here, a Source-Code Directory is searched for the first file or directory that
761     * is found which has a {@link #lastModified} value greater than 12:01 AM, today.
762     * 
763     * <DIV CLASS=EXAMPLE>{@code
764     * // Create a LocalDateTime object for 12:01 AM of today, and converts that to milliseconds
765     * // From the Java Time Package (java.time.*)
766     * 
767     * final long TODAY = LocalDateTime
768     *      .of(LocalDate.now(), LocalTime.of(0, 1));
769     *      .toInstant(ZoneOffset.UTC).toEpochMilli();
770     * 
771     * String todaysFile = FileNode
772     *      .createRoot("src/main/")
773     *      .loadTree()
774     *      .findFirst((FileNode fn) -> fn.lastModified >= TODAY)
775     *      .getFullPathName();
776     * }</DIV>
777     * 
778     * @param f Any filter may be used for selecting the file instance being searched.
779     * 
780     * @return The first {@code FileNode} instance in {@code 'this'} tree that matches the
781     * provided filter-predicate.
782     * 
783     * <BR /><BR />If no matching node is found, then this method shall return null.
784     * 
785     * @throws DirExpectedException If {@code 'this'} instance of {@code FileNode} is a file,
786     * not a directory, then this exception shall throw.  Only directories can contain other
787     * instances of {@code FileNode}.
788     * 
789     * @see #children
790     * @see #isDirectory
791     */
792    public FileNode findFirst(FileNodeFilter f)
793    {
794        // Only directories may contain other instances of FileNode
795        DirExpectedException.check(this);
796
797        return ffINTERNAL(f);
798    }
799
800    // This is included to optimize away the preliminary exception check in the previous method,
801    // that is directly above.  Other than the exception-check these two methods are identical.
802
803    private FileNode ffINTERNAL(FileNodeFilter f)
804    {
805        for (FileNode fn : children) if (f.test(fn)) return fn;
806
807        for (FileNode fn : children)
808            if (fn.isDirectory)
809                if ((fn = fn.ffINTERNAL(f)) != null)
810                    return fn;
811
812        return null;
813    }
814
815    /**
816     * Traverses {@code 'this'} tree instance looking for any {@code FileNode} instance that 
817     * is a directory, and matches the filter selector parameter {@code 'f'} predicate
818     * {@code 'test'} method.
819     * 
820     * <BR /><BR />This method will exit and return the first such match it encounters in the
821     * tree.
822     * 
823     * <BR /><BR />In the example below, a {@code FileNode}-Tree is built out of one particular
824     * {@code 'src/main'} directory, and then that entire directory is searched for any sub-folder
825     * (anywhere in the sub-tree) whose name is equal to {@code 'MyImportantClasses'}.
826     * 
827     * <DIV CLASS=EXAMPLE>{@code
828     * FileNode myFolder = FileNode
829     *      .createRoot("My Source Code/src/main/")
830     *      .loadTree()
831     *      .findFirstDir((FileNode fn) -> fn.name.equals("MyImportantClasses"))
832     * }</DIV>
833     * 
834     * <BR /><BR />In this example, a local directories' "sub-tree" is searched for any sub-folder
835     * that has at least 15 non-directory files inside.
836     * 
837     * <DIV CLASS=EXAMPLE>{@code
838     * FileNode atLeast10 = FileNode
839     *      .createRoot("My Saved PDFs")
840     *      .loadTree()
841     *      .findFirstDir((FileNode fn) -> fn.numFileChildren() >= 15);
842     * }</DIV>
843     * 
844     * @param f Any filter may be used for selecting the {@code FileNode} directory instance being
845     * searched.
846     * 
847     * @return The first {@code FileNode} instance in {@code 'this'} tree whose
848     * {@link #isDirectory} flag is {@code TRUE} and, furthermore, matches the provided
849     * filter-predicate.
850     * 
851     * <BR /><BR />If no matching directory is found, then this method shall return null.
852     * 
853     * @throws DirExpectedException If {@code 'this'} instance of {@code FileNode} is a file,
854     * not a directory, then this exception shall throw.  Only directories can contain other
855     * instances of {@code FileNode}.
856     * 
857     * @see #children
858     * @see #isDirectory
859     */
860    public FileNode findFirstDir(FileNodeFilter f)
861    {
862        // Only directories may contain other instances of FileNode
863        DirExpectedException.check(this);
864
865        return ffdINTERNAL(f);
866    }
867
868    // Optimizes away the exception check
869    private FileNode ffdINTERNAL(FileNodeFilter f)
870    {
871        for (final FileNode fn : children) if (fn.isDirectory && f.test(fn)) return fn;
872
873        for (FileNode fn : children)
874            if (fn.isDirectory)
875                if ((fn = fn.ffdINTERNAL(f)) != null)
876                    return fn;
877
878        return null;
879    }
880
881    /**
882     * This method is extremely similar to {@link #findFirstDir(FileNodeFilter)}, but searches
883     * for leaf-node files, instead of sub-folders / directories.
884     * 
885     * @param f Any filter may be used for selecting the file instance being searched.
886     * 
887     * @return The first {@code FileNode} instance in {@code 'this'} tree whose
888     * {@link #isDirectory} flag is {@code FALSE} and, furthermore, matches the provided
889     * filter-predicate.
890     * 
891     * <BR /><BR />If no matching directory is found, then this method shall return null.
892     * 
893     * @throws DirExpectedException If {@code 'this'} instance of {@code FileNode} is a file,
894     * not a directory, then this exception shall throw.  Only directories can contain other
895     * instances of {@code FileNode}.
896     * 
897     * @see #children
898     * @see #isDirectory
899     */
900    public FileNode findFirstFile(FileNodeFilter f)
901    {
902        // Only directories may contain other instances of FileNode
903        DirExpectedException.check(this);
904
905        return fffINTERNAL(f);
906    }
907
908    // Optimizes away the exception check
909    private FileNode fffINTERNAL(FileNodeFilter f)
910    {
911        for (final FileNode fn : children) if ((! fn.isDirectory) && f.test(fn)) return fn;
912
913        for (FileNode fn : children)
914            if (fn.isDirectory)
915                if ((fn = fn.fffINTERNAL(f)) != null)
916                    return fn;
917
918        return null;
919    }
920
921
922    // ********************************************************************************************
923    // ********************************************************************************************
924    // poll operations
925    // ********************************************************************************************
926    // ********************************************************************************************
927
928
929    /**
930     * Convenience Method.
931     * <BR />Invokes: {@link #pollDir(String, boolean)}
932     * <BR />Does <B>NOT</B> ignore case
933     */
934    public FileNode pollDir(String dirName) { return pollDir(dirName, false); }
935
936    /**
937     * Retrieves the sub-directory {@code FileNode} instance named by parameter {@code 'dirName'}
938     * if there is a {@code FileNode} that is a <B>Direct Descendant</B> of {@code 'this'}
939     * instance of {@code FileNode}.
940     * 
941     * <EMBED CLASS='external-html' DATA-KIND=dir DATA-NAME=directory DATA-FILE-ID=FN_POLL_DIRFILE>
942     * 
943     * @param dirName This is the name of any directory.
944     * 
945     * <BR /><BR /><B STYLE="color: red">IMPORTANT:</B> This must be the <I><B>name-only</I></B>
946     * leaving out all parent-directory or drive-letter text.
947     * 
948     * <BR /><BR /><B STYLE="color: red">FURTHERMORE:</B> The forward slash ({@code '/'}) or the
949     * back-slash ({@code '\'}) character that sometimes is appended to a directory-name
950     * <B><I>may not</I></B> be included in this name (unless a forward-slash or back-slash is
951     * a part of the name of the directory).
952     * 
953     * <BR /><BR /><B STYLE="color: red">FINALLY:</B> When this directory is extracted, none
954     * of the child pointers contained by this directory-instance of {@code FileNode} will be
955     * modified.  In essence, the entire sub-tree - <I>starting at the directory that was 
956     * specified</I> - will be extracted from the parent-tree.  Any / all contents of the
957     * sub-tree shall be in the same state as they were prior to the extraction.
958     * 
959     * @param ignoreCase For some files and directories, on some operating systems (Microsoft
960     * Windows, for instance) File-System name case is not considered relevant when matching
961     * directory names.  If this parameter is passed {@code TRUE}, then name comparisons will use
962     * a case-insensitive comparison mechanism.
963     * 
964     * @return The child {@code FileNode} (sub-directory) of {@code 'this'} directory whose name
965     * matches the name provided by parameter {@code 'dirName'}.  It's {@code 'parent'} field
966     * will be null, and the parent {@code FileNode} instance will not have a pointer to the
967     * instance that is returned.
968     * 
969     * <BR /><BR />If no matching directory is found, then this method shall return null.
970     * 
971     * @throws DirExpectedException If {@code 'this'} instance of {@code FileNode} is a file,
972     * not a directory, then this exception shall throw.  Only directories can contain other
973     * instances of {@code FileNode}.
974     * 
975     * @see #dir(String, boolean)
976     * @see #children
977     */
978    public FileNode pollDir(String dirName, boolean ignoreCase)
979    {
980        FileNode ret = dir(dirName, ignoreCase);
981
982        if (ret != null)
983        {
984            children.remove(ret);
985            ret.parent = null;
986        }
987
988        return ret;
989    }
990
991    /** 
992     * Convenience Method.
993     * <BR />Invokes: {@link #pollFile(String, boolean)}
994     * <BR />Does <B>NOT</B> ignore case
995     */
996    public FileNode pollFile(String fileName) { return pollFile(fileName, false); }
997
998    /**
999     * Retrieves a {@code FileNode} instance named by parameter {@code 'fileName'} if there is
1000     * a {@code FileNode} that is a <B>Direct Descendant</B> of {@code 'this'} instance
1001     * of {@code FileNode}, <B><I>and</I></B> that instance is a file (not a directory) whose
1002     * name matches parameter {@code 'fileName'}.
1003     * 
1004     * <EMBED CLASS='external-html' DATA-KIND=file DATA-NAME=file DATA-FILE-ID=FN_POLL_DIRFILE>
1005     * 
1006     * @param fileName This is the name of any file.
1007     * 
1008     * <BR /><BR /><B STYLE="color: red">IMPORTANT:</B> This must be the <I><B>name-only</I></B>
1009     * leaving out all parent-directory or drive-letter text.
1010     * 
1011     * @param ignoreCase For some files and directories, on some operating systems (Microsoft
1012     * Windows, for instance) File-System name case is not considered relevant when matching
1013     * file-names.  If this parameter is passed {@code TRUE}, then name comparisons will use
1014     * a case-insensitive comparison mechanism.
1015     * 
1016     * @return The child {@code FileNode} of {@code 'this'} directory whose name matches the
1017     * name provided by parameter {@code 'fileName'}.  It's {@code 'parent'} field
1018     * will be null, and the parent {@code FileNode} instance will not have a pointer to the
1019     * instance that is returned.
1020     * 
1021     * <BR /><BR />If no matching file is found, then this method shall return null.
1022     * 
1023     * @throws DirExpectedException If {@code 'this'} instance of {@code FileNode} is a file,
1024     * not a directory, then this exception shall throw.  Only directories can contain other
1025     * instances of {@code FileNode}.
1026     * 
1027     * @see #file(String, boolean)
1028     */
1029    public FileNode pollFile(String fileName, boolean ignoreCase)
1030    {
1031        FileNode ret = file(fileName, ignoreCase);
1032
1033        if (ret != null)
1034        {
1035            children.remove(ret);
1036            ret.parent = null;
1037        }
1038
1039        return ret;
1040    }
1041
1042    /**
1043     * Extracts {@code 'this' FileNode} from its parent's tree.
1044     * @return returns {@code 'this'} for convenience.
1045     */
1046    public FileNode pollThis()
1047    {
1048        if (this.parent == null) throw new FileNodeException 
1049            ("Attempting to poll a FileNode, but it's directory-parent FileNode is null");
1050
1051        boolean             removed = false;
1052        Iterator<FileNode>  iter    = this.parent.children.iterator();
1053
1054        while (iter.hasNext())
1055        {
1056            FileNode fn = iter.next();
1057
1058            if (fn == this)
1059            {
1060                iter.remove();
1061                removed = true;
1062                break;
1063            }
1064        }
1065
1066        // This is a simple-variant on Java's assert statement.  It is saying that the parent
1067        // FileNode better know where its children are, or else it means this FileNode tree has
1068        // some kind of bug.
1069
1070        if (! removed) throw new UnreachableError();
1071
1072        // Erase this node's parent
1073        this.parent = null;
1074
1075        return this;
1076    }
1077
1078
1079    // ********************************************************************************************
1080    // ********************************************************************************************
1081    // These methods satisfy the Cloneable, Comparable, CharSequence Interfaces
1082    // ********************************************************************************************
1083    // ********************************************************************************************
1084
1085
1086    /**
1087     * This satisfies Java's "hash-code" method requirement.  This can facilitate saving instances
1088     * of this class into tables, maps, lists, etc.
1089     *
1090     * @return A hash-code to be used by a hash-algorithm with likely few crashes.  Note that the
1091     * hash from Java's {@code java.lang.String} is simply reused.
1092     */
1093    public int hashCode() { return toString().hashCode(); }
1094
1095    /*
1096     * Java's {@code equals(Object o)} method requirements.
1097     *
1098     * <BR /><BR /><B CLASS=JDDescLabel>Final Method:</B>
1099     * 
1100     * <BR />This method is final, and cannot be modified by sub-classes.
1101     * 
1102     * @param o This may be any {@code java.lang.Object}, but only ones of {@code 'this'} type
1103     * whose internal-values are identical will cause this method to return {@code TRUE}.
1104     *
1105     * @return {@code TRUE} If {@code 'this'} instance' internal-fields are equal to the
1106     * internal-fields another {@code FileNode} instance.
1107     * 
1108     * <BR /><BR /><B><SPAN STYLE='color: red;">DEEP-EQUALS:</B></SPAN> Due to how Java's
1109     * {@code class Vector} has implemented it's {@code Vector.equals(other)} method - which is
1110     * how the child tree-branches of a directory {@code FileNode} stores it's directory
1111     * branches - this method <I>does, indeed, perform a 'Deep Equals'</I>.
1112     *
1113     * @see FileNode#name
1114     * @see FileNode#parent
1115     * @see FileNode#isDirectory
1116     * @see FileNode#children
1117     */
1118    public final boolean equals(Object o)
1119    {
1120        FileNode other;
1121
1122        return (this == o)
1123            ||  ((o != null)
1124            &&  (this.getClass().equals(o.getClass()))
1125            &&  ((other = (FileNode) o).name.equals(this.name))
1126            &&  (this.parent        == other.parent)        // NOTE: A "Reference Comparison"
1127            &&  (this.isDirectory   == other.isDirectory)
1128            &&  (this.fileSize      == other.fileSize)
1129            &&  (this.lastModified  == other.lastModified)
1130            &&  this.name.equals(other.name)
1131            &&  (   ((this.children == null) && (other.children == null))
1132                ||  (this.children.equals(other.children)))
1133        );
1134    }
1135
1136    /**
1137     * Java's {@code interface Cloneable} requirements.  This instantiates a new {@code FileNode}
1138     * with identical fields.  The field {@code Vector<FileNode> 'children'} shall be cloned too.
1139     *
1140     * @return A new {@code FileNode} whose internal fields are identical to this one.
1141     *
1142     * <BR /><BR /><B><SPAN STYLE="color: red;">IMPORTANT (DEEP-CLONE) NOTE:</SPAN></B> This
1143     * <B>does not</B> perform a deep-tree-traversal clone.  Instead, {@code 'this'} instance is
1144     * merely copied, and it's child nodes have references inserted into the internal list of
1145     * child-nodes.
1146     *
1147     * @see FileNode#name
1148     * @see FileNode#parent
1149     * @see FileNode#isDirectory
1150     */
1151    public FileNode clone()
1152    {
1153        if (this.isDirectory)
1154        {
1155            FileNode ret = new FileNode(this.name, this.parent, this.lastModified);
1156            ret.children.addAll(this.children);
1157            return ret;
1158        }
1159
1160        else
1161            return new FileNode(this.name, this.parent, this.fileSize, this.lastModified);
1162    }
1163
1164    /**
1165     * Java's {@code Comparable<T>} Interface-Requirements.  This does a very simple comparison 
1166     * using the results to a call of method {@link #getFullPathName()}
1167     *
1168     * @param fn Any other {@code FileNode} to be compared to {@code 'this' FileNode}.  The file
1169     * or directories {@code getFullPathName()} is used to perform a "String" comparison.
1170     *
1171     * @return An integer that fulfils Java's {@code interface Comparable<T> public boolean 
1172     * compareTo(T t)} method requirements.
1173     *
1174     * @see #getFullPathName()
1175     */
1176    public final int compareTo(FileNode fn)
1177    { return this.getFullPathName().compareTo(fn.getFullPathName()); }
1178
1179    /**
1180     * This is an "alternative Comparitor" that can be used for sorting instances of this class.
1181     * It should work with the {@code Collections.sort(List, Comparator)} method in the standard 
1182     * JDK package {@code java.util.*;}
1183     *
1184     * <BR /><BR /><B CLASS=JDDescLabel>Comparison Heuristic:</B>
1185     * 
1186     * <BR />This version utilizes the standard JDK {@code String.compareToIgnoreCase(String)}
1187     * method.
1188     *
1189     * @see #getFullPathName()
1190     */
1191    public static final Comparator<FileNode> comp2 = (FileNode fn1, FileNode fn2) ->
1192        fn1.getFullPathName().compareToIgnoreCase(fn2.getFullPathName());
1193
1194    /**
1195     * Converts {@code 'this' FileNode} to a {@code String}.
1196     *
1197     * @return The complete-full path-name of this file or directory.
1198     */
1199    public String toString() { return this.getFullPathName(); }
1200
1201    /**
1202     * Returns the {@code char} value at the specified index of the results to a call of method
1203     * {@link #getFullPathName()}.  An index ranges from {@code zero} to {@code length() - 1}.  The
1204     * first {@code char} value of the sequence is at index {@code zero}, the next at index
1205     * {@code one}, and so on and so forth - as per array indexing.
1206     *
1207     * <BR /><BR /><B CLASS=JDDescLabel>Character Surrogates:</B>
1208     * 
1209     * <BR />If the {@code char} value specified by the index is a surrogate,
1210     * the surrogate value is returned.
1211     *
1212     * <BR /><BR /><B CLASS=JDDescLabel>Final Method:</B>
1213     * 
1214     * <BR />This method is final, and cannot be modified by sub-classes.
1215     * 
1216     * @param index The index of the {@code char} value to be returned
1217     *
1218     * @return The specified {@code char} value
1219     *
1220     * @see #getFullPathName()
1221     */
1222    public final char charAt(int index) { return this.getFullPathName().charAt(index); }
1223
1224    /**
1225     * Returns the length of the {@code String} returned by {@code public String getFullPathName()}
1226     * The length is the number of 16-bit characters in the sequence.
1227     *
1228     * <BR /><BR /><B CLASS=JDDescLabel>Final Method:</B>
1229     * 
1230     * <BR />This method is final, and cannot be modified by sub-classes.
1231     * 
1232     * @return the number of characters in the "Full Path Name" for {@code 'this'} file or\
1233     * directory.
1234     *
1235     * @see #getFullPathName()
1236     */
1237    public final int length() { return this.getFullPathName().length(); }
1238
1239    /**
1240     * Returns a {@code java.lang.CharSequence} that is a subsequence of the results to a call of
1241     * method {@link #getFullPathName()}
1242     *
1243     * <BR /><BR /> The subsequence starts with the {@code char} value at the specified index and 
1244     * ends with the {@code char} value at index {@code 'end - 1'}.  The length (in characters) of
1245     * the returned sequence is {@code 'end - start'},  so in the case where
1246     * {@code 'start == end'} then an empty sequence is returned.
1247     *
1248     * <BR /><BR /><B CLASS=JDDescLabel>Final Method:</B>
1249     * 
1250     * <BR />This method is final, and cannot be modified by sub-classes.
1251     * 
1252     * @param start The start index, inclusive
1253     * @param end The end index, exclusive
1254     *
1255     * @return The specified subsequence
1256     * @see #getFullPathName()
1257     */
1258    public final CharSequence subSequence(int start, int end)
1259    { return this.getFullPathName().substring(start, end); }
1260
1261
1262    // ********************************************************************************************
1263    // ********************************************************************************************
1264    // Deep-Tree Traversal
1265    // ********************************************************************************************
1266    // ********************************************************************************************
1267
1268
1269    /**
1270     * Whereas the standard Java {@code clone()} method in this class returns a new, cloned,
1271     * instance of {@code FileNode}, if {@code 'this'} instance of {@code FileNode} is a directory,
1272     * the tree-branch represented by {@code 'this' FileNode} instance would not be copied by an
1273     * invocation of {@code 'clone'}.  However, if using this method, {@code 'deepClone'}, on a
1274     * directory-{@code FileNode} instance, <B><I>the entire tree-branch represented by
1275     * {@code 'this' FileNode} instance is copied.</I></B>.
1276     * 
1277     * <BR /><BR /><B CLASS=JDDescLabel>Deep-Clone:</B>
1278     * 
1279     * <BR />The method's {@code clone()} and {@code deepClone()} shall return identical results
1280     * when used on an instance of {@code FileNode} that represents a file, rather than a directory
1281     * (<I>and, therefore, does not have any tree-branch information associated with it.</I>).
1282     * 
1283     * @return a <B>"Deep Clone"</B> of {@code 'this' FileNode} instance.  If {@code 'this'}
1284     * instance of {@code FileNode} represents a file, not a directory, the results of this method
1285     * shall be identical to the results of an invocation of the standard {@code 'clone()'} method.
1286     * If {@code 'this' FileNode} represents an operation-system directory (not a file), then 
1287     * each and every child of this tree-branch shall also be copied / cloned by this method.
1288     */
1289    public FileNode deepClone()
1290    {
1291        if (this.isDirectory)
1292        {
1293            FileNode ret = new FileNode(this.name, this.parent, this.lastModified);
1294            for (FileNode child : children) ret.children.add(child.deepClone());
1295            return ret;
1296        }
1297
1298        else return this.clone();
1299    }
1300
1301
1302    // ********************************************************************************************
1303    // ********************************************************************************************
1304    // Basic Methods
1305    // ********************************************************************************************
1306    // ********************************************************************************************
1307
1308
1309    /**
1310     * This returns the name of the file, but leaves off the "extension"
1311     * @return Returns the name <I>without the file-extension</I>
1312     * 
1313     * @throws FileExpectedException Since only files may have extensions, if {@code 'this'}
1314     * instance of {@code FileNode} is a directory, the {@code FileExpectedException} will throw.
1315     */
1316    public String nameNoExt()
1317    {
1318        FileExpectedException.check(this);  // Directories do not have extensions
1319
1320        int pos = name.lastIndexOf('.');
1321
1322        if (pos == -1) return name;
1323
1324        return name.substring(0, pos);
1325    }
1326
1327    /**
1328     * This returns the extension of the file.  If this file does not have an extension,
1329     * then null shall be returned.
1330     *
1331     * @param includeTheDot if the user would like to have the {@code '.'} included in the
1332     * return {@code String}, then {@code TRUE} should be passed to this parameter.  
1333     *
1334     * @return Returns this file's extension
1335     * 
1336     * @throws FileExpectedException Since only files may have extensions, if {@code 'this'}
1337     * instance of {@code FileNode} is a directory, the {@code FileExpectedException} will throw.
1338     */
1339    public String ext(boolean includeTheDot)
1340    {
1341        FileExpectedException.check(this);  // Directories do not have extensions
1342
1343        int pos = name.lastIndexOf('.');
1344
1345        if (pos == -1) return null;
1346
1347        return includeTheDot ? name.substring(pos) : name.substring(pos+1);        
1348    }
1349
1350    /**
1351     * Invokes the input Java {@code Consumer<FileNode>} on each element in {@code 'this'} 
1352     * {@code FileNode}-Tree.  Note that if {@code 'this'} instance of is a file, not a directory,
1353     * then the passed {@code Consumer} shall only be invoked once (on {@code 'this'} instance,
1354     * since files do not have sub-directories).
1355     *
1356     * @param c This is any java {@code Consumer<FileNode>}
1357     */
1358    public void forEach(Consumer<FileNode> c)
1359    {
1360        c.accept(this);
1361        if (children != null) children.forEach((FileNode fn) -> fn.forEach(c));
1362    }
1363
1364
1365    // ********************************************************************************************
1366    // ********************************************************************************************
1367    // Print Tree - self explanatory
1368    // ********************************************************************************************
1369    // ********************************************************************************************
1370
1371
1372    /** 
1373     * Convenience Method.
1374     * <BR />Passes: {@code System.out} to {@code Appendable}, and nulls
1375     * <BR />Invokes: {@link #printTree(Appendable, boolean, FileNodeFilter, FileNodeFilter)}
1376     * <BR />Catches: {@code Appendable's IOException}.  Prints Stack Trace.
1377     */
1378    public void printTree()
1379    {
1380        try
1381            { printTree(System.out, false, null, null); }
1382
1383        catch (IOException e)
1384            { e.printStackTrace(); }
1385    }
1386
1387    /** 
1388     * Convenience Method.
1389     * <BR />Passes: 'null' to {@code Appendable} parameter (uses {@code System.out})  
1390     * <BR />Invokes: {@link #printTree(Appendable, boolean, FileNodeFilter, FileNodeFilter)}
1391     * <BR />Catches: {@code Appendable's IOException} 
1392     */
1393    public void printTreeNOIOE
1394        (boolean showSizes, FileNodeFilter fileTest, FileNodeFilter directoryTest)
1395    { try { printTree(null, showSizes, fileTest, directoryTest); } catch (IOException ioe) { } }
1396
1397    /**
1398     * This will print the directory tree to the {@code java.lang.Appendable} passed as a
1399     * parameter.  Specific Test-{@code Predicate's} may be sent to this method to identify which
1400     * branches of the File-System Directory-Tree should be printed.
1401     *
1402     * @param a If this is null, then {@code System.out} is used.  If it is not null, then
1403     * information is printed to this Java {@code java.lang.Appendable}.
1404     * 
1405     * <EMBED CLASS='external-html' DATA-FILE-ID=APPENDABLE>
1406     *
1407     * @param showSizes If this is true, then "file-size" information will also be printed with the
1408     * file.
1409     *
1410     * @param fileTest If this is null, then it is ignored, and all <B>files</B> in the
1411     * {@code FileNode} Directory-Tree pass (are accepted).
1412     * 
1413     * <BR /><BR />If this parameter is not null, then each {@code FileNode} that is not a
1414     * directory is run through this {@code Predicate's} test method.  If the test returns
1415     * {@code FALSE}, then this file is not printed to the output.
1416     *
1417     * @param directoryTest If this is null, then it is ignored, and all <B>directories</B> in
1418     * the {@code FileNode} Directory-Tree pass (are accepted).
1419     * 
1420     * <BR /><BR />If this parameter is not null, then each {@code FileNode} that is a directory is
1421     * run through this {@code Predicate's} test method, and any directories that fail this
1422     * {@code Predicate's test()} method (when {@code directoryTest.test(dir)} returns
1423     * {@code FALSE}), that directory will not be printed to the output.
1424     *
1425     * @throws IOException Java's {@code interface Appendable} mandates that the unchecked Java
1426     * {@code IOException} must be caught when using this interface.
1427     *
1428     * @see #printTree()
1429     * @see #getDirContentsFiles()
1430     * @see #getDirContentsDirs()
1431     * @see #fileSize
1432     * @see #getFullPathName
1433     */
1434    public void printTree
1435        (Appendable a, boolean showSizes, FileNodeFilter fileTest, FileNodeFilter directoryTest)
1436        throws IOException
1437    {
1438        if (a == null) a = System.out;
1439
1440        for (FileNode file : getDirContentsFiles(fileTest))
1441            a.append((showSizes ? (file.fileSize + ",\t") : "") + file.getFullPathName() + '\n');
1442
1443        for (FileNode dir : getDirContentsDirs(directoryTest))
1444        {
1445            a.append(dir.getFullPathName() + '\n');
1446            dir.printTree(a, showSizes, fileTest, directoryTest);
1447        }
1448    }
1449
1450
1451    // ********************************************************************************************
1452    // ********************************************************************************************
1453    // These check the size of a directory's contents.  The perform the sums using recursion
1454    // ********************************************************************************************
1455    // ********************************************************************************************
1456
1457
1458    /**
1459     * Convenience Method.
1460     * <BR />Invokes: {@link #getDirContentsSize(FileNodeFilter)}
1461     * <BR />Passes: null to filter-parameter {@code 'fileTest'}. (All file-sizes are counted)
1462     */
1463    public long getDirContentsSize()
1464    { return getDirContentsSize(null); }
1465
1466    /**
1467     * This sums the file-sizes of each file <B>in the current directory, not sub-directories</B>
1468     * that pass the requirements of the {@code Predicate<FileNode>} here.  If
1469     * {@code p.test(fileNode)} fails, then the size of a {@code FileNode} is not counted in the
1470     * total sum.
1471     *
1472     * <BR /><BR /><B CLASS=JDDescLabel>Non-Recursive Method:</B>
1473     * 
1474     * <BR />This only retrieves the contents of {@code 'this'} directory - and does not expand or
1475     * visit any sub-directories - when computing the total size of the files!
1476     *
1477     * @param fileTest Any Java Lambda-Expression that satisfies the requirement of having a
1478     * {@code public boolean test(FileNode); } method.   An instance of the interface
1479     * {@code 'FileNodeFilter'} will also work.
1480     *
1481     * <BR /><BR />This is used to "test" whether to include the files in a directory'
1482     * {@link #fileSize} in the summed return value.  When {@code TRUE} is returned by the 
1483     * {@code Predicate test(...)} method, a file's size will be included in the sum-total
1484     * directory-size.  When the {@code Predicate test(...)} method returns {@code FALSE}, the
1485     * tested file's size will be ignored and not included in the total.
1486     *
1487     * <BR /><BR />This may be null, and if it is, it is ignored.  This means that file-sizes for
1488     * all files in the directory will count towards the total-size returned by this method.
1489     *
1490     * @return The sum of file-sizes for each file which passes the {@code Predicate} test in this
1491     * directory.
1492     *
1493     * @throws DirExpectedException If {@code 'this'} instance of {@code FileNode} is not a
1494     * directory, but rather a file, then this exception is thrown.  (Files <I>may not</I> have
1495     * child-nodes, only directories).
1496     *
1497     * @see #fileSize
1498     * @see #children
1499     * @see #isDirectory
1500     */
1501    public long getDirContentsSize(FileNodeFilter fileTest)
1502    {
1503        DirExpectedException.check(this);
1504
1505        long size=0;           
1506
1507        for (FileNode f : children)
1508            if (! f.isDirectory)
1509                if ((fileTest == null) || fileTest.test(f))
1510                    size += f.fileSize;
1511
1512        return size;        
1513    }
1514
1515    /**
1516     * Convenience Method.
1517     * <BR />Invokes: {@link #getDirTotalContentsSize(FileNodeFilter, FileNodeFilter)}
1518     * <BR />Passes: null to both-filters (all file-sizes counted, no directories skipped)
1519     */
1520    public long getDirTotalContentsSize()
1521    { return getDirTotalContentsSize(null, null); }
1522
1523    /**
1524     * This sums the file contents in the current directory - and all sub-directories as well.
1525     * Only files that pass the {@code Predicate 'fileTest'} parameter are counted.  Furthermore,
1526     * only directories that pass the {@code Predicate 'directoryTest'} will be traversed and
1527     * inspected.
1528     * 
1529     * <BR /><BR /><B CLASS=JDDescLabel>Recursive Method:</B>
1530     * 
1531     * <BR />This method computes the sizes of the files, recursively.  Tbis method enters 
1532     * sub-directories (provided they pass the {@code 'directoryTest'}) to compute the total file
1533     * size.
1534     *
1535     * @param fileTest Any Java Lambda-Expression that satisfies the requirement of having a
1536     * {@code public boolean test(FileNode); } method.   An instance of the interface
1537     * {@code 'FileNodeFilter'} will also work.
1538     *
1539     * <BR /><BR />This is used to "test" whether to include the {@link #fileSize} for a specific
1540     * file in a directory in the summed return value.  When {@code TRUE} is returned by the
1541     * {@code Predicate 'test'}  method, a file's size will be included in the sum-total
1542     * directory-size.  When the {@code Predicate 'test'} method returns {@code FALSE}, the tested
1543     * file's size will be ignored, and not included in the total.
1544     *
1545     * <BR /><BR />This may be null, and if it is, it is ignored.  This means that file-sizes for
1546     * all files in the directory will count towards the total-size returned by this method.
1547     *
1548     * @param directoryTest Any Java Lambda-Expression that satisfies the requirement of having a
1549     * {@code public boolean test(FileNode); } method.   An instance of the interface
1550     * {@code 'FileNodeFilter'} will also work.
1551     *
1552     * <BR /><BR />This is used to test directories, rather than files, for inclusion in the total
1553     * file-size returned by this method.  When {@code TRUE} is returned by the filter's
1554     * {@code 'test'} method, then that directory shall be traversed, inspected, and its contents
1555     * shall have their {@code fileSize's} included in the computed result.
1556     *
1557     * <BR /><BR />This parameter may be null, and if it is, it is ignored.  This would mean that
1558     * all sub-directories shall be traversed when computing the total directory size.
1559     *
1560     * @return The sum of all file-sizes for each file in this directory that pass
1561     * {@code 'fileTest'}, and all sub-dir's that pass the {@code 'directoryTest'}.
1562     *
1563     * @throws DirExpectedException If {@code 'this'} instance of {@code FileNode} is not a 
1564     * directory, but rather a file, then this exception is thrown.  (Files <I>may not</I> have
1565     * child-nodes, only directories).
1566     (
1567     * @see #fileSize
1568     * @see #children
1569     * @see #getDirTotalContentsSize()
1570     */
1571    public long getDirTotalContentsSize(FileNodeFilter fileTest, FileNodeFilter directoryTest)
1572    {
1573        DirExpectedException.check(this);
1574
1575        long size=0;
1576
1577        for (FileNode f : children)
1578
1579            if (f.isDirectory)
1580            {
1581                if ((directoryTest == null) || directoryTest.test(f))
1582                    size += f.getDirTotalContentsSize(fileTest, directoryTest);
1583            }
1584
1585            else
1586            {
1587                if ((fileTest == null) || fileTest.test(f))
1588                    size += f.fileSize;
1589            }                 
1590
1591        return size;
1592    }
1593
1594
1595    // ********************************************************************************************
1596    // ********************************************************************************************
1597    // These count files and sub-directories
1598    // ********************************************************************************************
1599    // ********************************************************************************************
1600
1601
1602    /**
1603     * Convenience Method.
1604     * <BR />Invokes: {@link #count(FileNodeFilter, FileNodeFilter)}
1605     * <BR />Passes: null to both filter-parameters. (All files and directories are counted)
1606     */
1607    public int count() { return count(null, null); }
1608
1609    /**
1610     * Performs a count on the total number of files and directories contained by {@code 'this'}
1611     * directory.  This method is recursive, and traverses both {@code 'this'} directory, and all
1612     * sub-directories when calculating the return-value.
1613     *
1614     * @param fileFilter This allows a user to eliminate certain files from the total count.
1615     * 
1616     * <BR /><BR />The filter provided should be a {@code Predicate<FileNode>} that returns
1617     * {@code TRUE} if the file <I>should be counted</I>, and {@code FALSE} if the file <I>should
1618     * <B>not</B></I> be counted.
1619     *
1620     * <BR /><BR />This parameter may be {@code 'null'}, and if it is, it will be ignored.  In
1621     * such cases, all files will be included in the total count.
1622     * 
1623     * @param directoryFilter This allows a user to skip branches of the directory-tree when
1624     * performing the count.
1625     * 
1626     * <BR /><BR />The filter provided should be a {@code Predicate<FileNode>} that returns
1627     * {@code TRUE} if the sub-directory <I>should be entered</I> (and counted), and {@code FALSE}
1628     * if the sub-directory tree-branch <I>should be skipped</I> completely.
1629     *
1630     * <EMBED CLASS='external-html' DATA-FILE-ID=FN_COUNT_DIRFILT>
1631     *
1632     * @return A total count of all files and sub-directories contained by {@code 'this'} instance
1633     * of {@code FileNode} - less the files and directory-tree branches that were excluded by the
1634     * filters that may or may not have been passed to this method.
1635     *
1636     * @throws DirExpectedException If the user has attempted to perform a count on a
1637     * {@code FileNode} that is a 'file' rather than a 'directory'.
1638     */
1639    public int count(FileNodeFilter fileFilter, FileNodeFilter directoryFilter)
1640    {
1641        DirExpectedException.check(this);
1642
1643        // This was moved to an "INTERNAL" method to avoid invoking the above exception check
1644        // every time this (recursive) code encounters a directory.
1645
1646        return countINTERNAL(fileFilter, directoryFilter);
1647    }
1648
1649    private int countINTERNAL(FileNodeFilter fileFilter, FileNodeFilter directoryFilter)
1650    {
1651        int count = 0;
1652
1653        for (FileNode fn : children)
1654
1655            if (fn.isDirectory)
1656            {
1657                if ((directoryFilter == null) || directoryFilter.test(fn))
1658                    count += 1 /* 'this' adds 1 */ + fn.countINTERNAL(fileFilter, directoryFilter);
1659            }
1660            else
1661                if ((fileFilter == null) || fileFilter.test(fn))
1662                    count++;
1663
1664        return count;
1665    }
1666
1667    /**
1668     * Convenience Method.
1669     * <BR />Invokes: {@link #countJustFiles(FileNodeFilter, FileNodeFilter)}
1670     * <BR />Passes: null to both filter-parameters
1671     * (all <B>files</B> counted, no directories skipped).
1672     */
1673    public int countJustFiles() { return countJustFiles(null, null); }
1674
1675    /**
1676     * Performs a count on the total number of <I><B>files only</I></B> (does not count sub
1677     * directories) contained by {@code 'this'} directory.  This method is recursive, and traverses
1678     * both {@code 'this'} directory, and all sub-directories when calculating the return-value.
1679     *
1680     * @param fileFilter This allows a user to eliminate certain files from the total count.
1681     * 
1682     * <BR /><BR />The filter provided should be a {@link FileNodeFilter} (Predicate} /
1683     * Lambda-Expression that returns {@code TRUE} if the file <I>should be counted</I>, and
1684     * {@code FALSE} if the file <I>should <B>not</B></I> be counted.
1685     *
1686     * <BR /><BR />This parameter may be {@code 'null'}, and if it is, it will be ignored.  In
1687     * such cases, all files will be included in the total count.
1688     * 
1689     * @param directoryFilter This allows a user to skip branches of the directory-tree when
1690     * performing the count.
1691     * 
1692     * <BR /><BR />The filter provided should be a {@link FileNodeFilter} (Predicate} /
1693     * Lambda-Expression that returns {@code TRUE} if the sub-directory <I>should be entered</I>
1694     * (the directory itself will not contribute to the count).  When this filter returns
1695     * {@code FALSE} the sub-directory tree-branch <I>will be skipped</I> completely, and any files
1696     * in those sub-directories will not contribute to the total file-count.
1697     *
1698     * <EMBED CLASS='external-html' DATA-FILE-ID=FN_COUNT_DIRFILT>
1699     *
1700     * @return A total count of all files (excluding sub-directories) contained by {@code 'this'}
1701     * instance of {@code FileNode} - less the files that reside in directory-tree branches that
1702     * were excluded by the {@code 'directoryFilter'} parameter <B><I>and</I></B> less the files
1703     * that were excluded by {@code 'fileFilter'}.
1704     *
1705     * @throws DirExpectedException If the user has attempted to perform a count on a
1706     * {@code FileNode} that is a 'file' rather than a 'directory'.
1707     */
1708    public int countJustFiles(FileNodeFilter fileFilter, FileNodeFilter directoryFilter)
1709    {
1710        DirExpectedException.check(this);
1711
1712        // This was moved to an "INTERNAL" method to avoid invoking the above exception check
1713        // every time this (recursive) code encounters a directory.
1714
1715        return countJustFilesINTERNAL(fileFilter, directoryFilter);
1716    }
1717
1718    private int countJustFilesINTERNAL(FileNodeFilter fileFilter, FileNodeFilter directoryFilter)
1719    {
1720        int count = 0;
1721
1722        for (FileNode fn : children)
1723
1724            if (fn.isDirectory)
1725            {
1726                if ((directoryFilter == null) || directoryFilter.test(fn))
1727                    count += fn.countJustFilesINTERNAL(fileFilter, directoryFilter);
1728            }
1729
1730            else // fn is a file, not a dir.
1731                if ((fileFilter == null) || fileFilter.test(fn))
1732                    count++;
1733
1734        return count;
1735    }
1736
1737    /**
1738     * Convenience Method.
1739     * <BR />Invokes: {@link #countJustDirs(FileNodeFilter)}
1740     * <BR />Passes: null to {@code 'directorFilter'} (all <B>directories</B> are counted).
1741     */
1742    public int countJustDirs() { return countJustDirs(null); }
1743
1744    /**
1745     * Performs a count on the total number of sub-directories contained by {@code 'this'}
1746     * directory.  This method is recursive, and traverses all sub-directories when calculating
1747     * the return-value.
1748     *
1749     * @param directoryFilter This allows a user to skip branches of the directory-tree when
1750     * performing the count.
1751     * 
1752     * <BR /><BR />The filter provided should be a {@link FileNodeFilter} (Predicate} /
1753     * Lambda-Expression that returns {@code TRUE} if the sub-directory <I>should be entered</I>
1754     * and {@code FALSE} if sub-directory tree-branch <I>should be skipped</I> completely.
1755     *
1756     * <EMBED CLASS='external-html' DATA-FILE-ID=FN_COUNT_DIRFILT>
1757     *
1758     * @return A total count of all sub-directories contained by {@code 'this'}
1759     * instance of {@code FileNode} - less the sub-directories that reside in directory-tree
1760     * branches that were excluded by the {@code 'directoryFilter'} parameter.
1761     *
1762     * @throws DirExpectedException If the user has attempted to perform a count on a
1763     * {@code FileNode} that is a 'file' rather than a 'directory'.
1764     */
1765    public int countJustDirs(FileNodeFilter directoryFilter)
1766    {
1767        DirExpectedException.check(this);
1768
1769        // This was moved to an "INTERNAL" method to avoid invoking the above exception check
1770        // every time this (recursive) code encounters a directory.
1771
1772        return countJustDirsINTERNAL(directoryFilter);
1773    }
1774
1775    private int countJustDirsINTERNAL
1776        (FileNodeFilter directoryFilter)
1777    {
1778        int count = 0;
1779
1780        if (directoryFilter == null)
1781
1782            for (FileNode fn1 : children)
1783                if (fn1.isDirectory)
1784                    count += 1 /* 'this' adds 1 */ + fn1.countJustDirsINTERNAL(directoryFilter);
1785
1786        else
1787
1788            for (FileNode fn2 : children)
1789                if (fn2.isDirectory)
1790                    if (directoryFilter.test(fn2))
1791                        count +=1 /* 'this' adds 1 */ + fn2.countJustDirsINTERNAL(directoryFilter);
1792
1793        return count;
1794    }
1795
1796
1797    // ********************************************************************************************
1798    // ********************************************************************************************
1799    // ALL - a single level in the file-tree.
1800    // ********************************************************************************************
1801    // ********************************************************************************************
1802
1803
1804    /**
1805     * Convenience Method.
1806     * <BR />Automatically Selects: {@link RTC#VECTOR()}
1807     * <BR />Invokes: {@link #getDirContents(RTC, FileNodeFilter)}
1808     * <BR />Passes: null to parameter {@code 'filter'}
1809     * (all files and directories found will be returned).
1810     */
1811    public Vector<FileNode> getDirContents()
1812    { return getDirContents(RTC.VECTOR(), null); }
1813
1814    /**
1815     * Convenience Method.
1816     * <BR />Accepts: {@link RTC}.  (Specifies Output Data-Structure &amp; Contents)
1817     * <BR />Invokes: {@link #getDirContents(RTC, FileNodeFilter)}
1818     * <BR />Passes: null to parameter {@code 'filter'}
1819     * (all files and directories found will be returned).
1820     */
1821    public <T> T getDirContents(RTC<T> returnedDataStructureChoice)
1822    { return getDirContents(returnedDataStructureChoice, null); }
1823
1824    /**
1825     * Convenience Method.
1826     * <BR />Automatically Selects: {@link RTC#VECTOR()}
1827     * <BR />Accepts: {@link FileNodeFilter}
1828     * <BR />Invokes: {@link #getDirContents(RTC, FileNodeFilter)}
1829     */
1830    public Vector<FileNode> getDirContents(FileNodeFilter filter)
1831    { return getDirContents(RTC.VECTOR(), filter); }
1832
1833    /**
1834     * This method returns the contents of a <I>single-directory in the directory-tree</I>, the 
1835     * sub-directories are returned, but the contents of the sub-directories are not.  Any method
1836     * whose name begins with {@code 'getDirContents ...'} will not traverse the directory tree.
1837     * Instead, <I>only the contents of the internal {@code 'children' Vector<FileNode>} of
1838     * {@code 'this'} instance of {@code FileNode} are iterated and returned.</I>
1839     * 
1840     * <EMBED CLASS='external-html' DATA-FILE-ID=FN_DIR_CONTENTS>
1841     *
1842     * @param <T> <EMBED CLASS='external-html' DATA-FILE-ID=FN_RTC_TYPE_PARAM>
1843     * @param returnedDataStructureChoice <EMBED CLASS='external-html' DATA-FILE-ID=FN_RTC_PARAM>
1844     * 
1845     * @param filter When this parameter is used, any files or directories that do not pass the
1846     * {@code filter's 'test'} method shall not be included in the returne data-structure.
1847     * 
1848     * <BR /><BR />The {@code filter} that is passed should return {@code TRUE} when a file or
1849     * directory needs to be included in the returned-result.  When the provided {@code filter}
1850     * returns {@code FALSE} as a result of testing a file or directory, the returned
1851     * Data-Structure will exclude it.
1852     *
1853     * <BR /><BR />If this parameter is null, it will be ignored, and every {@code FileNode}
1854     * contained by {@code 'this'} directory-instance will be included in the result.
1855     * 
1856     * @return A list containing the files &amp; sub-directories inside {@code 'this'} directory. 
1857     * <EMBED CLASS='external-html' DATA-FILE-ID=FN_RTC_RET>
1858     *
1859     * @throws DirExpectedException <EMBED CLASS='external-html' DATA-FILE-ID=FN_DIR_C_DIR_EXP_EX>
1860     * @see FileNodeFilter
1861     * @see #children
1862     */
1863    public <T> T getDirContents(RTC<T> returnedDataStructureChoice, FileNodeFilter filter)
1864    {
1865        DirExpectedException.check(this);
1866
1867        if (filter != null)
1868            children.forEach((FileNode fn) ->
1869                { if (filter.test(fn)) returnedDataStructureChoice.inserter.accept(fn); });
1870
1871        else 
1872            children.forEach((FileNode fn) -> returnedDataStructureChoice.inserter.accept(fn));
1873
1874        return returnedDataStructureChoice.finisher.get();
1875    }
1876
1877
1878    // ********************************************************************************************
1879    // ********************************************************************************************
1880    // DIRECTORIES - a single level in the file-tree.
1881    // ********************************************************************************************
1882    // ********************************************************************************************
1883
1884
1885    /**
1886     * Convenience Method.
1887     * <BR />Automatically Selects: {@link RTC#VECTOR()}
1888     * <BR />Invokes: {@link #getDirContentsDirs(RTC, FileNodeFilter)}
1889     * <BR />Passes: null to parameter {@code 'filter'} (all directories found are returned).
1890     */
1891    public Vector<FileNode> getDirContentsDirs()
1892    { return getDirContentsDirs(RTC.VECTOR(), null); }
1893
1894    /**
1895     * Convenience Method.
1896     * <BR />Accepts: {@link RTC}  (Specifies Output Data-Structure &amp; Contents)
1897     * <BR />Invokes: {@link #getDirContentsDirs(RTC, FileNodeFilter)}
1898     * <BR />Passes: null to parameter {@code 'filter'} (all directories found are returned).
1899     */
1900    public <T> T getDirContentsDirs(RTC<T> returnedDataStructureChoice)
1901    { return getDirContentsDirs(returnedDataStructureChoice, null); }
1902
1903    /**
1904     * Convenience Method.
1905     * <BR />Automatically Selects: {@link RTC#VECTOR()}
1906     * <BR />Accepts: {@link FileNodeFilter}
1907     * <BR />Invokes: {@link #getDirContentsDirs(RTC, FileNodeFilter)}
1908     */
1909    public Vector<FileNode> getDirContentsDirs(FileNodeFilter filter)
1910    { return getDirContentsDirs(RTC.VECTOR(), filter); }
1911
1912    /**
1913     * <EMBED CLASS='external-html' DATA-INCL=directories DATA-EXCL=files
1914     *      DATA-FILE-ID=FN_DIR_CONTENTS_2>
1915     * <EMBED CLASS='external-html' DATA-FILE-ID=FN_DIR_CONTENTS>
1916     *
1917     * @param <T> <EMBED CLASS='external-html' DATA-FILE-ID=FN_RTC_TYPE_PARAM>
1918     * @param returnedDataStructureChoice <EMBED CLASS='external-html' DATA-FILE-ID=FN_RTC_PARAM>
1919     * 
1920     * @param filter Any Lambda-Expression that will select directories to include in the
1921     * return Data-Structure.  This parameter may be null, and if it is it will be ignored and all
1922     * sub-directories will be added to the return-instance. 
1923     * 
1924     * @return A list containing sub-directories rooted at {@code 'this'} directory.
1925     * <EMBED CLASS='external-html' DATA-FILE-ID=FN_RTC_RET>
1926     *
1927     * @throws DirExpectedException <EMBED CLASS='external-html' DATA-FILE-ID=FN_DIR_C_DIR_EXP_EX>
1928     * @see FileNodeFilter
1929     * @see #isDirectory
1930     */
1931    public <T> T getDirContentsDirs(RTC<T> returnedDataStructureChoice, FileNodeFilter filter)
1932    {
1933        return getDirContents(
1934            returnedDataStructureChoice,
1935            (filter != null) ? DIR_ONLY.and(filter) : DIR_ONLY
1936        );
1937    }
1938
1939    private static final FileNodeFilter DIR_ONLY = (FileNode fn) -> fn.isDirectory;
1940
1941
1942    // ********************************************************************************************
1943    // ********************************************************************************************
1944    // FILES - a single level in the file-tree.
1945    // ********************************************************************************************
1946    // ********************************************************************************************
1947
1948
1949    /**
1950     * Convenience Method.
1951     * <BR />Automatically Selects: {@link RTC#VECTOR()}
1952     * <BR />Invokes: {@link #getDirContentsFiles(RTC, FileNodeFilter)}
1953     * <BR />Passes: null to parameter {@code 'filter'} (all files found are returned).
1954     */
1955    public Vector<FileNode> getDirContentsFiles()
1956    { return getDirContentsFiles(RTC.VECTOR(), null); }
1957
1958    /**
1959     * Convenience Method.
1960     * <BR />Accepts: {@link RTC}  (Specifies Output Data-Structure &amp; Contents)
1961     * <BR />Invokes: {@link #getDirContentsFiles(RTC, FileNodeFilter)}
1962     * <BR />Passes: null to parameter {@code 'filter'} (all files found are returned).
1963     */
1964    public <T> T getDirContentsFiles(RTC<T> returnedDataStructureChoice)
1965    { return getDirContentsFiles(returnedDataStructureChoice, null); }
1966
1967    /**
1968     * Convenience Method.
1969     * <BR />Automatically Selects: {@link RTC#VECTOR()}
1970     * <BR />Accepts: {@link FileNodeFilter}
1971     * <BR />Invokes: {@link #getDirContentsFiles(RTC, FileNodeFilter)}
1972     */
1973    public Vector<FileNode> getDirContentsFiles(FileNodeFilter filter)
1974    { return getDirContentsFiles(RTC.VECTOR(), filter); }
1975
1976    /**
1977     * <EMBED CLASS='external-html' DATA-INCL=files DATA-EXCL=directories
1978     *      DATA-FILE-ID=FN_DIR_CONTENTS_2>
1979     * <EMBED CLASS='external-html' DATA-FILE-ID=FN_DIR_CONTENTS>
1980     *
1981     * @param <T> <EMBED CLASS='external-html' DATA-FILE-ID=FN_RTC_TYPE_PARAM>
1982     * @param returnedDataStructureChoice <EMBED CLASS='external-html' DATA-FILE-ID=FN_RTC_PARAM>
1983     * 
1984     * @param filter Any Lambda-Expression that will select files to include in the return
1985     * Data-Structure.  This parameter may be null, and if it is it will be ignored and all files
1986     * will be added to the return-instance. 
1987     * 
1988     * @return A {@code Vector} that contains the files inside the current directory.
1989     * <EMBED CLASS='external-html' DATA-FILE-ID=FN_RTC_RET>
1990     * 
1991     * @throws DirExpectedException <EMBED CLASS='external-html' DATA-FILE-ID=FN_DIR_C_DIR_EXP_EX>
1992     *
1993     * @see FileNodeFilter
1994     * @see #isDirectory
1995     */
1996    public <T> T getDirContentsFiles(RTC<T> returnedDataStructureChoice, FileNodeFilter filter)
1997    {
1998        return getDirContents(
1999            returnedDataStructureChoice,
2000            (filter != null) ? FILE_ONLY.and(filter) : FILE_ONLY
2001        );
2002    }
2003
2004    private static final FileNodeFilter FILE_ONLY = (FileNode fn) -> ! fn.isDirectory;
2005
2006    
2007    // ********************************************************************************************
2008    // ********************************************************************************************
2009    // FLATTEN - Just Directories
2010    // ********************************************************************************************
2011    // ********************************************************************************************
2012
2013
2014    /** 
2015     * Convenience Method.
2016     * <BR />Automatically Selects: {@link RTC#VECTOR()}
2017     * <BR />Invokes: {@link #flatten(RTC, int, FileNodeFilter, boolean, FileNodeFilter, boolean)}
2018     * <BR />Parameter: {@code 'includeDirectoriesInResult'} set {@code TRUE}
2019     * <BR />Parameter: {@code 'includeFilesInResult'} set {@code FALSE}
2020     * <BR />Passes: null to both filter-parameters (all files &amp; directories are returned)
2021     */
2022    public Vector<FileNode> flattenJustDirs()
2023    { return flatten(RTC.VECTOR(), -1, null, false, null, true); }
2024
2025    /** 
2026     * Convenience Method.
2027     * <BR />Accepts: {@link RTC}  (Specifies Output Data-Structure &amp; Contents)
2028     * <BR />Invokes: {@link #flatten(RTC, int, FileNodeFilter, boolean, FileNodeFilter, boolean)} 
2029     * <BR />Parameter: {@code 'includeDirectoriesInResult'} set {@code TRUE}
2030     * <BR />Parameter: {@code 'includeFilesInResult'} set {@code FALSE}
2031     * <BR />Passes: null to both filter-parameters (all directories are returned by this method).
2032     */
2033    public <T> T flattenJustDirs(RTC<T> returnedDataStructureChoice)
2034    { return flatten(returnedDataStructureChoice, -1, null, false, null, true); }
2035
2036    /**
2037     * Convenience Method.
2038     * <BR />Automatically Selects: {@link RTC#VECTOR()}
2039     * <BR />Invokes:
2040     *      {@link #flatten(RTC, int, FileNodeFilter, boolean, FileNodeFilter, boolean)}
2041     * <BR />Parameter: {@code 'includeDirectoriesInResult'} set {@code TRUE}
2042     * <BR />Parameter: {@code 'includeFilesInResult'} set {@code FALSE}
2043     * <BR />Accepts: {@code 'directoryFilter'} parameter.
2044     */
2045    public Vector<FileNode> flattenJustDirs(int maxTreeDepth, FileNodeFilter directoryFilter)
2046    { return flatten(RTC.VECTOR(), maxTreeDepth, null, false, directoryFilter, true); }
2047
2048    /**
2049     * Convenience Method.
2050     * <BR />Accepts: {@link RTC}  (Specifies Output Data-Structure &amp; Contents)
2051     * <BR />Invokes: {@link #flatten(RTC, int, FileNodeFilter, boolean, FileNodeFilter, boolean)}
2052     * <BR />Parameter: {@code 'includeDirectoriesInResult'} set {@code TRUE}
2053     * <BR />Parameter: {@code 'includeFilesInResult'} set {@code FALSE}
2054     * <BR />Accepts: {@code 'directoryFilter'} parameter
2055     */
2056    public <T> T flattenJustDirs
2057        (RTC<T> returnedDataStructureChoice, int maxTreeDepth, FileNodeFilter directoryFilter)
2058    {
2059        return flatten
2060            (returnedDataStructureChoice, maxTreeDepth, null, false, directoryFilter, true);
2061    }
2062
2063
2064    // ********************************************************************************************
2065    // ********************************************************************************************
2066    // FLATTEN - Just Files
2067    // ********************************************************************************************
2068    // ********************************************************************************************
2069
2070
2071    /**
2072     * Convenience Method.
2073     * <BR />Automatically Selects: {@link RTC#VECTOR()}
2074     * <BR />Invokes:
2075     *      {@link #flatten(RTC, int, FileNodeFilter, boolean, FileNodeFilter, boolean)}
2076     * <BR />Parameter: {@code includeDirectoriesInResult} set {@code FALSE}
2077     * <BR />Parameter: {@code includeFilesInResult} set {@code TRUE}
2078     * <BR />Passes: null to both filter-parameters (all files are returned)
2079     */
2080    public Vector<FileNode> flattenJustFiles()
2081    { return flatten(RTC.VECTOR(), -1, null, true, null, false); }
2082
2083    /**
2084     * Convenience Method.
2085     * <BR />Accepts: {@link RTC} (Specifies Output Data-Structure &amp; Contents)
2086     * <BR />Invokes: {@link #flatten(RTC, int, FileNodeFilter, boolean, FileNodeFilter, boolean)}
2087     * <BR />Parameter: {@code includeDirectoriesInResult} set {@code FALSE}
2088     * <BR />Parameter: {@code includeFilesInResult} set {@code TRUE}
2089     * <BR />Passes: null to both filter-parameters (all files are returned)
2090     */
2091    public <T> T flattenJustFiles(RTC<T> returnedDataStructureChoice)
2092    { return flatten(returnedDataStructureChoice, -1, null, true, null, false); }
2093
2094    /**
2095     * Convenience Method.
2096     * <BR />Automatically Selects: {@link RTC#VECTOR()}
2097     * <BR />Invokes: {@link #flatten(RTC, int, FileNodeFilter, boolean, FileNodeFilter, boolean)}
2098     * <BR />Parameter: {@code includeDirectoriesInResult} set {@code FALSE}
2099     * <BR />Parameter: {@code includeFilesInResult} set {@code TRUE}
2100     * <BR />Accepts: {@code 'fileFilter'} parameter
2101     */
2102    public Vector<FileNode> flattenJustFiles(int maxTreeDepth, FileNodeFilter fileFilter)
2103    { return flatten(RTC.VECTOR(), maxTreeDepth, fileFilter, true, null, false); }
2104
2105    /**
2106     * Convenience Method.
2107     * <BR />Accepts: {@link RTC}  (Specifies Output Data-Structure &amp; Contents)
2108     * <BR />Invokes: {@link #flatten(RTC, int, FileNodeFilter, boolean, FileNodeFilter, boolean)}
2109     * <BR />Parameter: {@code includeDirectoriesInResult} set {@code FALSE}
2110     * <BR />Parameter: {@code includeFilesInResult} set {@code TRUE}
2111     */
2112    public <T> T flattenJustFiles
2113        (RTC<T> returnedDataStructureChoice, int maxTreeDepth, FileNodeFilter fileFilter)
2114    { return flatten(returnedDataStructureChoice, maxTreeDepth, fileFilter, true, null, false); }
2115
2116
2117    // ********************************************************************************************
2118    // ********************************************************************************************
2119    // Core Flatten Routines
2120    // ********************************************************************************************
2121    // ********************************************************************************************
2122
2123
2124    /**
2125     * Convenience Method.
2126     * <BR />Automatically Selects: {@link RTC#VECTOR()}
2127     * <BR />Invokes: {@link #flatten(RTC, int, FileNodeFilter, boolean, FileNodeFilter, boolean)}
2128     * <BR />Parameters: {@code includeFilesInResult, includeDirectoriesInResult} both set
2129     *      {@code TRUE}
2130     * <BR />Passes: null to both filter-parameers (all files &amp; directories are returned)
2131     */
2132    public Vector<FileNode> flatten()
2133    { return flatten(RTC.VECTOR(), -1, null, true, null, true); }
2134
2135    /**
2136     * Convenience Method.
2137     * <BR />Accepts: {@link RTC}  (Specifies Output Data-Structure &amp; Contents)
2138     * <BR />Invokes: {@link #flatten(RTC, int, FileNodeFilter, boolean, FileNodeFilter, boolean)}
2139     * <BR />Parameters: {@code includeFilesInResult, includeDirectoriesInResult} both set
2140     *      {@code TRUE}
2141     * <BR />Passes: null to both filter-parameers (all files &amp; directories are returned)
2142     */
2143    public <T> T flatten(RTC<T> returnedDataStructureChoice)
2144    { return flatten(returnedDataStructureChoice, -1, null, true, null, true); }
2145
2146    /**
2147     * Convenience Method.
2148     * <BR />Automatically Selects: {@link RTC#VECTOR()}
2149     * <BR />Invokes: {@link #flatten(RTC, int, FileNodeFilter, boolean, FileNodeFilter, boolean)}
2150     * <BR />Accepts: Both filters ({@code directoryFilter, fileFilter}) may be passed.
2151     * <BR />Accepts: Both {@code 'includeIn'} boolean-flags may be passed.
2152     */
2153    public Vector<FileNode> flatten(
2154            int maxTreeDepth,
2155            FileNodeFilter fileFilter,      boolean includeFilesInResult, 
2156            FileNodeFilter directoryFilter, boolean includeDirectoriesInResult
2157        )
2158    {
2159        return flatten(RTC.VECTOR(), maxTreeDepth,
2160            fileFilter, includeFilesInResult,
2161            directoryFilter, includeDirectoriesInResult
2162        );
2163    }
2164
2165    /**
2166     * This flattens the {@code FileNode} tree into a data-structure of your choosing.  Review
2167     * &amp; read the parameter explanations below, closely, to see what the specifiers do.
2168     * 
2169     * <BR /><BR />The concept of "flatten" is identical to the concept of "retrieve" or
2170     * "search."  All of these methods perform the 'copying' of a set of {@code filter}-matches
2171     * into a return-container.  If one wishes to scour and search a {@code FileNode} tree to
2172     * obtain some or all Tree-Nodes for saving into a list (or other data-structure of your
2173     * choosing), this can be easily done using this method.
2174     * 
2175     * <BR /><BR />Write the necessary Lambda-Expressions (filter-predicates) to choose the files
2176     * and directories that need to be included in the result-container, and then invoke any one
2177     * of the overloaded {@code flattan(...)} methods offered by this class.
2178     *
2179     * <BR /><BR />If you would like to "flatten" the entire tree into a {@code Vector} or some
2180     * other type of list or data-structure, then leave both of the {@code filter} parameters blank
2181     * (by passing them null), and also pass {@code '-1'} to parameter {@code 'maxTreeDepth'}.
2182     * Everything in the directory-tree that is rooted at {@code 'this'} instance of
2183     * {@code FileNode} is returned into a data-structure of your choosing.
2184     *
2185     * @param <T> <EMBED CLASS='external-html' DATA-FILE-ID=FN_RTC_TYPE_PARAM>
2186     * @param returnedDataStructureChoice <EMBED CLASS='external-html' DATA-FILE-ID=FN_RTC_PARAM>
2187     * @param maxTreeDepth <EMBED CLASS='external-html' DATA-FILE-ID=FN_MAX_TREE_DEPTH>
2188     * 
2189     * @param fileFilter This is a Java 8 "accept" {@code interface java.util.function.Predicate}.
2190     * Implementing the {@code 'test(FileNode)'} method, allows one to pick &amp; choose which
2191     * files will be visited as the tree is recursively traversed.  Use a lambda-expression, if
2192     * needed or for convenience.
2193     *
2194     * <BR /><BR /><B>NOTE:</B> This parameter may be null, and if so - <I>all files</I> are
2195     * will be presumed to pass the {@code filter test}. 
2196     *
2197     * <BR /><BR /><B><SPAN STYLE="color: red;">JAVA STREAM'S</SPAN></B> The behavior of this
2198     * {@code filter}-logic is identical to the Java 8+ Streams-Method
2199     * {@code 'filter(Predicate<...>)'}.  Specifically, when the {@code filter} returns a
2200     * {@code TRUE} value for a particular {@code FileNode}, that {@code FileNode} shall be
2201     * retained, or 'kept', in the returned result-set.  When the {@code filter} returns
2202     * {@code FALSE} for a {@code FileNode}, that file or directory will be removed from the
2203     * result-set.
2204     * 
2205     * <BR /><BR />One way to think about which files are included in the results of a 
2206     * {@code 'flatten'} operation is by this list below:
2207     *
2208     * <BR /><BR /><UL CLASS=JDUL>
2209     * <LI> Whether/if the {@code boolean includeFilesInResult} boolean-flag has been
2210     *      set to {@code TRUE}.
2211     *      </LI>
2212     * <LI> Whether the {@code FileNode} would pass the {@code fileFilter.test} predicate
2213     *      (if one has been provided, otherwise ignore this metric).
2214     *      </LI>
2215     * <LI> Whether the containing directory's {@code FileNode} would pass the
2216     *      {@code directoryFilter.test} predicate (if one has been provided, otherwise ignore this
2217     *      metric).
2218     *      </LI>
2219     * <LI> Whether or not <I>all parent-containing directories</I> of the {@code FileNode} would
2220     *      pass the {@code directoryFilter.test} predicate (if one were provided).
2221     *      </LI>
2222     * </UL>
2223     *
2224     * @param includeFilesInResult If this parameter is {@code TRUE}, then files will be included
2225     * in the resulting {@code Vector}.
2226     *
2227     * <BR /><BR /><SPAN STYLE="color: red;"><B>NOTE:</B></SPAN> If this parameter is
2228     * {@code FALSE}, this value will "over-ride" any results that may be produced from the public
2229     * {@code fileFilter.test(this)} method (if such a filter had been provided).
2230     *
2231     * @param directoryFilter This is also a Java 8 "Predicate Filter"  {@code interface
2232     * java.util.function.Predicate}.  Implementing the {@code 'test(FileNode)'} method, allows
2233     * one to pick &amp; choose which directories will be visited as the tree is recursively
2234     * traversed.  Use a lambda-expression, if needed or for convenience.
2235     *
2236     * <BR /><BR /><B>NOTE:</B> This parameter may be null, and if so - <I>all directories</I>
2237     * will be traversed.
2238     *
2239     * <BR /><BR /><B><SPAN STYLE="color: red;">JAVA STREAM'S</SPAN></B> The behavior of this
2240     * {@code filter}-logic is identical to the Java 8+ Streams-Method
2241     * {@code 'filter(Predicate<...>)'.}  Specifically, when the {@code filter} returns a
2242     * {@code TRUE} value for a particular {@code FileNode}, that {@code FileNode} shall be
2243     * retained, or 'kept', in the returned result-set.  When the {@code filter} returns
2244     * {@code FALSE} for a {@code FileNode}, that file or directory will be removed from the
2245     * result-set.
2246     *
2247     * <BR /><BR /><SPAN STYLE="color: red;"><B>IMPORTANT:</B></SPAN> There is no way to
2248     * differentiate between which directories are traversed and which directories are included in
2249     * the result set - if a directory is not traversed or examined, then that directory, <I>and
2250     * any/all files and sub-directories contained by that directory</I> will all be eliminted
2251     * from the returned-results.
2252     * 
2253     * <BR /><BR />One way to think about which directories are included in the results of a 
2254     * {@code 'flatten'} operation is by this list below:
2255     *
2256     * <BR /><BR /><UL CLASS=JDUL>
2257     * <LI> Whether/if the {@code boolean includeDirectoriesInResult} boolean-flag has been
2258     *      set to {@code TRUE}.
2259     *      </LI>
2260     * <LI> Whether that {@code FileNode} would pass the {@code directoryFilter.test} predicate 
2261     *      (if one has been provided, otherwise ignore this metric).
2262     *      </LI>
2263     * <LI> Whether or not <I>all parent directories</I> of the {@code FileNode} would also pass
2264     *      the {@code directoryFilter.test} predicate (if one were provided).
2265     *      </LI>
2266     * </UL>
2267     *
2268     * @param includeDirectoriesInResult If this parameter is {@code TRUE}, then directories will
2269     * be included in the resulting {@code Vector}.
2270     *
2271     * <BR /><BR /><SPAN STYLE="color: red;"><B>NOTE:</B></SPAN> If this parameter is
2272     * {@code FALSE}, this value will "over-ride" any results that may be produced from the public
2273     * {@code directoryFilter.test(this)} method.
2274     *
2275     * @return A flattened version of this tree.
2276     *
2277     * <EMBED CLASS='external-html' DATA-FILE-ID=FN_RTC_RET>
2278     *
2279     * @throws DirExpectedException If {@code 'this'} instance of {@code FileNode} is not a
2280     * directory, but a file, then this exception is thrown.  (Files <I>may not</I> have 
2281     * child-nodes, only directories).
2282     *
2283     * @throws IllegalArgumentException If the value of {@code 'maxTreeDepth'} is set to
2284     * {@code zero}, then this exception shall be thrown because the method-invocation would not be
2285     * making an actual request to do anything.
2286     *
2287     * <BR /><BR />This exception shall <I><B>*also* be throw if</I></B> both of the boolean
2288     * parameters are set to {@code FALSE}, for the same reason being that the method-invocation
2289     * would not be making a request.
2290     */
2291    public <T> T flatten(
2292            RTC<T>  returnedDataStructureChoice,
2293            int     maxTreeDepth,
2294            FileNodeFilter fileFilter,      boolean includeFilesInResult, 
2295            FileNodeFilter directoryFilter, boolean includeDirectoriesInResult)
2296    {
2297        DirExpectedException.check(this);
2298
2299        if (maxTreeDepth == 0) throw new IllegalArgumentException(
2300            "flatten(int, FileNodeFilter, boolean, directoryFilter, boolean) has been invoked " +
2301            "with the maxTreeDepth (integer) parameter set to zero.  This means that there is " +
2302            "nothing for the method to do."
2303        );
2304
2305        if ((! includeFilesInResult) && (! includeDirectoriesInResult))
2306
2307            throw new IllegalArgumentException(
2308                "flatten(int, FileNodeFilter, boolean, directoryFilter, boolean) has been " +
2309                "invoked with both of the two boolean search criteria values set to FALSE.  " +
2310                "This means that there is nothing for the method to do."
2311            );
2312
2313        // 'this' directory needs to be included (unless filtering directories)
2314        if (    includeDirectoriesInResult
2315            &&  ((directoryFilter == null) || directoryFilter.test(this))
2316        )
2317            returnedDataStructureChoice.inserter.accept(this);
2318
2319        // Call the general-purpose flatten method.
2320        flattenINTERNAL(
2321            this,               returnedDataStructureChoice.inserter,
2322            fileFilter,         includeFilesInResult,
2323            directoryFilter,    includeDirectoriesInResult,
2324            0,                  maxTreeDepth
2325        );
2326
2327        // retrieve the Container specified by the user.
2328        return returnedDataStructureChoice.finisher.get();
2329    }
2330
2331    private static final FileNodeFilter ALWAYS_TRUE = (FileNode f) -> true;
2332
2333    private static void flattenINTERNAL(
2334            FileNode cur,                   Consumer<FileNode> inserter,
2335            FileNodeFilter fileFilter,      boolean includeFilesInResult, 
2336            FileNodeFilter directoryFilter, boolean includeDirectoriesInResult,
2337            int curTreeDepth,               int maxTreeDepth
2338        )
2339    {
2340        if ((maxTreeDepth >= 0) && (curTreeDepth > maxTreeDepth)) return;
2341 
2342        if (VERBOSE) System.out.println(cur.name);
2343
2344        directoryFilter = (directoryFilter == null) ? ALWAYS_TRUE : directoryFilter;
2345        fileFilter      = (fileFilter == null)      ? ALWAYS_TRUE : fileFilter;
2346
2347        for (FileNode fn : cur.children)
2348
2349            if (fn.isDirectory)
2350                { if (includeDirectoriesInResult && directoryFilter.test(fn)) inserter.accept(fn);}
2351
2352            else
2353                { if (includeFilesInResult && fileFilter.test(fn)) inserter.accept(fn); }
2354
2355
2356        for (FileNode fn : cur.children) if (fn.isDirectory && directoryFilter.test(fn))
2357
2358            flattenINTERNAL(
2359                fn,                 inserter,
2360                fileFilter,         includeFilesInResult,
2361                directoryFilter,    includeDirectoriesInResult,
2362                curTreeDepth+1,     maxTreeDepth
2363            );
2364    }
2365
2366
2367    // ********************************************************************************************
2368    // ********************************************************************************************
2369    // Prune Tree
2370    // ********************************************************************************************
2371    // ********************************************************************************************
2372
2373
2374    /** 
2375     * Convenience Method.
2376     * <BR />Invokes: {@link #pruneTree(FileNodeFilter, boolean)}
2377     * <BR />Returns: {@code 'this'} rather than {@code 'int'}
2378     */
2379    public FileNode prune(FileNodeFilter fileFilter, boolean nullThePointers)
2380    { this.pruneTree(fileFilter, nullThePointers); return this; }
2381
2382    /**
2383     * This removes instances of {@code FileNode} that meet these conditions:
2384     *
2385     * <BR /><BR /><OL CLASS=JDOL>
2386     * 
2387     * <LI> Are file instances, not directories. Specifically: {@code public final boolean
2388     *      isDirectory == false;}<BR />
2389     *      </LI>
2390     * 
2391     * <LI> Do not pass the {@code 'fileFilter.test(...)'} method.  If the test method returns
2392     *      {@code FALSE}, the file shall be removed from the containing directory's 
2393     *      {@link #children} {@code Vector<FileNode>} File-List.
2394     *      </LI>
2395     * 
2396     * </OL>
2397     *
2398     * <BR /><BR /><B CLASS=JDDescLabel>Recursive Method:</B>
2399     * 
2400     * <BR />This method shall skip through, 'traverse', the entire {@code FileNode} tree and prune
2401     * all 'file-leaves' that do not meet the criteria specified by the {@code 'fileFilter'}
2402     * parameter.
2403     *
2404     * @param fileFilter This is the test used to filter out files from the directory-tree that
2405     * begins at {@code 'this'} instance.  Returning {@code FALSE} shall eliminate the file from
2406     * its containing parent, and when this filter returns {@code TRUE} that file shall remain.
2407     *
2408     * @param nullThePointers The primary use of this boolean is to remind users that this
2409     * data-structure {@code class FileNode} is actually a tree that maintains pointers in both
2410     * directions - upwards and downwards.  Generally, trees have the potential to make programming
2411     * an order of magnitude more complicated.  Fortunately, because this data-structure merely
2412     * represents the File-System, <I><B>and because</I></B> the data-structure itself (READ:
2413     * {@code 'this'} tree) does not have much use for being modified itself...  The fact that
2414     * {@code FileNode} is a two-way, bi-directional tree rarely seems important.  The most useful
2415     * methods are those that "flatten" the tree, and then process the data in the files listed.
2416     *
2417     * <BR /><BR /><B>POINT:</B> When this parameter is set to {@code TRUE}, all parent pointers
2418     * shall be nulled, and this can make garbage-collection easier.
2419     *
2420     * @return The number of files that were removed.
2421     *
2422     * @throws DirExpectedException If {@code 'this'} instance of {@code FileNode} does not
2423     * represent a 'directory' on the File-System, then this exception shall throw.
2424     *
2425     * @see #prune(FileNodeFilter, boolean)
2426     * @see #isDirectory
2427     * @see #parent
2428     */
2429    public int pruneTree(FileNodeFilter fileFilter, boolean nullThePointers)
2430    {
2431        DirExpectedException.check(this);
2432
2433        Iterator<FileNode>  iter        = this.children.iterator();
2434        int                 removeCount = 0;
2435
2436        while (iter.hasNext())
2437        {
2438            FileNode fn = iter.next();
2439
2440            /*
2441            DEBUGGING, KEEP HERE.
2442            System.out.print(
2443                "Testing: fn.name: " + fn.name + "\tfn.getParentDir().name: " +
2444                fn.getParentDir().name
2445            );
2446            */
2447
2448            if (! fn.isDirectory)
2449
2450                // NOTE: This only filters 'files' (leaf-nodes) out of the tree.  This 'tree-prune'
2451                //       operation does not have any bearing on 'directory-nodes' (branch-nodes) in
2452                //       the tree.
2453
2454                if (! fileFilter.test(fn))
2455                {
2456                    // These types of lines can help the Java Garbage-Collector.
2457                    // They also prevent the user from ever utilizing this object reference again.
2458
2459                    if (nullThePointers) fn.parent = null;
2460
2461                    // System.out.println("\tRemoving...");
2462
2463                    // This iterator is one generated by class 'Vector<FileNode>', and its remove()
2464                    // operation, therefore, is fully-supported.  This removes FileNode fn from
2465                    // 'this' private, final field 'private Vector<FileNode> children'
2466
2467                    iter.remove();
2468
2469                    removeCount++;
2470                    continue;
2471                }
2472
2473            // Keep Here, for Testing
2474            // System.out.println("\tKeeping...");
2475
2476            if (fn.isDirectory) removeCount += fn.pruneTree(fileFilter, nullThePointers);
2477        }
2478
2479        return removeCount;
2480    }
2481
2482
2483    // ********************************************************************************************
2484    // ********************************************************************************************
2485    // Simple stuff
2486    // ********************************************************************************************
2487    // ********************************************************************************************
2488
2489
2490    /**
2491     * Returns the parent of {@code 'this' FileNode}
2492     *
2493     * @return {@code this.parent}
2494     *
2495     * <BR /><BR /><B>NOTE</B> If this is a "root node" or "root directory" then null will be
2496     * returned here.
2497     *
2498     * @see #parent
2499     */
2500    public FileNode getParentDir() { return parent; }
2501
2502    /**
2503     * Move's a file or directory from "inside" or "within" the contents of the current/parent
2504     * directory, and into a new/destination/parent directory.  If the {@code destinationDir} is
2505     * not actually a directory, then an exception is thrown.  If this is already a child/member
2506     * of the {@code destinationDir}, then an exception is also thrown.
2507     *
2508     * <BR /><BR /><B CLASS=JDDescLabel>File-System Safety:</B>
2509     * 
2510     * <BR />This method <I>does not modify</I> the underlying UNIX or MS-DOS File-System - just
2511     * the {@code FileNode} Tree  representation in Java Memory!   (No UNIX, Apple, MS-DOS etc. 
2512     * files are actually moved by this method)
2513     *
2514     * @param destinationDir the destination directory
2515     *
2516     * @throws java.util.InputMismatchException
2517     * @throws DirExpectedException If {@code 'destinationDir'} is not a directory, but a file,
2518     * then this exception is thrown.  (Files <I>may not</I> contain child-nodes, only directories)
2519     *
2520     * @see #parent
2521     * @see #name
2522     * @see #children
2523     */
2524    public void move(FileNode destinationDir)
2525    {
2526        DirExpectedException.check(destinationDir);
2527
2528        if (this.parent == destinationDir) throw new java.util.InputMismatchException(
2529            "[" + name + "] - is already a member of the target directory " +
2530            "[" + destinationDir.name + "]"
2531        );
2532
2533        parent = destinationDir;
2534
2535        destinationDir.children.addElement(this);
2536    }
2537
2538    /**
2539     * This deletes a file from the {@code FileNode} tree.  It's only operation is to remove
2540     * {@code 'this'} from the parent-node's {@code Vector<FileNode> children} node-list!  For
2541     * Java garbage collection purposes, it also empties (calls
2542     * {@code children.removeAllElements()}) on the children {@code Vector} - if there is one
2543     * (a.k.a) if {@code 'this' FileNode} represents a directory, not a file!
2544     *
2545     * <BR /><BR /><B CLASS=JDDescLabel>File-System Safety:</B>
2546     * 
2547     * <BR />This method <I>does not modify</I> the underlying UNIX or MS-DOS File-System - just
2548     * the {@code FileNode}-Tree representation in Java Memory!  (No UNIX, Apple, MS-DOS, etc.
2549     * files are actually deleted by this method)
2550     *
2551     * @see #parent
2552     * @see #children
2553     * @see #isDirectory
2554     */
2555    public void del()
2556    {
2557        parent.children.removeElement(this);
2558
2559        if (isDirectory) children.removeAllElements();
2560
2561        parent = null;
2562    }
2563
2564    /**
2565     * This visits the {@code '.name'} field for {@code 'this' FileNode}, as well as all parent
2566     * instances of {@code 'this' FileNode}, and concatenates those {@code String's}.
2567     * 
2568     * @return the full, available path name for {@code 'this' FileNode} as a {@code String}
2569     * @see #parent
2570     * @see #isDirectory
2571     * @see #name
2572     * @see #getFullPathName()
2573     */
2574    public String getFullPathName()
2575    {
2576        if (parent == null)
2577
2578            // This is tested in this class constructor, If this is TRUE, isDirectory must be true
2579            // RECENT ISSUE: May, 2022 - Google Cloud Shell Root Directory.
2580
2581            return name.equals(File.separator) 
2582                ? name
2583                : name + (isDirectory ? File.separatorChar : "");
2584                    // All other nodes where 'isDirectory' is TRUE
2585                    // must have the file.separator appended
2586                                                
2587        else
2588            return parent.getFullPathName() + name + (isDirectory ? File.separatorChar : "");
2589    }
2590
2591    /**
2592     * Returns the as much of the "Full Path Name" of the file referenced by {@code 'this'}
2593     * filename as is possible for this particular {@code FileNode}.
2594     * 
2595     * <BR /><BR />If this file or directory does not have a parent, then the empty (zero-length)
2596     * {@code String} will be returned.  Usually, unless certain tree modification operations have
2597     * been performed, only a <I><B>root-node</B></I> {@code FileNode} will have a 'null' parent.
2598     *
2599     * @return the full, available path name to {@code 'this' FileNode} - leaving out the actual
2600     * name of this file.
2601     *
2602     * <BR /><BR /><B>SPECIFICALLY:</B>
2603     * 
2604     * <BR /><BR /><UL CLASS=JDUL>
2605     * <LI>for a file such as {@code 'directory-1/subdirectory-2/filename.txt'}</LI>
2606     * <LI>{@code 'directory-1/subdirectory-2/'} would be returned</LI>
2607     * </UL>
2608     *
2609     * @see #parent
2610     * @see #getFullPathName()
2611     */
2612    public String getParentPathName()
2613    { if (parent == null) return ""; else return parent.getFullPathName(); }
2614
2615    /**
2616     * Gets the {@code java.io.File} version of a file.  The java class for files has quite a bit
2617     * of interactive stuff for a file system - including checking for {@code 'r/w/x'} permissions.
2618     * This can be useful.
2619     *
2620     * @return Gets the {@code java.io.File} instance of {@code 'this' FileNode}
2621     * @see #getFullPathName()
2622     */
2623    public File getJavaIOFile() { return new File(getFullPathName()); }
2624
2625    /**
2626     * This presumes that {@code 'this'} instance of {@code FileNode} is not a directory, but
2627     * rather a file.  If it is not a file, then an exception shall throw.  This method also
2628     * requires that {@code 'this'} file represents a <B>text-file</B> that may be loaded into a
2629     * {@code String}.
2630     * 
2631     * <BR /><BR />This method will load the contents of {@code 'this'} file into a 
2632     * {@code java.lang.String} and then pass that {@code String} (along with {@code 'this'
2633     * FileNode} to the method {@code 'accept(FileNode, String'} provided by the
2634     * {@code BiConsumer<FileNode, String>} input-parameter {@code 'c'}.
2635     *
2636     * @param c This is the java {@code FunctionalInterface 'BiConsumer'}.  As a
2637     * {@code functional-interface}, it has a method named {@code 'accept'} and this method
2638     * {@code 'accept'} receives two parameters itself: 
2639     *
2640     * <BR /><BR /><OL CLASS=JDOL>
2641     * <LI>The first Parameter shall be {@code 'this'} instance of {@code 'FileNode'}</LI>
2642     * 
2643     * <LI> The second Parameter shall be the file-contents on the File-System of
2644     *      {@code 'this' FileNode} - passed as a {@code java.lang.String}.
2645     *      </LI>
2646     * </OL>
2647     * 
2648     * @param ioeh This an is instance of {@code FunctionalInterface 'IOExceptionHandler'}.  It
2649     * receives an instance of an {@code IOException}, and the programmer may insert any type of
2650     * code he wants to see happen when an {@code IOException} is thrown.  The 'added-value' of
2651     * including this handler is that when batches of {@code accept's} are performed one a 
2652     * {@code FileNode}-tree, one file causing an exception throw does not have to halt the entire
2653     * batch-process.
2654     *
2655     * <BR /><BR /><B>NOTE:</B> This parameter may be null, and if it is, it shall be ignored.
2656     * It is only invoked if it is not null, and if an exception occurs when either reading or
2657     * writing the file to/from the File-System.
2658     *
2659     * @throws FileExpectedException If {@code 'destinationDir'} is not a file, but a directory,
2660     * then this exception is thrown.
2661     *
2662     * @see FileRW#loadFileToString(String)
2663     * @see #getFullPathName()
2664     * @see IOExceptionHandler#accept(FileNode, IOException)
2665     */
2666    public void accept(BiConsumer<FileNode, String> c, IOExceptionHandler ioeh)
2667    {
2668        // This method can only be used with 'file' FileNode's.
2669        // FileNode's that are 'directories' do not have "text-contents" or "file-contents"
2670
2671        FileExpectedException.check(this);
2672
2673        try
2674            { c.accept(this, FileRW.loadFileToString(getFullPathName())); }
2675
2676        catch (IOException ioe)
2677
2678            // if an I/O exception did occur, send the information to the
2679            // I/O exception handler provided by the user (if and only if the
2680            // actually provided a non-null exception handler)
2681
2682            { if (ioeh != null) ioeh.accept(this, ioe); }
2683    }
2684
2685    /**
2686     * This presumes that {@code 'this'} instance of {@code FileNode} is not a directory, but
2687     * rather a file.  If it is not a file, then an exception shall throw.  This method also
2688     * requires that {@code 'this'} file represents a <B>text-file</B> that may be loaded into a
2689     * {@code String}.
2690     * 
2691     * <BR /><BR />This method will load the contents of {@code 'this'} file into a 
2692     * {@code java.lang.String} and then pass that {@code String} (along with {@code 'this'
2693     * FileNode} to the method {@code 'ask(FileNode, String'} provided by the
2694     * {@code BiPredicate<FileNode, String>} input-parameter {@code 'p'}.
2695     *
2696     * <BR /><BR />This is the type of method that could easily be used in conjunction with a
2697     * {@code java.util.stream.Stream} or a {@code java.util.Vector}.
2698     *
2699     * <BR /><BR /><UL CLASS=JDUL>
2700     * <LI> {@code Stream<FileNode>.filter
2701     *      (fileNode -> fileNode.ask(myFilterPred, myIOEHandler));}
2702     *      </LI>
2703     * 
2704     * <LI> {@code Vector<FileNode>.removeIf
2705     *      (fileNode -> fileNode.ask(myFilterPred, myIOEHandler));}
2706     *      </LI>
2707     * </UL>
2708     *
2709     * @param p This is the java {@code FunctionalInterface 'BiPredicate'}.  As a
2710     * {@code functional-interface}, it has a method named {@code 'test'} and this method
2711     * {@code 'test'} receives two parameters itself: 
2712     *
2713     * <BR /><BR /><OL CLASS=JDOL>
2714     * <LI>The first parameter shall be {@code 'this'} instance of {@code 'FileNode'}</LI>
2715     * <LI>The second parameter shall be the file-contents on the File-System of
2716     *     {@code 'this' FileNode} - passed as a {@code java.lang.String}.</LI>
2717     * </OL>
2718     *
2719     * <BR /><BR /><B>IMPORTANT:</B> The {@code functional-interface} that is passed to parameter
2720     * {@code 'p'} should provide a return {@code boolean}-value that is to act as a pass/fail
2721     * filter criteria.  It is important to note that the Java {@code Vector} method
2722     * {@code Vector.removeIf(Predicate)} and the Java method {@code Stream.filter(Predicate)} 
2723     * will produce <B><I>exactly opposite outputs</I></B> based on the same filter logic.
2724     * 
2725     * <BR /><BR />To explain further, when {@code Vector.removeIf(Predicate)} is used, the
2726     * predicate should return {@code FALSE} to indicate that the {@code FileNode} needs to be
2727     * eliminated not retained.  When {@code Stream.filter(Predicate)} is used, {@code TRUE} should
2728     * indicate that the {@code FileNode} should be retained not eliminated.
2729     *
2730     * @param ioeh This is an instance of functional-interface class, {@code IOExceptionHandler}.
2731     * It receives an instance of an {@code IOException}, and the programmer may insert any type of
2732     * code he wants to see happen when an {@code IOException} is thrown.  The 'added-value' of
2733     * including this handler is that when batches of {@code ask's} are performed on a 
2734     * {@code FileNode}-tree, one file causing an exception throw does not have to halt the entire
2735     * batch-process.
2736     *
2737     * <BR /><BR /><B>NOTE:</B> This parameter may be null, and if it is, it shall be ignored.  It
2738     * is only invoked if it is not null, and if an exception occurs when either reading or writing
2739     * the file to/from the File-System.
2740     *
2741     * @return This method returns {@code TRUE} if there were no I/O faults when either reading or
2742     * writing the file.
2743     *
2744     * @throws FileExpectedException If {@code 'destinationDir'} is not a file, but a directory,
2745     * then this exception is thrown.
2746     *
2747     * @see #getFullPathName()
2748     * @see FileRW#loadFileToString(String)
2749     * @see IOExceptionHandler#accept(FileNode, IOException)
2750     */
2751    public boolean ask(BiPredicate<FileNode, String> p, IOExceptionHandler ioeh)
2752    {
2753        // This method can only be used with 'file' FileNode's.
2754        // FileNode's that are 'directories' do not have "text-contents" or "file-contents"
2755
2756        FileExpectedException.check(this);
2757
2758        try
2759            { return p.test(this, FileRW.loadFileToString(getFullPathName())); }
2760
2761        catch (IOException ioe)
2762            { if (ioeh != null) ioeh.accept(this, ioe); return false; }
2763    }
2764
2765    /**
2766     * There are not any "Tree Structures" present in the HTML Search, Update, and Scrape Packages.
2767     * In the Java Packages, the {@code class 'FileNode'} is the lone source of "Tree Structures."
2768     * The Java Garbage Collector sometimes seems to work in mysterious ways. 
2769     *
2770     * <BR /><BR />This method will 'null-ify' all references (pointers) in a
2771     * {@code 'FileNode'}-Tree.  The {@code FileNode}-Tree can be a great asset or tool during the
2772     * development process when looking through file-contents and trying to modify them - <I>or
2773     * just find files with certain characteristics.</I>
2774     *
2775     * @see #getDirContents()
2776     * @see #getDirContentsDirs()
2777     * @see #parent
2778     * @see #children
2779     */
2780    public void NULL_THE_TREE()
2781    {
2782        Iterator<FileNode> iter = getDirContents(RTC.ITERATOR());
2783
2784        while (iter.hasNext()) iter.next().parent = null;
2785
2786        iter = getDirContentsDirs(RTC.ITERATOR());
2787
2788        while (iter.hasNext()) iter.next().NULL_THE_TREE();
2789
2790        children.clear();
2791    }
2792}