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}