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