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 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}