FileUtils.java
/*
* This file is part of Waarp Project (named also Waarp or GG).
*
* Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
* tags. See the COPYRIGHT.txt in the distribution for a full listing of
* individual contributors.
*
* All Waarp Project is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Waarp . If not, see <http://www.gnu.org/licenses/>.
*/
package org.waarp.common.file;
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.waarp.common.command.exception.Reply550Exception;
import org.waarp.common.digest.FilesystemBasedDigest;
import org.waarp.common.logging.SysErrLogger;
import java.io.File;
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.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import static com.google.common.base.Preconditions.*;
/**
* File Utils
*/
public final class FileUtils {
public static final int ZERO_COPY_CHUNK_SIZE = 64 * 1024;
private static final File[] FILE_0_LENGTH = new File[0];
private FileUtils() {
}
public static void close(final Reader stream) {
if (stream == null) {
return;
}
try {
stream.close();
} catch (final Exception ignored) {
SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
}
}
public static void close(final Writer stream) {
if (stream == null) {
return;
}
try {
stream.flush();
} catch (final Exception ignored) {
SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
}
try {
stream.close();
} catch (final Exception ignored) {
SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
}
}
public static void close(final InputStream stream) {
if (stream == null) {
return;
}
try {
stream.close();
} catch (final Exception ignored) {
SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
}
}
public static void close(final OutputStream stream) {
if (stream == null) {
return;
}
try {
stream.flush();
} catch (final Exception ignored) {
SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
}
try {
stream.close();
} catch (final Exception ignored) {
SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
}
}
public static void close(final RandomAccessFile accessFile) {
if (accessFile == null) {
return;
}
try {
accessFile.close();
} catch (final Exception ignored) {
SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
}
}
/**
* Delete the directory associated with the File as path if empty
*
* @param directory
*
* @return True if deleted, False else.
*/
public static boolean deleteDir(final File directory) {
if (directory == null) {
return true;
}
if (!directory.exists()) {
return true;
}
if (!directory.isDirectory()) {
return false;
}
return directory.delete();
}
/**
* Delete physically the file
*
* @param file
*
* @return True if OK, else if not (or if the file never exists).
*/
public static boolean delete(final File file) {
if (!file.exists()) {
return true;
}
return file.delete();
}
/**
* Copy from a directory to a directory with recursivity
*
* @param from
* @param directoryTo
* @param move True if the copy is in fact a move operation
*
* @return the group of copy files or null (partially or totally) if an
* error occurs
*
* @throws Reply550Exception
*/
public static File[] copyRecursive(final File from, final File directoryTo,
final boolean move)
throws Reply550Exception {
if (from == null || directoryTo == null) {
return null;
}
final ArrayList<File> to = new ArrayList<File>();
if (createDir(directoryTo)) {
final File[] subfrom = from.listFiles();
if (subfrom != null) {
for (final File element : subfrom) {
if (element.isFile()) {
to.add(copyToDir(element, directoryTo, move));
} else {
final File newTo = new File(directoryTo, element.getName());
newTo.mkdirs(); //NOSONAR
final File[] copied = copyRecursive(element, newTo, move);
to.addAll(Arrays.asList(copied));
}
}
}
}
return to.toArray(FILE_0_LENGTH);
}
/**
* Copy a group of files to a directory (will ignore directories)
*
* @param from
* @param directoryTo
* @param move True if the copy is in fact a move operation
*
* @return the group of copy files or null (partially or totally) if an
* error occurs
*
* @throws Reply550Exception
*/
public static File[] copy(final File[] from, final File directoryTo,
final boolean move) throws Reply550Exception {
if (from == null || directoryTo == null) {
return null;
}
File[] to = null;
if (createDir(directoryTo)) {
to = new File[from.length];
for (int i = 0; i < from.length; i++) {
if (from[i].isFile()) {
to[i] = copyToDir(from[i], directoryTo, move);
}
}
}
return to;
}
/**
* Create the directory associated with the File as path
*
* @param directory
*
* @return True if created, False else.
*/
public static boolean createDir(final File directory) {
if (directory == null) {
return false;
}
if (directory.isDirectory()) {
return true;
}
return directory.mkdirs();
}
/**
* Copy one file to a directory
*
* @param from
* @param directoryTo
* @param move True if the copy is in fact a move operation
*
* @return The copied file or null if an error occurs
*
* @throws Reply550Exception
*/
private static File copyToDir(final File from, final File directoryTo,
final boolean move) throws Reply550Exception {
if (from == null || directoryTo == null) {
throw new Reply550Exception("Source or Destination is null");
}
if (!from.isFile()) {
throw new Reply550Exception("Source file is a directory");
}
if (createDir(directoryTo)) {
final File to = new File(directoryTo, from.getName());
copy(from, to, move, false);
return to;
}
throw new Reply550Exception("Cannot access to parent dir of destination");
}
/**
* Copy one file to another one
*
* @param from
* @param to
* @param move True if the copy is in fact a move operation
* @param append True if the copy is in append
*
* @throws Reply550Exception
*/
public static void copy(final File from, final File to, final boolean move,
final boolean append) throws Reply550Exception {
if (from == null || to == null) {
throw new Reply550Exception("Source or Destination is null");
}
if (!from.isFile()) {
throw new Reply550Exception("Source file is a directory");
}
final File directoryTo = to.getParentFile();
if (createDir(directoryTo)) {
if (move && from.renameTo(to)) {
return;
}
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
try {
try {
inputStream = new FileInputStream(from.getPath());
} catch (final FileNotFoundException e) {
throw new Reply550Exception("Cannot read source file");
}
try {
outputStream = new FileOutputStream(to.getPath(), append);
} catch (final FileNotFoundException e) {
throw new Reply550Exception("Cannot write destination file");
}
try {
copy(inputStream, outputStream);
} catch (final IOException e) {
throw new Reply550Exception("Cannot copy, e");
}
} finally {
close(outputStream);
close(inputStream);
}
if (move) {
// do not test the delete
from.delete(); // NOSONAR
}
return;
}
throw new Reply550Exception("Cannot access to parent dir of destination");
}
/**
* Delete the directory and its subdirs associated with the File dir if
* empty
*
* @param dir
*
* @return True if deleted, False else.
*/
private static boolean deleteRecursiveFileDir(final File dir) {
if (dir == null) {
return true;
}
boolean retour = true;
if (!dir.exists()) {
return true;
}
final File[] list = dir.listFiles();
if (list == null || list.length == 0) {
return dir.delete();
}
for (final File file : list) {
if (file.isDirectory()) {
if (!deleteRecursiveFileDir(file)) {
retour = false;
}
} else {
return false;
}
}
if (retour) {
retour = dir.delete();
}
return retour;
}
/**
* Delete all
*
* @param directory
*
* @return True if all sub directories or files are deleted, False else.
*/
private static boolean forceDeleteRecursiveSubDir(final File directory) {
if (directory == null) {
return true;
}
boolean retour = true;
if (!directory.exists()) {
return true;
}
if (!directory.isDirectory()) {
return directory.delete();
}
final File[] list = directory.listFiles();
if (list == null || list.length == 0) {
return true;
}
for (final File file : list) {
if (file.isDirectory()) {
if (!forceDeleteRecursiveSubDir(file)) {
retour = false;
}
} else {
if (!file.delete()) {
retour = false;
}
}
}
if (retour) {
retour = directory.delete();
}
return retour;
}
/**
* Delete all its subdirs and files associated, not itself
*
* @param directory
*
* @return True if all sub directories or files are deleted, False else.
*/
public static boolean forceDeleteRecursiveDir(final File directory) {
if (directory == null) {
return true;
}
boolean retour = true;
if (!directory.exists()) {
return true;
}
if (!directory.isDirectory()) {
return false;
}
final File[] list = directory.listFiles();
if (list == null || list.length == 0) {
return true;
}
for (final File file : list) {
if (file.isDirectory()) {
if (!forceDeleteRecursiveSubDir(file)) {
retour = false;
}
} else {
if (!file.delete()) {
retour = false;
}
}
}
return retour;
}
/**
* Delete the directory and its subdirs associated with the File as path if
* empty
*
* @param directory
*
* @return True if deleted, False else.
*/
public static boolean deleteRecursiveDir(final File directory) {
if (directory == null) {
return true;
}
boolean retour = true;
if (!directory.exists()) {
return true;
}
if (!directory.isDirectory()) {
return false;
}
final File[] list = directory.listFiles();
if (list == null || list.length == 0) {
retour = directory.delete();
return retour;
}
for (final File file : list) {
if (file.isDirectory()) {
if (!deleteRecursiveFileDir(file)) {
retour = false;
}
} else {
retour = false;
}
}
if (retour) {
retour = directory.delete();
}
return retour;
}
/**
* @param fileName
* @param path
*
* @return true if the file exist in the specified path
*/
public static boolean fileExist(final String fileName, final String path) {
boolean exist = false;
final String fileString = path + File.separator + fileName;
final File file = new File(fileString);
if (file.exists()) {
exist = true;
}
return exist;
}
/**
* Get the list of files from a given directory
*
* @param directory
*
* @return the list of files (as an array)
*/
public static File[] getFiles(final File directory) {
if (directory == null || !directory.isDirectory()) {
return FILE_0_LENGTH;
}
return directory.listFiles();
}
/**
* Compute global hash (if possible) from a file but up to length
*
* @param digest
* @param file
* @param length
*/
public static void computeGlobalHash(final FilesystemBasedDigest digest,
final File file, final int length) {
if (digest == null) {
return;
}
final byte[] bytes = new byte[ZERO_COPY_CHUNK_SIZE];
int still = length;
int len = Math.min(still, ZERO_COPY_CHUNK_SIZE);
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
int read = 1;
while (read > 0) {
read = inputStream.read(bytes, 0, len);
if (read <= 0) {
break;
}
digest.Update(bytes, 0, read);
still -= read;
if (still <= 0) {
break;
}
len = Math.min(still, ZERO_COPY_CHUNK_SIZE);
}
} catch (final FileNotFoundException e) {
// error
} catch (final IOException e) {
// error
} finally {
close(inputStream);
}
}
/**
* Get the list of files from a given directory and a filter
*
* @param directory
* @param filter
*
* @return the list of files (as an array)
*/
public static File[] getFiles(final File directory,
final FilenameFilter filter) {
if (directory == null || !directory.isDirectory()) {
return FILE_0_LENGTH;
}
return directory.listFiles(filter);
}
/**
* Read one compressed file and return the associated BufferedReader
*
* @param fileIn
* @param fileOut
*
* @return the size of the uncompressed file or -1 if an error occurs
*/
public static long uncompressedBz2File(final File fileIn,
final File fileOut) {
FileInputStream fin = null;
CompressorInputStream input = null;
FileOutputStream output = null;
try {
fin = new FileInputStream(fileIn);
input = new BZip2CompressorInputStream(fin);
output = new FileOutputStream(fileOut);
return copy(input, output);
} catch (final IOException e) {
SysErrLogger.FAKE_LOGGER.syserr(e);
return -1;
} finally {
FileUtils.close(output);
FileUtils.close(input);
FileUtils.close(fin);
}
}
/**
* From Google Guava copy within ByteStreams
*
* Copies all bytes from the input stream to the output stream. Does not close or flush either
* stream.
*
* @param from the input stream to read from
* @param to the output stream to write to
*
* @return the number of bytes copied
*
* @throws IOException if an I/O error occurs
*/
public static long copy(final InputStream from, final OutputStream to)
throws IOException {
checkNotNull(from);
checkNotNull(to);
final byte[] buf = new byte[ZERO_COPY_CHUNK_SIZE];
long total = 0;
while (true) {
final int r = from.read(buf);
if (r == -1) {
break;
}
to.write(buf, 0, r);
total += r;
}
return total;
}
/**
* Helper method to write from InputStream to OutputStream,
* closing streams
*
* @param blockSize
* @param inputStream
* @param out
*
* @throws IOException
*/
public static void copy(final int blockSize, final InputStream inputStream,
final OutputStream out) throws IOException {
final byte[] buffer = new byte[blockSize];
while (true) {
final int r = inputStream.read(buffer);
if (r == -1) {
break;
}
out.write(buffer, 0, r);
}
out.flush();
close(out);
}
/**
* Check if the file (directory or file) is in directory dir or in sub
* directories of dir
*
* @param dir
* @param file
*
* @return True if in (sub) directory (of) dir
*/
public static boolean isInSubdirectory(final File dir, final File file) {
if (file == null || dir == null) {
return false;
}
return file.equals(dir) || isInSubdirectory(dir, file.getParentFile());
}
}