View Javadoc

1   package org.apache.maven.it.util;
2   
3   /* ====================================================================
4    * The Apache Software License, Version 1.1
5    *
6    * Copyright (c) 2001 The Apache Software Foundation.  All rights
7    * reserved.
8    *
9    * Redistribution and use in source and binary forms, with or without
10   * modification, are permitted provided that the following conditions
11   * are met:
12   *
13   * 1. Redistributions of source code must retain the above copyright
14   *    notice, this list of conditions and the following disclaimer.
15   *
16   * 2. Redistributions in binary form must reproduce the above copyright
17   *    notice, this list of conditions and the following disclaimer in
18   *    the documentation and/or other materials provided with the
19   *    distribution.
20   *
21   * 3. The end-user documentation included with the redistribution,
22   *    if any, must include the following acknowledgment:
23   *       "This product includes software developed by the
24   *        Apache Software Foundation (http://www.apache.org/)."
25   *    Alternately, this acknowledgment may appear in the software itself,
26   *    if and wherever such third-party acknowledgments normally appear.
27   *
28   * 4. The names "Apache" and "Apache Software Foundation" and
29   *    "Apache Turbine" must not be used to endorse or promote products
30   *    derived from this software without prior written permission. For
31   *    written permission, please contact apache@apache.org.
32   *
33   * 5. Products derived from this software may not be called "Apache",
34   *    "Apache Turbine", nor may "Apache" appear in their name, without
35   *    prior written permission of the Apache Software Foundation.
36   *
37   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48   * SUCH DAMAGE.
49   * ====================================================================
50   *
51   * This software consists of voluntary contributions made by many
52   * individuals on behalf of the Apache Software Foundation.  For more
53   * information on the Apache Software Foundation, please see
54   * <http://www.apache.org/>.
55   *
56   */
57  
58  import java.io.BufferedReader;
59  import java.io.File;
60  import java.io.FileInputStream;
61  import java.io.FileOutputStream;
62  import java.io.FileReader;
63  import java.io.FileWriter;
64  import java.io.IOException;
65  import java.io.InputStream;
66  import java.io.InputStreamReader;
67  import java.io.OutputStreamWriter;
68  import java.io.Reader;
69  import java.io.Writer;
70  import java.net.URL;
71  import java.text.DecimalFormat;
72  import java.util.ArrayList;
73  import java.util.Arrays;
74  import java.util.Iterator;
75  import java.util.List;
76  import java.util.Random;
77  import java.util.Vector;
78  
79  /**
80   * This class provides basic facilities for manipulating files and file paths.
81   * <p/>
82   * <h3>Path-related methods</h3>
83   * <p/>
84   * <p>Methods exist to retrieve the components of a typical file path. For example
85   * <code>/www/hosted/mysite/index.html</code>, can be broken into:
86   * <ul>
87   * <li><code>/www/hosted/mysite/</code> -- retrievable through {@link #getPath}</li>
88   * <li><code>index.html</code> -- retrievable through {@link #removePath}</li>
89   * <li><code>/www/hosted/mysite/index</code> -- retrievable through {@link #removeExtension}</li>
90   * <li><code>html</code> -- retrievable through {@link #getExtension}</li>
91   * </ul>
92   * There are also methods to {@link #catPath concatenate two paths}, {@link #resolveFile resolve a
93   * path relative to a File} and {@link #normalize} a path.
94   * </p>
95   * <p/>
96   * <h3>File-related methods</h3>
97   * <p/>
98   * There are methods to  create a {@link #toFile File from a URL}, copy a
99   * {@link #copyFileToDirectory File to a directory},
100  * copy a {@link #copyFile File to another File},
101  * copy a {@link #copyURLToFile URL's contents to a File},
102  * as well as methods to {@link #deleteDirectory(File) delete} and {@link #cleanDirectory(File)
103  * clean} a directory.
104  * </p>
105  * <p/>
106  * Common {@link java.io.File} manipulation routines.
107  * <p/>
108  * Taken from the commons-utils repo.
109  * Also code from Alexandria's FileUtils.
110  * And from Avalon Excalibur's IO.
111  * And from Ant.
112  *
113  * @author <a href="mailto:burton@relativity.yi.org">Kevin A. Burton</A>
114  * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
115  * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
116  * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph.Reck</a>
117  * @author <a href="mailto:peter@apache.org">Peter Donald</a>
118  * @author <a href="mailto:jefft@apache.org">Jeff Turner</a>
119  * @version $Id: FileUtils.java 723852 2008-12-05 20:28:26Z bentmann $
120  */
121 public class FileUtils
122 {
123     /**
124      * The number of bytes in a kilobyte.
125      */
126     public static final int ONE_KB = 1024;
127 
128     /**
129      * The number of bytes in a megabyte.
130      */
131     public static final int ONE_MB = ONE_KB * ONE_KB;
132 
133     /**
134      * The number of bytes in a gigabyte.
135      */
136     public static final int ONE_GB = ONE_KB * ONE_MB;
137 
138     public static String FS = System.getProperty( "file.separator" );
139 
140     public static String[] getDefaultExcludes()
141     {
142         return DirectoryScanner.DEFAULTEXCLUDES;
143     }
144 
145     public static List getDefaultExcludesAsList()
146     {
147         return Arrays.asList( getDefaultExcludes() );
148     }
149 
150     /**
151      * Returns a human-readable version of the file size (original is in
152      * bytes).
153      *
154      * @param size The number of bytes.
155      * @return A human-readable display value (includes units).
156      */
157     public static String byteCountToDisplaySize( int size )
158     {
159         String displaySize;
160 
161         if ( size / ONE_GB > 0 )
162         {
163             displaySize = String.valueOf( size / ONE_GB ) + " GB";
164         }
165         else if ( size / ONE_MB > 0 )
166         {
167             displaySize = String.valueOf( size / ONE_MB ) + " MB";
168         }
169         else if ( size / ONE_KB > 0 )
170         {
171             displaySize = String.valueOf( size / ONE_KB ) + " KB";
172         }
173         else
174         {
175             displaySize = String.valueOf( size ) + " bytes";
176         }
177 
178         return displaySize;
179     }
180 
181     /**
182      * Returns the directory path portion of a file specification string.
183      * Matches the equally named unix command.
184      *
185      * @return The directory portion excluding the ending file separator.
186      */
187     public static String dirname( String filename )
188     {
189         int i = filename.lastIndexOf( File.separator );
190         return ( i >= 0 ? filename.substring( 0, i ) : "" );
191     }
192 
193     /**
194      * Returns the filename portion of a file specification string.
195      *
196      * @return The filename string with extension.
197      */
198     public static String filename( String filename )
199     {
200         int i = filename.lastIndexOf( File.separator );
201         return ( i >= 0 ? filename.substring( i + 1 ) : filename );
202     }
203 
204     /**
205      * Returns the filename portion of a file specification string.
206      * Matches the equally named unix command.
207      *
208      * @return The filename string without extension.
209      */
210     public static String basename( String filename )
211     {
212         return basename( filename, extension( filename ) );
213     }
214 
215     /**
216      * Returns the filename portion of a file specification string.
217      * Matches the equally named unix command.
218      */
219     public static String basename( String filename, String suffix )
220     {
221         int i = filename.lastIndexOf( File.separator ) + 1;
222         int lastDot = ( ( suffix != null ) && ( suffix.length() > 0 ) ) ? filename.lastIndexOf( suffix ) : -1;
223 
224         if ( lastDot >= 0 )
225         {
226             return filename.substring( i, lastDot );
227         }
228         else if ( i > 0 )
229         {
230             return filename.substring( i );
231         }
232         else
233         {
234             return filename; // else returns all (no path and no extension)
235         }
236     }
237 
238     /**
239      * Returns the extension portion of a file specification string.
240      * This everything after the last dot '.' in the filename (NOT including
241      * the dot).
242      */
243     public static String extension( String filename )
244     {
245         int lastDot = filename.lastIndexOf( '.' );
246 
247         if ( lastDot >= 0 )
248         {
249             return filename.substring( lastDot + 1 );
250         }
251         else
252         {
253             return "";
254         }
255     }
256 
257     /**
258      * Check if a file exits.
259      *
260      * @param fileName The name of the file to check.
261      * @return true if file exists.
262      */
263     public static boolean fileExists( String fileName )
264     {
265         File file = new File( fileName );
266         return file.exists();
267     }
268 
269     public static String fileRead( String file )
270         throws IOException
271     {
272         return fileRead( new File( file ) );
273     }
274 
275     public static String fileRead( File file )
276         throws IOException
277     {
278         return fileRead( file, null );
279     }
280 
281     public static String fileRead( File file, String encoding )
282         throws IOException
283     {
284         StringBuffer buf = new StringBuffer();
285 
286         Reader reader = null;
287 
288         try
289         {
290             if ( encoding != null && encoding.length() > 0 )
291             {
292                 reader = new InputStreamReader( new FileInputStream( file ), encoding );
293             }
294             else
295             {
296                 reader = new InputStreamReader( new FileInputStream( file ) );
297             }
298             int count;
299             char[] b = new char[512];
300             while ( ( count = reader.read( b ) ) > 0 ) // blocking read
301             {
302                 buf.append( b, 0, count );
303             }
304         }
305         finally
306         {
307             IOUtil.close( reader );
308         }
309 
310         return buf.toString();
311     }
312 
313     /**
314      * Appends data to a file. The file will be created if it does not exist.
315      *
316      * @param fileName The name of the file to write.
317      * @param data     The content to write to the file.
318      */
319     public static void fileAppend( String fileName, String data )
320         throws IOException
321     {
322         FileOutputStream out = null;
323         try
324         {
325             out = new FileOutputStream( fileName, true );
326             out.write( data.getBytes() );
327         }
328         finally
329         {
330             IOUtil.close( out );
331         }
332     }
333 
334     /**
335      * Writes data to a file. The file will be created if it does not exist.
336      *
337      * @param fileName The name of the file to write.
338      * @param data     The content to write to the file.
339      */
340     public static void fileWrite( String fileName, String data )
341         throws IOException
342     {
343         fileWrite( fileName, null, data );
344     }
345 
346     /**
347      * Writes data to a file. The file will be created if it does not exist.
348      * 
349      * @param fileName The name of the file to write.
350      * @param encoding The encoding of the file.
351      * @param data The content to write to the file.
352      */
353     public static void fileWrite( String fileName, String encoding, String data )
354         throws IOException
355     {
356         FileOutputStream out = null;
357         try
358         {
359             out = new FileOutputStream( fileName );
360             if ( encoding != null && encoding.length() > 0 )
361             {
362                 out.write( data.getBytes( encoding ) );
363             }
364             else
365             {
366                 out.write( data.getBytes() );
367             }
368         }
369         finally
370         {
371             IOUtil.close( out );
372         }
373     }
374 
375     /**
376      * Deletes a file.
377      *
378      * @param fileName The name of the file to delete.
379      */
380     public static void fileDelete( String fileName )
381     {
382         File file = new File( fileName );
383         file.delete();
384     }
385 
386     /**
387      * Waits for NFS to propagate a file creation, imposing a timeout.
388      *
389      * @param fileName The name of the file.
390      * @param seconds  The maximum time in seconds to wait.
391      * @return True if file exists.
392      */
393     public static boolean waitFor( String fileName, int seconds )
394     {
395         return waitFor( new File( fileName ), seconds );
396     }
397 
398     public static boolean waitFor( File file, int seconds )
399     {
400         int timeout = 0;
401         int tick = 0;
402         while ( !file.exists() )
403         {
404             if ( tick++ >= 10 )
405             {
406                 tick = 0;
407                 if ( timeout++ > seconds )
408                 {
409                     return false;
410                 }
411             }
412             try
413             {
414                 Thread.sleep( 100 );
415             }
416             catch ( InterruptedException ignore )
417             {
418             }
419         }
420         return true;
421     }
422 
423     /**
424      * Creates a file handle.
425      *
426      * @param fileName The name of the file.
427      * @return A <code>File</code> manager.
428      */
429     public static File getFile( String fileName )
430     {
431         return new File( fileName );
432     }
433 
434     /**
435      * Given a directory and an array of extensions return an array of compliant files.
436      * <p/>
437      * TODO Should an ignore list be passed in?
438      * TODO Should a recurse flag be passed in?
439      * <p/>
440      * The given extensions should be like "java" and not like ".java"
441      */
442     public static String[] getFilesFromExtension( String directory, String[] extensions )
443     {
444 
445         Vector files = new Vector();
446 
447         java.io.File currentDir = new java.io.File( directory );
448 
449         String[] unknownFiles = currentDir.list();
450 
451         if ( unknownFiles == null )
452         {
453             return new String[0];
454         }
455 
456         for ( int i = 0; i < unknownFiles.length; ++i )
457         {
458             String currentFileName = directory + System.getProperty( "file.separator" ) + unknownFiles[i];
459             java.io.File currentFile = new java.io.File( currentFileName );
460 
461             if ( currentFile.isDirectory() )
462             {
463 
464                 //ignore all CVS directories...
465                 if ( currentFile.getName().equals( "CVS" ) )
466                 {
467                     continue;
468                 }
469 
470                 //ok... transverse into this directory and get all the files... then combine
471                 //them with the current list.
472 
473                 String[] fetchFiles = getFilesFromExtension( currentFileName, extensions );
474                 files = blendFilesToVector( files, fetchFiles );
475 
476             }
477             else
478             {
479                 //ok... add the file
480 
481                 String add = currentFile.getAbsolutePath();
482                 if ( isValidFile( add, extensions ) )
483                 {
484                     files.addElement( add );
485 
486                 }
487 
488             }
489         }
490 
491         //ok... move the Vector into the files list...
492 
493         String[] foundFiles = new String[files.size()];
494         files.copyInto( foundFiles );
495 
496         return foundFiles;
497 
498     }
499 
500 
501     /**
502      * Private hepler method for getFilesFromExtension()
503      */
504     private static Vector blendFilesToVector( Vector v, String[] files )
505     {
506 
507         for ( int i = 0; i < files.length; ++i )
508         {
509             v.addElement( files[i] );
510         }
511 
512         return v;
513     }
514 
515     /**
516      * Checks to see if a file is of a particular type(s).
517      * Note that if the file does not have an extension, an empty string
518      * (&quot;&quot;) is matched for.
519      */
520     private static boolean isValidFile( String file, String[] extensions )
521     {
522 
523         String extension = extension( file );
524         if ( extension == null )
525         {
526             extension = "";
527         }
528 
529         //ok.. now that we have the "extension" go through the current know
530         //excepted extensions and determine if this one is OK.
531 
532         for ( int i = 0; i < extensions.length; ++i )
533         {
534             if ( extensions[i].equals( extension ) )
535             {
536                 return true;
537             }
538         }
539 
540         return false;
541 
542     }
543 
544     /**
545      * Simple way to make a directory
546      */
547     public static void mkdir( String dir )
548     {
549         File file = new File( dir );
550         if ( !file.exists() )
551         {
552             file.mkdirs();
553         }
554     }
555 
556     /**
557      * Compare the contents of two files to determine if they are equal or not.
558      *
559      * @param file1 the first file
560      * @param file2 the second file
561      * @return true if the content of the files are equal or they both don't exist, false otherwise
562      */
563     public static boolean contentEquals( final File file1, final File file2 )
564         throws IOException
565     {
566         final boolean file1Exists = file1.exists();
567         if ( file1Exists != file2.exists() )
568         {
569             return false;
570         }
571 
572         if ( !file1Exists )
573         {
574             // two not existing files are equal
575             return true;
576         }
577 
578         if ( file1.isDirectory() || file2.isDirectory() )
579         {
580             // don't want to compare directory contents
581             return false;
582         }
583 
584         InputStream input1 = null;
585         InputStream input2 = null;
586         try
587         {
588             input1 = new FileInputStream( file1 );
589             input2 = new FileInputStream( file2 );
590             return IOUtil.contentEquals( input1, input2 );
591 
592         }
593         finally
594         {
595             IOUtil.close( input1 );
596             IOUtil.close( input2 );
597         }
598     }
599 
600     /**
601      * Convert from a <code>URL</code> to a <code>File</code>.
602      *
603      * @param url File URL.
604      * @return The equivalent <code>File</code> object, or <code>null</code> if the URL's protocol
605      *         is not <code>file</code>
606      */
607     public static File toFile( final URL url )
608     {
609         if ( url.getProtocol().equals( "file" ) == false )
610         {
611             return null;
612         }
613         else
614         {
615             final String filename = url.getFile().replace( '/', File.separatorChar );
616             return new File( filename );
617         }
618     }
619 
620     /**
621      * Convert the array of Files into a list of URLs.
622      *
623      * @param files the array of files
624      * @return the array of URLs
625      * @throws IOException if an error occurs
626      */
627     public static URL[] toURLs( final File[] files )
628         throws IOException
629     {
630         final URL[] urls = new URL[files.length];
631 
632         for ( int i = 0; i < urls.length; i++ )
633         {
634             urls[i] = files[i].toURL();
635         }
636 
637         return urls;
638     }
639 
640     /**
641      * Remove extension from filename.
642      * ie
643      * <pre>
644      * foo.txt    --> foo
645      * a\b\c.jpg --> a\b\c
646      * a\b\c     --> a\b\c
647      * </pre>
648      *
649      * @param filename the filename
650      * @return the filename minus extension
651      */
652     public static String removeExtension( final String filename )
653     {
654         final int index = filename.lastIndexOf( '.' );
655 
656         if ( -1 == index )
657         {
658             return filename;
659         }
660         else
661         {
662             return filename.substring( 0, index );
663         }
664     }
665 
666     /**
667      * Get extension from filename.
668      * ie
669      * <pre>
670      * foo.txt    --> "txt"
671      * a\b\c.jpg --> "jpg"
672      * a\b\c     --> ""
673      * </pre>
674      *
675      * @param filename the filename
676      * @return the extension of filename or "" if none
677      */
678     public static String getExtension( final String filename )
679     {
680         final int index = filename.lastIndexOf( '.' );
681 
682         if ( -1 == index )
683         {
684             return "";
685         }
686         else
687         {
688             return filename.substring( index + 1 );
689         }
690     }
691 
692     /**
693      * Remove path from filename. Equivalent to the unix command <code>basename</code>
694      * ie.
695      * <pre>
696      * a/b/c.txt --> c.txt
697      * a.txt     --> a.txt
698      * </pre>
699      *
700      * @param filepath the filepath
701      * @return the filename minus path
702      */
703     public static String removePath( final String filepath )
704     {
705         return removePath( filepath, File.separatorChar );
706     }
707 
708     /**
709      * Remove path from filename.
710      * ie.
711      * <pre>
712      * a/b/c.txt --> c.txt
713      * a.txt     --> a.txt
714      * </pre>
715      *
716      * @param filepath the filepath
717      * @return the filename minus path
718      */
719     public static String removePath( final String filepath, final char fileSeparatorChar )
720     {
721         final int index = filepath.lastIndexOf( fileSeparatorChar );
722 
723         if ( -1 == index )
724         {
725             return filepath;
726         }
727         else
728         {
729             return filepath.substring( index + 1 );
730         }
731     }
732 
733     /**
734      * Get path from filename. Roughly equivalent to the unix command <code>dirname</code>.
735      * ie.
736      * <pre>
737      * a/b/c.txt --> a/b
738      * a.txt     --> ""
739      * </pre>
740      *
741      * @param filepath the filepath
742      * @return the filename minus path
743      */
744     public static String getPath( final String filepath )
745     {
746         return getPath( filepath, File.separatorChar );
747     }
748 
749     /**
750      * Get path from filename.
751      * ie.
752      * <pre>
753      * a/b/c.txt --> a/b
754      * a.txt     --> ""
755      * </pre>
756      *
757      * @param filepath the filepath
758      * @return the filename minus path
759      */
760     public static String getPath( final String filepath, final char fileSeparatorChar )
761     {
762         final int index = filepath.lastIndexOf( fileSeparatorChar );
763         if ( -1 == index )
764         {
765             return "";
766         }
767         else
768         {
769             return filepath.substring( 0, index );
770         }
771     }
772 
773     /**
774      * Copy file from source to destination. If <code>destinationDirectory</code> does not exist, it
775      * (and any parent directories) will be created. If a file <code>source</code> in
776      * <code>destinationDirectory</code> exists, it will be overwritten.
777      *
778      * @param source               An existing <code>File</code> to copy.
779      * @param destinationDirectory A directory to copy <code>source</code> into.
780      * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file.
781      * @throws IllegalArgumentException      if <code>destinationDirectory</code> isn't a directory.
782      * @throws IOException                   if <code>source</code> does not exist, the file in
783      *                                       <code>destinationDirectory</code> cannot be written to, or an IO error occurs during copying.
784      */
785     public static void copyFileToDirectory( final String source, final String destinationDirectory )
786         throws IOException
787     {
788         copyFileToDirectory( new File( source ), new File( destinationDirectory ) );
789     }
790 
791     /**
792      * Copy file from source to destination only if source is newer than the target file.
793      * If <code>destinationDirectory</code> does not exist, it
794      * (and any parent directories) will be created. If a file <code>source</code> in
795      * <code>destinationDirectory</code> exists, it will be overwritten.
796      *
797      * @param source               An existing <code>File</code> to copy.
798      * @param destinationDirectory A directory to copy <code>source</code> into.
799      * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file.
800      * @throws IllegalArgumentException      if <code>destinationDirectory</code> isn't a directory.
801      * @throws IOException                   if <code>source</code> does not exist, the file in
802      *                                       <code>destinationDirectory</code> cannot be written to, or an IO error occurs during copying.
803      */
804     public static void copyFileToDirectoryIfModified( final String source, final String destinationDirectory )
805         throws IOException
806     {
807         copyFileToDirectoryIfModified( new File( source ), new File( destinationDirectory ) );
808     }
809 
810     /**
811      * Copy file from source to destination. If <code>destinationDirectory</code> does not exist, it
812      * (and any parent directories) will be created. If a file <code>source</code> in
813      * <code>destinationDirectory</code> exists, it will be overwritten.
814      *
815      * @param source               An existing <code>File</code> to copy.
816      * @param destinationDirectory A directory to copy <code>source</code> into.
817      * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file.
818      * @throws IllegalArgumentException      if <code>destinationDirectory</code> isn't a directory.
819      * @throws IOException                   if <code>source</code> does not exist, the file in
820      *                                       <code>destinationDirectory</code> cannot be written to, or an IO error occurs during copying.
821      */
822     public static void copyFileToDirectory( final File source, final File destinationDirectory )
823         throws IOException
824     {
825         if ( destinationDirectory.exists() && !destinationDirectory.isDirectory() )
826         {
827             throw new IllegalArgumentException( "Destination is not a directory" );
828         }
829 
830         copyFile( source, new File( destinationDirectory, source.getName() ) );
831     }
832 
833     /**
834      * Copy file from source to destination only if source is newer than the target file.
835      * If <code>destinationDirectory</code> does not exist, it
836      * (and any parent directories) will be created. If a file <code>source</code> in
837      * <code>destinationDirectory</code> exists, it will be overwritten.
838      *
839      * @param source               An existing <code>File</code> to copy.
840      * @param destinationDirectory A directory to copy <code>source</code> into.
841      * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file.
842      * @throws IllegalArgumentException      if <code>destinationDirectory</code> isn't a directory.
843      * @throws IOException                   if <code>source</code> does not exist, the file in
844      *                                       <code>destinationDirectory</code> cannot be written to, or an IO error occurs during copying.
845      */
846     public static void copyFileToDirectoryIfModified( final File source, final File destinationDirectory )
847         throws IOException
848     {
849         if ( destinationDirectory.exists() && !destinationDirectory.isDirectory() )
850         {
851             throw new IllegalArgumentException( "Destination is not a directory" );
852         }
853 
854         copyFileIfModified( source, new File( destinationDirectory, source.getName() ) );
855     }
856 
857 
858     /**
859      * Copy file from source to destination. The directories up to <code>destination</code> will be
860      * created if they don't already exist. <code>destination</code> will be overwritten if it
861      * already exists.
862      *
863      * @param source      An existing non-directory <code>File</code> to copy bytes from.
864      * @param destination A non-directory <code>File</code> to write bytes to (possibly
865      *                    overwriting).
866      * @throws IOException                   if <code>source</code> does not exist, <code>destination</code> cannot be
867      *                                       written to, or an IO error occurs during copying.
868      * @throws java.io.FileNotFoundException if <code>destination</code> is a directory
869      *                                       (use {@link #copyFileToDirectory}).
870      */
871     public static void copyFile( final File source, final File destination )
872         throws IOException
873     {
874         //check source exists
875         if ( !source.exists() )
876         {
877             final String message = "File " + source + " does not exist";
878             throw new IOException( message );
879         }
880 
881         //does destinations directory exist ?
882         if ( destination.getParentFile() != null && !destination.getParentFile().exists() )
883         {
884             destination.getParentFile().mkdirs();
885         }
886 
887         //make sure we can write to destination
888         if ( destination.exists() && !destination.canWrite() )
889         {
890             final String message = "Unable to open file " + destination + " for writing.";
891             throw new IOException( message );
892         }
893 
894         FileInputStream input = null;
895         FileOutputStream output = null;
896         try
897         {
898             input = new FileInputStream( source );
899             output = new FileOutputStream( destination );
900             IOUtil.copy( input, output );
901         }
902         finally
903         {
904             IOUtil.close( input );
905             IOUtil.close( output );
906         }
907 
908         if ( source.length() != destination.length() )
909         {
910             final String message = "Failed to copy full contents from " + source + " to " + destination;
911             throw new IOException( message );
912         }
913     }
914 
915     /**
916      * Copy file from source to destination only if source timestamp is later than the destination timestamp.
917      * The directories up to <code>destination</code> will be created if they don't already exist.
918      * <code>destination</code> will be overwritten if it already exists.
919      *
920      * @param source      An existing non-directory <code>File</code> to copy bytes from.
921      * @param destination A non-directory <code>File</code> to write bytes to (possibly
922      *                    overwriting).
923      * @throws IOException                   if <code>source</code> does not exist, <code>destination</code> cannot be
924      *                                       written to, or an IO error occurs during copying.
925      * @throws java.io.FileNotFoundException if <code>destination</code> is a directory
926      *                                       (use {@link #copyFileToDirectory}).
927      */
928     public static boolean copyFileIfModified( final File source, final File destination )
929         throws IOException
930     {
931         if ( destination.lastModified() < source.lastModified() )
932         {
933             copyFile( source, destination );
934 
935             return true;
936         }
937 
938         return false;
939     }
940 
941     /**
942      * Copies bytes from the URL <code>source</code> to a file <code>destination</code>.
943      * The directories up to <code>destination</code> will be created if they don't already exist.
944      * <code>destination</code> will be overwritten if it already exists.
945      *
946      * @param source      A <code>URL</code> to copy bytes from.
947      * @param destination A non-directory <code>File</code> to write bytes to (possibly
948      *                    overwriting).
949      * @throws IOException if
950      *                     <ul>
951      *                     <li><code>source</code> URL cannot be opened</li>
952      *                     <li><code>destination</code> cannot be written to</li>
953      *                     <li>an IO error occurs during copying</li>
954      *                     </ul>
955      */
956     public static void copyURLToFile( final URL source, final File destination )
957         throws IOException
958     {
959         //does destination directory exist ?
960         if ( destination.getParentFile() != null && !destination.getParentFile().exists() )
961         {
962             destination.getParentFile().mkdirs();
963         }
964 
965         //make sure we can write to destination
966         if ( destination.exists() && !destination.canWrite() )
967         {
968             final String message = "Unable to open file " + destination + " for writing.";
969             throw new IOException( message );
970         }
971 
972         InputStream input = null;
973         FileOutputStream output = null;
974         try
975         {
976             input = source.openStream();
977             output = new FileOutputStream( destination );
978             IOUtil.copy( input, output );
979         }
980         finally
981         {
982             IOUtil.close( input );
983             IOUtil.close( output );
984         }
985     }
986 
987     /**
988      * Normalize a path.
989      * Eliminates "/../" and "/./" in a string. Returns <code>null</code> if the ..'s went past the
990      * root.
991      * Eg:
992      * <pre>
993      * /foo//               -->     /foo/
994      * /foo/./              -->     /foo/
995      * /foo/../bar          -->     /bar
996      * /foo/../bar/         -->     /bar/
997      * /foo/../bar/../baz   -->     /baz
998      * //foo//./bar         -->     /foo/bar
999      * /../                 -->     null
1000      * </pre>
1001      *
1002      * @param path the path to normalize
1003      * @return the normalized String, or <code>null</code> if too many ..'s.
1004      */
1005     public static String normalize( final String path )
1006     {
1007         String normalized = path;
1008         // Resolve occurrences of "//" in the normalized path
1009         while ( true )
1010         {
1011             int index = normalized.indexOf( "//" );
1012             if ( index < 0 )
1013             {
1014                 break;
1015             }
1016             normalized = normalized.substring( 0, index ) + normalized.substring( index + 1 );
1017         }
1018 
1019         // Resolve occurrences of "/./" in the normalized path
1020         while ( true )
1021         {
1022             int index = normalized.indexOf( "/./" );
1023             if ( index < 0 )
1024             {
1025                 break;
1026             }
1027             normalized = normalized.substring( 0, index ) + normalized.substring( index + 2 );
1028         }
1029 
1030         // Resolve occurrences of "/../" in the normalized path
1031         while ( true )
1032         {
1033             int index = normalized.indexOf( "/../" );
1034             if ( index < 0 )
1035             {
1036                 break;
1037             }
1038             if ( index == 0 )
1039             {
1040                 return null;  // Trying to go outside our context
1041             }
1042             int index2 = normalized.lastIndexOf( '/', index - 1 );
1043             normalized = normalized.substring( 0, index2 ) + normalized.substring( index + 3 );
1044         }
1045 
1046         // Return the normalized path that we have completed
1047         return normalized;
1048     }
1049 
1050     /**
1051      * Will concatenate 2 paths.  Paths with <code>..</code> will be
1052      * properly handled.
1053      * <p>Eg.,<br />
1054      * <code>/a/b/c</code> + <code>d</code> = <code>/a/b/d</code><br />
1055      * <code>/a/b/c</code> + <code>../d</code> = <code>/a/d</code><br />
1056      * </p>
1057      * <p/>
1058      * Thieved from Tomcat sources...
1059      *
1060      * @return The concatenated paths, or null if error occurs
1061      */
1062     public static String catPath( final String lookupPath, final String path )
1063     {
1064         // Cut off the last slash and everything beyond
1065         int index = lookupPath.lastIndexOf( "/" );
1066         String lookup = lookupPath.substring( 0, index );
1067         String pth = path;
1068 
1069         // Deal with .. by chopping dirs off the lookup path
1070         while ( pth.startsWith( "../" ) )
1071         {
1072             if ( lookup.length() > 0 )
1073             {
1074                 index = lookup.lastIndexOf( "/" );
1075                 lookup = lookup.substring( 0, index );
1076             }
1077             else
1078             {
1079                 // More ..'s than dirs, return null
1080                 return null;
1081             }
1082 
1083             index = pth.indexOf( "../" ) + 3;
1084             pth = pth.substring( index );
1085         }
1086 
1087         return new StringBuffer( lookup ).append( "/" ).append( pth ).toString();
1088     }
1089 
1090     /**
1091      * Resolve a file <code>filename</code> to it's canonical form. If <code>filename</code> is
1092      * relative (doesn't start with <code>/</code>), it will be resolved relative to
1093      * <code>baseFile</code>, otherwise it is treated as a normal root-relative path.
1094      *
1095      * @param baseFile Where to resolve <code>filename</code> from, if <code>filename</code> is
1096      *                 relative.
1097      * @param filename Absolute or relative file path to resolve.
1098      * @return The canonical <code>File</code> of <code>filename</code>.
1099      */
1100     public static File resolveFile( final File baseFile, String filename )
1101     {
1102         String filenm = filename;
1103         if ( '/' != File.separatorChar )
1104         {
1105             filenm = filename.replace( '/', File.separatorChar );
1106         }
1107 
1108         if ( '\\' != File.separatorChar )
1109         {
1110             filenm = filename.replace( '\\', File.separatorChar );
1111         }
1112 
1113         // deal with absolute files
1114         if ( filenm.startsWith( File.separator ) || ( Os.isFamily( "windows" ) && filenm.indexOf( ":" ) > 0 ) )
1115         {
1116             File file = new File( filenm );
1117 
1118             try
1119             {
1120                 file = file.getCanonicalFile();
1121             }
1122             catch ( final IOException ioe )
1123             {
1124             }
1125 
1126             return file;
1127         }
1128         // FIXME: I'm almost certain this // removal is unnecessary, as getAbsoluteFile() strips
1129         // them. However, I'm not sure about this UNC stuff. (JT)
1130         final char[] chars = filename.toCharArray();
1131         final StringBuffer sb = new StringBuffer();
1132 
1133         //remove duplicate file separators in succession - except
1134         //on win32 at start of filename as UNC filenames can
1135         //be \\AComputer\AShare\myfile.txt
1136         int start = 0;
1137         if ( '\\' == File.separatorChar )
1138         {
1139             sb.append( filenm.charAt( 0 ) );
1140             start++;
1141         }
1142 
1143         for ( int i = start; i < chars.length; i++ )
1144         {
1145             final boolean doubleSeparator = File.separatorChar == chars[i] && File.separatorChar == chars[i - 1];
1146 
1147             if ( !doubleSeparator )
1148             {
1149                 sb.append( chars[i] );
1150             }
1151         }
1152 
1153         filenm = sb.toString();
1154 
1155         //must be relative
1156         File file = ( new File( baseFile, filenm ) ).getAbsoluteFile();
1157 
1158         try
1159         {
1160             file = file.getCanonicalFile();
1161         }
1162         catch ( final IOException ioe )
1163         {
1164         }
1165 
1166         return file;
1167     }
1168 
1169     /**
1170      * Delete a file. If file is directory delete it and all sub-directories.
1171      */
1172     public static void forceDelete( final String file )
1173         throws IOException
1174     {
1175         forceDelete( new File( file ) );
1176     }
1177 
1178     /**
1179      * Delete a file. If file is directory delete it and all sub-directories.
1180      */
1181     public static void forceDelete( final File file )
1182         throws IOException
1183     {
1184         if ( file.isDirectory() )
1185         {
1186             deleteDirectory( file );
1187         }
1188         else
1189         {
1190             /*
1191              * NOTE: Always try to delete the file even if it appears to be non-existent. This will ensure that a
1192              * symlink whose target does not exist is deleted, too.
1193              */
1194             boolean filePresent = file.getCanonicalFile().exists();
1195             if ( !deleteFile( file ) && filePresent )
1196             {
1197                 final String message = "File " + file + " unable to be deleted.";
1198                 throw new IOException( message );
1199             }
1200         }
1201     }
1202 
1203     /**
1204      * Accommodate Windows bug encountered in both Sun and IBM JDKs.
1205      * Others possible. If the delete does not work, call System.gc(),
1206      * wait a little and try again.
1207      */
1208     private static boolean deleteFile( File file )
1209         throws IOException
1210     {
1211         if ( file.isDirectory() )
1212         {
1213             throw new IOException( "File " + file + " isn't a file." );
1214         }
1215 
1216         if ( !file.delete() )
1217         {
1218             if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
1219             {
1220                 System.gc();
1221             }
1222 
1223             try
1224             {
1225                 Thread.sleep( 10 );
1226                 return file.delete();
1227             }
1228             catch ( InterruptedException ex )
1229             {
1230                 return file.delete();
1231             }
1232         }
1233 
1234         return true;
1235     }
1236 
1237     /**
1238      * Schedule a file to be deleted when JVM exits.
1239      * If file is directory delete it and all sub-directories.
1240      */
1241     public static void forceDeleteOnExit( final File file )
1242         throws IOException
1243     {
1244         if ( !file.exists() )
1245         {
1246             return;
1247         }
1248 
1249         if ( file.isDirectory() )
1250         {
1251             deleteDirectoryOnExit( file );
1252         }
1253         else
1254         {
1255             file.deleteOnExit();
1256         }
1257     }
1258 
1259     /**
1260      * Recursively schedule directory for deletion on JVM exit.
1261      */
1262     private static void deleteDirectoryOnExit( final File directory )
1263         throws IOException
1264     {
1265         if ( !directory.exists() )
1266         {
1267             return;
1268         }
1269 
1270         cleanDirectoryOnExit( directory );
1271         directory.deleteOnExit();
1272     }
1273 
1274     /**
1275      * Clean a directory without deleting it.
1276      */
1277     private static void cleanDirectoryOnExit( final File directory )
1278         throws IOException
1279     {
1280         if ( !directory.exists() )
1281         {
1282             final String message = directory + " does not exist";
1283             throw new IllegalArgumentException( message );
1284         }
1285 
1286         if ( !directory.isDirectory() )
1287         {
1288             final String message = directory + " is not a directory";
1289             throw new IllegalArgumentException( message );
1290         }
1291 
1292         IOException exception = null;
1293 
1294         final File[] files = directory.listFiles();
1295         for ( int i = 0; i < files.length; i++ )
1296         {
1297             final File file = files[i];
1298             try
1299             {
1300                 forceDeleteOnExit( file );
1301             }
1302             catch ( final IOException ioe )
1303             {
1304                 exception = ioe;
1305             }
1306         }
1307 
1308         if ( null != exception )
1309         {
1310             throw exception;
1311         }
1312     }
1313 
1314 
1315     /**
1316      * Make a directory. If there already exists a file with specified name or
1317      * the directory is unable to be created then an exception is thrown.
1318      */
1319     public static void forceMkdir( final File file )
1320         throws IOException
1321     {
1322         if ( file.exists() )
1323         {
1324             if ( file.isFile() )
1325             {
1326                 final String message =
1327                     "File " + file + " exists and is " + "not a directory. Unable to create directory.";
1328                 throw new IOException( message );
1329             }
1330         }
1331         else
1332         {
1333             if ( false == file.mkdirs() )
1334             {
1335                 final String message = "Unable to create directory " + file;
1336                 throw new IOException( message );
1337             }
1338         }
1339     }
1340 
1341     /**
1342      * Recursively delete a directory.
1343      */
1344     public static void deleteDirectory( final String directory )
1345         throws IOException
1346     {
1347         deleteDirectory( new File( directory ) );
1348     }
1349 
1350     /**
1351      * Recursively delete a directory.
1352      */
1353     public static void deleteDirectory( final File directory )
1354         throws IOException
1355     {
1356         if ( !directory.exists() )
1357         {
1358             return;
1359         }
1360 
1361         cleanDirectory( directory );
1362         if ( !directory.delete() )
1363         {
1364             final String message = "Directory " + directory + " unable to be deleted.";
1365             throw new IOException( message );
1366         }
1367     }
1368 
1369     /**
1370      * Clean a directory without deleting it.
1371      */
1372     public static void cleanDirectory( final String directory )
1373         throws IOException
1374     {
1375         cleanDirectory( new File( directory ) );
1376     }
1377 
1378     /**
1379      * Clean a directory without deleting it.
1380      */
1381     public static void cleanDirectory( final File directory )
1382         throws IOException
1383     {
1384         if ( !directory.exists() )
1385         {
1386             final String message = directory + " does not exist";
1387             throw new IllegalArgumentException( message );
1388         }
1389 
1390         if ( !directory.isDirectory() )
1391         {
1392             final String message = directory + " is not a directory";
1393             throw new IllegalArgumentException( message );
1394         }
1395 
1396         IOException exception = null;
1397 
1398         final File[] files = directory.listFiles();
1399 
1400         if ( files == null )
1401         {
1402             return;
1403         }
1404 
1405         for ( int i = 0; i < files.length; i++ )
1406         {
1407             final File file = files[i];
1408             try
1409             {
1410                 forceDelete( file );
1411             }
1412             catch ( final IOException ioe )
1413             {
1414                 exception = ioe;
1415             }
1416         }
1417 
1418         if ( null != exception )
1419         {
1420             throw exception;
1421         }
1422     }
1423 
1424     /**
1425      * Recursively count size of a directory.
1426      *
1427      * @return size of directory in bytes.
1428      */
1429     public static long sizeOfDirectory( final String directory )
1430     {
1431         return sizeOfDirectory( new File( directory ) );
1432     }
1433 
1434     /**
1435      * Recursively count size of a directory.
1436      *
1437      * @return size of directory in bytes.
1438      */
1439     public static long sizeOfDirectory( final File directory )
1440     {
1441         if ( !directory.exists() )
1442         {
1443             final String message = directory + " does not exist";
1444             throw new IllegalArgumentException( message );
1445         }
1446 
1447         if ( !directory.isDirectory() )
1448         {
1449             final String message = directory + " is not a directory";
1450             throw new IllegalArgumentException( message );
1451         }
1452 
1453         long size = 0;
1454 
1455         final File[] files = directory.listFiles();
1456         for ( int i = 0; i < files.length; i++ )
1457         {
1458             final File file = files[i];
1459 
1460             if ( file.isDirectory() )
1461             {
1462                 size += sizeOfDirectory( file );
1463             }
1464             else
1465             {
1466                 size += file.length();
1467             }
1468         }
1469 
1470         return size;
1471     }
1472 
1473     /**
1474      * Return the files contained in the directory, using inclusion and exclusion Ant patterns,
1475      * including the directory name in each of the files
1476      *
1477      * @param directory the directory to scan
1478      * @param includes  the includes pattern, comma separated
1479      * @param excludes  the excludes pattern, comma separated
1480      * @return a list of File objects
1481      * @throws IOException
1482      */
1483     public static List getFiles( File directory, String includes, String excludes )
1484         throws IOException
1485     {
1486         return getFiles( directory, includes, excludes, true );
1487     }
1488 
1489     /**
1490      * Return the files contained in the directory, using inclusion and exclusion Ant patterns
1491      *
1492      * @param directory      the directory to scan
1493      * @param includes       the includes pattern, comma separated
1494      * @param excludes       the excludes pattern, comma separated
1495      * @param includeBasedir true to include the base dir in each file
1496      * @return a list of File objects
1497      * @throws IOException
1498      */
1499     public static List getFiles( File directory, String includes, String excludes, boolean includeBasedir )
1500         throws IOException
1501     {
1502         List fileNames = getFileNames( directory, includes, excludes, includeBasedir );
1503 
1504         List files = new ArrayList();
1505 
1506         for ( Iterator i = fileNames.iterator(); i.hasNext(); )
1507         {
1508             files.add( new File( (String) i.next() ) );
1509         }
1510 
1511         return files;
1512     }
1513 
1514     /**
1515      * Return a list of files as String depending options.
1516      * This method use case sensitive file name.
1517      *
1518      * @param directory      the directory to scan
1519      * @param includes       the includes pattern, comma separated
1520      * @param excludes       the excludes pattern, comma separated
1521      * @param includeBasedir true to include the base dir in each String of file
1522      * @return a list of files as String
1523      * @throws IOException
1524      */
1525     public static List getFileNames( File directory, String includes, String excludes, boolean includeBasedir )
1526         throws IOException
1527     {
1528         return getFileNames( directory, includes, excludes, includeBasedir, true );
1529     }
1530 
1531     /**
1532      * Return a list of files as String depending options.
1533      *
1534      * @param directory       the directory to scan
1535      * @param includes        the includes pattern, comma separated
1536      * @param excludes        the excludes pattern, comma separated
1537      * @param includeBasedir  true to include the base dir in each String of file
1538      * @param isCaseSensitive true if case sensitive
1539      * @return a list of files as String
1540      * @throws IOException
1541      */
1542     public static List getFileNames( File directory, String includes, String excludes, boolean includeBasedir,
1543                                      boolean isCaseSensitive )
1544         throws IOException
1545     {
1546         return getFileAndDirectoryNames( directory, includes, excludes, includeBasedir, isCaseSensitive, true, false );
1547     }
1548 
1549     /**
1550      * Return a list of directories as String depending options.
1551      * This method use case sensitive file name.
1552      *
1553      * @param directory      the directory to scan
1554      * @param includes       the includes pattern, comma separated
1555      * @param excludes       the excludes pattern, comma separated
1556      * @param includeBasedir true to include the base dir in each String of file
1557      * @return a list of directories as String
1558      * @throws IOException
1559      */
1560     public static List getDirectoryNames( File directory, String includes, String excludes, boolean includeBasedir )
1561         throws IOException
1562     {
1563         return getDirectoryNames( directory, includes, excludes, includeBasedir, true );
1564     }
1565 
1566     /**
1567      * Return a list of directories as String depending options.
1568      *
1569      * @param directory       the directory to scan
1570      * @param includes        the includes pattern, comma separated
1571      * @param excludes        the excludes pattern, comma separated
1572      * @param includeBasedir  true to include the base dir in each String of file
1573      * @param isCaseSensitive true if case sensitive
1574      * @return a list of directories as String
1575      * @throws IOException
1576      */
1577     public static List getDirectoryNames( File directory, String includes, String excludes, boolean includeBasedir,
1578                                           boolean isCaseSensitive )
1579         throws IOException
1580     {
1581         return getFileAndDirectoryNames( directory, includes, excludes, includeBasedir, isCaseSensitive, false, true );
1582     }
1583 
1584     /**
1585      * Return a list of files as String depending options.
1586      *
1587      * @param directory       the directory to scan
1588      * @param includes        the includes pattern, comma separated
1589      * @param excludes        the excludes pattern, comma separated
1590      * @param includeBasedir  true to include the base dir in each String of file
1591      * @param isCaseSensitive true if case sensitive
1592      * @param getFiles        true if get files
1593      * @param getDirectories  true if get directories
1594      * @return a list of files as String
1595      * @throws IOException
1596      */
1597     public static List getFileAndDirectoryNames( File directory, String includes, String excludes,
1598                                                  boolean includeBasedir, boolean isCaseSensitive, boolean getFiles,
1599                                                  boolean getDirectories )
1600         throws IOException
1601     {
1602         DirectoryScanner scanner = new DirectoryScanner();
1603 
1604         scanner.setBasedir( directory );
1605 
1606         if ( includes != null )
1607         {
1608             scanner.setIncludes( StringUtils.split( includes, "," ) );
1609         }
1610 
1611         if ( excludes != null )
1612         {
1613             scanner.setExcludes( StringUtils.split( excludes, "," ) );
1614         }
1615 
1616         scanner.setCaseSensitive( isCaseSensitive );
1617 
1618         scanner.scan();
1619 
1620         List list = new ArrayList();
1621 
1622         if ( getFiles )
1623         {
1624             String[] files = scanner.getIncludedFiles();
1625 
1626             for ( int i = 0; i < files.length; i++ )
1627             {
1628                 if ( includeBasedir )
1629                 {
1630                     list.add( directory + FileUtils.FS + files[i] );
1631                 }
1632                 else
1633                 {
1634                     list.add( files[i] );
1635                 }
1636             }
1637         }
1638 
1639         if ( getDirectories )
1640         {
1641             String[] directories = scanner.getIncludedDirectories();
1642 
1643             for ( int i = 0; i < directories.length; i++ )
1644             {
1645                 if ( includeBasedir )
1646                 {
1647                     list.add( directory + FileUtils.FS + directories[i] );
1648                 }
1649                 else
1650                 {
1651                     list.add( directories[i] );
1652                 }
1653             }
1654         }
1655 
1656         return list;
1657     }
1658 
1659     public static void copyDirectory( File sourceDirectory, File destinationDirectory )
1660         throws IOException
1661     {
1662         copyDirectory( sourceDirectory, destinationDirectory, "**", null );
1663     }
1664 
1665     public static void copyDirectory( File sourceDirectory, File destinationDirectory, String includes,
1666                                       String excludes )
1667         throws IOException
1668     {
1669         if ( !sourceDirectory.exists() )
1670         {
1671             return;
1672         }
1673 
1674         List files = getFiles( sourceDirectory, includes, excludes );
1675 
1676         for ( Iterator i = files.iterator(); i.hasNext(); )
1677         {
1678             File file = (File) i.next();
1679 
1680             copyFileToDirectory( file, destinationDirectory );
1681         }
1682     }
1683 
1684     /**
1685      * Copies a entire directory structure.
1686      * <p/>
1687      * Note:
1688      * <ul>
1689      * <li>It will include empty directories.
1690      * <li>The <code>sourceDirectory</code> must exists.
1691      * </ul>
1692      *
1693      * @param sourceDirectory
1694      * @param destinationDirectory
1695      * @throws IOException
1696      */
1697     public static void copyDirectoryStructure( File sourceDirectory, File destinationDirectory )
1698         throws IOException
1699     {
1700         copyDirectoryStructure( sourceDirectory, destinationDirectory, destinationDirectory, false );
1701     }
1702 
1703     /**
1704      * Copies an entire directory structure but only source files with timestamp later than the destinations'.
1705      * <p/>
1706      * Note:
1707      * <ul>
1708      * <li>It will include empty directories.
1709      * <li>The <code>sourceDirectory</code> must exists.
1710      * </ul>
1711      *
1712      * @param sourceDirectory
1713      * @param destinationDirectory
1714      * @throws IOException
1715      */
1716     public static void copyDirectoryStructureIfModified( File sourceDirectory, File destinationDirectory )
1717         throws IOException
1718     {
1719         copyDirectoryStructure( sourceDirectory, destinationDirectory, destinationDirectory, true );
1720     }
1721 
1722     private static void copyDirectoryStructure( File sourceDirectory, File destinationDirectory,
1723                                                 File rootDestinationDirectory, boolean onlyModifiedFiles )
1724         throws IOException
1725     {
1726         if ( sourceDirectory == null )
1727         {
1728             throw new IOException( "source directory can't be null." );
1729         }
1730 
1731         if ( destinationDirectory == null )
1732         {
1733             throw new IOException( "destination directory can't be null." );
1734         }
1735 
1736         if ( sourceDirectory.equals( destinationDirectory ) )
1737         {
1738             throw new IOException( "source and destination are the same directory." );
1739         }
1740 
1741         if ( !sourceDirectory.exists() )
1742         {
1743             throw new IOException( "Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ")." );
1744         }
1745 
1746         File[] files = sourceDirectory.listFiles();
1747 
1748         String sourcePath = sourceDirectory.getAbsolutePath();
1749 
1750         for ( int i = 0; i < files.length; i++ )
1751         {
1752             File file = files[i];
1753 
1754             if ( file.equals( rootDestinationDirectory ) )
1755             {
1756                 // We don't copy the destination directory in itself
1757                 continue;
1758             }
1759 
1760             String dest = file.getAbsolutePath();
1761 
1762             dest = dest.substring( sourcePath.length() + 1 );
1763 
1764             File destination = new File( destinationDirectory, dest );
1765 
1766             if ( file.isFile() )
1767             {
1768                 destination = destination.getParentFile();
1769 
1770                 if ( onlyModifiedFiles )
1771                 {
1772                     copyFileToDirectoryIfModified( file, destination );
1773                 }
1774                 else
1775                 {
1776                     copyFileToDirectory( file, destination );
1777                 }
1778             }
1779             else if ( file.isDirectory() )
1780             {
1781                 if ( !destination.exists() && !destination.mkdirs() )
1782                 {
1783                     throw new IOException(
1784                         "Could not create destination directory '" + destination.getAbsolutePath() + "'." );
1785                 }
1786 
1787                 copyDirectoryStructure( file, destination, rootDestinationDirectory, onlyModifiedFiles );
1788             }
1789             else
1790             {
1791                 throw new IOException( "Unknown file type: " + file.getAbsolutePath() );
1792             }
1793         }
1794     }
1795 
1796     /**
1797      * Renames a file, even if that involves crossing file system boundaries.
1798      * <p/>
1799      * <p>This will remove <code>to</code> (if it exists), ensure that
1800      * <code>to</code>'s parent directory exists and move
1801      * <code>from</code>, which involves deleting <code>from</code> as
1802      * well.</p>
1803      *
1804      * @param from the file to move
1805      * @param to   the new file name
1806      * @throws IOException if anything bad happens during this
1807      *                     process.  Note that <code>to</code> may have been deleted
1808      *                     already when this happens.
1809      */
1810     public static void rename( File from, File to )
1811         throws IOException
1812     {
1813         if ( to.exists() && !to.delete() )
1814         {
1815             throw new IOException( "Failed to delete " + to + " while trying to rename " + from );
1816         }
1817 
1818         File parent = to.getParentFile();
1819         if ( parent != null && !parent.exists() && !parent.mkdirs() )
1820         {
1821             throw new IOException( "Failed to create directory " + parent + " while trying to rename " + from );
1822         }
1823 
1824         if ( !from.renameTo( to ) )
1825         {
1826             copyFile( from, to );
1827             if ( !from.delete() )
1828             {
1829                 throw new IOException( "Failed to delete " + from + " while trying to rename it." );
1830             }
1831         }
1832     }
1833 
1834     /**
1835      * Create a temporary file in a given directory.
1836      * <p/>
1837      * <p>The file denoted by the returned abstract pathname did not
1838      * exist before this method was invoked, any subsequent invocation
1839      * of this method will yield a different file name.</p>
1840      * <p/>
1841      * The filename is prefixNNNNNsuffix where NNNN is a random number
1842      * </p>
1843      * <p>This method is different to File.createTempFile of JDK 1.2
1844      * as it doesn't create the file itself.
1845      * It uses the location pointed to by java.io.tmpdir
1846      * when the parentDir attribute is
1847      * null.</p>
1848      *
1849      * @param prefix    prefix before the random number
1850      * @param suffix    file extension; include the '.'
1851      * @param parentDir Directory to create the temporary file in -
1852      *                  java.io.tmpdir used if not specificed
1853      * @return a File reference to the new temporary file.
1854      */
1855     public static File createTempFile( String prefix, String suffix, File parentDir )
1856     {
1857 
1858         File result = null;
1859         String parent = System.getProperty( "java.io.tmpdir" );
1860         if ( parentDir != null )
1861         {
1862             parent = parentDir.getPath();
1863         }
1864         DecimalFormat fmt = new DecimalFormat( "#####" );
1865         Random rand = new Random( System.currentTimeMillis() + Runtime.getRuntime().freeMemory() );
1866         synchronized ( rand )
1867         {
1868             do
1869             {
1870                 result = new File( parent, prefix + fmt.format( Math.abs( rand.nextInt() ) ) + suffix );
1871             }
1872             while ( result.exists() );
1873         }
1874         return result;
1875     }
1876 
1877     public static void copyFile( File from, File to, String encoding, FilterWrapper[] wrappers )
1878         throws IOException
1879     {
1880         if ( wrappers != null && wrappers.length > 0 )
1881         {
1882             // buffer so it isn't reading a byte at a time!
1883             Reader fileReader = null;
1884             Writer fileWriter = null;
1885             try
1886             {
1887                 if ( encoding == null || encoding.length() < 1 )
1888                 {
1889                     fileReader = new BufferedReader( new FileReader( from ) );
1890                     fileWriter = new FileWriter( to );
1891                 }
1892                 else
1893                 {
1894                     FileInputStream instream = new FileInputStream( from );
1895 
1896                     FileOutputStream outstream = new FileOutputStream( to );
1897 
1898                     fileReader = new BufferedReader( new InputStreamReader( instream, encoding ) );
1899 
1900                     fileWriter = new OutputStreamWriter( outstream, encoding );
1901                 }
1902 
1903                 Reader reader = fileReader;
1904                 for ( int i = 0; i < wrappers.length; i++ )
1905                 {
1906                     FilterWrapper wrapper = wrappers[i];
1907                     reader = wrapper.getReader( reader );
1908                 }
1909 
1910                 IOUtil.copy( reader, fileWriter );
1911             }
1912             finally
1913             {
1914                 IOUtil.close( fileReader );
1915                 IOUtil.close( fileWriter );
1916             }
1917         }
1918         else
1919         {
1920             if ( to.lastModified() < from.lastModified() )
1921             {
1922                 copyFile( from, to );
1923             }
1924         }
1925     }
1926 
1927     public static abstract class FilterWrapper
1928     {
1929         public abstract Reader getReader( Reader fileReader );
1930     }
1931 
1932     public static List loadFile( File file ) throws IOException
1933     {
1934         List lines = new ArrayList();
1935 
1936         if ( file.exists() )
1937         {
1938             BufferedReader reader = new BufferedReader( new FileReader( file ) );
1939 
1940             String line = reader.readLine();
1941 
1942             while ( line != null )
1943             {
1944                 line = line.trim();
1945 
1946                 if ( !line.startsWith( "#" ) && line.length() != 0 )
1947                 {
1948                     lines.add ( line );
1949                 }
1950                 line = reader.readLine();
1951             }
1952 
1953             reader.close();
1954         }
1955 
1956         return lines;
1957     }
1958 }