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