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