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