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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 * & 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 & 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 & 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}