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