Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**
* Copyright (C) 2006-2015 phloc systems
* http://www.phloc.com
* office[at]phloc[dot]com
*
* Licensed 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 com.phloc.commons.io.file;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.List;
import javax.annotation.CheckForSigned;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.WillNotClose;
import javax.annotation.concurrent.Immutable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.phloc.commons.CGlobal;
import com.phloc.commons.SystemProperties;
import com.phloc.commons.ValueEnforcer;
import com.phloc.commons.annotations.PresentForCodeCoverage;
import com.phloc.commons.annotations.ReturnsMutableCopy;
import com.phloc.commons.collections.ContainerHelper;
import com.phloc.commons.io.EAppend;
import com.phloc.commons.io.misc.SizeHelper;
import com.phloc.commons.io.streams.ByteBufferInputStream;
import com.phloc.commons.io.streams.ByteBufferOutputStream;
import com.phloc.commons.io.streams.CountingFileInputStream;
import com.phloc.commons.io.streams.CountingFileOutputStream;
import com.phloc.commons.io.streams.StreamUtils;
import com.phloc.commons.state.EChange;
import com.phloc.commons.state.EValidity;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Miscellaneous file utility methods.
*
* @author Philip Helger
*/
@Immutable
public final class FileUtils
{
private static final boolean USE_MEMORY_MAPPED_FILES = false;
private static final Logger s_aLogger = LoggerFactory.getLogger (FileUtils.class);
@PresentForCodeCoverage
@SuppressWarnings ("unused")
private static final FileUtils s_aInstance = new FileUtils ();
private FileUtils ()
{}
/**
* Check if the passed file exists. Must be existing and a file.
*
* @param aFile
* The file to be checked for existence. May not be null .
* @return true if the {@link File} is a file and exists,
* false otherwise.
*/
public static boolean existsFile (@Nonnull final File aFile)
{
ValueEnforcer.notNull (aFile, "File");
return aFile.isFile ();
}
/**
* Check if the passed directory exists. Must be existing and must be a
* directory!
*
* @param aDir
* The directory to be checked for existence. May not be
* null.
* @return true if the {@link File} is a directory and exists,
* false otherwise.
*/
public static boolean existsDir (@Nonnull final File aDir)
{
ValueEnforcer.notNull (aDir, "Directory");
// returns true if it exists() AND is a directory!
return aDir.isDirectory ();
}
/**
* Tests whether the application can read the file denoted by this abstract
* pathname.
*
* @param aFile
* The file to be checked. May not be null.
* @return true if and only if the file specified by this
* abstract pathname exists and can be read by the
* application; false otherwise
*/
public static boolean canRead (@Nonnull final File aFile)
{
ValueEnforcer.notNull (aFile, "File");
return aFile.canRead ();
}
/**
* Tests whether the application can modify the file denoted by this abstract
* pathname.
*
* @param aFile
* The file to be checked. May not be null.
* @return true if and only if the file system actually contains
* a file denoted by this abstract pathname and the
* application is allowed to write to the file; false
* otherwise.
*/
public static boolean canWrite (@Nonnull final File aFile)
{
ValueEnforcer.notNull (aFile, "File");
return aFile.canWrite ();
}
/**
* Tests whether the application can execute the file denoted by this abstract
* pathname.
*
* @param aFile
* The file to be checked. May not be null.
* @return true if and only if the abstract pathname exists
* and the application is allowed to execute the file
*/
public static boolean canExecute (@Nonnull final File aFile)
{
ValueEnforcer.notNull (aFile, "File");
// IFJDK5
// return true;
// ELSE
return aFile.canExecute ();
// ENDIF
}
/**
* Check if the passed file can read and write. If the file already exists,
* the file itself is checked. If the file does not exist, the parent
* directory
*
* @param aFile
* The file to be checked. May not be null.
* @return true if the file can be read or write
*/
public static boolean canReadAndWriteFile (@Nonnull final File aFile)
{
ValueEnforcer.notNull (aFile, "File");
if (existsFile (aFile))
{
// File exists
if (!canRead (aFile) || !canWrite (aFile))
return false;
}
else
{
// File does not exist
final File aParentFile = aFile.getParentFile ();
if (aParentFile != null && aParentFile.isDirectory ())
{
// Check parent directory
if (!canRead (aParentFile) || !canWrite (aParentFile))
return false;
}
}
return true;
}
@Nonnull
public static EChange ensureParentDirectoryIsPresent (@Nonnull final File aFile)
{
ValueEnforcer.notNull (aFile, "File");
// If the file has no parent, it is located in the root...
final File aParent = aFile.getParentFile ();
if (aParent == null || aParent.exists ())
{
if (aParent != null && !aParent.isDirectory ())
s_aLogger.warn ("Parent object specified is not a directory: '" + aParent + "'");
return EChange.UNCHANGED;
}
// Now try to create the directory
final FileIOError aError = FileOperations.createDirRecursive (aParent);
if (aError.isFailure ())
throw new IllegalStateException ("Failed to create parent of " + aFile.getAbsolutePath () + ": " + aError);
// Check again if it exists, to be 100% sure :)
if (!aParent.exists ())
throw new IllegalStateException ("Parent of " + aFile.getAbsolutePath () + " is still not existing!");
return EChange.CHANGED;
}
@Nullable
public static File getCanonicalFile (@Nullable final File aFile) throws IOException
{
return aFile == null ? null : aFile.getCanonicalFile ();
}
/**
* Check if the searched directory is a parent object of the start directory
*
* @param aSearchDirectory
* The directory to be searched. May not be null.
* @param aStartDirectory
* The directory where the search starts. May not be null.
* @return true if the search directory is a parent of the start
* directory, false otherwise.
*/
@SuppressFBWarnings ("IL_INFINITE_LOOP")
public static boolean isParentDirectory (@Nonnull final File aSearchDirectory, @Nonnull final File aStartDirectory)
{
ValueEnforcer.notNull (aSearchDirectory, "SearchDirectory");
ValueEnforcer.notNull (aStartDirectory, "StartDirectory");
File aRealSearchDirectory = aSearchDirectory.getAbsoluteFile ();
File aRealStartDirectory = aStartDirectory.getAbsoluteFile ();
try
{
aRealSearchDirectory = getCanonicalFile (aRealSearchDirectory);
aRealStartDirectory = getCanonicalFile (aRealStartDirectory);
}
catch (final IOException ex)
{
// ignore
}
if (!aRealSearchDirectory.isDirectory ())
return false;
File aParent = aRealStartDirectory;
while (aParent != null)
{
if (aParent.equals (aRealSearchDirectory))
return true;
aParent = aParent.getParentFile ();
}
return false;
}
@CheckForSigned
public static long getFileSize (@Nonnull @WillNotClose final FileChannel aChannel)
{
if (aChannel != null)
try
{
return aChannel.size ();
}
catch (final IOException ex)
{
// fall-through
}
return -1;
}
@Nullable
private static FileInputStream _getFileInputStream (@Nonnull final File aFile)
{
try
{
return new CountingFileInputStream (aFile);
}
catch (final FileNotFoundException ex)
{
return null;
}
}
@Nullable
private static InputStream _getMappedInputStream (@Nonnull @WillNotClose final FileChannel aChannel,
@Nonnull final File aFile)
{
try
{
final MappedByteBuffer aBuffer = aChannel.map (MapMode.READ_ONLY, 0, aChannel.size ());
s_aLogger.info ("Created memory mapped input stream for " + aFile);
return new ByteBufferInputStream (aBuffer);
}
catch (final IOException ex)
{
s_aLogger.warn ("Failed to create memory mapped input stream for " + aFile + ": " + ex.getMessage ());
return null;
}
}
@Nullable
public static FileChannel getFileReadChannel (@Nonnull final String sFilename)
{
return getFileReadChannel (new File (sFilename));
}
@Nullable
public static FileChannel getFileReadChannel (@Nonnull final File aFile)
{
ValueEnforcer.notNull (aFile, "File");
final FileInputStream aFIS = _getFileInputStream (aFile);
return aFIS == null ? null : aFIS.getChannel ();
}
@Nullable
public static InputStream getInputStream (@Nonnull final String sFilename)
{
return getInputStream (new File (sFilename));
}
@Nullable
public static InputStream getInputStream (@Nonnull final File aFile)
{
ValueEnforcer.notNull (aFile, "File");
final FileInputStream aFIS = _getFileInputStream (aFile);
if (USE_MEMORY_MAPPED_FILES && aFIS != null)
{
// Check if using a memory mapped file makes sense (file size > 1MB)
final FileChannel aChannel = aFIS.getChannel ();
if (getFileSize (aChannel) > CGlobal.BYTES_PER_MEGABYTE)
{
// Check if mapping is possible
final InputStream aIS = _getMappedInputStream (aChannel, aFile);
if (aIS != null)
return aIS;
// Mapping failed - fall through
}
}
return aFIS;
}
/**
* Get an input stream to the specified file, using memory mapping. If memory
* mapping fails, a regular {@link FileInputStream} is returned.
*
* @param aFile
* The file to use. May not be null.
* @return The Input stream to use.
*/
@Nullable
public static InputStream getMappedInputStream (@Nonnull final File aFile)
{
ValueEnforcer.notNull (aFile, "File");
// Open regular
final FileInputStream aFIS = _getFileInputStream (aFile);
if (aFIS == null)
return null;
// Try to memory map it
final InputStream aIS = _getMappedInputStream (aFIS.getChannel (), aFile);
if (aIS != null)
return aIS;
// Memory mapping failed - return the original input stream
s_aLogger.warn ("Failed to map file " + aFile + ". Falling though to regular FileInputStream");
return aFIS;
}
@Nullable
private static FileOutputStream _getFileOutputStream (@Nonnull final File aFile, @Nonnull final EAppend eAppend)
{
try
{
return new CountingFileOutputStream (aFile, eAppend);
}
catch (final FileNotFoundException ex)
{
s_aLogger.warn ("Failed to create output stream for '" +
aFile +
"'; append: " +
eAppend +
": " +
ex.getClass ().getName () +
" - " +
ex.getMessage ());
return null;
}
}
@Nullable
private static OutputStream _getMappedOutputStream (@Nonnull @WillNotClose final FileChannel aChannel,
@Nonnull final File aFile)
{
try
{
// Maximum is Integer.MAX_VALUE even if a long is taken!
final MappedByteBuffer aBuffer = aChannel.map (MapMode.READ_WRITE, 0, Integer.MAX_VALUE);
s_aLogger.info ("Created memory mapped output stream for " + aFile);
return new ByteBufferOutputStream (aBuffer, false);
}
catch (final IOException ex)
{
s_aLogger.warn ("Failed to create memory mapped output stream for " + aFile + ": " + ex.getMessage ());
return null;
}
}
@Nullable
public static FileChannel getFileWriteChannel (@Nonnull final String sFilename)
{
return getFileWriteChannel (sFilename, EAppend.DEFAULT);
}
@Nullable
public static FileChannel getFileWriteChannel (@Nonnull final String sFilename, @Nonnull final EAppend eAppend)
{
return getFileWriteChannel (new File (sFilename), eAppend);
}
@Nullable
public static FileChannel getFileWriteChannel (@Nonnull final File aFile)
{
return getFileWriteChannel (aFile, EAppend.DEFAULT);
}
@Nullable
public static FileChannel getFileWriteChannel (@Nonnull final File aFile, @Nonnull final EAppend eAppend)
{
ValueEnforcer.notNull (aFile, "File");
ValueEnforcer.notNull (eAppend, "Append");
final FileOutputStream aFOS = _getFileOutputStream (aFile, eAppend);
return aFOS == null ? null : aFOS.getChannel ();
}
/**
* Get an output stream for writing to a file. Any existing file is
* overwritten.
*
* @param sFilename
* The name of the file to write to. May not be null.
* @return null if the file could not be opened
*/
@Nullable
public static OutputStream getOutputStream (@Nonnull final String sFilename)
{
return getOutputStream (sFilename, EAppend.DEFAULT);
}
/**
* Get an output stream for writing to a file.
*
* @param sFilename
* The name of the file to write to. May not be null.
* @param eAppend
* Appending mode. May not be null.
* @return null if the file could not be opened
*/
@Nullable
public static OutputStream getOutputStream (@Nonnull final String sFilename, @Nonnull final EAppend eAppend)
{
return getOutputStream (new File (sFilename), eAppend);
}
/**
* Get an output stream for writing to a file.
*
* @param aFile
* The file to write to. May not be null.
* @return null if the file could not be opened
*/
@Nullable
public static OutputStream getOutputStream (@Nonnull final File aFile)
{
return getOutputStream (aFile, EAppend.DEFAULT);
}
@Nonnull
private static EValidity _checkParentDirectoryExistanceAndAccess (@Nonnull final File aFile)
{
try
{
ensureParentDirectoryIsPresent (aFile);
}
catch (final IllegalStateException ex)
{
// Happens e.g. when the parent directory is " "
s_aLogger.warn ("Failed to create parent directory of '" + aFile + "'", ex);
return EValidity.INVALID;
}
// Check if parent directory is writable, to avoid catching the
// FileNotFoundException with "permission denied" afterwards
final File aParentDir = aFile.getParentFile ();
if (aParentDir != null && !canWrite (aParentDir))
{
s_aLogger.warn ("Parent directory '" +
aParentDir +
"' of '" +
aFile +
"' is not writable for current user '" +
SystemProperties.getUserName () +
"'");
return EValidity.INVALID;
}
return EValidity.VALID;
}
/**
* Get an output stream for writing to a file.
*
* @param aFile
* The file to write to. May not be null.
* @param eAppend
* Appending mode. May not be null.
* @return null if the file could not be opened
*/
@Nullable
public static OutputStream getOutputStream (@Nonnull final File aFile, @Nonnull final EAppend eAppend)
{
ValueEnforcer.notNull (aFile, "File");
ValueEnforcer.notNull (eAppend, "Append");
if (_checkParentDirectoryExistanceAndAccess (aFile).isInvalid ())
return null;
// OK, parent is present and writable
return _getFileOutputStream (aFile, eAppend);
}
@Nullable
public static OutputStream getMappedOutputStream (@Nonnull final String sFilename)
{
return getMappedOutputStream (new File (sFilename));
}
@Nullable
public static OutputStream getMappedOutputStream (@Nonnull final String sFilename, @Nonnull final EAppend eAppend)
{
return getMappedOutputStream (new File (sFilename), eAppend);
}
@Nullable
public static OutputStream getMappedOutputStream (@Nonnull final File aFile)
{
return getMappedOutputStream (aFile, EAppend.DEFAULT);
}
@Nullable
public static OutputStream getMappedOutputStream (@Nonnull final File aFile, @Nonnull final EAppend eAppend)
{
ValueEnforcer.notNull (aFile, "File");
ValueEnforcer.notNull (eAppend, "Append");
if (_checkParentDirectoryExistanceAndAccess (aFile).isInvalid ())
return null;
// Open random access file, as only those files deliver a channel that is
// readable and writable
final RandomAccessFile aRAF = getRandomAccessFile (aFile, ERandomAccessFileMode.READ_WRITE);
if (aRAF == null)
{
s_aLogger.error ("Failed to open random access file " + aFile);
return null;
}
// Try to memory map it
final OutputStream aOS = _getMappedOutputStream (aRAF.getChannel (), aFile);
if (aOS != null)
return aOS;
// Memory mapping failed - return the original output stream
StreamUtils.close (aRAF);
s_aLogger.warn ("Failed to map file " + aFile + ". Falling though to regular FileOutputStream");
return _getFileOutputStream (aFile, eAppend);
}
@Nullable
public static RandomAccessFile getRandomAccessFile (@Nonnull final String sFilename,
@Nonnull final ERandomAccessFileMode eMode)
{
return getRandomAccessFile (new File (sFilename), eMode);
}
@Nullable
public static RandomAccessFile getRandomAccessFile (@Nonnull final File aFile,
@Nonnull final ERandomAccessFileMode eMode)
{
ValueEnforcer.notNull (aFile, "File");
ValueEnforcer.notNull (eMode, "Mode");
try
{
return new RandomAccessFile (aFile, eMode.getMode ());
}
catch (final FileNotFoundException ex)
{
return null;
}
}
/**
* Returns true if the first file is newer than the second file.
* Returns true if the first file exists and the second file does
* not exist. Returns false if the first file is older than the
* second file. Returns false if the first file does not exists
* but the second does. Returns false if none of the files exist.
*
* @param aFile1
* First file. May not be null.
* @param aFile2
* Second file. May not be null.
* @return true if the first file is newer than the second file,
* false otherwise.
*/
public static boolean isFileNewer (@Nonnull final File aFile1, @Nonnull final File aFile2)
{
ValueEnforcer.notNull (aFile1, "File1");
ValueEnforcer.notNull (aFile2, "aFile2");
// Compare with the same file?
if (aFile1.equals (aFile2))
return false;
// if the first file does not exists, always false
if (!aFile1.exists ())
return false;
// first file exists, but second file does not
if (!aFile2.exists ())
return true;
// both exist, compare file times
return aFile1.lastModified () > aFile2.lastModified ();
}
@Nonnull
public static String getFileSizeDisplay (@Nullable final File aFile)
{
return getFileSizeDisplay (aFile, 0);
}
@Nonnull
public static String getFileSizeDisplay (@Nullable final File aFile, @Nonnegative final int nDecimals)
{
if (aFile != null && aFile.exists ())
return getFileSizeDisplay (aFile.length (), nDecimals);
return "";
}
@Nonnull
public static String getFileSizeDisplay (final long nFileSize)
{
return getFileSizeDisplay (nFileSize, 0);
}
@Nonnull
public static String getFileSizeDisplay (@Nonnegative final long nFileSize, @Nonnegative final int nDecimals)
{
ValueEnforcer.isGE0 (nFileSize, "FileSize");
ValueEnforcer.isGE0 (nDecimals, "Decimals");
return SizeHelper.getSizeHelperOfLocale (CGlobal.LOCALE_FIXED_NUMBER_FORMAT).getAsMatching (nFileSize, nDecimals);
}
/**
* Get a secure {@link File} object based on the passed file object. First all
* relative paths ("." and "..") are resolved and all eventually contained
* '\0' characters are eliminated. Than all file names are checked for
* validity (so that no special characters are contained).
*
* @param aFile
* The file to be secured.
* @return null if the passed file is null.
*/
@Nullable
public static File getSecureFile (@Nullable final File aFile)
{
if (aFile == null)
return null;
String sRet = null;
// Go through all directories and secure them
File aWork = new File (FilenameHelper.getCleanPath (aFile));
while (aWork != null)
{
String sSecuredName;
if (aWork.getParent () == null)
{
// For the root directory
sSecuredName = aWork.getPath ();
}
else
{
// Secure path element name
sSecuredName = FilenameHelper.getAsSecureValidFilename (aWork.getName ());
}
sRet = sRet == null ? sSecuredName : sSecuredName + "/" + sRet;
aWork = aWork.getParentFile ();
}
return new File (sRet);
}
/**
* Returns the number of files and directories contained in the passed
* directory excluding the system internal directories.
*
* @param aDirectory
* The directory to check. May not be null and must be a
* directory.
* @return A non-negative number of objects in that directory.
* @see FilenameHelper#isSystemInternalDirectory(CharSequence)
*/
@Nonnegative
public static int getDirectoryObjectCount (@Nonnull final File aDirectory)
{
ValueEnforcer.notNull (aDirectory, "Directory");
if (!aDirectory.isDirectory ())
throw new IllegalArgumentException ("Passed object is not a directory: " + aDirectory);
int ret = 0;
for (final File aChild : getDirectoryContent (aDirectory))
if (!FilenameHelper.isSystemInternalDirectory (aChild.getName ()))
ret++;
return ret;
}
@Nonnull
@ReturnsMutableCopy
private static List _getDirectoryContent (@Nonnull final File aDirectory,
@Nullable final File [] aSelectedContent)
{
if (aSelectedContent == null)
{
// No content returned
if (!aDirectory.isDirectory ())
s_aLogger.warn ("Cannot list non-directory: " + aDirectory.getAbsolutePath ());
else
if (!canExecute (aDirectory))
{
// If this happens, the resulting File objects are neither files nor
// directories (isFile() and isDirectory() both return false!!)
s_aLogger.warn ("Existing directory is missing the listing permission: " + aDirectory.getAbsolutePath ());
}
else
if (!canRead (aDirectory))
s_aLogger.warn ("Cannot list directory because of no read-rights: " + aDirectory.getAbsolutePath ());
else
s_aLogger.warn ("Directory listing failed because of IO error: " + aDirectory.getAbsolutePath ());
}
else
{
if (!canExecute (aDirectory))
{
// If this happens, the resulting File objects are neither files nor
// directories (isFile() and isDirectory() both return false!!)
s_aLogger.warn ("Directory is missing the listing permission: " + aDirectory.getAbsolutePath ());
}
}
return ContainerHelper.newList (aSelectedContent);
}
/**
* This is a replacement for File.listFiles() doing some
* additional checks on permissions. The order of the returned files is
* defined by the underlying {@link File#listFiles()} method.
*
* @param aDirectory
* The directory to be listed. May not be null.
* @return Never null.
*/
@Nonnull
@ReturnsMutableCopy
public static List getDirectoryContent (@Nonnull final File aDirectory)
{
ValueEnforcer.notNull (aDirectory, "Directory");
return _getDirectoryContent (aDirectory, aDirectory.listFiles ());
}
/**
* This is a replacement for File.listFiles(FilenameFilter) doing
* some additional checks on permissions. The order of the returned files is
* defined by the underlying {@link File#listFiles(FilenameFilter)} method.
*
* @param aDirectory
* The directory to be listed. May not be null.
* @param aFilenameFilter
* The filename filter to be used. May not be null.
* @return Never null.
*/
@Nonnull
@ReturnsMutableCopy
public static List getDirectoryContent (@Nonnull final File aDirectory,
@Nonnull final FilenameFilter aFilenameFilter)
{
ValueEnforcer.notNull (aDirectory, "Directory");
ValueEnforcer.notNull (aFilenameFilter, "FilenameFilter");
return _getDirectoryContent (aDirectory, aDirectory.listFiles (aFilenameFilter));
}
/**
* This is a replacement for File.listFiles(FileFilter) doing
* some additional checks on permissions. The order of the returned files is
* defined by the underlying {@link File#listFiles(FileFilter)} method.
*
* @param aDirectory
* The directory to be listed. May not be null.
* @param aFileFilter
* The file filter to be used. May not be null.
* @return Never null.
*/
@Nonnull
@ReturnsMutableCopy
public static List getDirectoryContent (@Nonnull final File aDirectory, @Nonnull final FileFilter aFileFilter)
{
ValueEnforcer.notNull (aDirectory, "Directory");
ValueEnforcer.notNull (aFileFilter, "FileFilter");
return _getDirectoryContent (aDirectory, aDirectory.listFiles (aFileFilter));
}
}