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