blob: b345c948e4cdf7a5303715eae21c541797b44cc6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.api.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;
import org.apache.directory.api.i18n.I18n;
/**
* This code comes from Apache commons.io library.
*
* Origin of code: Excalibur, Alexandria, Tomcat, Commons-Utils.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public final class FileUtils
{
/**
* The Windows separator character.
*/
private static final char WINDOWS_SEPARATOR = '\\';
/**
* The system separator character.
*/
private static final char SYSTEM_SEPARATOR = File.separatorChar;
/**
* The number of bytes in a kilobyte.
*/
public static final long ONE_KB = 1024;
/**
* The number of bytes in a megabyte.
*/
public static final long ONE_MB = ONE_KB * ONE_KB;
/**
* The file copy buffer size (30 MB)
*/
private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30;
/**
* Creates a new instance of FileUtils.
*/
private FileUtils()
{
// Nothing to do.
}
/**
* Deletes a directory recursively.
*
* @param directory directory to delete
* @throws IOException in case deletion is unsuccessful
*/
public static void deleteDirectory( File directory ) throws IOException
{
if ( !directory.exists() )
{
return;
}
if ( !isSymlink( directory ) )
{
cleanDirectory( directory );
}
if ( !directory.delete() )
{
throw new IOException( I18n.err( I18n.ERR_17004_UNABLE_DELETE_DIR, directory ) );
}
}
/**
* Determines whether the specified file is a Symbolic Link rather than an actual file.
* <p>
* Will not return true if there is a Symbolic Link anywhere in the path,
* only if the specific file is.
* <p>
* <b>Note:</b> the current implementation always returns {@code false} if the system
* is detected as Windows
* <p>
* For code that runs on Java 1.7 or later, use the following method instead:
* <br>
* {@code boolean java.nio.file.Files.isSymbolicLink(Path path)}
* @param file the file to check
* @return true if the file is a Symbolic Link
* @throws IOException if an IO error occurs while checking the file
* @since 2.0
*/
public static boolean isSymlink( File file ) throws IOException
{
if ( file == null )
{
throw new NullPointerException( I18n.err( I18n.ERR_17005_FILE_MUST_NOT_BE_NULL ) );
}
if ( SYSTEM_SEPARATOR == WINDOWS_SEPARATOR )
{
return false;
}
File fileInCanonicalDir;
if ( file.getParent() == null )
{
fileInCanonicalDir = file;
}
else
{
File canonicalDir = file.getParentFile().getCanonicalFile();
fileInCanonicalDir = new File( canonicalDir, file.getName() );
}
return !fileInCanonicalDir.getCanonicalFile().equals( fileInCanonicalDir.getAbsoluteFile() );
}
/**
* Deletes a directory recursively.
*
* @param directory directory to delete
* @throws IOException in case deletion is unsuccessful
*/
public static void cleanDirectory( File directory ) throws IOException
{
if ( !directory.exists() )
{
throw new IllegalArgumentException( I18n.err( I18n.ERR_17006_DOES_NOT_EXIST, directory ) );
}
if ( !directory.isDirectory() )
{
throw new IllegalArgumentException( I18n.err( I18n.ERR_17007_IS_NOT_DIRECTORY, directory ) );
}
File[] files = directory.listFiles();
if ( files == null )
{
// null if security restricted
throw new IOException( I18n.err( I18n.ERR_17008_FAIL_LIST_DIR, directory ) );
}
IOException exception = null;
for ( File file : files )
{
try
{
forceDelete( file );
}
catch ( IOException ioe )
{
exception = ioe;
}
}
if ( null != exception )
{
throw exception;
}
}
/**
* Deletes a file. If file is a directory, delete it and all sub-directories.
* <p>
* The difference between File.delete() and this method are:
* <ul>
* <li>A directory to be deleted does not have to be empty.</li>
* <li>You get exceptions when a file or directory cannot be deleted.
* (java.io.File methods returns a boolean)</li>
* </ul>
*
* @param file file or directory to delete, must not be {@code null}
* @throws NullPointerException if the directory is {@code null}
* @throws FileNotFoundException if the file was not found
* @throws IOException in case deletion is unsuccessful
*/
public static void forceDelete( File file ) throws IOException
{
if ( file.isDirectory() )
{
deleteDirectory( file );
}
else
{
boolean filePresent = file.exists();
if ( !file.delete() )
{
if ( !filePresent )
{
throw new FileNotFoundException( I18n.err( I18n.ERR_17009_FILE_DOES_NOT_EXIST, file ) );
}
throw new IOException( I18n.err( I18n.ERR_17010_UNABLE_DELETE_FILE, file ) );
}
}
}
/**
* Returns the path to the system temporary directory.
*
* @return the path to the system temporary directory.
*
* @since 2.0
*/
public static String getTempDirectoryPath()
{
return System.getProperty( "java.io.tmpdir" );
}
/**
* Reads the contents of a file into a String using the default encoding for the VM.
* The file is always closed.
*
* @param file the file to read, must not be {@code null}
* @return the file contents, never {@code null}
* @throws IOException in case of an I/O error
* @since 1.3.1
* @deprecated 2.5 use {@link #readFileToString(File, Charset)} instead
*/
@Deprecated
public static String readFileToString( File file ) throws IOException
{
return readFileToString( file, Charset.defaultCharset() );
}
/**
* Reads the contents of a file into a String.
* The file is always closed.
*
* @param file the file to read, must not be {@code null}
* @param encoding the encoding to use, {@code null} means platform default
* @return the file contents, never {@code null}
* @throws IOException in case of an I/O error
* @since 2.3
*/
public static String readFileToString( File file, Charset encoding ) throws IOException
{
InputStream in = null;
try
{
in = openInputStream( file );
return IOUtils.toString( in, IOUtils.toCharset( encoding ) );
}
finally
{
IOUtils.closeQuietly( in );
}
}
/**
* Reads the contents of a file into a String. The file is always closed.
*
* @param file the file to read, must not be {@code null}
* @param encoding the encoding to use, {@code null} means platform default
* @return the file contents, never {@code null}
* @throws IOException in case of an I/O error
* @since 2.3
*/
public static String readFileToString( File file, String encoding ) throws IOException
{
InputStream in = null;
try
{
in = openInputStream( file );
return IOUtils.toString( in, IOUtils.toCharset( encoding ) );
}
finally
{
IOUtils.closeQuietly( in );
}
}
/**
* Opens a {@link FileInputStream} for the specified file, providing better
* error messages than simply calling <code>new FileInputStream(file)</code>.
* <p>
* At the end of the method either the stream will be successfully opened,
* or an exception will have been thrown.
* <p>
* An exception is thrown if the file does not exist.
* An exception is thrown if the file object exists but is a directory.
* An exception is thrown if the file exists but cannot be read.
*
* @param file the file to open for input, must not be {@code null}
* @return a new {@link InputStream} for the specified file
* @throws FileNotFoundException if the file does not exist
* @throws IOException if the file object is a directory
* @throws IOException if the file cannot be read
* @since 1.3
*/
public static InputStream openInputStream( File file ) throws IOException
{
if ( file.exists() )
{
if ( file.isDirectory() )
{
throw new IOException( I18n.err( I18n.ERR_17011_FILE_IS_DIR, file ) );
}
if ( !file.canRead() )
{
throw new IOException( I18n.err( I18n.ERR_17012_CANNOT_READ_FILE, file ) );
}
}
else
{
throw new FileNotFoundException( I18n.err( I18n.ERR_17013_FILE_DOES_NOT_EXIST, file ) );
}
return Files.newInputStream( Paths.get( file.getPath() ) );
}
/**
* Writes a String to a file creating the file if it does not exist using the default encoding for the VM.
*
* @param file the file to write
* @param data the content to write to the file
* @throws IOException in case of an I/O error
* @deprecated 2.5 use {@link #writeStringToFile(File, String, Charset, boolean)} instead
*/
@Deprecated
public static void writeStringToFile( File file, String data ) throws IOException
{
writeStringToFile( file, data, Charset.defaultCharset(), false );
}
/**
* Writes a String to a file creating the file if it does not exist.
*
* NOTE: As from v1.3, the parent directories of the file will be created
* if they do not exist.
*
* @param file the file to write
* @param data the content to write to the file
* @param encoding the encoding to use, {@code null} means platform default
* @throws IOException in case of an I/O error
* @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
*/
public static void writeStringToFile( File file, String data, String encoding ) throws IOException
{
writeStringToFile( file, data, IOUtils.toCharset( encoding ), false );
}
/**
* Writes a String to a file creating the file if it does not exist.
*
* @param file the file to write
* @param data the content to write to the file
* @param encoding the encoding to use, {@code null} means platform default
* @param append if {@code true}, then the String will be added to the
* end of the file rather than overwriting
* @throws IOException in case of an I/O error
* @since 2.3
*/
public static void writeStringToFile( File file, String data, Charset encoding, boolean append ) throws IOException
{
OutputStream out = null;
try
{
out = openOutputStream( file, append );
IOUtils.write( data, out, encoding );
out.close(); // don't swallow close Exception if copy completes normally
}
finally
{
IOUtils.closeQuietly( out );
}
}
/**
* Opens a {@link FileOutputStream} for the specified file, checking and
* creating the parent directory if it does not exist.
* <p>
* At the end of the method either the stream will be successfully opened,
* or an exception will have been thrown.
* <p>
* The parent directory will be created if it does not exist.
* The file will be created if it does not exist.
* An exception is thrown if the file object exists but is a directory.
* An exception is thrown if the file exists but cannot be written to.
* An exception is thrown if the parent directory cannot be created.
*
* @param file the file to open for output, must not be {@code null}
* @param append if {@code true}, then bytes will be added to the
* end of the file rather than overwriting
* @return a new {@link OutputStream} for the specified file
* @throws IOException if the file object is a directory
* @throws IOException if the file cannot be written to
* @throws IOException if a parent directory needs creating but that fails
* @since 2.1
*/
public static OutputStream openOutputStream( File file, boolean append ) throws IOException
{
if ( file.exists() )
{
if ( file.isDirectory() )
{
throw new IOException( I18n.err( I18n.ERR_17011_FILE_IS_DIR, file ) );
}
if ( !file.canWrite() )
{
throw new IOException( I18n.err( I18n.ERR_17014_CANNOT_WRITE_FILE, file ) );
}
}
else
{
File parent = file.getParentFile();
if ( ( parent != null ) && ( !parent.mkdirs() && !parent.isDirectory() ) )
{
throw new IOException( I18n.err( I18n.ERR_17015_CANNOT_CREATE_DIR, parent ) );
}
}
if ( append )
{
return Files.newOutputStream( Paths.get( file.getPath() ), StandardOpenOption.CREATE, StandardOpenOption.APPEND );
}
else
{
return Files.newOutputStream( Paths.get( file.getPath() ) );
}
}
/**
* Returns a {@link File} representing the system temporary directory.
*
* @return the system temporary directory.
*
* @since 2.0
*/
public static File getTempDirectory()
{
return new File( getTempDirectoryPath() );
}
/**
* Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories.
* <p>
* The difference between File.delete() and this method are:
* <ul>
* <li>A directory to be deleted does not have to be empty.</li>
* <li>No exceptions are thrown when a file or directory cannot be deleted.</li>
* </ul>
*
* @param file file or directory to delete, can be {@code null}
* @return {@code true} if the file or directory was deleted, otherwise
* {@code false}
*
* @since 1.4
*/
public static boolean deleteQuietly( File file )
{
if ( file == null )
{
return false;
}
try
{
if ( file.isDirectory() )
{
cleanDirectory( file );
}
}
catch ( Exception ignored )
{
}
try
{
return file.delete();
}
catch ( Exception ignored )
{
return false;
}
}
/**
* Writes a byte array to a file creating the file if it does not exist.
* <p>
* NOTE: As from v1.3, the parent directories of the file will be created
* if they do not exist.
*
* @param file the file to write to
* @param data the content to write to the file
* @throws IOException in case of an I/O erroe
* @since 1.1
*/
public static void writeByteArrayToFile( final File file, final byte[] data ) throws IOException
{
writeByteArrayToFile( file, data, false );
}
/**
* Writes a byte array to a file creating the file if it does not exist.
*
* @param file the file to write to
* @param data the content to write to the file
* @param append if {@code true}, then bytes will be added to the
* end of the file rather than overwriting
* @throws IOException in case of an I/O error
* @since 2.1
*/
public static void writeByteArrayToFile( File file, byte[] data, boolean append ) throws IOException
{
writeByteArrayToFile( file, data, 0, data.length, append );
}
/**
* Writes {@code len} bytes from the specified byte array starting
* at offset {@code off} to a file, creating the file if it does
* not exist.
*
* @param file the file to write to
* @param data the content to write to the file
* @param off the start offset in the data
* @param len the number of bytes to write
* @param append if {@code true}, then bytes will be added to the
* end of the file rather than overwriting
* @throws IOException in case of an I/O error
* @since 2.5
*/
public static void writeByteArrayToFile( File file, byte[] data, int off, int len, boolean append ) throws IOException
{
OutputStream out = null;
try
{
out = openOutputStream( file, append );
out.write( data, off, len );
out.close(); // don't swallow close Exception if copy completes normally
}
finally
{
IOUtils.closeQuietly( out );
}
}
/**
* Reads the contents of a file into a byte array.
* The file is always closed.
*
* @param file the file to read, must not be {@code null}
* @return the file contents, never {@code null}
* @throws IOException in case of an I/O error
* @since 1.1
*/
public static byte[] readFileToByteArray( File file ) throws IOException
{
InputStream in = null;
try
{
in = openInputStream( file );
return IOUtils.toByteArray( in, file.length() );
}
finally
{
IOUtils.closeQuietly( in );
}
}
/**
* Opens a {@link FileOutputStream} for the specified file, checking and
* creating the parent directory if it does not exist.
* <p>
* At the end of the method either the stream will be successfully opened,
* or an exception will have been thrown.
* <p>
* The parent directory will be created if it does not exist.
* The file will be created if it does not exist.
* An exception is thrown if the file object exists but is a directory.
* An exception is thrown if the file exists but cannot be written to.
* An exception is thrown if the parent directory cannot be created.
*
* @param file the file to open for output, must not be {@code null}
* @return a new {@link OutputStream} for the specified file
* @throws IOException if the file object is a directory
* @throws IOException if the file cannot be written to
* @throws IOException if a parent directory needs creating but that fails
* @since 1.3
*/
public static OutputStream openOutputStream( File file ) throws IOException
{
return openOutputStream( file, false );
}
/**
* Reads the contents of a file line by line to a List of Strings using the default encoding for the VM.
* The file is always closed.
*
* @param file the file to read, must not be {@code null}
* @return the list of Strings representing each line in the file, never {@code null}
* @throws IOException in case of an I/O error
* @since 1.3
* @deprecated 2.5 use {@link #readLines(File, Charset)} instead
*/
@Deprecated
public static List<String> readLines( File file ) throws IOException
{
return readLines( file, Charset.defaultCharset() );
}
/**
* Reads the contents of a file line by line to a List of Strings.
* The file is always closed.
*
* @param file the file to read, must not be {@code null}
* @param encoding the encoding to use, {@code null} means platform default
* @return the list of Strings representing each line in the file, never {@code null}
* @throws IOException in case of an I/O error
* @since 2.3
*/
public static List<String> readLines( File file, Charset encoding ) throws IOException
{
InputStream in = null;
try
{
in = openInputStream( file );
return IOUtils.readLines( in, IOUtils.toCharset( encoding ) );
}
finally
{
IOUtils.closeQuietly( in );
}
}
}