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    }