View Javadoc

1   /*
2    * $Id: FileUtils.java 22154 2011-06-08 20:33:39Z aperepel $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.util;
12  
13  import org.mule.api.MuleRuntimeException;
14  import org.mule.config.i18n.MessageFactory;
15  
16  import java.io.BufferedOutputStream;
17  import java.io.BufferedWriter;
18  import java.io.File;
19  import java.io.FileInputStream;
20  import java.io.FileNotFoundException;
21  import java.io.FileOutputStream;
22  import java.io.FileWriter;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.io.UnsupportedEncodingException;
27  import java.net.JarURLConnection;
28  import java.net.URI;
29  import java.net.URL;
30  import java.net.URLConnection;
31  import java.net.URLDecoder;
32  import java.nio.channels.Channel;
33  import java.nio.channels.FileChannel;
34  import java.util.Enumeration;
35  import java.util.jar.JarEntry;
36  import java.util.jar.JarFile;
37  import java.util.zip.ZipEntry;
38  import java.util.zip.ZipFile;
39  
40  import org.apache.commons.lang.StringUtils;
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  
44  /**
45   * <code>FileUtils</code> contains useful methods for dealing with files &
46   * directories.
47   */
48  // @ThreadSafe
49  public class FileUtils extends org.apache.commons.io.FileUtils
50  {
51      private static final Log logger = LogFactory.getLog(FileUtils.class);
52      public static String DEFAULT_ENCODING = "UTF-8";
53      
54      public static synchronized void copyStreamToFile(InputStream input, File destination) throws IOException
55      {
56          if (destination.exists() && !destination.canWrite())
57          {
58              throw new IOException("Destination file does not exist or is not writeable");
59          }
60  
61          try
62          {
63              FileOutputStream output = new FileOutputStream(destination);
64              try
65              {
66                  IOUtils.copy(input, output);
67              }
68              finally
69              {
70                  IOUtils.closeQuietly(output);
71              }
72          }
73          finally
74          {
75              IOUtils.closeQuietly(input);
76          }
77      }
78  
79      // TODO Document me!
80      public static File createFile(String filename) throws IOException
81      {
82          File file = FileUtils.newFile(filename);
83          if (!file.canWrite())
84          {
85              String dirName = file.getPath();
86              int i = dirName.lastIndexOf(File.separator);
87              if (i > -1)
88              {
89                  dirName = dirName.substring(0, i);
90                  File dir = FileUtils.newFile(dirName);
91                  dir.mkdirs();
92              }
93              file.createNewFile();
94          }
95          return file;
96      }
97  
98      // TODO Document me!
99      public static String prepareWinFilename(String filename)
100     {
101         filename = filename.replaceAll("<", "(");
102         filename = filename.replaceAll(">", ")");
103         filename = filename.replaceAll("[/\\*?|:;\\]\\[\"]", "-");
104         return filename;
105     }
106 
107     // TODO Document me!
108     public static File openDirectory(String directory) throws IOException
109     {
110         File dir = FileUtils.newFile(directory);
111         if (!dir.exists())
112         {
113             dir.mkdirs();
114         }
115         if (!dir.isDirectory() || !dir.canRead())
116         {
117             throw new IOException("Path: " + directory + " exists but isn't a directory");
118         }
119         return dir;
120     }
121 
122     /**
123      * Reads the incoming String into a file at at the given destination.
124      *
125      * @param filename name and path of the file to create
126      * @param data     the contents of the file
127      * @return the new file.
128      * @throws IOException If the creating or writing to the file stream fails
129      */
130     public static File stringToFile(String filename, String data) throws IOException
131     {
132         return stringToFile(filename, data, false);
133     }
134 
135     // TODO Document me!
136     public static synchronized File stringToFile(String filename, String data, boolean append)
137             throws IOException
138     {
139         return stringToFile(filename, data, append, false);
140     }
141 
142     // TODO Document me!
143     public static synchronized File stringToFile(String filename, String data, boolean append, boolean newLine)
144             throws IOException
145     {
146         File f = createFile(filename);
147         BufferedWriter writer = null;
148         try
149         {
150             writer = new BufferedWriter(new FileWriter(f, append));
151             writer.write(data);
152             if (newLine)
153             {
154                 writer.newLine();
155             }
156         }
157         finally
158         {
159             if (writer != null)
160             {
161                 writer.close();
162             }
163         }
164         return f;
165     }
166 
167     // TODO Document me!
168     public static String getResourcePath(String resourceName, Class callingClass) throws IOException
169     {
170         return getResourcePath(resourceName, callingClass, DEFAULT_ENCODING);
171     }
172 
173     // TODO Document me!
174     public static String getResourcePath(String resourceName, Class callingClass, String encoding)
175             throws IOException
176     {
177         if (resourceName == null)
178         {
179             // no name
180             return null;
181         }
182 
183         URL url = IOUtils.getResourceAsUrl(resourceName, callingClass);
184         if (url == null)
185         {
186             // not found
187             return null;
188         }
189         return normalizeFilePath(url, encoding);
190     }
191 
192     /**
193      * Remove from uri to file prefix file:/
194      * Add if need file separator to begin
195      *
196      * @param url      file uri to resource
197      * @param encoding - Java encoding names
198      * @return normalized file path
199      * @throws UnsupportedEncodingException if encoding is unknown
200      */
201     public static String normalizeFilePath(URL url, String encoding) throws UnsupportedEncodingException
202     {
203         String resource = URLDecoder.decode(url.toExternalForm(), encoding);
204         if (resource != null)
205         {
206             if (resource.startsWith("file:/"))
207             {
208                 resource = resource.substring(6);
209 
210                 if (!resource.startsWith(File.separator))
211                 {
212                     resource = File.separator + resource;
213                 }
214             }
215         }
216         return resource;
217     }
218 
219 
220     /**
221      * Delete a file tree recursively.
222      * @param dir dir to wipe out
223      * @return false when the first unsuccessful attempt encountered
224      */
225     public static boolean deleteTree(File dir)
226     {
227         return deleteTree(dir, null);
228     }
229 
230     /**
231      * Delete a file tree recursively. This method additionally tries to be
232      * gentle with specified top-level dirs. E.g. this is the case when a
233      * transaction manager asynchronously handles the recovery log, and the test
234      * wipes out everything, leaving the transaction manager puzzled.  
235      * @param dir dir to wipe out
236      * @param topLevelDirsToIgnore which top-level directories to ignore,
237      *        if null or empty then ignored
238      * @return false when the first unsuccessful attempt encountered
239      */
240     public static boolean deleteTree(File dir, final String[] topLevelDirsToIgnore)
241     {
242         if (dir == null || !dir.exists())
243         {
244             return true;
245         }
246         File[] files = dir.listFiles();
247         if (files != null)
248         {
249             for (int i = 0; i < files.length; i++)
250             {
251                 OUTER:
252                 if (files[i].isDirectory())
253                 {
254                     if (topLevelDirsToIgnore != null)
255                     {
256                         for (int j = 0; j < topLevelDirsToIgnore.length; j++)
257                         {
258                             String ignored = topLevelDirsToIgnore[j];
259                             if (ignored.equals(FilenameUtils.getBaseName(files[i].getName())))
260                             {
261                                 break OUTER;
262                             }
263                         }
264                     }
265                     if (!deleteTree(files[i]))
266                     {
267                         return false;
268                     }
269                 }
270                 else
271                 {
272                     if (!files[i].delete())
273                     {
274                         return false;
275                     }
276                 }
277             }
278         }
279         return dir.delete();
280     }
281 
282     /**
283      * Unzip the specified archive to the given directory
284      */
285     public static void unzip(File archive, File directory) throws IOException
286     {
287         ZipFile zip = null;
288 
289         if (directory.exists())
290         {
291             if (!directory.isDirectory())
292             {
293                 throw new IOException("Directory is not a directory: " + directory);
294             }
295         }
296         else
297         {
298             if (!directory.mkdirs())
299             {
300                 throw new IOException("Could not create directory: " + directory);
301             }
302         }
303         try
304         {
305             zip = new ZipFile(archive);
306             for (Enumeration entries = zip.entries(); entries.hasMoreElements();)
307             {
308                 ZipEntry entry = (ZipEntry) entries.nextElement();
309                 File f = FileUtils.newFile(directory, entry.getName());
310                 if (entry.isDirectory())
311                 {
312                     if (!f.exists() && !f.mkdirs())
313                     {
314                         throw new IOException("Could not create directory: " + f);
315                     }
316                 }
317                 else
318                 {
319                     InputStream is = zip.getInputStream(entry);
320                     OutputStream os = new BufferedOutputStream(new FileOutputStream(f));
321                     IOUtils.copy(is, os);
322                     IOUtils.closeQuietly(is);
323                     IOUtils.closeQuietly(os);
324                 }
325             }
326         }
327         finally
328         {
329             if (zip != null)
330             {
331                 zip.close();
332             }
333         }
334     }
335 
336     /**
337      * Workaround for JDK bug <a href="http://bugs.sun.com/bugdatabase/view_bug.do;:YfiG?bug_id=4117557">
338      * 4117557</a>. More in-context information at
339      * <a href="http://mule.mulesoft.org/jira/browse/MULE-1112">MULE-1112</a>
340      * <p/>
341      * Factory methods correspond to constructors of the <code>java.io.File class</code>.
342      * No physical file created in this method.
343      *
344      * @see File
345      */
346     public static File newFile(String pathName)
347     {
348         try
349         {
350             return new File(pathName).getCanonicalFile();
351         }
352         catch (IOException e)
353         {
354             throw new MuleRuntimeException(
355                     MessageFactory.createStaticMessage("Unable to create a canonical file for " + pathName),
356                     e);
357         }
358     }
359 
360     /**
361      * Workaround for JDK bug <a href="http://bugs.sun.com/bugdatabase/view_bug.do;:YfiG?bug_id=4117557">
362      * 4117557</a>. More in-context information at
363      * <a href="http://mule.mulesoft.org/jira/browse/MULE-1112">MULE-1112</a>
364      * <p/>
365      * Factory methods correspond to constructors of the <code>java.io.File class</code>.
366      * No physical file created in this method.
367      *
368      * @see File
369      */
370     public static File newFile(URI uri)
371     {
372         try
373         {
374             return new File(uri).getCanonicalFile();
375         }
376         catch (IOException e)
377         {
378             throw new MuleRuntimeException(
379                     MessageFactory.createStaticMessage("Unable to create a canonical file for " + uri),
380                     e);
381         }
382     }
383 
384     /**
385      * Workaround for JDK bug <a href="http://bugs.sun.com/bugdatabase/view_bug.do;:YfiG?bug_id=4117557">
386      * 4117557</a>. More in-context information at
387      * <a href="http://mule.mulesoft.org/jira/browse/MULE-1112">MULE-1112</a>
388      * <p/>
389      * Factory methods correspond to constructors of the <code>java.io.File class</code>.
390      * No physical file created in this method.
391      *
392      * @see File
393      */
394     public static File newFile(File parent, String child)
395     {
396         try
397         {
398             return new File(parent, child).getCanonicalFile();
399         }
400         catch (IOException e)
401         {
402             throw new MuleRuntimeException(
403                     MessageFactory.createStaticMessage("Unable to create a canonical file for parent: "
404                             + parent + " and child: " + child),
405                     e);
406         }
407     }
408 
409     /**
410      * Workaround for JDK bug <a href="http://bugs.sun.com/bugdatabase/view_bug.do;:YfiG?bug_id=4117557">
411      * 4117557</a>. More in-context information at
412      * <a href="http://mule.mulesoft.org/jira/browse/MULE-1112">MULE-1112</a>
413      * <p/>
414      * Factory methods correspond to constructors of the <code>java.io.File class</code>.
415      * No physical file created in this method.
416      *
417      * @see File
418      */
419     public static File newFile(String parent, String child)
420     {
421         try
422         {
423             return new File(parent, child).getCanonicalFile();
424         }
425         catch (IOException e)
426         {
427             throw new MuleRuntimeException(
428                     MessageFactory.createStaticMessage("Unable to create a canonical file for parent: "
429                             + parent + " and child: " + child),
430                     e);
431         }
432     }
433 
434     /**
435      * Extract the specified resource to the given directory for
436      * remain all directory struct
437      *
438      * @param resourceName        - full resource name
439      * @param callingClass        - classloader for this class is used
440      * @param outputDir           - extract to this directory
441      * @param keepParentDirectory true -  full structure of directories is kept; false - file - removed all directories, directory - started from resource point
442      * @throws IOException if any errors
443      */
444     public static void extractResources(String resourceName, Class callingClass, File outputDir, boolean keepParentDirectory) throws IOException
445     {
446         URL url = callingClass.getClassLoader().getResource(resourceName);
447         URLConnection connection = url.openConnection();
448         if (connection instanceof JarURLConnection)
449         {
450             extractJarResources((JarURLConnection) connection, outputDir, keepParentDirectory);
451         }
452         else
453         {
454             extractFileResources(normalizeFilePath(url, DEFAULT_ENCODING),
455                                                    outputDir, resourceName, keepParentDirectory);
456         }
457     }
458 
459     /**
460      * Extract resources contain in file
461      *
462      * @param path                - path to file
463      * @param outputDir           Directory for unpack recources
464      * @param resourceName
465      * @param keepParentDirectory true -  full structure of directories is kept; false - file - removed all directories, directory - started from resource point
466      * @throws IOException if any error
467      */
468     private static void extractFileResources(String path, File outputDir, String resourceName, boolean keepParentDirectory) throws IOException
469     {
470         File file = FileUtils.newFile(path);
471         if (!file.exists())
472         {
473             throw new IOException("The resource by path " + path + " ");
474         }
475         if (file.isDirectory())
476         {
477             if (keepParentDirectory)
478             {
479                 outputDir = FileUtils.newFile(outputDir.getPath() + File.separator + resourceName);
480                 if (!outputDir.exists())
481                 {
482                     outputDir.mkdirs();
483                 }
484             }
485             else
486             {
487                 outputDir = FileUtils.newFile(outputDir.getPath());
488             }
489             copyDirectory(file, outputDir);
490         }
491         else
492         {
493 
494             if (keepParentDirectory)
495             {
496                 outputDir = FileUtils.newFile(outputDir.getPath() + File.separator + resourceName);
497             }
498             else
499             {
500                 outputDir = FileUtils.newFile(outputDir.getPath() + File.separator + file.getName());
501             }
502             copyFile(file, outputDir);
503         }
504     }
505 
506     /**
507      * Extract recources contain if jar (have to in classpath)
508      *
509      * @param connection          JarURLConnection to jar library
510      * @param outputDir           Directory for unpack recources
511      * @param keepParentDirectory true -  full structure of directories is kept; false - file - removed all directories, directory - started from resource point
512      * @throws IOException if any error
513      */
514     private static void extractJarResources(JarURLConnection connection, File outputDir, boolean keepParentDirectory) throws IOException
515     {
516         JarFile jarFile = connection.getJarFile();
517         JarEntry jarResource = connection.getJarEntry();
518         Enumeration entries = jarFile.entries();
519         InputStream inputStream = null;
520         OutputStream outputStream = null;
521         int jarResourceNameLenght = jarResource.getName().length();
522         for (; entries.hasMoreElements();)
523         {
524             JarEntry entry = (JarEntry) entries.nextElement();
525             if (entry.getName().startsWith(jarResource.getName()))
526             {
527 
528                 String path = outputDir.getPath() + File.separator + entry.getName();
529 
530                 //remove directory struct for file and first dir for directory
531                 if (!keepParentDirectory)
532                 {
533                     if (entry.isDirectory())
534                     {
535                         if (entry.getName().equals(jarResource.getName()))
536                         {
537                             continue;
538                         }
539                         path = outputDir.getPath() + File.separator + entry.getName().substring(jarResourceNameLenght, entry.getName().length());
540                     }
541                     else
542                     {
543                         if (entry.getName().length() > jarResourceNameLenght)
544                         {
545                             path = outputDir.getPath() + File.separator + entry.getName().substring(jarResourceNameLenght, entry.getName().length());
546                         }
547                         else
548                         {
549                             path = outputDir.getPath() + File.separator + entry.getName().substring(entry.getName().lastIndexOf("/"), entry.getName().length());
550                         }
551                     }
552                 }
553 
554                 File file = FileUtils.newFile(path);
555                 if (!file.getParentFile().exists())
556                 {
557                     if (!file.getParentFile().mkdirs())
558                     {
559                         throw new IOException("Could not create directory: " + file.getParentFile());
560                     }
561                 }
562                 if (entry.isDirectory())
563                 {
564                     if (!file.exists() && !file.mkdirs())
565                     {
566                         throw new IOException("Could not create directory: " + file);
567                     }
568 
569                 }
570                 else
571                 {
572                     try
573                     {
574                         inputStream = jarFile.getInputStream(entry);
575                         outputStream = new BufferedOutputStream(new FileOutputStream(file));
576                         IOUtils.copy(inputStream, outputStream);
577                     }
578                     finally
579                     {
580                         IOUtils.closeQuietly(inputStream);
581                         IOUtils.closeQuietly(outputStream);
582                     }
583                 }
584 
585             }
586         }
587     }
588 
589     public static boolean renameFileHard(String srcFilePath, String destFilePath)
590     {
591         if (StringUtils.isNotBlank(srcFilePath) && StringUtils.isNotBlank(destFilePath))
592         {
593             return renameFileHard(new File(srcFilePath), new File(destFilePath));
594         }
595         else
596         {
597             return false;
598         }
599     }
600     
601     public static boolean renameFileHard(File srcFile, File destFile)
602     {
603         boolean isRenamed = false;
604         if (srcFile != null && destFile != null)
605         {
606             logger.debug("Moving file " + srcFile.getAbsolutePath() + " to " + destFile.getAbsolutePath());
607             if (!destFile.exists())
608             {
609                 try
610                 {
611                     if (srcFile.isFile())
612                     {
613                         logger.debug("Trying to rename file");
614                         FileInputStream in = null;
615                         FileOutputStream out = null;
616                         try
617                         {
618                             in = new FileInputStream(srcFile);
619                             out = new FileOutputStream(destFile);
620                             out.getChannel().transferFrom(in.getChannel(), 0, srcFile.length());
621                             isRenamed = true;
622                         }
623                         catch (Exception e)
624                         {
625                             logger.debug(e);
626                         }
627                         finally
628                         {
629                             if (in != null)
630                             {
631                                 try
632                                 {
633                                     in.close();
634                                 }
635                                 catch (Exception inNotClosed)
636                                 {
637                                     logger.debug(inNotClosed);
638                                 }
639                             }
640                             if (out != null)
641                             {
642                                 try
643                                 {
644                                     out.close();
645                                 }
646                                 catch (Exception outNotClosed)
647                                 {
648                                     logger.debug(outNotClosed);
649                                 }
650                             }
651                         }
652                         logger.debug("File renamed: " + isRenamed);
653                         if (isRenamed)
654                         {
655                             srcFile.delete();
656                         }
657                         else
658                         {
659                             destFile.delete();
660                         }
661                     }
662                     else
663                     {
664                         logger.debug(srcFile.getAbsolutePath() + " is not a valid file.");
665                     }
666                 }
667                 catch (Exception e)
668                 {
669                     logger.debug("Error renaming file from " + srcFile.getAbsolutePath() + " to " + destFile.getAbsolutePath());
670                 }
671             }
672             else
673             {
674                 logger.debug("Error renaming file " + srcFile.getAbsolutePath() + ". Destination file " + destFile.getAbsolutePath() + " already exists.");
675             }
676         }
677         return isRenamed;
678     }
679 
680     public static boolean renameFile(String srcFilePath, String destFilePath)
681     {
682         if (StringUtils.isNotBlank(srcFilePath) && StringUtils.isNotBlank(destFilePath))
683         {
684             return renameFile(new File(srcFilePath), new File(destFilePath));
685         }
686         else
687         {
688             return false;
689         }
690     }
691     
692     public static boolean renameFile(File srcFile, File destFile)
693     {
694         boolean isRenamed = false;
695         if (srcFile != null && destFile != null)
696         {
697             logger.debug("Moving file " + srcFile.getAbsolutePath() + " to " + destFile.getAbsolutePath());
698             if (!destFile.exists())
699             {
700                 try
701                 {
702                     if (srcFile.isFile())
703                     {
704                         logger.debug("Trying to rename file");
705                         isRenamed = srcFile.renameTo(destFile);
706                         if (!isRenamed && srcFile.exists())
707                         {
708                             logger.debug("Trying hard copy, assuming partition crossing ...");
709                             isRenamed = renameFileHard(srcFile, destFile);
710                         }
711                         logger.debug("File renamed: " + isRenamed);
712                     }
713                     else
714                     {
715                         logger.debug(srcFile.getAbsolutePath() + " is not a valid file");
716                     }
717                 }
718                 catch (Exception e)
719                 {
720                     logger.debug("Error moving file from " + srcFile.getAbsolutePath() + " to " + destFile.getAbsolutePath(), e);
721                 }
722             }
723             else
724             {
725                 logger.debug("Error renaming file " + srcFile.getAbsolutePath() + ". Destination file " + destFile.getAbsolutePath() + " already exists.");
726             }
727         }
728         else
729         {
730             logger.debug("Error renaming file. Source or destination file is null.");
731         }
732     
733         return isRenamed;
734     }
735     
736     /** 
737      * Try to move a file by renaming with backup attempt by copying/deleting via NIO 
738      */
739     public static boolean moveFileWithCopyFallback(File sourceFile, File destinationFile)
740     {
741         // try fast file-system-level move/rename first
742         boolean success = sourceFile.renameTo(destinationFile);
743 
744         if (!success)
745         {
746             // try again using NIO copy
747             FileInputStream fis = null;
748             FileOutputStream fos = null;
749             try
750             {
751                 fis = new FileInputStream(sourceFile);
752                 fos = new FileOutputStream(destinationFile);
753                 FileChannel srcChannel = fis.getChannel();
754                 FileChannel dstChannel = fos.getChannel();
755                 dstChannel.transferFrom(srcChannel, 0, srcChannel.size());
756                 srcChannel.close();
757                 dstChannel.close();
758                 success = sourceFile.delete();
759             }
760             catch (IOException ioex)
761             {
762                 // grr!
763                 success = false;
764             }
765             finally
766             {
767                 IOUtils.closeQuietly(fis);
768                 IOUtils.closeQuietly(fos);
769             }
770         }
771 
772         return success;
773     }
774 
775     
776     /**
777      * Copy in file to out file
778      * 
779      * Don't use java.nio as READ_ONLY memory mapped files cannot be deleted
780      * 
781      * @param in
782      * @param out
783      */
784     public static void safeCopyFile(File in, File out) throws IOException
785     {
786         try
787         {
788             FileInputStream fis = new FileInputStream(in);
789             FileOutputStream fos = new FileOutputStream(out);
790             try
791             {
792                 byte[] buf = new byte[1024];
793                 int i = 0;
794                 while ((i = fis.read(buf)) != -1)
795                 {
796                     fos.write(buf, 0, i);
797                 }
798             }
799             catch (IOException e)
800             {
801                 throw e;
802             }
803             finally
804             {
805                 try
806                 {
807                     if (fis != null) fis.close();
808                     if (fos != null) fos.close();
809                 }
810                 catch (IOException e)
811                 {
812                     throw e;
813                 }
814 
815             }
816         }
817         catch (FileNotFoundException e)
818         {
819             throw e;
820         }
821     }
822     
823     // Override the following methods to use a new version of doCopyFile(File
824     // srcFile, File destFile, boolean preserveFileDate) that uses nio to copy file
825 
826     /**
827      * Copies a file to a new location.
828      * <p>
829      * This method copies the contents of the specified source file to the specified
830      * destination file. The directory holding the destination file is created if it
831      * does not exist. If the destination file exists, then this method will
832      * overwrite it.
833      * 
834      * @param srcFile an existing file to copy, must not be <code>null</code>
835      * @param destFile the new file, must not be <code>null</code>
836      * @param preserveFileDate true if the file date of the copy should be the same
837      *            as the original
838      * @throws NullPointerException if source or destination is <code>null</code>
839      * @throws IOException if source or destination is invalid
840      * @throws IOException if an IO error occurs during copying
841      * @see #copyFileToDirectory(File, File, boolean)
842      */
843     public static void copyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException
844     {
845         if (srcFile == null)
846         {
847             throw new NullPointerException("Source must not be null");
848         }
849         if (destFile == null)
850         {
851             throw new NullPointerException("Destination must not be null");
852         }
853         if (srcFile.exists() == false)
854         {
855             throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
856         }
857         if (srcFile.isDirectory())
858         {
859             throw new IOException("Source '" + srcFile + "' exists but is a directory");
860         }
861         if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath()))
862         {
863             throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same");
864         }
865         if (destFile.getParentFile() != null && destFile.getParentFile().exists() == false)
866         {
867             if (destFile.getParentFile().mkdirs() == false)
868             {
869                 throw new IOException("Destination '" + destFile + "' directory cannot be created");
870             }
871         }
872         if (destFile.exists() && destFile.canWrite() == false)
873         {
874             throw new IOException("Destination '" + destFile + "' exists but is read-only");
875         }
876         doCopyFile(srcFile, destFile, preserveFileDate);
877     }
878 
879     /**
880      * Internal copy file method.
881      * 
882      * @param srcFile the validated source file, must not be <code>null</code>
883      * @param destFile the validated destination file, must not be <code>null</code>
884      * @param preserveFileDate whether to preserve the file date
885      * @throws IOException if an error occurs
886      */
887     private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException
888     {
889         if (destFile.exists() && destFile.isDirectory())
890         {
891             throw new IOException("Destination '" + destFile + "' exists but is a directory");
892         }
893 
894         FileChannel input = new FileInputStream(srcFile).getChannel();
895         try
896         {
897             FileChannel output = new FileOutputStream(destFile).getChannel();
898             try
899             {
900                 output.transferFrom(input, 0, input.size());
901             }
902             finally
903             {
904                 closeQuietly(output);
905             }
906         }
907         finally
908         {
909             closeQuietly(input);
910         }
911 
912         if (srcFile.length() != destFile.length())
913         {
914             throw new IOException("Failed to copy full contents from '" + srcFile + "' to '" + destFile + "'");
915         }
916         if (preserveFileDate)
917         {
918             destFile.setLastModified(srcFile.lastModified());
919         }
920     }
921 
922     /**
923      * Unconditionally close a <code>Channel</code>.
924      * <p>
925      * Equivalent to {@link Channel#close()}, except any exceptions will be ignored.
926      * This is typically used in finally blocks.
927      * 
928      * @param channel the Channel to close, may be null or already closed
929      */
930     public static void closeQuietly(Channel channel)
931     {
932         try
933         {
934             if (channel != null)
935             {
936                 channel.close();
937             }
938         }
939         catch (IOException ioe)
940         {
941             // ignore
942         }
943     }
944 
945     public static boolean isFile(URL url)
946     {
947         return "file".equals(url.getProtocol());
948     }
949 
950     /**
951      * Returns a file timestamp.
952      *
953      * @param url the file URL.
954      * @return the file's timestamp if the URL has the file protocol, otherwise.
955      *         returns -1.
956      */
957     public static long getFileTimeStamp(URL url)
958     {
959         long timeStamp = -1;
960 
961         if (isFile(url))
962         {
963             timeStamp = new File(url.getFile()).lastModified();
964         }
965 
966         return timeStamp;
967     }
968 }