View Javadoc
1   /*
2    *   Licensed to the Apache Software Foundation (ASF) under one
3    *   or more contributor license agreements.  See the NOTICE file
4    *   distributed with this work for additional information
5    *   regarding copyright ownership.  The ASF licenses this file
6    *   to you under the Apache License, Version 2.0 (the
7    *   "License"); you may not use this file except in compliance
8    *   with the License.  You may obtain a copy of the License at
9    *
10   *     https://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing,
13   *   software distributed under the License is distributed on an
14   *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *   KIND, either express or implied.  See the License for the
16   *   specific language governing permissions and limitations
17   *   under the License.
18   *
19   */
20  
21  package org.apache.directory.api.util;
22  
23  
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileNotFoundException;
27  import java.io.FileOutputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.OutputStream;
31  import java.nio.charset.Charset;
32  import java.nio.file.Files;
33  import java.nio.file.Paths;
34  import java.nio.file.StandardOpenOption;
35  import java.util.List;
36  
37  import org.apache.directory.api.i18n.I18n;
38  
39  
40  /**
41   * This code comes from Apache commons.io library.
42   * 
43   * Origin of code: Excalibur, Alexandria, Tomcat, Commons-Utils.
44   *
45   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
46   */
47  public final class FileUtils
48  {
49      /**
50       * The Windows separator character.
51       */
52      private static final char WINDOWS_SEPARATOR = '\\';
53  
54      /**
55       * The system separator character.
56       */
57      private static final char SYSTEM_SEPARATOR = File.separatorChar;
58  
59      /**
60       * The number of bytes in a kilobyte.
61       */
62      public static final long ONE_KB = 1024;
63  
64      /**
65       * The number of bytes in a megabyte.
66       */
67      public static final long ONE_MB = ONE_KB * ONE_KB;
68  
69      /**
70       * The file copy buffer size (30 MB)
71       */
72      private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30;
73  
74  
75      /**
76       * Creates a new instance of FileUtils.
77       */
78      private FileUtils()
79      {
80          // Nothing to do.
81      }
82  
83  
84      /**
85       * Deletes a directory recursively.
86       *
87       * @param directory  directory to delete
88       * @throws IOException in case deletion is unsuccessful
89       */
90      public static void deleteDirectory( File directory ) throws IOException
91      {
92          if ( !directory.exists() )
93          {
94              return;
95          }
96  
97          if ( !isSymlink( directory ) )
98          {
99              cleanDirectory( directory );
100         }
101 
102         if ( !directory.delete() )
103         {
104             throw new IOException( I18n.err( I18n.ERR_17004_UNABLE_DELETE_DIR, directory ) );
105         }
106     }
107 
108 
109     /**
110      * Determines whether the specified file is a Symbolic Link rather than an actual file.
111      * <p>
112      * Will not return true if there is a Symbolic Link anywhere in the path,
113      * only if the specific file is.
114      * <p>
115      * <b>Note:</b> the current implementation always returns {@code false} if the system
116      * is detected as Windows
117      * <p>
118      * For code that runs on Java 1.7 or later, use the following method instead:
119      * <br>
120      * {@code boolean java.nio.file.Files.isSymbolicLink(Path path)}
121      * @param file the file to check
122      * @return true if the file is a Symbolic Link
123      * @throws IOException if an IO error occurs while checking the file
124      * @since 2.0
125      */
126     public static boolean isSymlink( File file ) throws IOException
127     {
128         if ( file == null )
129         {
130             throw new NullPointerException( I18n.err( I18n.ERR_17005_FILE_MUST_NOT_BE_NULL ) );
131         }
132 
133         if ( SYSTEM_SEPARATOR == WINDOWS_SEPARATOR )
134         {
135             return false;
136         }
137 
138         File fileInCanonicalDir;
139 
140         if ( file.getParent() == null )
141         {
142             fileInCanonicalDir = file;
143         }
144         else
145         {
146             File canonicalDir = file.getParentFile().getCanonicalFile();
147             fileInCanonicalDir = new File( canonicalDir, file.getName() );
148         }
149 
150         return !fileInCanonicalDir.getCanonicalFile().equals( fileInCanonicalDir.getAbsoluteFile() );
151     }
152 
153 
154     /**
155      * Deletes a directory recursively.
156      *
157      * @param directory  directory to delete
158      * @throws IOException in case deletion is unsuccessful
159      */
160     public static void cleanDirectory( File directory ) throws IOException
161     {
162         if ( !directory.exists() )
163         {
164             throw new IllegalArgumentException( I18n.err( I18n.ERR_17006_DOES_NOT_EXIST, directory ) );
165         }
166 
167         if ( !directory.isDirectory() )
168         {
169             throw new IllegalArgumentException( I18n.err( I18n.ERR_17007_IS_NOT_DIRECTORY, directory ) );
170         }
171 
172         File[] files = directory.listFiles();
173 
174         if ( files == null )
175         {
176             // null if security restricted
177             throw new IOException( I18n.err( I18n.ERR_17008_FAIL_LIST_DIR, directory ) );
178         }
179 
180         IOException exception = null;
181 
182         for ( File file : files )
183         {
184             try
185             {
186                 forceDelete( file );
187             }
188             catch ( IOException ioe )
189             {
190                 exception = ioe;
191             }
192         }
193 
194         if ( null != exception )
195         {
196             throw exception;
197         }
198     }
199 
200 
201     /**
202      * Deletes a file. If file is a directory, delete it and all sub-directories.
203      * <p>
204      * The difference between File.delete() and this method are:
205      * <ul>
206      * <li>A directory to be deleted does not have to be empty.</li>
207      * <li>You get exceptions when a file or directory cannot be deleted.
208      *      (java.io.File methods returns a boolean)</li>
209      * </ul>
210      *
211      * @param file  file or directory to delete, must not be {@code null}
212      * @throws NullPointerException if the directory is {@code null}
213      * @throws FileNotFoundException if the file was not found
214      * @throws IOException in case deletion is unsuccessful
215      */
216     public static void forceDelete( File file ) throws IOException
217     {
218         if ( file.isDirectory() )
219         {
220             deleteDirectory( file );
221         }
222         else
223         {
224             boolean filePresent = file.exists();
225 
226             if ( !file.delete() )
227             {
228                 if ( !filePresent )
229                 {
230                     throw new FileNotFoundException( I18n.err( I18n.ERR_17009_FILE_DOES_NOT_EXIST, file ) );
231                 }
232 
233                 throw new IOException( I18n.err( I18n.ERR_17010_UNABLE_DELETE_FILE, file ) );
234             }
235         }
236     }
237 
238 
239     /**
240      * Returns the path to the system temporary directory.
241      *
242      * @return the path to the system temporary directory.
243      *
244      * @since 2.0
245      */
246     public static String getTempDirectoryPath()
247     {
248         return System.getProperty( "java.io.tmpdir" );
249     }
250 
251 
252     /**
253      * Reads the contents of a file into a String using the default encoding for the VM.
254      * The file is always closed.
255      *
256      * @param file  the file to read, must not be {@code null}
257      * @return the file contents, never {@code null}
258      * @throws IOException in case of an I/O error
259      * @since 1.3.1
260      * @deprecated 2.5 use {@link #readFileToString(File, Charset)} instead
261      */
262     @Deprecated
263     public static String readFileToString( File file ) throws IOException
264     {
265         return readFileToString( file, Charset.defaultCharset() );
266     }
267 
268 
269     /**
270      * Reads the contents of a file into a String.
271      * The file is always closed.
272      *
273      * @param file  the file to read, must not be {@code null}
274      * @param encoding  the encoding to use, {@code null} means platform default
275      * @return the file contents, never {@code null}
276      * @throws IOException in case of an I/O error
277      * @since 2.3
278      */
279     public static String readFileToString( File file, Charset encoding ) throws IOException
280     {
281         InputStream in = null;
282 
283         try
284         {
285             in = openInputStream( file );
286             return IOUtils.toString( in, IOUtils.toCharset( encoding ) );
287         }
288         finally
289         {
290             IOUtils.closeQuietly( in );
291         }
292     }
293 
294 
295     /**
296      * Reads the contents of a file into a String. The file is always closed.
297      *
298      * @param file the file to read, must not be {@code null}
299      * @param encoding the encoding to use, {@code null} means platform default
300      * @return the file contents, never {@code null}
301      * @throws IOException in case of an I/O error
302      * @since 2.3
303      */
304     public static String readFileToString( File file, String encoding ) throws IOException
305     {
306         InputStream in = null;
307 
308         try
309         {
310             in = openInputStream( file );
311             return IOUtils.toString( in, IOUtils.toCharset( encoding ) );
312         }
313         finally
314         {
315             IOUtils.closeQuietly( in );
316         }
317     }
318 
319 
320     /**
321      * Opens a {@link FileInputStream} for the specified file, providing better
322      * error messages than simply calling <code>new FileInputStream(file)</code>.
323      * <p>
324      * At the end of the method either the stream will be successfully opened,
325      * or an exception will have been thrown.
326      * <p>
327      * An exception is thrown if the file does not exist.
328      * An exception is thrown if the file object exists but is a directory.
329      * An exception is thrown if the file exists but cannot be read.
330      *
331      * @param file  the file to open for input, must not be {@code null}
332      * @return a new {@link InputStream} for the specified file
333      * @throws FileNotFoundException if the file does not exist
334      * @throws IOException if the file object is a directory
335      * @throws IOException if the file cannot be read
336      * @since 1.3
337      */
338     public static InputStream openInputStream( File file ) throws IOException
339     {
340         if ( file.exists() )
341         {
342             if ( file.isDirectory() )
343             {
344                 throw new IOException( I18n.err( I18n.ERR_17011_FILE_IS_DIR, file ) );
345             }
346 
347             if ( !file.canRead() )
348             {
349                 throw new IOException( I18n.err( I18n.ERR_17012_CANNOT_READ_FILE, file ) );
350             }
351         }
352         else
353         {
354             throw new FileNotFoundException( I18n.err( I18n.ERR_17013_FILE_DOES_NOT_EXIST, file ) );
355         }
356 
357         return Files.newInputStream( Paths.get( file.getPath() ) );
358     }
359 
360 
361     /**
362      * Writes a String to a file creating the file if it does not exist using the default encoding for the VM.
363      *
364      * @param file  the file to write
365      * @param data  the content to write to the file
366      * @throws IOException in case of an I/O error
367      * @deprecated 2.5 use {@link #writeStringToFile(File, String, Charset, boolean)} instead
368      */
369     @Deprecated
370     public static void writeStringToFile( File file, String data ) throws IOException
371     {
372         writeStringToFile( file, data, Charset.defaultCharset(), false );
373     }
374 
375 
376     /**
377      * Writes a String to a file creating the file if it does not exist.
378      *
379      * NOTE: As from v1.3, the parent directories of the file will be created
380      * if they do not exist.
381      *
382      * @param file  the file to write
383      * @param data  the content to write to the file
384      * @param encoding  the encoding to use, {@code null} means platform default
385      * @throws IOException in case of an I/O error
386      * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
387      */
388     public static void writeStringToFile( File file, String data, String encoding ) throws IOException
389     {
390         writeStringToFile( file, data, IOUtils.toCharset( encoding ), false );
391     }
392 
393 
394     /**
395      * Writes a String to a file creating the file if it does not exist.
396      *
397      * @param file  the file to write
398      * @param data  the content to write to the file
399      * @param encoding  the encoding to use, {@code null} means platform default
400      * @param append if {@code true}, then the String will be added to the
401      * end of the file rather than overwriting
402      * @throws IOException in case of an I/O error
403      * @since 2.3
404      */
405     public static void writeStringToFile( File file, String data, Charset encoding, boolean append ) throws IOException
406     {
407         OutputStream out = null;
408 
409         try
410         {
411             out = openOutputStream( file, append );
412             IOUtils.write( data, out, encoding );
413             out.close(); // don't swallow close Exception if copy completes normally
414         }
415         finally
416         {
417             IOUtils.closeQuietly( out );
418         }
419     }
420 
421 
422     /**
423      * Opens a {@link FileOutputStream} for the specified file, checking and
424      * creating the parent directory if it does not exist.
425      * <p>
426      * At the end of the method either the stream will be successfully opened,
427      * or an exception will have been thrown.
428      * <p>
429      * The parent directory will be created if it does not exist.
430      * The file will be created if it does not exist.
431      * An exception is thrown if the file object exists but is a directory.
432      * An exception is thrown if the file exists but cannot be written to.
433      * An exception is thrown if the parent directory cannot be created.
434      *
435      * @param file  the file to open for output, must not be {@code null}
436      * @param append if {@code true}, then bytes will be added to the
437      * end of the file rather than overwriting
438      * @return a new {@link OutputStream} for the specified file
439      * @throws IOException if the file object is a directory
440      * @throws IOException if the file cannot be written to
441      * @throws IOException if a parent directory needs creating but that fails
442      * @since 2.1
443      */
444     public static OutputStream openOutputStream( File file, boolean append ) throws IOException
445     {
446         if ( file.exists() )
447         {
448             if ( file.isDirectory() )
449             {
450                 throw new IOException( I18n.err( I18n.ERR_17011_FILE_IS_DIR, file ) );
451             }
452 
453             if ( !file.canWrite() )
454             {
455                 throw new IOException( I18n.err( I18n.ERR_17014_CANNOT_WRITE_FILE, file ) );
456             }
457         }
458         else
459         {
460             File parent = file.getParentFile();
461 
462             if ( ( parent != null ) && ( !parent.mkdirs() && !parent.isDirectory() ) )
463             {
464                 throw new IOException( I18n.err( I18n.ERR_17015_CANNOT_CREATE_DIR, parent ) );
465             }
466         }
467 
468         if ( append )
469         {
470             return Files.newOutputStream( Paths.get( file.getPath() ), StandardOpenOption.CREATE, StandardOpenOption.APPEND );
471         }
472         else
473         {
474             return Files.newOutputStream( Paths.get( file.getPath() ) );
475         }
476     }
477 
478 
479     /**
480      * Returns a {@link File} representing the system temporary directory.
481      *
482      * @return the system temporary directory.
483      *
484      * @since 2.0
485      */
486     public static File getTempDirectory()
487     {
488         return new File( getTempDirectoryPath() );
489     }
490 
491 
492     /**
493      * Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories.
494      * <p>
495      * The difference between File.delete() and this method are:
496      * <ul>
497      * <li>A directory to be deleted does not have to be empty.</li>
498      * <li>No exceptions are thrown when a file or directory cannot be deleted.</li>
499      * </ul>
500      *
501      * @param file  file or directory to delete, can be {@code null}
502      * @return {@code true} if the file or directory was deleted, otherwise
503      * {@code false}
504      *
505      * @since 1.4
506      */
507     public static boolean deleteQuietly( File file )
508     {
509         if ( file == null )
510         {
511             return false;
512         }
513 
514         try
515         {
516             if ( file.isDirectory() )
517             {
518                 cleanDirectory( file );
519             }
520         }
521         catch ( Exception ignored )
522         {
523         }
524 
525         try
526         {
527             return file.delete();
528         }
529         catch ( Exception ignored )
530         {
531             return false;
532         }
533     }
534 
535 
536     /**
537      * Writes a byte array to a file creating the file if it does not exist.
538      * <p>
539      * NOTE: As from v1.3, the parent directories of the file will be created
540      * if they do not exist.
541      *
542      * @param file  the file to write to
543      * @param data  the content to write to the file
544      * @throws IOException in case of an I/O erroe
545      * @since 1.1
546      */
547     public static void writeByteArrayToFile( final File file, final byte[] data ) throws IOException
548     {
549         writeByteArrayToFile( file, data, false );
550     }
551 
552 
553     /**
554      * Writes a byte array to a file creating the file if it does not exist.
555      *
556      * @param file  the file to write to
557      * @param data  the content to write to the file
558      * @param append if {@code true}, then bytes will be added to the
559      * end of the file rather than overwriting
560      * @throws IOException in case of an I/O error
561      * @since 2.1
562      */
563     public static void writeByteArrayToFile( File file, byte[] data, boolean append ) throws IOException
564     {
565         writeByteArrayToFile( file, data, 0, data.length, append );
566     }
567 
568 
569     /**
570      * Writes {@code len} bytes from the specified byte array starting
571      * at offset {@code off} to a file, creating the file if it does
572      * not exist.
573      *
574      * @param file  the file to write to
575      * @param data  the content to write to the file
576      * @param off   the start offset in the data
577      * @param len   the number of bytes to write
578      * @param append if {@code true}, then bytes will be added to the
579      * end of the file rather than overwriting
580      * @throws IOException in case of an I/O error
581      * @since 2.5
582      */
583     public static void writeByteArrayToFile( File file, byte[] data, int off, int len, boolean append ) throws IOException
584     {
585         OutputStream out = null;
586         
587         try
588         {
589             out = openOutputStream( file, append );
590             out.write( data, off, len );
591             out.close(); // don't swallow close Exception if copy completes normally
592         }
593         finally
594         {
595             IOUtils.closeQuietly( out );
596         }
597     }
598 
599     
600     /**
601      * Reads the contents of a file into a byte array.
602      * The file is always closed.
603      *
604      * @param file  the file to read, must not be {@code null}
605      * @return the file contents, never {@code null}
606      * @throws IOException in case of an I/O error
607      * @since 1.1
608      */
609     public static byte[] readFileToByteArray( File file ) throws IOException 
610     {
611         InputStream in = null;
612         
613         try 
614         {
615             in = openInputStream( file );
616             return IOUtils.toByteArray( in, file.length() );
617         } 
618         finally 
619         {
620             IOUtils.closeQuietly( in );
621         }
622     }
623 
624     
625     /**
626      * Opens a {@link FileOutputStream} for the specified file, checking and
627      * creating the parent directory if it does not exist.
628      * <p>
629      * At the end of the method either the stream will be successfully opened,
630      * or an exception will have been thrown.
631      * <p>
632      * The parent directory will be created if it does not exist.
633      * The file will be created if it does not exist.
634      * An exception is thrown if the file object exists but is a directory.
635      * An exception is thrown if the file exists but cannot be written to.
636      * An exception is thrown if the parent directory cannot be created.
637      *
638      * @param file  the file to open for output, must not be {@code null}
639      * @return a new {@link OutputStream} for the specified file
640      * @throws IOException if the file object is a directory
641      * @throws IOException if the file cannot be written to
642      * @throws IOException if a parent directory needs creating but that fails
643      * @since 1.3
644      */
645     public static OutputStream openOutputStream( File file ) throws IOException 
646     {
647         return openOutputStream( file, false );
648     }
649     
650     
651     /**
652      * Reads the contents of a file line by line to a List of Strings using the default encoding for the VM.
653      * The file is always closed.
654      *
655      * @param file  the file to read, must not be {@code null}
656      * @return the list of Strings representing each line in the file, never {@code null}
657      * @throws IOException in case of an I/O error
658      * @since 1.3
659      * @deprecated 2.5 use {@link #readLines(File, Charset)} instead
660      */
661     @Deprecated
662     public static List<String> readLines( File file ) throws IOException 
663     {
664         return readLines( file, Charset.defaultCharset() );
665     }
666     
667     
668     /**
669      * Reads the contents of a file line by line to a List of Strings.
670      * The file is always closed.
671      *
672      * @param file  the file to read, must not be {@code null}
673      * @param encoding  the encoding to use, {@code null} means platform default
674      * @return the list of Strings representing each line in the file, never {@code null}
675      * @throws IOException in case of an I/O error
676      * @since 2.3
677      */
678     public static List<String> readLines( File file, Charset encoding ) throws IOException 
679     {
680         InputStream in = null;
681         
682         try 
683         {
684             in = openInputStream( file );
685             return IOUtils.readLines( in, IOUtils.toCharset( encoding ) );
686         } 
687         finally 
688         {
689             IOUtils.closeQuietly( in );
690         }
691     }
692 }