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