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