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 * ("") 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 }