package com.artfulbits.io;
import com.artfulbits.utils.CleanupUtils;
import com.artfulbits.utils.LogEx;
import com.artfulbits.utils.StringUtils;
import com.artfulbits.utils.ValidUtils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.logging.Logger;
/** Collection of utility methods that helps make file IO operations faster and safer. */
public class FileUtils {
/* [ CONSTANTS ] ======================================================================================================================================= */
/** Our own class Logger instance. */
private final static Logger _log = LogEx.getLogger(FileUtils.class);
/** Name of the digest algorithm - MD5. */
public final static String MD5 = "MD5";
/** Size of hash calculation buffer. Default: 4Kb. */
private final static int BUFFER_SIZE = 4 * 1024;
/** Default size of read/write operation buffer. Default: 16Kb. */
private final static int BUFFER_READ_WRITE_SIZE = 4 * BUFFER_SIZE;
/** Set of known to us measurement units. */
private final static String[] Units = new String[]{
"B",
"KiB",
"MiB",
"GiB",
"TiB"};
/* [ CONSTRUCTORS ] ==================================================================================================================================== */
/** Hidden constructor. */
private FileUtils() {
throw new AssertionError();
}
/* [ STATIC METHODS ] ================================================================================================================================== */
public static FileInputStream safeInput(final File file) {
ValidUtils.isNull(file, "File instance required.");
FileInputStream input = null;
if (exists(file, 0, -1)) {
try {
input = new FileInputStream(file);
} catch (final FileNotFoundException ignored) {
_log.warning(LogEx.dump(ignored));
}
}
return input;
}
public static BufferedInputStream safeBufferedInput(final File file) {
final InputStream fis = safeInput(file);
BufferedInputStream bis = null;
if (null != fis) {
bis = new BufferedInputStream(fis, BUFFER_READ_WRITE_SIZE);
}
return bis;
}
public static FileOutputStream safeOutput(final File file) {
ValidUtils.isNull(file, "File instance required.");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
} catch (FileNotFoundException ignored) {
_log.warning(LogEx.dump(ignored));
}
return fos;
}
public static BufferedOutputStream safeBufferedOutput(final File file) {
final OutputStream fos = safeOutput(file);
BufferedOutputStream bos = null;
if (null != fos) {
bos = new BufferedOutputStream(fos, BUFFER_READ_WRITE_SIZE);
}
return bos;
}
public static BufferedReader safeReader(final File file) {
ValidUtils.isNull(file, "File instance required.");
BufferedReader input = null;
if (file.exists()) {
try {
input = new BufferedReader(new FileReader(file), BUFFER_READ_WRITE_SIZE);
} catch (FileNotFoundException ignored) {
_log.warning(LogEx.dump(ignored));
}
}
return input;
}
/**
* Copy source file to destination. In case of success return true.
*
* @param src source file.
* @param dst destination file.
* @return True - success, otherwise False.
*/
public static boolean safeCopy(final File src, final File dst) {
try {
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst);
copy(in, out);
return true;
} catch (Throwable ignored) {
_log.warning(LogEx.dump(ignored));
}
return false;
}
/**
* Copy input stream to output without exceptions raising.
*
* @param in instance of input stream.
* @param out instance of output stream.
* @return True - success, otherwise False.
*/
public static boolean safeCopy(final InputStream in, final OutputStream out) {
ValidUtils.isNull(in, "InputStream instance required.");
ValidUtils.isNull(out, "OutputStream instance required.");
try {
copy(in, out);
return true;
} catch (Throwable ignored) {
_log.warning(LogEx.dump(ignored));
}
return false;
}
/**
* Extract input stream content to byte array.
*
* @param is input stream instance.
* @return extracted bytes.
*/
public static byte[] toBytes(final InputStream is) {
ValidUtils.isNull(is, "InputStream instance required.");
final ByteArrayOutputStream baos = new ByteArrayOutputStream(BUFFER_READ_WRITE_SIZE);
try {
copy(is, baos);
} catch (final IOException ignored) {
_log.warning(LogEx.dump(ignored));
}
return baos.toByteArray();
}
/**
* Extract from input stream content into string.
*
* @param is input stream instance.
* @return extracted string.
*/
public static String toString(final InputStream is) {
ValidUtils.isNull(is, "InputStream instance required.");
StringWriter writer = new StringWriter(BUFFER_READ_WRITE_SIZE);
InputStreamReader isr = new InputStreamReader(is);
try {
copy(isr, writer);
} catch (final IOException ignored) {
_log.warning(LogEx.dump(ignored));
}
CleanupUtils.destroy(isr);
return writer.toString();
}
/**
* Prepare file storage for data. Create folders if needed. Drop existing file, if found one.
*
* @param file instance of file.
* @param doDrop True - if drop of file required, otherwise False.
* @return True - success, otherwise False.
*/
public static boolean prepareStorage(final File file, boolean doDrop) {
final String dir = file.getParent();
if (file.exists()) {
if (doDrop) {
return file.delete();
}
return true;
}
if (new File(dir).exists()) {
return true;
}
if (new File(dir).mkdirs()) {
return true;
}
return false;
}
/**
* Check that file exists and it length in range of expected.
*
* @param file reference on file.
* @param moreThan -1 - if check should be skipped, otherwise size of file more which it should
* be.
* @param lessThan -1 - if check should be skipped, otherwise size of file less which it should
* be.
* @return True - if file match criteria, otherwise False.
*/
public static boolean exists(final File file, long moreThan, long lessThan) {
ValidUtils.isNull(file, "File instance required.");
boolean exist = file.exists();
boolean less = (lessThan < 0 ? true : (file.length() < lessThan));
boolean more = (moreThan < 0 ? true : (file.length() > moreThan));
return (exist && less && more);
}
/**
* Copy source file to destination.
*
* @param src Source file instance.
* @param dst Destination file instance.
* @throws IOException read/write errors.
*/
public static void copy(final File src, final File dst) throws IOException {
ValidUtils.isNull(src, "Source file instance required.");
ValidUtils.isNull(dst, "Destination file instance required.");
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst);
copy(in, out);
}
/**
* Copy input stream to output.
*
* @param in instance of input stream.
* @param out instance of output stream.
* @throws IOException read/write operation failure.
*/
public static void copy(final InputStream in, final OutputStream out) throws IOException {
ValidUtils.isNull(in, "InputStream instance required.");
ValidUtils.isNull(out, "OutputStream instance required.");
// Transfer bytes from in to out
final byte[] buf = new byte[BUFFER_READ_WRITE_SIZE];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
}
/**
* Copy all from reader to writer.
*
* @param in instance of reader.
* @param out instance of writer.
* @throws IOException read/write operation failure.
*/
public static void copy(final Reader in, final Writer out) throws IOException {
char[] buffer = new char[BUFFER_READ_WRITE_SIZE];
int n = 0;
while (-1 != (n = in.read(buffer))) {
out.write(buffer, 0, n);
}
}
/**
* Copy source file or directory to the specified destination folder.
*
* @param sourceFileOrDir file path to source file or dir.
* @param destinationDir destination directory.
* @return -1 - in case of error, otherwise quantity of copied files.
*/
public static int copyToDir(final String sourceFileOrDir, final String destinationDir) {
int quantity = 0;
final File source = new File(sourceFileOrDir);
final File destination = new File(destinationDir);
// first create a destination directories
if (!destination.canWrite() || (!destination.exists() && !destination.mkdirs())) {
return -1;
}
// copy one file to destination directory
if (source.isFile()) {
final File copy = new File(destinationDir, source.getName());
quantity = (safeCopy(source, copy)) ? -1 : 1;
}
// source is a directory and we are copying all files from it
else if (source.isDirectory()) {
final String files[] = source.list();
for (int i = 0, len = files.length; i < len; i++) {
final File sourceNew = new File(sourceFileOrDir, files[i]);
final String destNew = (sourceNew.isDirectory()) ? destinationDir + File.separator + files[i]
: destinationDir;
int result = copyToDir(sourceNew.getAbsolutePath(), destNew);
if (result > 0) {
quantity += result;
}
}
}
return quantity;
}
/**
* Find all files from specified directory recursively.
*
* @param dir root directory.
* @return Collection of found files.
*/
public static ArrayList<File> recursiveFind(final File dir) {
ValidUtils.isNull(dir, "Directory file instance required");
return recursiveFind(new File[]{dir}, new ArrayList<File>());
}
/**
* Find all files recursively in array of files.
*
* @param files array of roots for search.
* @param searchResults collection of results.
* @return Collection of found files.
*/
public static ArrayList<File> recursiveFind(final File[] files, final ArrayList<File> searchResults) {
int i = 0;
if (null != files) {
while (i != files.length) {
final File subFile = files[i];
if (subFile.isFile()) {
searchResults.add(subFile);
} else if (subFile.isDirectory()) {
final File subFiles[] = subFile.listFiles();
recursiveFind(subFiles, searchResults);
}
i++;
}
}
return searchResults;
}
/**
* Calculate MD5 checksum for file.
*
* @param filename file to check
* @return calculated hash.
*
* @throws Exception read/write exception.
*/
public static byte[] createChecksum(final File filename) throws Exception {
final InputStream fis = new BufferedInputStream(new FileInputStream(filename), BUFFER_READ_WRITE_SIZE);
final byte[] checkSum = createChecksum(fis);
fis.close();
return checkSum;
}
/**
* Calculate MD5 checksum for input stream.
*
* @param fis instance of input stream.
* @return calculated hash.
*
* @throws Exception read/write exception.
*/
public static byte[] createChecksum(final InputStream fis) throws Exception {
final byte[] buffer = new byte[BUFFER_SIZE];
final MessageDigest complete = MessageDigest.getInstance(MD5);
int numRead;
do {
if ((numRead = fis.read(buffer)) > 0) {
complete.update(buffer, 0, numRead);
}
}
while (numRead != -1);
return complete.digest();
}
/**
* Calculate file MD5 hash checksum and return it as hex string.
*
* @param input input as a string value.
* @return calculated checksum hex string.
*
* @throws Exception something wrong with MD5 digest access.
*/
public static String md5(final String input) throws Exception {
final byte[] buffer = input.getBytes(StringUtils.UTF8);
final MessageDigest complete = MessageDigest.getInstance(MD5);
complete.update(buffer);
return toHex(complete.digest());
}
/**
* Calculate file MD5 hash checksum and return it as hex string.
*
* @param file file to check
* @return calculated checksum hex string.
*
* @throws Exception something wrong with file access.
*/
public static String md5(final File file) throws Exception {
final byte[] b = createChecksum(file);
return toHex(b);
}
/**
* Calculate file MD5 hash checksum and return it as hex string.
*
* @param is Input stream
* @return calculated checksum hex string.
*
* @throws Exception something wrong with file access.
*/
public static String md5(final InputStream is) throws Exception {
final byte[] b = createChecksum(is);
return toHex(b);
}
/**
* Convert byte array to hex string.
*
* @param b data to convert.
* @return Result of convert.
*/
public static String toHex(final byte[] b) {
final StringBuilder result = new StringBuilder(256);
for (int i = 0; i < b.length; i++) {
result.append(Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1));
}
return result.toString();
}
/**
* return text representation of file size.
*
* @param f file which size to format.
* @return formatted file size.
*/
public static String fileSize(final File f) {
ValidUtils.isNull(f, "File instance required.");
final long length = f.length();
final int digitGroups = (int) (Math.log10(length) / Math.log10(1024));
final double value = (double) length / Math.pow(1024, digitGroups);
// normalize unit index
final int index = Math.max(0, Math.min(digitGroups, Units.length - 1));
return new java.text.DecimalFormat("#,##0.# ").format(value) + Units[index];
}
}