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 @Override
483 protected String[] getExecString() {
484 return command;
485 }
486 @Override
487 protected void parseExecResult(BufferedReader lines) throws IOException {
488 String line = lines.readLine();
489 if (line == null) {
490 throw new IOException("Can't convert '" + command[2] +
491 " to a cygwin path");
492 }
493 result = line;
494 }
495 }
496
497 /**
498 * Convert a os-native filename to a path that works for the shell.
499 * @param filename The filename to convert
500 * @return The unix pathname
501 * @throws IOException on windows, there can be problems with the subprocess
502 */
503 public static String makeShellPath(String filename) throws IOException {
504 if (Path.WINDOWS) {
505 return new CygPathCommand(filename).getResult();
506 } else {
507 return filename;
508 }
509 }
510
511 /**
512 * Convert a os-native filename to a path that works for the shell.
513 * @param file The filename to convert
514 * @return The unix pathname
515 * @throws IOException on windows, there can be problems with the subprocess
516 */
517 public static String makeShellPath(File file) throws IOException {
518 return makeShellPath(file, false);
519 }
520
521 /**
522 * Convert a os-native filename to a path that works for the shell.
523 * @param file The filename to convert
524 * @param makeCanonicalPath
525 * Whether to make canonical path for the file passed
526 * @return The unix pathname
527 * @throws IOException on windows, there can be problems with the subprocess
528 */
529 public static String makeShellPath(File file, boolean makeCanonicalPath)
530 throws IOException {
531 if (makeCanonicalPath) {
532 return makeShellPath(file.getCanonicalPath());
533 } else {
534 return makeShellPath(file.toString());
535 }
536 }
537
538 /**
539 * Takes an input dir and returns the du on that local directory. Very basic
540 * implementation.
541 *
542 * @param dir
543 * The input dir to get the disk space of this local dir
544 * @return The total disk space of the input local directory
545 */
546 public static long getDU(File dir) {
547 long size = 0;
548 if (!dir.exists())
549 return 0;
550 if (!dir.isDirectory()) {
551 return dir.length();
552 } else {
553 File[] allFiles = dir.listFiles();
554 if(allFiles != null) {
555 for (int i = 0; i < allFiles.length; i++) {
556 boolean isSymLink;
557 try {
558 isSymLink = org.apache.commons.io.FileUtils.isSymlink(allFiles[i]);
559 } catch(IOException ioe) {
560 isSymLink = true;
561 }
562 if(!isSymLink) {
563 size += getDU(allFiles[i]);
564 }
565 }
566 }
567 return size;
568 }
569 }
570
571 /**
572 * Given a File input it will unzip the file in a the unzip directory
573 * passed as the second parameter
574 * @param inFile The zip file as input
575 * @param unzipDir The unzip directory where to unzip the zip file.
576 * @throws IOException
577 */
578 public static void unZip(File inFile, File unzipDir) throws IOException {
579 Enumeration<? extends ZipEntry> entries;
580 ZipFile zipFile = new ZipFile(inFile);
581
582 try {
583 entries = zipFile.entries();
584 while (entries.hasMoreElements()) {
585 ZipEntry entry = entries.nextElement();
586 if (!entry.isDirectory()) {
587 InputStream in = zipFile.getInputStream(entry);
588 try {
589 File file = new File(unzipDir, entry.getName());
590 if (!file.getParentFile().mkdirs()) {
591 if (!file.getParentFile().isDirectory()) {
592 throw new IOException("Mkdirs failed to create " +
593 file.getParentFile().toString());
594 }
595 }
596 OutputStream out = new FileOutputStream(file);
597 try {
598 byte[] buffer = new byte[8192];
599 int i;
600 while ((i = in.read(buffer)) != -1) {
601 out.write(buffer, 0, i);
602 }
603 } finally {
604 out.close();
605 }
606 } finally {
607 in.close();
608 }
609 }
610 }
611 } finally {
612 zipFile.close();
613 }
614 }
615
616 /**
617 * Given a Tar File as input it will untar the file in a the untar directory
618 * passed as the second parameter
619 *
620 * This utility will untar ".tar" files and ".tar.gz","tgz" files.
621 *
622 * @param inFile The tar file as input.
623 * @param untarDir The untar directory where to untar the tar file.
624 * @throws IOException
625 */
626 public static void unTar(File inFile, File untarDir) throws IOException {
627 if (!untarDir.mkdirs()) {
628 if (!untarDir.isDirectory()) {
629 throw new IOException("Mkdirs failed to create " + untarDir);
630 }
631 }
632
633 StringBuilder untarCommand = new StringBuilder();
634 boolean gzipped = inFile.toString().endsWith("gz");
635 if (gzipped) {
636 untarCommand.append(" gzip -dc '");
637 untarCommand.append(FileUtil.makeShellPath(inFile));
638 untarCommand.append("' | (");
639 }
640 untarCommand.append("cd '");
641 untarCommand.append(FileUtil.makeShellPath(untarDir));
642 untarCommand.append("' ; ");
643 untarCommand.append("tar -xf ");
644
645 if (gzipped) {
646 untarCommand.append(" -)");
647 } else {
648 untarCommand.append(FileUtil.makeShellPath(inFile));
649 }
650 String[] shellCmd = { "bash", "-c", untarCommand.toString() };
651 ShellCommandExecutor shexec = new ShellCommandExecutor(shellCmd);
652 shexec.execute();
653 int exitcode = shexec.getExitCode();
654 if (exitcode != 0) {
655 throw new IOException("Error untarring file " + inFile +
656 ". Tar process exited with exit code " + exitcode);
657 }
658 }
659
660 /**
661 * Class for creating hardlinks.
662 * Supports Unix, Cygwin, WindXP.
663 * @deprecated Use {@link org.apache.hadoop.fs.HardLink}
664 */
665 @Deprecated
666 public static class HardLink extends org.apache.hadoop.fs.HardLink {
667 // This is a stub to assist with coordinated change between
668 // COMMON and HDFS projects. It will be removed after the
669 // corresponding change is committed to HDFS.
670 }
671
672 /**
673 * Create a soft link between a src and destination
674 * only on a local disk. HDFS does not support this
675 * @param target the target for symlink
676 * @param linkname the symlink
677 * @return value returned by the command
678 */
679 public static int symLink(String target, String linkname) throws IOException{
680 String cmd = "ln -s " + target + " " + linkname;
681 Process p = Runtime.getRuntime().exec(cmd, null);
682 int returnVal = -1;
683 try{
684 returnVal = p.waitFor();
685 } catch(InterruptedException e){
686 //do nothing as of yet
687 }
688 return returnVal;
689 }
690
691 /**
692 * Change the permissions on a filename.
693 * @param filename the name of the file to change
694 * @param perm the permission string
695 * @return the exit code from the command
696 * @throws IOException
697 * @throws InterruptedException
698 */
699 public static int chmod(String filename, String perm
700 ) throws IOException, InterruptedException {
701 return chmod(filename, perm, false);
702 }
703
704 /**
705 * Change the permissions on a file / directory, recursively, if
706 * needed.
707 * @param filename name of the file whose permissions are to change
708 * @param perm permission string
709 * @param recursive true, if permissions should be changed recursively
710 * @return the exit code from the command.
711 * @throws IOException
712 * @throws InterruptedException
713 */
714 public static int chmod(String filename, String perm, boolean recursive)
715 throws IOException, InterruptedException {
716 StringBuilder cmdBuf = new StringBuilder();
717 cmdBuf.append("chmod ");
718 if (recursive) {
719 cmdBuf.append("-R ");
720 }
721 cmdBuf.append(perm).append(" ");
722 cmdBuf.append(filename);
723 String[] shellCmd = {"bash", "-c" ,cmdBuf.toString()};
724 ShellCommandExecutor shExec = new ShellCommandExecutor(shellCmd);
725 try {
726 shExec.execute();
727 }catch(Exception e) {
728 if (LOG.isDebugEnabled()) {
729 LOG.debug("Error while changing permission : " + filename
730 + " Exception: ", e);
731 }
732 }
733 return shExec.getExitCode();
734 }
735
736 /**
737 * Create a tmp file for a base file.
738 * @param basefile the base file of the tmp
739 * @param prefix file name prefix of tmp
740 * @param isDeleteOnExit if true, the tmp will be deleted when the VM exits
741 * @return a newly created tmp file
742 * @exception IOException If a tmp file cannot created
743 * @see java.io.File#createTempFile(String, String, File)
744 * @see java.io.File#deleteOnExit()
745 */
746 public static final File createLocalTempFile(final File basefile,
747 final String prefix,
748 final boolean isDeleteOnExit)
749 throws IOException {
750 File tmp = File.createTempFile(prefix + basefile.getName(),
751 "", basefile.getParentFile());
752 if (isDeleteOnExit) {
753 tmp.deleteOnExit();
754 }
755 return tmp;
756 }
757
758 /**
759 * Move the src file to the name specified by target.
760 * @param src the source file
761 * @param target the target file
762 * @exception IOException If this operation fails
763 */
764 public static void replaceFile(File src, File target) throws IOException {
765 /* renameTo() has two limitations on Windows platform.
766 * src.renameTo(target) fails if
767 * 1) If target already exists OR
768 * 2) If target is already open for reading/writing.
769 */
770 if (!src.renameTo(target)) {
771 int retries = 5;
772 while (target.exists() && !target.delete() && retries-- >= 0) {
773 try {
774 Thread.sleep(1000);
775 } catch (InterruptedException e) {
776 throw new IOException("replaceFile interrupted.");
777 }
778 }
779 if (!src.renameTo(target)) {
780 throw new IOException("Unable to rename " + src +
781 " to " + target);
782 }
783 }
784 }
785
786 /**
787 * A wrapper for {@link File#listFiles()}. This java.io API returns null
788 * when a dir is not a directory or for any I/O error. Instead of having
789 * null check everywhere File#listFiles() is used, we will add utility API
790 * to get around this problem. For the majority of cases where we prefer
791 * an IOException to be thrown.
792 * @param dir directory for which listing should be performed
793 * @return list of files or empty list
794 * @exception IOException for invalid directory or for a bad disk.
795 */
796 public static File[] listFiles(File dir) throws IOException {
797 File[] files = dir.listFiles();
798 if(files == null) {
799 throw new IOException("Invalid directory or I/O error occurred for dir: "
800 + dir.toString());
801 }
802 return files;
803 }
804
805 /**
806 * A wrapper for {@link File#list()}. This java.io API returns null
807 * when a dir is not a directory or for any I/O error. Instead of having
808 * null check everywhere File#list() is used, we will add utility API
809 * to get around this problem. For the majority of cases where we prefer
810 * an IOException to be thrown.
811 * @param dir directory for which listing should be performed
812 * @return list of file names or empty string list
813 * @exception IOException for invalid directory or for a bad disk.
814 */
815 public static String[] list(File dir) throws IOException {
816 String[] fileNames = dir.list();
817 if(fileNames == null) {
818 throw new IOException("Invalid directory or I/O error occurred for dir: "
819 + dir.toString());
820 }
821 return fileNames;
822 }
823 }