001 package com.softnetConsult.utils.files; 002 003 import java.io.File; 004 import java.io.FileOutputStream; 005 import java.io.IOException; 006 import java.io.PrintWriter; 007 import java.util.Calendar; 008 import java.util.GregorianCalendar; 009 import java.util.List; 010 011 012 /** 013 * This is a collection of static utility methods for dealing with files and parths. 014 * 015 * <p style="font-size:smaller;">This product includes software developed by the 016 * <strong>SoftNet-Consult Java Utility Library</strong> project and its contributors.<br /> 017 * (<a href="http://java-tools.sourceforge.net" target="_blank">http://java-tools.sourceforge.net</a>)<br /> 018 * Copyright (c) 2007-2008 SoftNet-Consult.<br /> 019 * Copyright (c) 2007-2008 G. Paperin.<br /> 020 * All rights reserved. 021 * </p> 022 * <p style="font-size:smaller;">File: FileTools.java<br /> 023 * Library API version: {@value com.softnetConsult.utils.APIProperties#apiVersion}<br /> 024 * Java compliance version: {@value com.softnetConsult.utils.APIProperties#javaComplianceVersion} 025 * </p> 026 * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or 027 * without modification, are permitted provided that the following terms and conditions are met: 028 * </p> 029 * <p style="font-size:smaller;">1. Redistributions of source code must retain the above 030 * acknowledgement of the SoftNet-Consult Java Utility Library project, the above copyright 031 * notice, this list of conditions and the following disclaimer.<br /> 032 * 2. Redistributions in binary form must reproduce the above acknowledgement of the 033 * SoftNet-Consult Java Utility Library project, the above copyright notice, this list of 034 * conditions and the following disclaimer in the documentation and/or other materials 035 * provided with the distribution.<br /> 036 * 3. All advertising materials mentioning features or use of this software or any derived 037 * software must display the following acknowledgement:<br /> 038 * <em>This product includes software developed by the SoftNet-Consult Java Utility Library 039 * project and its contributors.</em> 040 * </p> 041 * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY 042 * OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 043 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 044 * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 045 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 046 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 047 * </p> 048 * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>) 049 * @version {@value com.softnetConsult.utils.APIProperties#apiVersion} 050 */ 051 public final class FileTools { 052 053 054 public static final String FILE_EXTENSION_SEPARATOR = "."; 055 public static final String FILE_NAME_UNIQUE_MAKER_TAG = "%ts"; 056 057 058 /** 059 * Prevents instances of this class from being created 060 * as this class contains only static utility methods. 061 */ 062 private FileTools() {} 063 064 065 /** 066 * Gets the extension of the specified file name including the extension 067 * delimeter ({@value #FILE_EXTENSION_SEPARATOR}). 068 * The extension is the part of the file name after the last occurence 069 * of the extension delimeter. 070 * 071 * @param fileName A file name. 072 * @return The extension of the specified file name including the extension delimeter; 073 * if the file name does not include an extension delimeter, an empty string is returned. 074 */ 075 public static String getExtension(String fileName) { 076 return getExtension(fileName, true); 077 } 078 079 080 /** 081 * Gets the extension of the specified file name. 082 * The extension is the part of the file name after the last occurence 083 * of the extension delimeter ({@value #FILE_EXTENSION_SEPARATOR}). 084 * 085 * @param fileName A file name. 086 * @param includeExtSep Whether the extension delimeter 087 * ({@value #FILE_EXTENSION_SEPARATOR}) will be returned as part of the 088 * extension. 089 * @return The extension of the specified file name; if the file name 090 * does not include an extension delimeter, an empty string is returned. 091 */ 092 public static String getExtension(String fileName, boolean includeExtSep) { 093 094 if (null == fileName) 095 return ""; 096 097 int p = fileName.lastIndexOf(FILE_EXTENSION_SEPARATOR); 098 099 if (0 > p) 100 return ""; 101 102 if (includeExtSep) 103 return fileName.substring(p); 104 return fileName.substring(p + FILE_EXTENSION_SEPARATOR.length()); 105 } 106 107 108 /** 109 * Gets the extension of the specified file including the extension 110 * delimeter ({@value #FILE_EXTENSION_SEPARATOR}). 111 * The extension is the part of the file name after the last occurence 112 * of the extension delimeter. 113 * 114 * @param file A file 115 * @return The extension of the specified file including the extension delimeter; 116 * if the file name does not include an extension delimeter, an empty string is returned. 117 */ 118 public static String getExtension(File file) { 119 return getExtension(file, true); 120 } 121 122 123 /** 124 * Gets the extension of the specified file. 125 * The extension is the part of the file name after the last occurence 126 * of the extension delimeter ({@value #FILE_EXTENSION_SEPARATOR}). 127 * 128 * @param file A file. 129 * @param includeExtSep Whether the extension delimeter 130 * ({@value #FILE_EXTENSION_SEPARATOR}) will be returned as part of the 131 * extension. 132 * @return The extension of the specified name; if the file name 133 * does not include an extension delimeter, an empty string is returned. 134 */ 135 public static String getExtension(File file, boolean includeExtSep) { 136 if (null == file) 137 return ""; 138 return getExtension(file.getName(), includeExtSep); 139 } 140 141 142 /** 143 * Gets the base name of the specified file name. 144 * The base name is the part of the file name before the last occurence 145 * of the extension delimeter ({@value #FILE_EXTENSION_SEPARATOR}). 146 * 147 * @param fileName A file name. 148 * @return The base name of the specified file name. 149 */ 150 public static String getBaseName(String fileName) { 151 int nameIndex = fileName.lastIndexOf(File.separatorChar); 152 String name = fileName.substring(nameIndex + 1); 153 int extIndex = name.lastIndexOf(FILE_EXTENSION_SEPARATOR); 154 if (-1 == extIndex) 155 return name; 156 return name.substring(0, extIndex); 157 } 158 159 160 /** 161 * Gets the base name of the specified name. 162 * The base name is the part of the file name before the last occurence 163 * of the extension delimeter ({@value #FILE_EXTENSION_SEPARATOR}). 164 * 165 * @param file A file. 166 * @return The base name of the specified file. 167 */ 168 public static String getBaseName(File file) { 169 if (null == file) 170 return ""; 171 return getBaseName(file.getName()); 172 } 173 174 175 /** 176 * Concatenates a directory name and a file name to build a path by inserting a 177 * file separator ({@code '/'} or {@code '\\'}) between them if necessary.<br /> 178 * For example all of the following calls will produce the same result:<br /> 179 * - {@code concatDirFile("dir/subdir", "file.name")}<br /> 180 * - {@code concatDirFile("dir/subdir/", "file.name")}<br /> 181 * - {@code concatDirFile("dir/subdir", "/file.name")}<br /> 182 * - {@code concatDirFile("dir/subdir/", "/file.name")}<br /> 183 * RESULT: {@code "dir/subdir/file.name"}<br /> 184 * Note that {@code '/'} is used in the example, but {@code '\\'} is equally supported. 185 * 186 * @param dirName A directory name. 187 * @param fileName A file name 188 * @return A file path. 189 */ 190 public static final String concatDirFile(String dirName, String fileName) { 191 192 if (null == dirName) 193 dirName = ""; 194 else 195 dirName = dirName.trim(); 196 197 if (null == fileName ) 198 fileName = ""; 199 else 200 fileName = fileName.trim(); 201 202 StringBuffer s = new StringBuffer(); 203 s.append(dirName); 204 205 int dirNameLen = s.length(); 206 if (0 < dirNameLen) { 207 208 boolean dirEndsWithSeparator = dirName.endsWith(File.separator) 209 || dirName.endsWith("/") 210 || dirName.endsWith("\\"); 211 212 boolean fileStartsWithSeparator = fileName.startsWith(File.separator) 213 || fileName.startsWith("/") 214 || fileName.startsWith("\\"); 215 216 if (!dirEndsWithSeparator && !fileStartsWithSeparator) 217 s.append(File.separator); 218 else if (dirEndsWithSeparator && fileStartsWithSeparator) 219 s.setLength(dirNameLen - 1); 220 } 221 222 s.append(fileName); 223 return s.toString(); 224 } 225 226 227 /** 228 * Concatenates a file base nameand a file extension to build a full file name 229 * by inserting a file extension separator ({@code '.'}) between them if necessary.<br /> 230 * For example all of the following calls will produce the same result:<br /> 231 * - {@code concatDirFile("basename", "ext")}<br /> 232 * - {@code concatDirFile("basename.", "ext")}<br /> 233 * - {@code concatDirFile("basename", ".ext")}<br /> 234 * - {@code concatDirFile("basename.", ".ext")}<br /> 235 * RESULT: {@code "basename.ext"}<br /> 236 * 237 * @param fileBaseName A file base name. 238 * @param fileExt A file extension. 239 * @return The complete file name. 240 */ 241 public static final String concatFileExt(String fileBaseName, String fileExt) { 242 243 if (null == fileBaseName) 244 fileBaseName = ""; 245 else 246 fileBaseName = fileBaseName.trim(); 247 248 if (null == fileExt) 249 fileExt = ""; 250 else 251 fileExt = fileExt.trim(); 252 253 StringBuffer s = new StringBuffer(); 254 s.append(fileBaseName); 255 256 int baseNameLen = s.length(); 257 if (0 < baseNameLen) { 258 259 boolean baseEndsWithSeparator = fileBaseName.endsWith(FILE_EXTENSION_SEPARATOR); 260 boolean extStartsWithSeparator = fileExt.startsWith(FILE_EXTENSION_SEPARATOR); 261 262 if (!baseEndsWithSeparator && !extStartsWithSeparator) 263 s.append(FILE_EXTENSION_SEPARATOR); 264 else if (baseEndsWithSeparator && extStartsWithSeparator) 265 s.setLength(baseNameLen - 1); 266 } 267 268 s.append(fileExt); 269 return s.toString(); 270 } 271 272 /** 273 * Creates an absolute file name describing a file with the specified base name and extension 274 * as well as an optional timestamp and an optional counter to make the file 275 * name unique; the program start directory is used for the path.<br /> 276 * This methos works generally in the same way as 277 * {@link #findUniqueFileName(String, String, String, boolean)} and is equivalent to 278 * {@code findUniqueFile(fileName, fileExt, alwaysUseTimestamp).getAbsolutePath()}. 279 * 280 * @param fileName The base name for the unique albolute file name to be created. 281 * @param fileExt The extension for the unique albolute file name to be created. 282 * @param alwaysUseTimestamp Whether to use a timestamp if no timestamp position marker 283 * is specified. 284 * @return An absolute file name decribing a path based on the specified parameters 285 * modified as above to make sure that no file with the same name/path already exists. 286 */ 287 public static String findUniqueFileName(String fileName, String fileExt, boolean alwaysUseTimestamp) { 288 return findUniqueFile(fileName, fileExt, alwaysUseTimestamp).getAbsolutePath(); 289 } 290 291 292 /** 293 * Creates an absolute file name describing a file with the specified base name, extension 294 * and directory as well as an optional timestamp and an optional counter to make the file 295 * name unique.<br /> 296 * This methos works generally in the same way as 297 * {@link #findUniqueFileName(String, String, String, boolean)} and is equivalent to 298 * {@code findUniqueFile(fileDir, fileName, fileExt, alwaysUseTimestamp).getAbsolutePath()}. 299 * 300 * @param fileDir The directory for the unique albolute file name to be created. 301 * @param fileName The base name for the unique albolute file name to be created. 302 * @param fileExt The extension for the unique-path {@code File} object to be created. 303 * @param alwaysUseTimestamp Whether to use a timestamp if no timestamp position marker 304 * is specified. 305 * @return An absolute file name decribing a path based on the specified parameters 306 * modified as above to make sure that no file with the same name/path already exists. 307 */ 308 public static String findUniqueFileName(String fileDir, String fileName, String fileExt, 309 boolean alwaysUseTimestamp) { 310 return findUniqueFile(fileDir, fileName, fileExt, alwaysUseTimestamp).getAbsolutePath(); 311 } 312 313 314 /** 315 * Creates a {@code File} object describing a file with the specified base name and extension 316 * as well as an optional timestamp and an optional counter to make the file 317 * name unique; the program start directory is used for the path.<br /> 318 * This methos works generally in the same way as 319 * {@link #findUniqueFileName(String, String, String, boolean)} and is equivalent to 320 * {@code findUniqueFile(System.getProperty("user.dir"), fileName, fileExt, alwaysUseTimestamp)}. 321 * 322 * @param fileName The base name for the unique-path {@code File} object to be created. 323 * @param fileExt The extension for the unique-path {@code File} object to be created. 324 * @param alwaysUseTimestamp Whether to use a timestamp if no timestamp position marker 325 * is specified. 326 * @return A {@code File} object decribing a path based on the specified parameters 327 * modified to make sure that no file with the same name/path already exists. 328 */ 329 public static File findUniqueFile(String fileName, String fileExt, boolean alwaysUseTimestamp) { 330 return findUniqueFile(System.getProperty("user.dir"), fileName, fileExt, alwaysUseTimestamp); 331 } 332 333 334 /** 335 * Creates a {@code File} object describing a file with the specified base name, extension 336 * and directory as well as an optional timestamp and an optional counter to make the file 337 * name unique. This method works as follows:<br /> 338 * 339 * 1) A file name is created on the basis of the specified directory, base name and extension. 340 * (e.g. "{@code directory/basename.ext}").<br /> 341 * 2) If the specified file name contains a substring that defines a timestamp marker 342 * (the string "{@value #FILE_NAME_UNIQUE_MAKER_TAG}"), the marker is replaced 343 * by a timestamp based on the current system time. If the specified file name does not 344 * contain a timestamp marker and {@code alwaysUseTimestamp} is {@code true}, a timestamp 345 * is inserted after the filename and before the extension. If the specified file name does not 346 * contain a timestamp marker and {@code alwaysUseTimestamp} is {@code false}, no timespamp is 347 * inserted. Executed on 20 Jan 2008 at 18:30:15 the result may look like this: 348 * "{@code directory/basename.08.01.20-18.30.15.ext}".<br /> 349 * 3) If no physical file with the resulting file name exists already, the corresponding {@code File} 350 * object is returned. If a file with the resulting path exists, a counter is inserted just 351 * before the extension. The result may look like this: 352 * "{@code directory/basename.08.01.20-18.30.15(1).ext}".<br /> 353 * 4) The uniqueness counter is increased (starting with 1) until the resulting file name 354 * does not describe a physical file that already exists.<br /> 355 * Note that this method does not attempt to synchronise on the file system, i.e. if this 356 * method is invoked by different processes/threads at the same time the resulting file paths 357 * may or may not be be the same. 358 * 359 * @param fileDir The directory for the unique-path {@code File} object to be created. 360 * @param fileName The base name for the unique-path {@code File} object to be created. 361 * @param fileExt The extension for the unique-path {@code File} object to be created. 362 * @param alwaysUseTimestamp Whether to use a timestamp if no timestamp position marker 363 * is specified. 364 * @return A {@code File} object decribing a path based on the specified parameters 365 * modified as above to make sure that no file with the same name/path already exists. 366 */ 367 public static File findUniqueFile(String fileDir, String fileName, String fileExt, 368 boolean alwaysUseTimestamp) { 369 370 if (null == fileDir) 371 fileDir = ""; 372 fileDir = fileDir.trim(); 373 374 if (null == fileName) 375 fileName = ""; 376 fileName = fileName.trim(); 377 378 if (null == fileExt) 379 fileExt = ""; 380 fileExt = fileExt.trim(); 381 382 if (!fileDir.endsWith("/") && !fileDir.endsWith("\\")) 383 fileDir = fileDir + "/"; 384 385 if (!fileExt.startsWith(FILE_EXTENSION_SEPARATOR) && fileExt.length() > 0) 386 fileExt = FILE_EXTENSION_SEPARATOR + fileExt; 387 388 if (alwaysUseTimestamp && 0 > fileName.indexOf(FILE_NAME_UNIQUE_MAKER_TAG)) 389 fileName = fileName + ("." + FILE_NAME_UNIQUE_MAKER_TAG); 390 391 Calendar date = new GregorianCalendar(); 392 String uniqueMakerStr = String.format("%1$ty.%1$tm.%1$td-%1$tH.%1$tM.%1$tS", date); 393 394 StringBuffer uniqueFileName = new StringBuffer(); 395 uniqueFileName.append(fileDir); 396 uniqueFileName.append(fileName.replace(FILE_NAME_UNIQUE_MAKER_TAG, uniqueMakerStr)); 397 uniqueFileName.append(fileExt); 398 File uniqueFile = new File(uniqueFileName.toString()); 399 400 if (uniqueFile.exists()) { 401 402 int k = 1; 403 while(uniqueFile.exists()) { 404 405 uniqueFileName.setLength(0); 406 uniqueFileName.append(fileDir); 407 uniqueFileName.append(fileName.replace(FILE_NAME_UNIQUE_MAKER_TAG, uniqueMakerStr)); 408 uniqueFileName.append('('); 409 uniqueFileName.append(k); 410 uniqueFileName.append(')'); 411 uniqueFileName.append(fileExt); 412 uniqueFile = new File(uniqueFileName.toString()); 413 k++; 414 if (0 > k) 415 throw new RuntimeException("Cannot find a unique file name; last filename tried: " 416 + uniqueFile.getAbsolutePath()); 417 } 418 } 419 420 return uniqueFile; 421 } 422 423 424 /** 425 * Writes a specified string to the specified file; the file is always overwritten. 426 * 427 * @param fileName The name of the file to write to. 428 * @param str The string to write to the file. 429 * @return {@code false} if the specified file name was {@code null} or if an IO exception 430 * occured; {@code true} otherwise. 431 */ 432 public static boolean writeToFile(String fileName, String str) { 433 return writeToFile(fileName, str, false); 434 } 435 436 437 /** 438 * Writes a specified string to the specified file. 439 * 440 * @param fileName The name of the file to write to. 441 * @param str The string to write to the file. 442 * @param append If {@code true} than the string will be appended at the end of the file, 443 * otherwise the file will be overwritten. 444 * @return {@code false} if the specified file name was {@code null} or if an IO exception 445 * occured; {@code true} otherwise. 446 */ 447 public static boolean writeToFile(String fileName, String str, boolean append) { 448 449 if (null == fileName) 450 return false; 451 452 try { 453 PrintWriter out = new PrintWriter(new FileOutputStream(fileName, append)); 454 try { out.print(str); } 455 finally { out.close(); } 456 } catch (IOException e) { 457 return false; 458 } 459 return true; 460 } 461 462 463 /** 464 * Deletes the specified file, if the file specifies a directory all contained 465 * files and subdirectories are also deleted recursively. Note that if an error 466 * occurs during the recursive deletion of files contained within a directory, 467 * some of the directory contents and the directory itself may not be deleted. 468 * (This is on contrary to the {@code deleteAllRecursively}-methods in this class 469 * that proceed attempting to delete as many of the contained files and subdirectories 470 * as possible.) 471 * 472 * @param path The file or directory to delete. 473 * @return If the specified file or directory as well as all contained files and 474 * subdirectories are successfully deleted this method returns {@code true}, 475 * otherwise it returns {@code false}. 476 */ 477 public static boolean deleteRecursively(String path) { 478 if (null == path) 479 return false; 480 return deleteRecursively(new File(path)); 481 } 482 483 /** 484 * Deletes the specified file, if the file specifies a directory all contained 485 * files and subdirectories are also deleted recursively. Note that if an error 486 * occurs during the recursive deletion of files contained within a directory, 487 * some of the directory contents and the directory itself may not be deleted. 488 * (This is on contrary to the {@code deleteAllRecursively}-methods in this class 489 * that proceed attempting to delete as many of the contained files and subdirectories 490 * as possible.) 491 * 492 * @param file The file or directory to delete. 493 * @return If the specified file or directory as well as all contained files and 494 * subdirectories are successfully deleted this method returns {@code true}, 495 * otherwise it returns {@code false}. 496 */ 497 public static boolean deleteRecursively(File file) { 498 499 if (null == file) 500 return false; 501 502 if (!file.exists()) 503 return false; 504 505 if (file.isFile()) 506 return file.delete(); 507 508 if (file.isDirectory()) { 509 510 File[] contents = file.listFiles(); 511 if (null == contents) 512 return false; 513 514 for (File f : contents) { 515 if (!deleteRecursively(f)) 516 return false; 517 } 518 519 return file.delete(); 520 } 521 522 return false; 523 } 524 525 526 /** 527 * Deletes the specified file; if the path specifies a directory all contained 528 * files and subdirectories are also deleted recursively. If an error occurs 529 * during the recursive deletion of files this method proceeds attempting to 530 * delete as many of the contained files and subdirectories as possible 531 * (this is on contrary to the {@code deleteRecursively}-methods in this class that 532 * return immediately if an error is encountered).<br /> 533 * This method is equivalent to 534 * {@code deleteAllRecursively(new File(path), null, null)}. 535 * 536 * @param path The file or directory to delete. 537 * @return If the specified file or directory as well as all contained files and 538 * subdirectories are successfully deleted this method returns {@code true}, 539 * otherwise it returns {@code false}. 540 */ 541 public static boolean deleteAllRecursively(String path) { 542 return deleteAllRecursively(new File(path), null, null); 543 } 544 545 546 /** 547 * Deletes the specified file; if the path specifies a directory all contained 548 * files and subdirectories are also deleted recursively. If an error occurs 549 * during the recursive deletion of files this method proceeds attempting to 550 * delete as many of the contained files and subdirectories as possible 551 * (this is on contrary to the {@code deleteRecursively}-methods in this class that 552 * return immediately if an error is encountered).<br /> 553 * If the list {@code deleted} is {@code null} it is ignored, otherwise all 554 * files that are successfully deteted will be added to the {@code deleted}-list. 555 * If the list {@code failed} is {@code null} it is ignored, otherwise all files that 556 * could not be successfully deteted will be added to the {@code failed}-list.<br /> 557 * This method is equivalent to 558 * {@code deleteAllRecursively(new File(path), deleted, failed)}. 559 * 560 * @param path The file or directory to delete. 561 * @param deleted A list to which all successfully deleted files are to be added 562 * (may be {@code null} in which case it is ignored). 563 * @param failed A list to which all files that could not be successfully deleted 564 * are to be added (may be {@code null} in which case it is ignored). 565 * @return If the specified file or directory as well as all contained files and 566 * subdirectories are successfully deleted this method returns {@code true}, 567 * otherwise it returns {@code false}. 568 */ 569 public static boolean deleteAllRecursively(String path, List<File> deleted, List<File> failed) { 570 return deleteAllRecursively(new File(path), deleted, failed); 571 } 572 573 574 /** 575 * Deletes the specified file; if the file specifies a directory all contained 576 * files and subdirectories are also deleted recursively. If an error occurs 577 * during the recursive deletion of files this method proceeds attempting to 578 * delete as many of the contained files and subdirectories as possible 579 * (this is on contrary to the {@code deleteRecursively}-methods in this class that 580 * return immediately if an error is encountered).<br /> 581 * This method is equivalent to {@code deleteAllRecursively(file, null, null)}. 582 * 583 * @param file The file or directory to delete. 584 * @return If the specified file or directory as well as all contained files and 585 * subdirectories are successfully deleted this method returns {@code true}, 586 * otherwise it returns {@code false}. 587 */ 588 public static boolean deleteAllRecursively(File file) { 589 return deleteAllRecursively(file, null, null); 590 } 591 592 593 /** 594 * Deletes the specified file; if the file specifies a directory all contained 595 * files and subdirectories are also deleted recursively. If an error occurs 596 * during the recursive deletion of files this method proceeds attempting to 597 * delete as many of the contained files and subdirectories as possible 598 * (this is on contrary to the {@code deleteRecursively}-methods in this class that 599 * return immediately if an error is encountered).<br /> 600 * If the list {@code deleted} is {@code null} it is ignored, otherwise all 601 * files that are successfully deteted will be added to the {@code deleted}-list. 602 * If the list {@code failed} is {@code null} it is ignored, otherwise all files 603 * that could not be successfully deteted will be added to the {@code failed}-list. 604 * 605 * @param file The file or directory to delete. 606 * @param deleted A list to which all successfully deleted files are to be added 607 * (may be {@code null} in which case it is ignored). 608 * @param failed A list to which all files that could not be successfully deleted 609 * are to be added (may be {@code null} in which case it is ignored). 610 * @return If the specified file or directory as well as all contained files and 611 * subdirectories are successfully deleted this method returns {@code true}, 612 * otherwise it returns {@code false}. 613 */ 614 public static boolean deleteAllRecursively(File file, List<File> deleted, List<File> failed) { 615 616 if (null == file) 617 return false; 618 619 if (!file.exists()) 620 return false; 621 622 if (file.isFile()) { 623 if (file.delete()) { 624 if (null != deleted) deleted.add(file); 625 return true; 626 } else { 627 if (null != failed) failed.add(file); 628 return false; 629 } 630 } 631 632 if (file.isDirectory()) { 633 634 boolean fullSuccess = true; 635 636 File[] contents = file.listFiles(); 637 if (null != contents) { 638 for (File f : contents) { 639 if (!deleteAllRecursively(f, deleted, failed)) 640 fullSuccess = false; 641 } 642 } 643 644 if (file.delete()) { 645 if (null != deleted) deleted.add(file); 646 return fullSuccess; 647 } else { 648 if (null != failed) failed.add(file); 649 return false; 650 } 651 } 652 653 return false; 654 } 655 656 } // public final class FileTools