All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.norconex.commons.lang.file.FileUtil Maven / Gradle / Ivy

Go to download

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).

There is a newer version: 2.0.2
Show newest version
/* 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