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