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