/*
* Rapid Beans Framework: FileHelper.java
*
* Copyright (C) 2009 Martin Bluemel
*
* Creation Date: 11/09/2005
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation;
* either version 3 of the License, or (at your option) any later version.
* This program 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 Lesser General Public License for more details.
* You should have received a copies of the GNU Lesser General Public License and the
* GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
package org.rapidbeans.core.util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;
import org.rapidbeans.core.basic.PropertyDate;
import org.rapidbeans.core.common.PrecisionDate;
import org.rapidbeans.core.exception.RapidBeansRuntimeException;
import org.rapidbeans.core.exception.UtilException;
/**
* A helper class for files handling.
*
* @author Martin Bluemel
*/
public final class FileHelper {
private static final Logger log = Logger.getLogger(FileHelper.class.getName());
/**
* copies directory trees.
*
* @param src
* the source directory
* @param tgt
* the target directory
*/
public static void copyDeep(final File src, final File tgt) {
copyDeep(src, tgt, true, false);
}
/**
* copies directory trees.
*
* @param src
* the source directory
* @param tgt
* the target directory
* @param force
* the force flag
*/
public static void copyDeep(final File src, final File tgt, final boolean force) {
copyDeep(src, tgt, force, false);
}
/**
* copies directory trees.
*
* @param src
* the source directory
* @param tgt
* the target directory
* @param force
* the force flag
* @param excludeSvnDirs
* flag
*/
public static void copyDeep(final File src, final File tgt, final boolean force, final boolean excludeSvnDirs) {
if (!src.exists()) {
throw new UtilException("source folder \"" + src.getAbsolutePath() + "\" not found.");
}
if (!src.isDirectory()) {
throw new UtilException("source file \"" + src.getAbsolutePath() + "\" isn't a folder.");
}
if (!tgt.exists()) {
if (!tgt.mkdirs()) {
throw new UtilException("Creation of target folder \"" + tgt.getAbsolutePath() + "\" failed.");
}
}
if (!tgt.isDirectory()) {
throw new UtilException("target file \"" + tgt.getAbsolutePath() + "\" isn't a folder.");
}
File[] subfiles = src.listFiles();
File subtgt;
for (int i = 0; i < subfiles.length; i++) {
subtgt = new File(tgt, subfiles[i].getName());
if (subfiles[i].isDirectory()) {
if ((!excludeSvnDirs) || (!subfiles[i].getName().equals(".svn"))) {
copyDeep(subfiles[i], subtgt, force, excludeSvnDirs);
}
} else {
copyFile(subfiles[i], subtgt, force);
}
}
}
/**
* copies a source file over a target file.
*
* @param src
* the source file
* @param tgt
* the target file
*/
public static void copyFile(final File src, final File tgt) {
copyFile(src, tgt, false);
}
/**
* the buffer size for copy.
*/
private static final int BUF_SIZE = 4096;
/**
* the time to sleep.
*/
private static final int SLEEP_TIME_100 = 100;
/**
* copies a source file over a target file.
*
* @param src
* the source file
* @param tgt
* the target file
* @param force
* forces copy regardless of modification dates
*/
public static void copyFile(final File src, final File tgt, final boolean force) {
if (!src.exists()) {
throw new UtilException("source file \"" + src.getAbsolutePath() + "\" not found.");
}
if (tgt.exists()) {
if (!tgt.canWrite()) {
throw new UtilException("target file \"" + tgt.getAbsolutePath() + "\" is not writable.");
}
}
if (force || src.lastModified() > tgt.lastModified()) {
byte[] buf = new byte[BUF_SIZE];
try {
FileInputStream is = new FileInputStream(src);
FileOutputStream os = new FileOutputStream(tgt);
int bytesRead;
while ((bytesRead = is.read(buf)) != -1) {
if (bytesRead > 0) {
os.write(buf, 0, bytesRead);
} else {
Thread.sleep(SLEEP_TIME_100);
}
}
is.close();
os.close();
} catch (InterruptedException e) {
throw new UtilException("InterruptedException: " + e.getMessage());
} catch (FileNotFoundException e) {
throw new UtilException("FileNotFound: " + e.getMessage());
} catch (IOException e) {
throw new UtilException("IOException: " + e.getMessage());
}
}
}
/**
* Deletes a whole directory tree.
*
* @param del
* the directory or file to delete
*/
public static void deleteDeep(final File del) {
deleteDeep(del, false);
}
/**
* Deletes a whole directory tree.
*
* @param del
* the directory or file to delete
* @param force
* enforces deletion of read only files and directories
*/
public static void deleteDeep(final File del, final boolean force) {
if (del.canWrite() || force) {
if (del.isDirectory()) {
File[] subfiles = del.listFiles();
for (final File file : subfiles) {
deleteDeep(file, force);
}
if (!del.delete()) {
throw new UtilException("Could not delete directory \"" + del.getAbsolutePath() + "\"");
}
} else {
if (!del.delete()) {
throw new UtilException("Could not delete file \"" + del.getAbsolutePath() + "\"");
}
}
}
}
/**
* Compares all files in a directory tree.
*
* @param dir1
* the first directory
* @param sdir2
* the path to the second directory
*
* @return true if all files equal, false if there are more ore less or
* different files.
*/
public static boolean dirsEqual(final File dir1, final String sdir2) {
return dirsEqual(dir1, new File(sdir2), -1, false);
}
/**
* Compares all files in a directory tree but exclude directories that start
* with dot.
*
* @param dir1
* the first directory
* @param sdir2
* the path to the second directory
*
* @return true if all files equal, false if there are more ore less or
* different files.
*/
public static boolean dirsEqualExcludeDotDirs(final File dir1, final String sdir2) {
return dirsEqual(dir1, new File(sdir2), -1, true);
}
/**
* Compares all files in a directory tree in a configurable manner. - you
* can configure the depth of the compare - you can configure if sub
* directories that start with . should be excluded from the comparison.
* This is useful for instance if the test data is version controlled with
* Subversion.
*
* directories that start with dot.
*
* @param dir1
* the first directory
* @param dir2
* the second directory
* @param depth
* the depth of comparison in the directory tree. To specify
* infinite depth just set this parameter to a negative number e.
* g. -1.
* @param excludedotdirs
* if true sub directories that start with . are excluded from
* comparison.
*
* @return true if all files equal, false if there are more ore less or
* different files.
*/
public static boolean dirsEqual(final File dir1, final File dir2, final int depth, final boolean excludedotdirs) {
boolean equals = true;
if (!dir1.exists()) {
throw new UtilException("folder \"" + dir1.getAbsolutePath() + "\" not found.");
}
if (!dir1.isDirectory()) {
throw new UtilException("folder \"" + dir1.getAbsolutePath() + "\" is not a directory.");
}
if (!dir2.exists()) {
throw new UtilException("folder \"" + dir2.getAbsolutePath() + "\" not found.");
}
if (!dir2.isDirectory()) {
throw new UtilException("folder \"" + dir2.getAbsolutePath() + "\" is not a directory.");
}
if (!dir1.getName().equals(dir2.getName())) {
log.fine("folders have different names:");
log.fine("- folder 1: " + dir1.getAbsolutePath());
log.fine("- folder 2: " + dir2.getAbsolutePath());
equals = false;
} else {
File[] subfiles1 = null;
File[] subfiles2 = null;
if (excludedotdirs) {
subfiles1 = listFilesExcludeFilter(dir1, ".*");
} else {
subfiles1 = dir1.listFiles();
}
if (excludedotdirs) {
subfiles2 = listFilesExcludeFilter(dir2, ".*");
} else {
subfiles2 = dir2.listFiles();
}
if (subfiles1.length != subfiles2.length) {
log.fine("folders have a different count of subfiles:");
log.fine("- folder 1: " + dir1.getAbsolutePath());
log.fine("- folder 2: " + dir2.getAbsolutePath());
equals = false;
}
for (int i = 0; i < subfiles1.length && equals; i++) {
if (subfiles1[i].isDirectory()) {
if (depth < 0) {
equals = dirsEqual(subfiles1[i], subfiles2[i], depth, excludedotdirs);
} else if (depth > 1) {
equals = dirsEqual(subfiles1[i], subfiles2[i], depth - 1, excludedotdirs);
}
} else {
equals = filesEqual(subfiles1[i], subfiles2[i]);
}
}
}
return equals;
}
/**
* compares the contents of two files byte per byte.
*
* @param file1
* the first file
* @param file2
* the second file
*
* @return true if the files' content is equal,<br/>
* false if there are differences
*/
public static boolean filesEqual(final File file1, final File file2) {
return filesEqual(file1, file2, false, false);
}
/**
* compares the contents of two files byte per byte.
*
* @param file1
* the first file
* @param file2
* the second file
* @param differentNamesAllowed
* if different file names are allowed
* @param compareLineByLine
* for text files only. You'll get the line where the first
* difference occurs in the error message
*
* @return true if the files' content is equal,<br/>
* false if there are differences
*/
public static boolean filesEqual(final File file1, final File file2, final boolean differentNamesAllowed,
final boolean compareLineByLine) {
boolean equals = true;
if (!file1.exists()) {
throw new UtilException("file \"" + file1.getAbsolutePath() + "\" not found.");
}
if (!file2.exists()) {
throw new UtilException("file \"" + file2.getAbsolutePath() + "\" not found.");
}
if (!file1.isFile()) {
throw new UtilException("file \"" + file1.getAbsolutePath() + "\" not a normal file.");
}
if (!file2.isFile()) {
return false;
}
if (!differentNamesAllowed) {
if (!file1.getName().equals(file2.getName())) {
log.fine("files have different names:");
log.fine("- file 1: " + file1.getAbsolutePath());
log.fine("- file 2: " + file2.getAbsolutePath());
equals = false;
}
}
FileInputStream is1 = null;
FileInputStream is2 = null;
LineNumberReader r1 = null;
LineNumberReader r2 = null;
try {
if (compareLineByLine) {
r1 = new LineNumberReader(new InputStreamReader(new FileInputStream(file1)));
r2 = new LineNumberReader(new InputStreamReader(new FileInputStream(file2)));
String l1 = r1.readLine();
String l2 = r2.readLine();
while (l1 != null) {
if (l2 == null) {
log.fine("files have different number of lines:");
log.fine("- file 1: " + file1.getAbsolutePath());
log.fine("- file 2: " + file2.getAbsolutePath());
log.fine(" line " + r1.getLineNumber() + " of file 1 not" + " found in file 2");
equals = false;
break;
}
if (!l1.equals(l2)) {
log.fine("files differ:");
log.fine("- file 1: " + file1.getAbsolutePath());
log.fine(" line " + r1.getLineNumber() + ": \"" + l1 + "\"");
log.fine("- file 2: " + file2.getAbsolutePath());
log.fine(" line " + r2.getLineNumber() + ": \"" + l2 + "\"");
equals = false;
break;
}
l1 = r1.readLine();
l2 = r2.readLine();
}
if (equals && l2 != null) {
log.fine("files have different number of lines:");
log.fine("- file 1: " + file1.getAbsolutePath());
log.fine("- file 2: " + file2.getAbsolutePath());
equals = false;
}
} else {
is1 = new FileInputStream(file1);
is2 = new FileInputStream(file2);
int i1 = is1.read();
int i2 = is2.read();
while (i1 != -1) {
if (i1 != i2) {
log.fine("files differ:");
log.fine("- file 1: " + file1.getAbsolutePath());
log.fine("- file 2: " + file2.getAbsolutePath());
equals = false;
break;
}
i1 = is1.read();
i2 = is2.read();
}
if (equals && i2 != -1) {
log.fine("files have different length:");
log.fine("- file 1: " + file1.getAbsolutePath());
log.fine("- file 2: " + file2.getAbsolutePath());
equals = false;
}
}
} catch (IOException e) {
throw new UtilException(e);
} finally {
try {
if (is1 != null) {
is1.close();
}
if (is2 != null) {
is2.close();
}
if (r1 != null) {
r1.close();
}
if (r2 != null) {
r2.close();
}
} catch (IOException e) {
throw new UtilException(e);
}
}
return equals;
}
/**
* List all files in a directory except the files with names matching the
* given pattern.
*
* @param dir
* the directory to list
* @param filter
* the filter pattern for filenames to exclude
*
* @return the found files and sub directories except the ones matching the
* pattern
*/
public static File[] listFilesExcludeFilter(final File dir, final String filter) {
ArrayList<File> l = new ArrayList<File>();
File[] files = dir.listFiles();
File[] excludedFiles = dir.listFiles(new FileFilterRegExp(filter));
for (File file : files) {
if (!containsFileWithName(excludedFiles, file.getName())) {
l.add(file);
}
}
return (File[]) l.toArray(FILE_ARRAY);
}
/**
* Search trough an array of File objects for a file with the given name.
*
* @param array
* the File array to search through
* @param filename
* the file name to search for
*
* @return true if the file has been found or null if no file has been found
*/
public static boolean containsFileWithName(final File[] array, final String filename) {
for (int i = 0; i < array.length; i++) {
if (array[i].getName().equals(filename)) {
return true;
}
}
return false;
}
/**
* Creates a directory / folder according to the given file handle. Parent
* directories / folders will also be created if necessary.
*
* @param dir
* specifies the directory / folder to be created.
*/
public static void mkdirs(final File dir) {
final ArrayList<File> parentDirsToCreate = new ArrayList<File>();
File parentDir = dir.getParentFile();
while (parentDir != null && (!parentDir.exists())) {
parentDirsToCreate.add(parentDir);
parentDir = parentDir.getParentFile();
}
final int len = parentDirsToCreate.size();
int i = len - 1;
for (; i >= 0; i--) {
if (!parentDirsToCreate.get(i).mkdir()) {
if (i < (len - 1)) {
parentDirsToCreate.get(len - 1).delete();
}
throw new RapidBeansRuntimeException("Creation of directory \"" + dir.getAbsolutePath() + "\" failed");
}
}
if (!dir.mkdir()) {
if (i < (len - 1)) {
parentDirsToCreate.get(len - 1).delete();
}
throw new RapidBeansRuntimeException("Creation of directory \"" + dir.getAbsolutePath() + "\" failed");
}
}
public static File backup(final File file) {
return backup(file, "bak");
}
public static File backup(final File file, final String extension) {
if (file.isDirectory()) {
throw new RapidBeansRuntimeException("Can not backup directory \"" + file.getAbsolutePath()
+ "\". Only flat files can be backed up.");
}
if (!file.exists()) {
throw new RapidBeansRuntimeException("Can not backup non existing file \"" + file.getAbsolutePath() + "\".");
}
String backupFileName = tmpFilename(file);
if (extension != null) {
backupFileName += "." + extension;
}
final File backupFile = new File(file.getParentFile(), backupFileName);
copyFile(file, backupFile);
return backupFile;
}
public static String tmpFilename(final File file) {
return tmpFilename(file.getName());
}
public static String tmpFilename(final String filename) {
return basename(filename) + "_" + backupDateString() + "." + extension(filename);
}
public static String backupDateString() {
return PropertyDate.format(new Date(), PrecisionDate.second);
}
public static String basename(final File file) {
return basename(file.getName());
}
public static String basename(final String filename) {
return StringHelper.splitBeforeLast(StringHelper.splitLast(filename, "/\\"), ".");
}
public static String extension(final File file) {
return extension(file.getName());
}
public static String extension(final String filename) {
return StringHelper.splitLast(StringHelper.splitLast(filename, "/\\"), ".");
}
/**
* example instance for array creation.
*/
private static final File[] FILE_ARRAY = new File[0];
/**
* prevent default constructor from being used.
*/
private FileHelper() {
}
public static void append(File file, String string) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file, true);
final int len = string.length();
for (int i = 0; i < len; i++) {
fos.write(string.charAt(i));
}
} catch (IOException e) {
throw new UtilException(e);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
throw new UtilException(e);
}
}
}
}
public static void changeCharAt(final File file, final int index, final char newChar) {
FileInputStream is = null;
FileOutputStream os = null;
try {
is = new FileInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length());
int c;
while ((c = is.read()) != -1) {
bos.write(c);
}
is.close();
is = null;
byte[] ba = bos.toByteArray();
ba[index] = (byte) newChar;
os = new FileOutputStream(file);
os.write(ba);
os.close();
} catch (IOException e) {
throw new UtilException(e);
} finally {
try {
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
} catch (IOException e) {
throw new UtilException(e);
}
}
}
/**
* Present a shortened platform specific shortend path
*
* @param path
* the path to shorten
* @param maxStart
* the maximal start string length (except we have one single
* start path component;
* @param shortcutSign
* the shortcut sign
* @param maxEnd
* the maximal end string length (except we have one single start
* path component;
*
* @return the shortened string
*/
public static String shortenPathCenter(final String string, final int maxStart, String shortcutSign, int maxEnd) {
if (string.length() <= maxStart + maxEnd) {
return string;
}
String separator;
if (string.contains("/")) {
separator = "/";
} else if (string.contains("\\")) {
separator = "\\";
} else {
return string;
}
final List<String> pathComponents = StringHelper.split(string, separator);
final StringBuffer buf = new StringBuffer();
int index = 0;
while (buf.length() < maxStart && index < pathComponents.size()) {
buf.append(pathComponents.get(index));
buf.append(File.separatorChar);
index++;
}
index--;
final StringBuffer bufEnd = new StringBuffer();
int indexEnd = pathComponents.size() - 1;
while (bufEnd.length() < maxEnd && indexEnd > index) {
bufEnd.insert(0, pathComponents.get(indexEnd));
if (indexEnd - index > 1) {
bufEnd.insert(0, File.separatorChar);
}
indexEnd--;
}
if (indexEnd - index > 1) {
buf.append(shortcutSign);
}
buf.append(bufEnd);
return buf.toString();
}
}