com.norconex.commons.lang.file.FileUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of norconex-commons-lang Show documentation
Show all versions of norconex-commons-lang Show documentation
Norconex Commons Lang is a Java library containing utility classes that complements the Java API and are not found in commonly available libraries (such as the great Apache Commons Lang, which it relies on).
/* Copyright 2010-2016 Norconex Inc.
*
* 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.norconex.commons.lang.file;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.LinkedList;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import com.norconex.commons.lang.Sleeper;
import com.norconex.commons.lang.io.IInputStreamFilter;
import com.norconex.commons.lang.io.ReverseFileInputStream;
/**
* Utility methods when dealing with files and directories.
* @author Pascal Essiembre
*/
public final class FileUtil {
private static final Logger LOG = LogManager.getLogger(FileUtil.class);
private static final int MAX_FILE_OPERATION_ATTEMPTS = 10;
private FileUtil() {
super();
}
/**
* Converts any String to a valid file-system file name representation. The
* valid file name is constructed so it can be written to virtually any
* operating system.
* Use {@link #fromSafeFileName(String)} to get back the original name.
* @param unsafeFileName the file name to make safe.
* @return valid file name
*/
public static String toSafeFileName(String unsafeFileName) {
if (unsafeFileName == null) {
return null;
}
StringBuilder b = new StringBuilder();
for (int i = 0; i < unsafeFileName.length(); i++){
char ch = unsafeFileName.charAt(i);
if (CharUtils.isAsciiAlphanumeric(ch) || ch == '-' || ch == '.') {
b.append(ch);
} else {
b.append('_');
b.append((int) ch);
b.append('_');
}
}
return b.toString();
}
/**
* Converts a "safe" file name originally created with
* {@link #toSafeFileName(String)} into its original string.
* @param safeFileName the file name to convert to its origianl form
* @return original string
*/
public static String fromSafeFileName(String safeFileName) {
if (safeFileName == null) {
return null;
}
StringBuilder b = new StringBuilder();
for (int i = 0; i < safeFileName.length(); i++){
char ch = safeFileName.charAt(i);
if (ch == '_') {
String intVal = StringUtils.substring(safeFileName, i + 1,
StringUtils.indexOf(safeFileName, '_', i + 1));
b.append((char) NumberUtils.toInt(intVal));
i += intVal.length() + 1;
} else {
b.append(ch);
}
}
return b.toString();
}
/**
* Moves a file to a directory. Like {@link #moveFile(File, File)}:
*
* - If the target directory does not exists, it creates it first.
* - If the target file already exists, it deletes it first.
* - If target file deletion does not work, it will try 10 times,
* waiting 1 second between each try to give a chance to whatever
* OS lock on the file to go.
* - It throws a IOException if the move failed (as opposed to fail
* silently).
*
* @param sourceFile source file to move
* @param targetDir target destination
* @throws IOException cannot move file.
*/
public static void moveFileToDir(File sourceFile, File targetDir)
throws IOException {
if (sourceFile == null || !sourceFile.isFile()) {
throw new IOException("Source file is not valid: " + sourceFile);
}
if (targetDir == null ||
targetDir.exists() && !targetDir.isDirectory()) {
throw new IOException("Target directory is not valid:" + targetDir);
}
if (!targetDir.exists()) {
FileUtils.forceMkdir(targetDir);
}
String fileName = sourceFile.getName();
File targetFile = new File(targetDir.getAbsolutePath()
+ SystemUtils.PATH_SEPARATOR + fileName);
moveFile(sourceFile, targetFile);
}
/**
* Moves a file to a new file location. This method is different from the
* {@link File#renameTo(File)} method in such that:
*
* - If the target file already exists, it deletes it first.
* - If target file deletion does not work, it will try 10 times,
* waiting 1 second between each try to give a chance to whatever
* OS lock on the file to go.
* - It throws a IOException if the move failed (as opposed to fail
* silently).
*
* @param sourceFile source file to move
* @param targetFile target destination
* @throws IOException cannot move file.
*/
public static void moveFile(File sourceFile, File targetFile)
throws IOException {
if (!isFile(sourceFile)) {
throw new IOException(
"Source file is not a file or is not valid: " + sourceFile);
}
if (targetFile == null || targetFile.exists() && !targetFile.isFile()) {
throw new IOException(
"Target file is not a file or is not valid: " + targetFile);
}
int failure = 0;
Exception ex = null;
while (failure < MAX_FILE_OPERATION_ATTEMPTS) {
if (targetFile.exists() && !targetFile.delete()
|| !sourceFile.renameTo(targetFile)) {
failure++;
Sleeper.sleepSeconds(1);
continue;
}
break;
}
if (failure >= MAX_FILE_OPERATION_ATTEMPTS) {
throw new IOException(
"Could not move \"" + sourceFile + "\" to \""
+ targetFile + "\".", ex);
}
}
/**
* Deletes a file or a directory recursively in a more robust way.
* This method applies the following strategies:
*
* - If file or directory deletion does not work, it will re-try 10
* times, waiting 1 second between each try to give a chance to
* whatever OS lock on the file to go.
* - After a first failed attempt, it invokes {@link System#gc()}
* in hope of releasing any handles left on files. This is in
* relation to a known Java bug mostly occurring on Windows
* (http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4715154).
* - It throws a IOException if the delete still fails after the 10
* attempts (as opposed to fail silently).
* - If file is
null
or does not exist, nothing happens.
*
* @param file file or directory to delete
* @throws IOException cannot delete file.
* @since 1.4. Renamed from deleteFile(File)
*/
public static void delete(File file) throws IOException {
if (file == null || !file.exists()) {
return;
}
boolean success = false;
int failure = 0;
while (!success && failure < MAX_FILE_OPERATION_ATTEMPTS) {
if (file.exists() && !FileUtils.deleteQuietly(file)) {
failure++;
System.gc();
Sleeper.sleepSeconds(1);
continue;
}
success = true;
}
if (!success) {
throw new IOException(
"Could not delete \"" + file + "\".");
}
}
/**
* @deprecated renamed to {@link #delete(File)}.
* @param file file or directory to delete
* @throws IOException cannot delete file.
*/
@Deprecated
public static void deleteFile(File file) throws IOException {
delete(file);
}
/**
* Deletes all directories that are empty from a given parent directory.
* @param parentDir the directory where to start looking for empty
* directories
* @return the number of deleted directories
*/
public static int deleteEmptyDirs(File parentDir) {
return deleteEmptyDirs(parentDir, null);
}
/**
* Deletes all directories that are empty and are older
* than the given date. If the date is null
, all empty
* directories will be deleted, regardless of their date.
* @param parentDir the directory where to start looking for empty
* directories
* @param date the date to compare empty directories against
* @return the number of deleted directories
* @since 1.3.0
*/
public static int deleteEmptyDirs(File parentDir, final Date date) {
final MutableInt dirCount = new MutableInt(0);
visitEmptyDirs(parentDir, new IFileVisitor() {
@Override
public void visit(File file) {
if (date == null || FileUtils.isFileOlder(file, date)) {
String[] children = file.list();
if (file.isDirectory()
&& (children == null || children.length == 0)) {
try {
FileUtil.delete(file);
dirCount.increment();
} catch (IOException e) {
LOG.error("Could not be delete directory: "
+ file, e);
}
}
}
}
});
return dirCount.intValue();
}
/**
* Create all parent directories for a file if they do not exists.
* If they exist already, this method does nothing. This method assumes
* the last segment is a file or will be a file.
* @param file the file to create parent directories for
* @return The newly created parent directory
* @throws IOException if something went wrong creating the parent
* directories
*/
public static File createDirsForFile(File file) throws IOException {
File parent = file.getParentFile();
if (parent != null) {
FileUtils.forceMkdir(parent);
return parent;
}
return new File("/");
}
/**
* Visits all files and directories under a directory.
* @param dir the directory
* @param visitor the visitor
*/
public static void visitAllDirsAndFiles(File dir, IFileVisitor visitor) {
visitAllDirsAndFiles(dir, visitor, null);
}
/**
* Visits all files and directories under a directory.
* @param dir the directory
* @param visitor the visitor
* @param filter an optional filter to restrict the files being visited
*/
public static void visitAllDirsAndFiles(
File dir, IFileVisitor visitor, FileFilter filter) {
visitor.visit(dir);
if (!dir.exists()) {
return;
} else if (dir.isDirectory()) {
File[] children = dir.listFiles(filter);
if (children != null) {
for (int i=0; i lines = new LinkedList<>();
BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(file), encoding));
int remainingLinesToRead = numberOfLinesToRead;
String line = StringUtils.EMPTY;
while(line != null && remainingLinesToRead-- > 0){
line = reader.readLine();
if (!stripBlankLines || StringUtils.isNotBlank(line)) {
if (filter != null && filter.accept(line)) {
lines.addFirst(line);
} else {
remainingLinesToRead++;
}
} else {
remainingLinesToRead++;
}
}
reader.close();
return lines.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
}
/**
* Returns the specified number of lines starting from the end
* of a text file.
* Since 1.5.0, UTF-8 is used as the default encoding.
* @param file the file to read lines from
* @param numberOfLinesToRead the number of lines to read
* @return array of file lines
* @throws IOException i/o problem
*/
public static String[] tail(File file, int numberOfLinesToRead)
throws IOException {
return tail(file, CharEncoding.UTF_8, numberOfLinesToRead);
}
/**
* Returns the specified number of lines starting from the end
* of a text file.
* @param file the file to read lines from
* @param encoding the file encoding
* @param numberOfLinesToRead the number of lines to read
* @return array of file lines
* @throws IOException i/o problem
*/
public static String[] tail(File file, String encoding,
int numberOfLinesToRead) throws IOException {
return tail(file, encoding, numberOfLinesToRead, true);
}
/**
* Returns the specified number of lines starting from the end
* of a text file.
* @param file the file to read lines from
* @param encoding the file encoding
* @param numberOfLinesToRead the number of lines to read
* @param stripBlankLines whether to return blank lines or not
* @return array of file lines
* @throws IOException i/o problem
*/
public static String[] tail(File file, String encoding,
int numberOfLinesToRead, boolean stripBlankLines)
throws IOException {
return tail(file, encoding, numberOfLinesToRead, stripBlankLines, null);
}
/**
* Returns the specified number of lines starting from the end
* of a text file.
* @param file the file to read lines from
* @param encoding the file encoding
* @param numberOfLinesToRead the number of lines to read
* @param stripBlankLines whether to return blank lines or not
* @param filter InputStream filter
* @return array of file lines
* @throws IOException i/o problem
*/
public static String[] tail(File file, String encoding,
final int numberOfLinesToRead, boolean stripBlankLines,
IInputStreamFilter filter)
throws IOException {
assertFile(file);
assertNumOfLinesToRead(numberOfLinesToRead);
LinkedList lines = new LinkedList<>();
BufferedReader reader = new BufferedReader(new InputStreamReader(
new ReverseFileInputStream(file), encoding));
int remainingLinesToRead = numberOfLinesToRead;
String line;
while ((line = reader.readLine()) != null) {
if (remainingLinesToRead-- <= 0) {
break;
}
String newLine = StringUtils.reverse(line);
if (!stripBlankLines || StringUtils.isNotBlank(line)) {
if (filter != null && filter.accept(newLine)) {
lines.addFirst(newLine);
} else {
remainingLinesToRead++;
}
} else {
remainingLinesToRead++;
}
}
reader.close();
return lines.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
}
/**
* Creates (if not already existing) a series of directories reflecting
* the current date, up to the day unit, under a given parent directory.
* For example, a date of 2000-12-31 will create the following directory
* structure:
*
* /<parentDir>/2000/12/31/
*
* @param parentDir the parent directory where to create date directories
* @return the directory representing the full path created
* @throws IOException if the parent directory is not valid
*/
public static File createDateDirs(File parentDir) throws IOException {
return createDateDirs(parentDir, new Date());
}
/**
* Creates (if not already existing) a series of directories reflecting
* a date, up to the day unit, under a given parent directory. For example,
* a date of 2000-12-31 will create the following directory structure:
*
* /<parentDir>/2000/12/31/
*
* @param parentDir the parent directory where to create date directories
* @param date the date to create directories from
* @return the directory representing the full path created
* @throws IOException if the parent directory is not valid
*/
public static File createDateDirs(File parentDir, Date date)
throws IOException {
if (parentDir == null) {
throw new IOException("Parent directory cannot be null.");
}
if (date == null) {
throw new IOException("Date cannot be null.");
}
if (parentDir.exists() && !parentDir.isDirectory()) {
throw new IOException("Parent directory \"" + parentDir
+ "\" already exists and is not a directory.");
}
File dateDir = new File(parentDir.getAbsolutePath()
+ "/" + DateFormatUtils.format(date, "yyyy/MM/dd"));
FileUtils.forceMkdir(dateDir);
return dateDir;
}
/**
* Creates (if not already existing) a series of directories reflecting
* the current date and time, up to the seconds, under a given parent
* directory. For example, a date of 2000-12-31T13:34:12 will create the
* following directory structure:
*
* /<parentDir>/2000/12/31/13/34/12/
*
* @param parentDir the parent directory where to create date directories
* @return the directory representing the full path created
* @throws IOException if the parent directory is not valid
*/
public static File createDateTimeDirs(File parentDir) throws IOException {
return createDateTimeDirs(parentDir, new Date());
}
/**
* Creates (if not already existing) a series of directories reflecting
* a date and time, up to the seconds, under a given parent directory.
* For example,
* a date of 2000-12-31T13:34:12 will create the following directory
* structure:
*
* /<parentDir>/2000/12/31/13/34/12/
*
* @param parentDir the parent directory where to create date directories
* @param dateTime the date to create directories from
* @return the directory representing the full path created
* @throws IOException if the parent directory is not valid
*/
public static File createDateTimeDirs(File parentDir, Date dateTime)
throws IOException {
if (parentDir == null) {
throw new IOException("Parent directory cannot be null.");
}
if (dateTime == null) {
throw new IOException("Date cannot be null.");
}
if (parentDir.exists() && !parentDir.isDirectory()) {
throw new IOException("Parent directory \"" + parentDir
+ "\" already exists and is not a directory.");
}
File dateDir = new File(parentDir.getAbsolutePath()
+ "/" + DateFormatUtils.format(
dateTime, "yyyy/MM/dd/HH/mm/ss"));
FileUtils.forceMkdir(dateDir);
return dateDir;
}
private static void assertNumOfLinesToRead(int num) {
if (num <= 0) {
throw new IllegalArgumentException(
"Not a valid number to read: " + num);
}
}
private static void assertFile(File file) throws IOException {
if (file == null || !file.exists()
|| !file.isFile() || !file.canRead()) {
throw new IOException("Not a valid file: " + file);
}
}
//TODO make public as a null-safe method, along with similar methods?
private static boolean isFile(File file) {
return file != null && file.isFile();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy