org.openl.util.FileUtils Maven / Gradle / Ivy
package org.openl.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collection;
import org.slf4j.LoggerFactory;
/**
* A set of methods to work with a file system.
*
* @author Yury Molchan
*/
public class FileUtils {
private static final int DEFAULT_BUFFER_SIZE = 8 * 1024 * 1024;
/**
* Returns the path to the system temporary directory.
*
* @return the path to the system temporary directory.
*/
public static String getTempDirectoryPath() {
return System.getProperty("java.io.tmpdir");
}
/**
* Copies a file to a new location preserving the file date.
*
* This method copies the contents of the specified source file to the specified destination file. The directory
* holding the destination file is created if it does not exist. If the destination file exists, then this method
* will overwrite it.
*
* Note: This method tries to preserve the file's last modified date/times using
* {@link File#setLastModified(long)}, however it is not guaranteed that the operation will succeed. If the
* modification operation fails, no indication is provided.
*
* @param src an existing file to copy, must not be {@code null}
* @param dest the new file, must not be {@code null}
* @throws NullPointerException if source or destination is {@code null}
* @throws IOException if source or destination is invalid
* @throws IOException if an IO error occurs during copying
*/
public static void copy(File src, File dest) throws IOException {
if (!src.exists()) {
throw new FileNotFoundException(String.format("Source '%s' does not exist", src));
}
final String srcPath = src.getCanonicalPath();
final String destPath = dest.getCanonicalPath();
if (srcPath.equals(destPath)) {
throw new IOException(String.format("Source '%s' and destination '%s' are the same", src, dest));
}
if (src.isDirectory()) {
Collection looped = getLoopedDirectories(src, dest);
doCopyDirectory(src, dest, looped);
} else {
if (destPath.startsWith(srcPath)) {
throw new IOException(
String.format("Destination '%s' has the same path of the source '%s'", dest, src));
}
File destFile = dest;
if (dest.isDirectory()) {
destFile = new File(dest, src.getName());
} else {
File parentFile = dest.getParentFile();
if (parentFile != null && !parentFile.mkdirs() && !parentFile.isDirectory()) {
throw new IOException(String.format("Destination '%s' directory cannot be created", parentFile));
}
}
doCopyFile(src, destFile);
}
}
/**
* Collects nested directories which should be excluded for copying to prevent an infinity loop of copying.
*
* @param src the source directory
* @param dest the destination directory
* @return the list of looped directories
* @throws IOException if an I/O error occurs
*/
private static Collection getLoopedDirectories(File src, File dest) throws IOException {
if (!dest.getCanonicalPath().startsWith(src.getCanonicalPath())) {
return null;
}
Collection looped = null;
File[] srcFiles = src.listFiles();
if (srcFiles != null && srcFiles.length > 0) {
looped = new ArrayList<>(srcFiles.length + 1);
for (File srcFile : srcFiles) {
File copiedFile = new File(dest, srcFile.getName());
if (srcFile.isDirectory()) {
looped.add(copiedFile.getCanonicalPath());
}
}
if (!dest.exists()) {
looped.add(dest.getCanonicalPath());
}
}
return looped;
}
/**
* Internal copy directory method.
*
* @param srcDir the validated source directory, must not be {@code null}
* @param destDir the validated destination directory, must not be {@code null}
* @param excluded the list of directories or files to exclude from the copy, may be null
* @throws IOException if an error occurs
*/
private static void doCopyDirectory(File srcDir, File destDir, Collection excluded) throws IOException {
File[] srcFiles = srcDir.listFiles();
if (srcFiles == null) { // null if security restricted
throw new IOException("Failed to list contents of " + srcDir);
}
if (destDir.exists()) {
if (!destDir.isDirectory()) {
throw new IOException(String.format("Destination '%s' exists but is not a directory", destDir));
}
} else {
if (!destDir.mkdirs() && !destDir.isDirectory()) {
throw new IOException(String.format("Destination '%s' directory cannot be created", destDir));
}
}
// recurse copying
for (File srcFile : srcFiles) {
File dstFile = new File(destDir, srcFile.getName());
if (excluded == null || !excluded.contains(srcFile.getCanonicalPath())) {
if (srcFile.isDirectory()) {
doCopyDirectory(srcFile, dstFile, excluded);
} else {
doCopyFile(srcFile, dstFile);
}
}
}
// Try to preserve file date
if (!destDir.setLastModified(srcDir.lastModified())) {
LoggerFactory.getLogger(FileUtils.class).warn("Failed to set modified time to file '{}'.", destDir);
}
}
/**
* Internal copy file method.
*
* @param srcFile the validated source file, must not be {@code null}
* @param destFile the validated destination file, must not be {@code null}
* @throws IOException if an error occurs
*/
private static void doCopyFile(File srcFile, File destFile) throws IOException {
if (destFile.exists() && destFile.isDirectory()) {
throw new IOException(String.format("Destination '%s' exists but is a directory", destFile));
}
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel input = null;
FileChannel output = null;
try {
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
input = fis.getChannel();
output = fos.getChannel();
long size = input.size();
long pos = 0;
while (pos < size) {
pos += output.transferFrom(input, pos, DEFAULT_BUFFER_SIZE);
}
} finally {
IOUtils.closeQuietly(output);
IOUtils.closeQuietly(fos);
IOUtils.closeQuietly(input);
IOUtils.closeQuietly(fis);
}
if (srcFile.length() != destFile.length()) {
throw new IOException(String.format("Failed to copy full contents from '%s' to '%s'", srcFile, destFile));
}
// Try to preserve file date
if (!destFile.setLastModified(srcFile.lastModified())) {
LoggerFactory.getLogger(FileUtils.class).warn("Failed to set modified time to file '{}'.", destFile);
}
}
/**
* Moves a directory or a file.
*
* When the destination directory or file is on another file system, do a "copy and delete".
*
* @param src the directory or the file to be moved
* @param dest the destination directory or file
* @throws NullPointerException if source or destination is {@code null}
* @throws IOException if source or destination is invalid
* @throws IOException if an IO error occurs moving the file
*/
public static void move(File src, File dest) throws IOException {
if (!src.exists()) {
throw new FileNotFoundException(String.format("Source '%s' does not exist", src));
}
if (dest.exists()) {
throw new IOException(String.format("Destination '%s' already exists", dest));
}
boolean rename = src.renameTo(dest);
if (!rename) {
if (src.isDirectory() && dest.getCanonicalPath().startsWith(src.getCanonicalPath())) {
throw new IOException(
String.format("Cannot move directory '%s' to a subdirectory of itself '%s'.", src, dest));
}
copy(src, dest);
delete(src);
if (src.exists()) {
throw new IOException(
String.format("Failed to delete original directory or file '%s' after copy to '%s'", src, dest));
}
}
}
/**
* Deletes a file. If file is a directory, delete it and all sub-directories.
*
* The difference between File.delete() and this method are:
*
* - A directory to be deleted does not have to be empty.
* - You get exceptions when a file or directory cannot be deleted.
*
*
* @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 has not been found
* @throws IOException in case deletion is unsuccessful
*/
public static void delete(File file) throws IOException {
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files == null) { // null if security restricted
throw new IOException("Failed to list contents of directory: " + file);
}
IOException exception = null;
for (File fl : files) {
try {
delete(fl);
} catch (IOException ioe) {
exception = ioe;
}
}
if (null != exception) {
throw exception;
}
if (!file.delete()) {
throw new IOException("Unable to delete directory: " + file);
}
} else {
boolean filePresent = file.exists();
if (!file.delete()) {
if (!filePresent) {
throw new FileNotFoundException("File does not exist: " + file);
}
throw new IOException("Unable to delete file: " + file);
}
}
}
/**
* Deletes a path. If provided path is a directory, delete it and all sub-directories.
*
* @param root path to file or directory to delete, must not be {@code null}
* @throws NullPointerException if the directory is {@code null}
* @throws FileNotFoundException if the file has not been found
* @throws IOException in case deletion is unsuccessful
*/
public static void delete(Path root) throws IOException {
if (!Files.exists(root)) {
throw new FileNotFoundException("Path does not exist: " + root);
}
Files.walkFileTree(root, new SimpleFileVisitor<>() {
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
delete0(file);
return FileVisitResult.CONTINUE;
}
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
delete0(dir);
return FileVisitResult.CONTINUE;
}
});
}
/**
* Delete file or directory using old API. Because {@link Files#delete(Path)} throws sometimes
* {@link java.nio.file.AccessDeniedException} by unknown reason on Windows environment
*
* @param path path to delete
* @throws IOException if failed to delete
*/
private static void delete0(Path path) throws IOException {
File toDelete = path.toFile();
if (!toDelete.delete()) {
throw new IOException("Failed to delete: " + path);
}
}
/**
* Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories.
*
* The difference between File.delete() and this method are:
*
* - A directory to be deleted does not have to be empty.
* - No exceptions are thrown when a file or directory cannot be deleted.
*
*
* @param file file or directory to delete, can be {@code null}
*/
public static void deleteQuietly(File file) {
if (file == null) {
return;
}
try {
delete(file);
} catch (Exception ignored) {
// ignore
}
}
public static void deleteQuietly(Path path) {
if (path == null) {
return;
}
try {
delete(path);
} catch (Exception ignored) {
// ignore
}
}
/**
* Gets the name minus the path from a full filename.
*
* This method will handle a file in either Unix or Windows format. The text after the last forward or backslash is
* returned.
*
*
* a/b/c.txt --> c.txt
* a.txt --> a.txt
* a/b/c --> c
* a/b/c/ --> ""
*
*
*
* @param filename the filename to query, null returns null
* @return the name of the file without the path, or an empty string if none exists
*/
public static String getName(String filename) {
if (filename == null) {
return null;
}
int sep = getSeparatorIndex(filename);
return filename.substring(sep + 1);
}
/**
* Gets the base name, minus the full path and extension, from a full filename.
*
* This method will handle a file in either Unix or Windows format. The text after the last forward or backslash and
* before the last dot is returned.
*
*
* a/b/c.txt --> c
* a.b.txt --> a.b
* a/b/c --> c
* a/b/c/ --> ""
*
*
*
* @param filename the filename to query, null returns null
* @return the name of the file without the path, or an empty string if none exists
*/
public static String getBaseName(String filename) {
if (filename == null) {
return null;
}
int dot = filename.lastIndexOf('.');
int sep = getSeparatorIndex(filename);
if (dot > sep) {
return filename.substring(sep + 1, dot);
} else {
return filename.substring(sep + 1);
}
}
/**
* Gets the extension of a filename.
*
* This method returns the textual part of the filename after the last dot. There must be no directory separator
* after the dot.
*
*
* a/b/c.txt --> txt
* a.b.txt --> txt
* a/b.txt/c --> ""
* a/b/c --> ""
*
*
*
* @param filename the filename to retrieve the extension of.
* @return the extension of the file or an empty string if none exists or {@code null} if the filename is
* {@code null}.
*/
public static String getExtension(String filename) {
if (filename == null) {
return null;
}
int dot = getExtensionIndex(filename);
if (dot == -1) {
return StringUtils.EMPTY;
} else {
return filename.substring(dot + 1);
}
}
/**
* Removes the extension from a filename.
*
* This method returns the textual part of the filename before the last dot. There must be no directory separator
* after the dot.
*
*
* foo.txt --> foo
* a\b\c.jpg --> a\b\c
* a\b\c --> a\b\c
* a.b\c --> a.b\c
*
*
*
* @param filename the filename to query, null returns null
* @return the filename minus the extension
*/
public static String removeExtension(String filename) {
if (filename == null) {
return null;
}
int dot = getExtensionIndex(filename);
if (dot == -1) {
return filename;
} else {
return filename.substring(0, dot);
}
}
private static int getSeparatorIndex(String filename) {
int winSep = filename.lastIndexOf('\\');
int unixSep = filename.lastIndexOf('/');
return Math.max(winSep, unixSep);
}
private static int getExtensionIndex(String filename) {
int dot = filename.lastIndexOf('.');
if (dot == -1) {
return -1;
}
int sep = getSeparatorIndex(filename);
if (dot > sep) {
return dot;
}
return -1;
}
}