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