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