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