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