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 019package org.apache.hadoop.fs; 020 021import java.io.*; 022import java.util.Arrays; 023import java.util.Enumeration; 024import java.util.zip.ZipEntry; 025import java.util.zip.ZipFile; 026 027import org.apache.hadoop.classification.InterfaceAudience; 028import org.apache.hadoop.classification.InterfaceStability; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.io.IOUtils; 031import org.apache.hadoop.util.Shell; 032import org.apache.hadoop.util.Shell.ShellCommandExecutor; 033 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036 037/** 038 * A collection of file-processing util methods 039 */ 040@InterfaceAudience.Public 041@InterfaceStability.Evolving 042public 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}