001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package org.apache.hadoop.fs;
020
021 import java.io.*;
022 import java.util.Arrays;
023 import java.util.Enumeration;
024 import java.util.zip.ZipEntry;
025 import java.util.zip.ZipFile;
026
027 import org.apache.hadoop.classification.InterfaceAudience;
028 import org.apache.hadoop.classification.InterfaceStability;
029 import org.apache.hadoop.conf.Configuration;
030 import org.apache.hadoop.io.IOUtils;
031 import org.apache.hadoop.util.Shell;
032 import org.apache.hadoop.util.Shell.ShellCommandExecutor;
033
034 import org.apache.commons.logging.Log;
035 import org.apache.commons.logging.LogFactory;
036
037 /**
038 * A collection of file-processing util methods
039 */
040 @InterfaceAudience.Public
041 @InterfaceStability.Evolving
042 public class FileUtil {
043
044 private static final Log LOG = LogFactory.getLog(FileUtil.class);
045
046 /**
047 * convert an array of FileStatus to an array of Path
048 *
049 * @param stats
050 * an array of FileStatus objects
051 * @return an array of paths corresponding to the input
052 */
053 public static Path[] stat2Paths(FileStatus[] stats) {
054 if (stats == null)
055 return null;
056 Path[] ret = new Path[stats.length];
057 for (int i = 0; i < stats.length; ++i) {
058 ret[i] = stats[i].getPath();
059 }
060 return ret;
061 }
062
063 /**
064 * convert an array of FileStatus to an array of Path.
065 * If stats if null, return path
066 * @param stats
067 * an array of FileStatus objects
068 * @param path
069 * default path to return in stats is null
070 * @return an array of paths corresponding to the input
071 */
072 public static Path[] stat2Paths(FileStatus[] stats, Path path) {
073 if (stats == null)
074 return new Path[]{path};
075 else
076 return stat2Paths(stats);
077 }
078
079 /**
080 * Delete a directory and all its contents. If
081 * we return false, the directory may be partially-deleted.
082 * (1) If dir is symlink to a file, the symlink is deleted. The file pointed
083 * to by the symlink is not deleted.
084 * (2) If dir is symlink to a directory, symlink is deleted. The directory
085 * pointed to by symlink is not deleted.
086 * (3) If dir is a normal file, it is deleted.
087 * (4) If dir is a normal directory, then dir and all its contents recursively
088 * are deleted.
089 */
090 public static boolean fullyDelete(final File dir) {
091 return fullyDelete(dir, false);
092 }
093
094 /**
095 * Delete a directory and all its contents. If
096 * we return false, the directory may be partially-deleted.
097 * (1) If dir is symlink to a file, the symlink is deleted. The file pointed
098 * to by the symlink is not deleted.
099 * (2) If dir is symlink to a directory, symlink is deleted. The directory
100 * pointed to by symlink is not deleted.
101 * (3) If dir is a normal file, it is deleted.
102 * (4) If dir is a normal directory, then dir and all its contents recursively
103 * are deleted.
104 * @param dir the file or directory to be deleted
105 * @param tryGrantPermissions true if permissions should be modified to delete a file.
106 * @return true on success false on failure.
107 */
108 public static boolean fullyDelete(final File dir, boolean tryGrantPermissions) {
109 if (tryGrantPermissions) {
110 // try to chmod +rwx the parent folder of the 'dir':
111 File parent = dir.getParentFile();
112 grantPermissions(parent);
113 }
114 if (deleteImpl(dir, false)) {
115 // dir is (a) normal file, (b) symlink to a file, (c) empty directory or
116 // (d) symlink to a directory
117 return true;
118 }
119 // handle nonempty directory deletion
120 if (!fullyDeleteContents(dir, tryGrantPermissions)) {
121 return false;
122 }
123 return deleteImpl(dir, true);
124 }
125
126 /*
127 * Pure-Java implementation of "chmod +rwx f".
128 */
129 private static void grantPermissions(final File f) {
130 f.setExecutable(true);
131 f.setReadable(true);
132 f.setWritable(true);
133 }
134
135 private static boolean deleteImpl(final File f, final boolean doLog) {
136 if (f == null) {
137 LOG.warn("null file argument.");
138 return false;
139 }
140 final boolean wasDeleted = f.delete();
141 if (wasDeleted) {
142 return true;
143 }
144 final boolean ex = f.exists();
145 if (doLog && ex) {
146 LOG.warn("Failed to delete file or dir ["
147 + f.getAbsolutePath() + "]: it still exists.");
148 }
149 return !ex;
150 }
151
152 /**
153 * Delete the contents of a directory, not the directory itself. If
154 * we return false, the directory may be partially-deleted.
155 * If dir is a symlink to a directory, all the contents of the actual
156 * directory pointed to by dir will be deleted.
157 */
158 public static boolean fullyDeleteContents(final File dir) {
159 return fullyDeleteContents(dir, false);
160 }
161
162 /**
163 * Delete the contents of a directory, not the directory itself. If
164 * we return false, the directory may be partially-deleted.
165 * If dir is a symlink to a directory, all the contents of the actual
166 * directory pointed to by dir will be deleted.
167 * @param tryGrantPermissions if 'true', try grant +rwx permissions to this
168 * and all the underlying directories before trying to delete their contents.
169 */
170 public static boolean fullyDeleteContents(final File dir, final boolean tryGrantPermissions) {
171 if (tryGrantPermissions) {
172 // to be able to list the dir and delete files from it
173 // we must grant the dir rwx permissions:
174 grantPermissions(dir);
175 }
176 boolean deletionSucceeded = true;
177 final File[] contents = dir.listFiles();
178 if (contents != null) {
179 for (int i = 0; i < contents.length; i++) {
180 if (contents[i].isFile()) {
181 if (!deleteImpl(contents[i], true)) {// normal file or symlink to another file
182 deletionSucceeded = false;
183 continue; // continue deletion of other files/dirs under dir
184 }
185 } else {
186 // Either directory or symlink to another directory.
187 // Try deleting the directory as this might be a symlink
188 boolean b = false;
189 b = deleteImpl(contents[i], false);
190 if (b){
191 //this was indeed a symlink or an empty directory
192 continue;
193 }
194 // if not an empty directory or symlink let
195 // fullydelete handle it.
196 if (!fullyDelete(contents[i], tryGrantPermissions)) {
197 deletionSucceeded = false;
198 // continue deletion of other files/dirs under dir
199 }
200 }
201 }
202 }
203 return deletionSucceeded;
204 }
205
206 /**
207 * Recursively delete a directory.
208 *
209 * @param fs {@link FileSystem} on which the path is present
210 * @param dir directory to recursively delete
211 * @throws IOException
212 * @deprecated Use {@link FileSystem#delete(Path, boolean)}
213 */
214 @Deprecated
215 public static void fullyDelete(FileSystem fs, Path dir)
216 throws IOException {
217 fs.delete(dir, true);
218 }
219
220 //
221 // If the destination is a subdirectory of the source, then
222 // generate exception
223 //
224 private static void checkDependencies(FileSystem srcFS,
225 Path src,
226 FileSystem dstFS,
227 Path dst)
228 throws IOException {
229 if (srcFS == dstFS) {
230 String srcq = src.makeQualified(srcFS).toString() + Path.SEPARATOR;
231 String dstq = dst.makeQualified(dstFS).toString() + Path.SEPARATOR;
232 if (dstq.startsWith(srcq)) {
233 if (srcq.length() == dstq.length()) {
234 throw new IOException("Cannot copy " + src + " to itself.");
235 } else {
236 throw new IOException("Cannot copy " + src + " to its subdirectory " +
237 dst);
238 }
239 }
240 }
241 }
242
243 /** Copy files between FileSystems. */
244 public static boolean copy(FileSystem srcFS, Path src,
245 FileSystem dstFS, Path dst,
246 boolean deleteSource,
247 Configuration conf) throws IOException {
248 return copy(srcFS, src, dstFS, dst, deleteSource, true, conf);
249 }
250
251 public static boolean copy(FileSystem srcFS, Path[] srcs,
252 FileSystem dstFS, Path dst,
253 boolean deleteSource,
254 boolean overwrite, Configuration conf)
255 throws IOException {
256 boolean gotException = false;
257 boolean returnVal = true;
258 StringBuilder exceptions = new StringBuilder();
259
260 if (srcs.length == 1)
261 return copy(srcFS, srcs[0], dstFS, dst, deleteSource, overwrite, conf);
262
263 // Check if dest is directory
264 if (!dstFS.exists(dst)) {
265 throw new IOException("`" + dst +"': specified destination directory " +
266 "doest not exist");
267 } else {
268 FileStatus sdst = dstFS.getFileStatus(dst);
269 if (!sdst.isDirectory())
270 throw new IOException("copying multiple files, but last argument `" +
271 dst + "' is not a directory");
272 }
273
274 for (Path src : srcs) {
275 try {
276 if (!copy(srcFS, src, dstFS, dst, deleteSource, overwrite, conf))
277 returnVal = false;
278 } catch (IOException e) {
279 gotException = true;
280 exceptions.append(e.getMessage());
281 exceptions.append("\n");
282 }
283 }
284 if (gotException) {
285 throw new IOException(exceptions.toString());
286 }
287 return returnVal;
288 }
289
290 /** Copy files between FileSystems. */
291 public static boolean copy(FileSystem srcFS, Path src,
292 FileSystem dstFS, Path dst,
293 boolean deleteSource,
294 boolean overwrite,
295 Configuration conf) throws IOException {
296 FileStatus fileStatus = srcFS.getFileStatus(src);
297 return copy(srcFS, fileStatus, dstFS, dst, deleteSource, overwrite, conf);
298 }
299
300 /** Copy files between FileSystems. */
301 private static boolean copy(FileSystem srcFS, FileStatus srcStatus,
302 FileSystem dstFS, Path dst,
303 boolean deleteSource,
304 boolean overwrite,
305 Configuration conf) throws IOException {
306 Path src = srcStatus.getPath();
307 dst = checkDest(src.getName(), dstFS, dst, overwrite);
308 if (srcStatus.isDirectory()) {
309 checkDependencies(srcFS, src, dstFS, dst);
310 if (!dstFS.mkdirs(dst)) {
311 return false;
312 }
313 FileStatus contents[] = srcFS.listStatus(src);
314 for (int i = 0; i < contents.length; i++) {
315 copy(srcFS, contents[i], dstFS,
316 new Path(dst, contents[i].getPath().getName()),
317 deleteSource, overwrite, conf);
318 }
319 } else {
320 InputStream in=null;
321 OutputStream out = null;
322 try {
323 in = srcFS.open(src);
324 out = dstFS.create(dst, overwrite);
325 IOUtils.copyBytes(in, out, conf, true);
326 } catch (IOException e) {
327 IOUtils.closeStream(out);
328 IOUtils.closeStream(in);
329 throw e;
330 }
331 }
332 if (deleteSource) {
333 return srcFS.delete(src, true);
334 } else {
335 return true;
336 }
337
338 }
339
340 /** Copy all files in a directory to one output file (merge). */
341 public static boolean copyMerge(FileSystem srcFS, Path srcDir,
342 FileSystem dstFS, Path dstFile,
343 boolean deleteSource,
344 Configuration conf, String addString) throws IOException {
345 dstFile = checkDest(srcDir.getName(), dstFS, dstFile, false);
346
347 if (!srcFS.getFileStatus(srcDir).isDirectory())
348 return false;
349
350 OutputStream out = dstFS.create(dstFile);
351
352 try {
353 FileStatus contents[] = srcFS.listStatus(srcDir);
354 Arrays.sort(contents);
355 for (int i = 0; i < contents.length; i++) {
356 if (contents[i].isFile()) {
357 InputStream in = srcFS.open(contents[i].getPath());
358 try {
359 IOUtils.copyBytes(in, out, conf, false);
360 if (addString!=null)
361 out.write(addString.getBytes("UTF-8"));
362
363 } finally {
364 in.close();
365 }
366 }
367 }
368 } finally {
369 out.close();
370 }
371
372
373 if (deleteSource) {
374 return srcFS.delete(srcDir, true);
375 } else {
376 return true;
377 }
378 }
379
380 /** Copy local files to a FileSystem. */
381 public static boolean copy(File src,
382 FileSystem dstFS, Path dst,
383 boolean deleteSource,
384 Configuration conf) throws IOException {
385 dst = checkDest(src.getName(), dstFS, dst, false);
386
387 if (src.isDirectory()) {
388 if (!dstFS.mkdirs(dst)) {
389 return false;
390 }
391 File contents[] = listFiles(src);
392 for (int i = 0; i < contents.length; i++) {
393 copy(contents[i], dstFS, new Path(dst, contents[i].getName()),
394 deleteSource, conf);
395 }
396 } else if (src.isFile()) {
397 InputStream in = null;
398 OutputStream out =null;
399 try {
400 in = new FileInputStream(src);
401 out = dstFS.create(dst);
402 IOUtils.copyBytes(in, out, conf);
403 } catch (IOException e) {
404 IOUtils.closeStream( out );
405 IOUtils.closeStream( in );
406 throw e;
407 }
408 } else {
409 throw new IOException(src.toString() +
410 ": No such file or directory");
411 }
412 if (deleteSource) {
413 return FileUtil.fullyDelete(src);
414 } else {
415 return true;
416 }
417 }
418
419 /** Copy FileSystem files to local files. */
420 public static boolean copy(FileSystem srcFS, Path src,
421 File dst, boolean deleteSource,
422 Configuration conf) throws IOException {
423 FileStatus filestatus = srcFS.getFileStatus(src);
424 return copy(srcFS, filestatus, dst, deleteSource, conf);
425 }
426
427 /** Copy FileSystem files to local files. */
428 private static boolean copy(FileSystem srcFS, FileStatus srcStatus,
429 File dst, boolean deleteSource,
430 Configuration conf) throws IOException {
431 Path src = srcStatus.getPath();
432 if (srcStatus.isDirectory()) {
433 if (!dst.mkdirs()) {
434 return false;
435 }
436 FileStatus contents[] = srcFS.listStatus(src);
437 for (int i = 0; i < contents.length; i++) {
438 copy(srcFS, contents[i],
439 new File(dst, contents[i].getPath().getName()),
440 deleteSource, conf);
441 }
442 } else {
443 InputStream in = srcFS.open(src);
444 IOUtils.copyBytes(in, new FileOutputStream(dst), conf);
445 }
446 if (deleteSource) {
447 return srcFS.delete(src, true);
448 } else {
449 return true;
450 }
451 }
452
453 private static Path checkDest(String srcName, FileSystem dstFS, Path dst,
454 boolean overwrite) throws IOException {
455 if (dstFS.exists(dst)) {
456 FileStatus sdst = dstFS.getFileStatus(dst);
457 if (sdst.isDirectory()) {
458 if (null == srcName) {
459 throw new IOException("Target " + dst + " is a directory");
460 }
461 return checkDest(null, dstFS, new Path(dst, srcName), overwrite);
462 } else if (!overwrite) {
463 throw new IOException("Target " + dst + " already exists");
464 }
465 }
466 return dst;
467 }
468
469 /**
470 * This class is only used on windows to invoke the cygpath command.
471 */
472 private static class CygPathCommand extends Shell {
473 String[] command;
474 String result;
475 CygPathCommand(String path) throws IOException {
476 command = new String[]{"cygpath", "-u", path};
477 run();
478 }
479 String getResult() throws IOException {
480 return result;
481 }
482 protected String[] getExecString() {
483 return command;
484 }
485 protected void parseExecResult(BufferedReader lines) throws IOException {
486 String line = lines.readLine();
487 if (line == null) {
488 throw new IOException("Can't convert '" + command[2] +
489 " to a cygwin path");
490 }
491 result = line;
492 }
493 }
494
495 /**
496 * Convert a os-native filename to a path that works for the shell.
497 * @param filename The filename to convert
498 * @return The unix pathname
499 * @throws IOException on windows, there can be problems with the subprocess
500 */
501 public static String makeShellPath(String filename) throws IOException {
502 if (Path.WINDOWS) {
503 return new CygPathCommand(filename).getResult();
504 } else {
505 return filename;
506 }
507 }
508
509 /**
510 * Convert a os-native filename to a path that works for the shell.
511 * @param file The filename to convert
512 * @return The unix pathname
513 * @throws IOException on windows, there can be problems with the subprocess
514 */
515 public static String makeShellPath(File file) throws IOException {
516 return makeShellPath(file, false);
517 }
518
519 /**
520 * Convert a os-native filename to a path that works for the shell.
521 * @param file The filename to convert
522 * @param makeCanonicalPath
523 * Whether to make canonical path for the file passed
524 * @return The unix pathname
525 * @throws IOException on windows, there can be problems with the subprocess
526 */
527 public static String makeShellPath(File file, boolean makeCanonicalPath)
528 throws IOException {
529 if (makeCanonicalPath) {
530 return makeShellPath(file.getCanonicalPath());
531 } else {
532 return makeShellPath(file.toString());
533 }
534 }
535
536 /**
537 * Takes an input dir and returns the du on that local directory. Very basic
538 * implementation.
539 *
540 * @param dir
541 * The input dir to get the disk space of this local dir
542 * @return The total disk space of the input local directory
543 */
544 public static long getDU(File dir) {
545 long size = 0;
546 if (!dir.exists())
547 return 0;
548 if (!dir.isDirectory()) {
549 return dir.length();
550 } else {
551 File[] allFiles = dir.listFiles();
552 if(allFiles != null) {
553 for (int i = 0; i < allFiles.length; i++) {
554 boolean isSymLink;
555 try {
556 isSymLink = org.apache.commons.io.FileUtils.isSymlink(allFiles[i]);
557 } catch(IOException ioe) {
558 isSymLink = true;
559 }
560 if(!isSymLink) {
561 size += getDU(allFiles[i]);
562 }
563 }
564 }
565 return size;
566 }
567 }
568
569 /**
570 * Given a File input it will unzip the file in a the unzip directory
571 * passed as the second parameter
572 * @param inFile The zip file as input
573 * @param unzipDir The unzip directory where to unzip the zip file.
574 * @throws IOException
575 */
576 public static void unZip(File inFile, File unzipDir) throws IOException {
577 Enumeration<? extends ZipEntry> entries;
578 ZipFile zipFile = new ZipFile(inFile);
579
580 try {
581 entries = zipFile.entries();
582 while (entries.hasMoreElements()) {
583 ZipEntry entry = entries.nextElement();
584 if (!entry.isDirectory()) {
585 InputStream in = zipFile.getInputStream(entry);
586 try {
587 File file = new File(unzipDir, entry.getName());
588 if (!file.getParentFile().mkdirs()) {
589 if (!file.getParentFile().isDirectory()) {
590 throw new IOException("Mkdirs failed to create " +
591 file.getParentFile().toString());
592 }
593 }
594 OutputStream out = new FileOutputStream(file);
595 try {
596 byte[] buffer = new byte[8192];
597 int i;
598 while ((i = in.read(buffer)) != -1) {
599 out.write(buffer, 0, i);
600 }
601 } finally {
602 out.close();
603 }
604 } finally {
605 in.close();
606 }
607 }
608 }
609 } finally {
610 zipFile.close();
611 }
612 }
613
614 /**
615 * Given a Tar File as input it will untar the file in a the untar directory
616 * passed as the second parameter
617 *
618 * This utility will untar ".tar" files and ".tar.gz","tgz" files.
619 *
620 * @param inFile The tar file as input.
621 * @param untarDir The untar directory where to untar the tar file.
622 * @throws IOException
623 */
624 public static void unTar(File inFile, File untarDir) throws IOException {
625 if (!untarDir.mkdirs()) {
626 if (!untarDir.isDirectory()) {
627 throw new IOException("Mkdirs failed to create " + untarDir);
628 }
629 }
630
631 StringBuilder untarCommand = new StringBuilder();
632 boolean gzipped = inFile.toString().endsWith("gz");
633 if (gzipped) {
634 untarCommand.append(" gzip -dc '");
635 untarCommand.append(FileUtil.makeShellPath(inFile));
636 untarCommand.append("' | (");
637 }
638 untarCommand.append("cd '");
639 untarCommand.append(FileUtil.makeShellPath(untarDir));
640 untarCommand.append("' ; ");
641 untarCommand.append("tar -xf ");
642
643 if (gzipped) {
644 untarCommand.append(" -)");
645 } else {
646 untarCommand.append(FileUtil.makeShellPath(inFile));
647 }
648 String[] shellCmd = { "bash", "-c", untarCommand.toString() };
649 ShellCommandExecutor shexec = new ShellCommandExecutor(shellCmd);
650 shexec.execute();
651 int exitcode = shexec.getExitCode();
652 if (exitcode != 0) {
653 throw new IOException("Error untarring file " + inFile +
654 ". Tar process exited with exit code " + exitcode);
655 }
656 }
657
658 /**
659 * Class for creating hardlinks.
660 * Supports Unix, Cygwin, WindXP.
661 * @deprecated Use {@link org.apache.hadoop.fs.HardLink}
662 */
663 @Deprecated
664 public static class HardLink extends org.apache.hadoop.fs.HardLink {
665 // This is a stub to assist with coordinated change between
666 // COMMON and HDFS projects. It will be removed after the
667 // corresponding change is committed to HDFS.
668 }
669
670 /**
671 * Create a soft link between a src and destination
672 * only on a local disk. HDFS does not support this
673 * @param target the target for symlink
674 * @param linkname the symlink
675 * @return value returned by the command
676 */
677 public static int symLink(String target, String linkname) throws IOException{
678 String cmd = "ln -s " + target + " " + linkname;
679 Process p = Runtime.getRuntime().exec(cmd, null);
680 int returnVal = -1;
681 try{
682 returnVal = p.waitFor();
683 } catch(InterruptedException e){
684 //do nothing as of yet
685 }
686 return returnVal;
687 }
688
689 /**
690 * Change the permissions on a filename.
691 * @param filename the name of the file to change
692 * @param perm the permission string
693 * @return the exit code from the command
694 * @throws IOException
695 * @throws InterruptedException
696 */
697 public static int chmod(String filename, String perm
698 ) throws IOException, InterruptedException {
699 return chmod(filename, perm, false);
700 }
701
702 /**
703 * Change the permissions on a file / directory, recursively, if
704 * needed.
705 * @param filename name of the file whose permissions are to change
706 * @param perm permission string
707 * @param recursive true, if permissions should be changed recursively
708 * @return the exit code from the command.
709 * @throws IOException
710 * @throws InterruptedException
711 */
712 public static int chmod(String filename, String perm, boolean recursive)
713 throws IOException, InterruptedException {
714 StringBuilder cmdBuf = new StringBuilder();
715 cmdBuf.append("chmod ");
716 if (recursive) {
717 cmdBuf.append("-R ");
718 }
719 cmdBuf.append(perm).append(" ");
720 cmdBuf.append(filename);
721 String[] shellCmd = {"bash", "-c" ,cmdBuf.toString()};
722 ShellCommandExecutor shExec = new ShellCommandExecutor(shellCmd);
723 try {
724 shExec.execute();
725 }catch(Exception e) {
726 if (LOG.isDebugEnabled()) {
727 LOG.debug("Error while changing permission : " + filename
728 + " Exception: ", e);
729 }
730 }
731 return shExec.getExitCode();
732 }
733
734 /**
735 * Create a tmp file for a base file.
736 * @param basefile the base file of the tmp
737 * @param prefix file name prefix of tmp
738 * @param isDeleteOnExit if true, the tmp will be deleted when the VM exits
739 * @return a newly created tmp file
740 * @exception IOException If a tmp file cannot created
741 * @see java.io.File#createTempFile(String, String, File)
742 * @see java.io.File#deleteOnExit()
743 */
744 public static final File createLocalTempFile(final File basefile,
745 final String prefix,
746 final boolean isDeleteOnExit)
747 throws IOException {
748 File tmp = File.createTempFile(prefix + basefile.getName(),
749 "", basefile.getParentFile());
750 if (isDeleteOnExit) {
751 tmp.deleteOnExit();
752 }
753 return tmp;
754 }
755
756 /**
757 * Move the src file to the name specified by target.
758 * @param src the source file
759 * @param target the target file
760 * @exception IOException If this operation fails
761 */
762 public static void replaceFile(File src, File target) throws IOException {
763 /* renameTo() has two limitations on Windows platform.
764 * src.renameTo(target) fails if
765 * 1) If target already exists OR
766 * 2) If target is already open for reading/writing.
767 */
768 if (!src.renameTo(target)) {
769 int retries = 5;
770 while (target.exists() && !target.delete() && retries-- >= 0) {
771 try {
772 Thread.sleep(1000);
773 } catch (InterruptedException e) {
774 throw new IOException("replaceFile interrupted.");
775 }
776 }
777 if (!src.renameTo(target)) {
778 throw new IOException("Unable to rename " + src +
779 " to " + target);
780 }
781 }
782 }
783
784 /**
785 * A wrapper for {@link File#listFiles()}. This java.io API returns null
786 * when a dir is not a directory or for any I/O error. Instead of having
787 * null check everywhere File#listFiles() is used, we will add utility API
788 * to get around this problem. For the majority of cases where we prefer
789 * an IOException to be thrown.
790 * @param dir directory for which listing should be performed
791 * @return list of files or empty list
792 * @exception IOException for invalid directory or for a bad disk.
793 */
794 public static File[] listFiles(File dir) throws IOException {
795 File[] files = dir.listFiles();
796 if(files == null) {
797 throw new IOException("Invalid directory or I/O error occurred for dir: "
798 + dir.toString());
799 }
800 return files;
801 }
802
803 /**
804 * A wrapper for {@link File#list()}. This java.io API returns null
805 * when a dir is not a directory or for any I/O error. Instead of having
806 * null check everywhere File#list() is used, we will add utility API
807 * to get around this problem. For the majority of cases where we prefer
808 * an IOException to be thrown.
809 * @param dir directory for which listing should be performed
810 * @return list of file names or empty string list
811 * @exception IOException for invalid directory or for a bad disk.
812 */
813 public static String[] list(File dir) throws IOException {
814 String[] fileNames = dir.list();
815 if(fileNames == null) {
816 throw new IOException("Invalid directory or I/O error occurred for dir: "
817 + dir.toString());
818 }
819 return fileNames;
820 }
821 }