001/*
002 *   Licensed to the Apache Software Foundation (ASF) under one
003 *   or more contributor license agreements.  See the NOTICE file
004 *   distributed with this work for additional information
005 *   regarding copyright ownership.  The ASF licenses this file
006 *   to you under the Apache License, Version 2.0 (the
007 *   "License"); you may not use this file except in compliance
008 *   with the License.  You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 *   Unless required by applicable law or agreed to in writing,
013 *   software distributed under the License is distributed on an
014 *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *   KIND, either express or implied.  See the License for the
016 *   specific language governing permissions and limitations
017 *   under the License.
018 *
019 */
020
021package org.apache.directory.api.util;
022
023
024import java.io.File;
025import java.io.FileInputStream;
026import java.io.FileNotFoundException;
027import java.io.FileOutputStream;
028import java.io.IOException;
029import java.io.InputStream;
030import java.io.OutputStream;
031import java.nio.charset.Charset;
032import java.nio.file.Files;
033import java.nio.file.Paths;
034import java.nio.file.StandardOpenOption;
035import java.util.List;
036
037import org.apache.directory.api.i18n.I18n;
038
039
040/**
041 * This code comes from Apache commons.io library.
042 * 
043 * Origin of code: Excalibur, Alexandria, Tomcat, Commons-Utils.
044 *
045 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
046 */
047public final class FileUtils
048{
049    /**
050     * The Windows separator character.
051     */
052    private static final char WINDOWS_SEPARATOR = '\\';
053
054    /**
055     * The system separator character.
056     */
057    private static final char SYSTEM_SEPARATOR = File.separatorChar;
058
059    /**
060     * The number of bytes in a kilobyte.
061     */
062    public static final long ONE_KB = 1024;
063
064    /**
065     * The number of bytes in a megabyte.
066     */
067    public static final long ONE_MB = ONE_KB * ONE_KB;
068
069    /**
070     * The file copy buffer size (30 MB)
071     */
072    private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30;
073
074
075    /**
076     * Creates a new instance of FileUtils.
077     */
078    private FileUtils()
079    {
080        // Nothing to do.
081    }
082
083
084    /**
085     * Deletes a directory recursively.
086     *
087     * @param directory  directory to delete
088     * @throws IOException in case deletion is unsuccessful
089     */
090    public static void deleteDirectory( File directory ) throws IOException
091    {
092        if ( !directory.exists() )
093        {
094            return;
095        }
096
097        if ( !isSymlink( directory ) )
098        {
099            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}