/*
* (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and contributors.
*
* 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.
*
* Contributors:
* Nuxeo - initial API and implementation
*
*/
package org.nuxeo.common.utils;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*
*/
public final class FileUtils {
private static final int BUFFER_SIZE = 1024 * 64; // 64K
private static final int MAX_BUFFER_SIZE = 1024 * 1024; // 64K
private static final int MIN_BUFFER_SIZE = 1024 * 8; // 64K
private static final Log log = LogFactory.getLog(FileUtils.class);
// This is an utility class
private FileUtils() {
}
public static void safeClose(Closeable stream) {
try {
stream.close();
} catch (IOException e) {
// do nothing
}
}
private static byte[] createBuffer(int preferredSize) {
if (preferredSize < 1) {
preferredSize = BUFFER_SIZE;
}
if (preferredSize > MAX_BUFFER_SIZE) {
preferredSize = MAX_BUFFER_SIZE;
} else if (preferredSize < MIN_BUFFER_SIZE) {
preferredSize = MIN_BUFFER_SIZE;
}
return new byte[preferredSize];
}
public static void copy(InputStream in, OutputStream out)
throws IOException {
byte[] buffer = createBuffer(in.available());
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
}
/**
* Read the byte stream as a string assuming a UTF-8 encoding.
*
* @deprecated use org.apache.commons.io.IOUtils.toString(in, "UTF-8")
* explicitly instead (or any other encoding when provided by
* the source of the byte stream).
*/
@Deprecated
public static String read(InputStream in) throws IOException {
// UTF-8 should is configured as the default "file.encoding" in a system
// property configured in the nuxeo.conf file.
// However this option might not be passed when running the Nuxeo as a
// library or using the maven test runner. Therefore we hardcode the
// default charset to "UTF-8" to ensure consistency.
return IOUtils.toString(in, "UTF-8");
}
public static byte[] readBytes(URL url) throws IOException {
return readBytes(url.openStream());
}
public static byte[] readBytes(InputStream in) throws IOException {
byte[] buffer = createBuffer(in.available());
int w = 0;
try {
int read = 0;
int len;
do {
w += read;
len = buffer.length - w;
if (len <= 0) { // resize buffer
byte[] b = new byte[buffer.length + BUFFER_SIZE];
System.arraycopy(buffer, 0, b, 0, w);
buffer = b;
len = buffer.length - w;
}
} while ((read = in.read(buffer, w, len)) != -1);
} finally {
in.close();
}
if (buffer.length > w) { // compact buffer
byte[] b = new byte[w];
System.arraycopy(buffer, 0, b, 0, w);
buffer = b;
}
return buffer;
}
public static String readFile(File file) throws IOException {
FileInputStream in = null;
try {
in = new FileInputStream(file);
return read(in);
} finally {
if (in != null) {
in.close();
}
}
}
public static List<String> readLines(File file) throws IOException {
List<String> lines = new ArrayList<String>();
BufferedReader reader = null;
try {
InputStream in = new FileInputStream(file);
reader = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
}
}
}
return lines;
}
public static void writeLines(File file, List<String> lines)
throws IOException {
PrintWriter out = null;
try {
out = new PrintWriter(new FileOutputStream(file));
for (String line : lines) {
out.println(line);
}
} finally {
if (out != null) {
out.close();
}
}
}
public static byte[] readBytes(File file) throws IOException {
FileInputStream in = null;
try {
in = new FileInputStream(file);
return readBytes(in);
} finally {
if (in != null) {
in.close();
}
}
}
public static void writeFile(File file, byte[] buf) throws IOException {
writeFile(file, buf, false);
}
/**
* @param file
* @param buf
* @param append
* @throws IOException
* @since 5.5
*/
public static void writeFile(File file, byte[] buf, boolean append)
throws IOException {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file, append);
fos.write(buf);
} finally {
if (fos != null) {
fos.close();
}
}
}
public static void writeFile(File file, String buf) throws IOException {
writeFile(file, buf.getBytes(), false);
}
/**
* @param dst
* @param content
* @param append
* @throws IOException
* @since 5.5
*/
public static void writeFile(File file, String buf, boolean append)
throws IOException {
writeFile(file, buf.getBytes(), append);
}
public static void download(URL url, File file) throws IOException {
InputStream in = url.openStream();
OutputStream out = new FileOutputStream(file);
try {
copy(in, out);
} finally {
if (in != null) {
in.close();
}
out.close();
}
}
/**
* @deprecated Since 5.6. Use
* {@link org.apache.commons.io.FileUtils#deleteDirectory(File)}
* or
* {@link org.apache.commons.io.FileUtils#deleteQuietly(File)}
* instead.
*/
@Deprecated
public static void deleteTree(File dir) {
emptyDirectory(dir);
dir.delete();
}
/**
* @deprecated Since 5.6. Use
* {@link org.apache.commons.io.FileUtils#deleteDirectory(File)}
* or
* {@link org.apache.commons.io.FileUtils#deleteQuietly(File)}
* instead. Warning: suggested methods will delete the root
* directory whereas current method doesn't.
*/
@Deprecated
public static void emptyDirectory(File dir) {
File[] files = dir.listFiles();
if (files == null) {
return;
}
int len = files.length;
for (int i = 0; i < len; i++) {
File file = files[i];
if (file.isDirectory()) {
deleteTree(file);
} else {
file.delete();
}
}
}
public static void copyToFile(InputStream in, File file) throws IOException {
OutputStream out = null;
try {
out = new FileOutputStream(file);
byte[] buffer = createBuffer(in.available());
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
} finally {
if (out != null) {
out.close();
}
}
}
public static void append(File src, File dst) throws IOException {
append(src, dst, false);
}
public static void append(File src, File dst, boolean appendNewLine)
throws IOException {
InputStream in = null;
try {
in = new FileInputStream(src);
append(in, dst, appendNewLine);
} finally {
if (in != null) {
in.close();
}
}
}
public static void append(InputStream in, File file) throws IOException {
append(in, file, false);
}
public static void append(InputStream in, File file, boolean appendNewLine)
throws IOException {
OutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(file, true));
if (appendNewLine) {
out.write(System.getProperty("line.separator").getBytes());
}
byte[] buffer = new byte[BUFFER_SIZE];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
} finally {
if (out != null) {
out.close();
}
}
}
/**
* Copies source to destination. If source and destination are the same,
* does nothing. Both single files and directories are handled.
*
* @param src the source file or directory
* @param dst the destination file or directory
* @throws IOException
*/
public static void copy(File src, File dst) throws IOException {
if (src.equals(dst)) {
return;
}
if (src.isFile()) {
copyFile(src, dst);
} else {
copyTree(src, dst);
}
}
public static void copy(File[] src, File dst) throws IOException {
for (File file : src) {
copy(file, dst);
}
}
public static void copyFile(File src, File dst) throws IOException {
if (dst.isDirectory()) {
dst = new File(dst, src.getName());
}
FileInputStream in = null;
FileOutputStream out = null;
try {
out = new FileOutputStream(dst);
in = new FileInputStream(src);
copy(in, out);
} finally {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
}
}
/**
* Copies recursively source to destination.
* <p>
* The source file is assumed to be a directory.
*
* @param src the source directory
* @param dst the destination directory
* @throws IOException
*/
public static void copyTree(File src, File dst) throws IOException {
if (src.isFile()) {
copyFile(src, dst);
} else if (src.isDirectory()) {
if (dst.exists()) {
dst = new File(dst, src.getName());
dst.mkdir();
} else { // allows renaming dest dir
dst.mkdirs();
}
File[] files = src.listFiles();
for (File file : files) {
copyTree(file, dst);
}
}
}
public static void copyTree(File src, File dst, PathFilter filter)
throws IOException {
copyTree(src, dst, new Path("/"), filter);
}
public static void copyTree(File src, File dst, Path prefix,
PathFilter filter) throws IOException {
if (!prefix.isAbsolute()) {
prefix = prefix.makeAbsolute();
}
int rootIndex = src.getPath().length() + 1;
for (File file : src.listFiles()) {
copyTree(rootIndex, file, new File(dst, file.getName()), prefix,
filter);
}
}
protected static void copyTree(int rootIndex, File src, File dst,
Path prefix, PathFilter filter) throws IOException {
if (src.isFile()) {
String relPath = src.getPath().substring(rootIndex);
if (!filter.accept(new Path(relPath))) {
return;
}
if (!prefix.isRoot()) { // remove prefix from path
String path = dst.getPath();
String pff = prefix.toString();
int prefixIndex = path.lastIndexOf(pff);
if (prefixIndex > 0) {
path = path.substring(0, prefixIndex)
+ path.substring(prefixIndex + pff.length());
dst = new File(path.toString());
}
}
dst.getParentFile().mkdirs();
copyFile(src, dst);
} else if (src.isDirectory()) {
File[] files = src.listFiles();
for (File file : files) {
copyTree(rootIndex, file, new File(dst, file.getName()),
prefix, filter);
}
}
}
/**
* Decodes an URL path so that is can be processed as a filename later.
*
* @param url the Url to be processed.
* @return the decoded path.
*/
public static String getFilePathFromUrl(URL url) {
String path = "";
if (url.getProtocol().equals("file")) {
try {
path = URLDecoder.decode(url.getPath(), "UTF-8");
} catch (UnsupportedEncodingException e) {
log.error(e);
}
}
return path;
}
public static File getFileFromURL(URL url) {
File file;
String filename = getFilePathFromUrl(url);
if (filename.equals("")) {
file = null;
} else {
file = new File(filename);
}
return file;
}
public static String getParentPath(String path) {
int p = path.lastIndexOf(File.separator);
if (p == -1) {
return null;
}
return path.substring(0, p);
}
public static String getFileName(String path) {
int p = path.lastIndexOf(File.separator);
if (p == -1) {
return path;
}
return path.substring(p + 1);
}
public static String getFileExtension(String path) {
int p = path.lastIndexOf('.');
if (p == -1) {
return null;
}
return path.substring(p + 1);
}
public static String getFileNameNoExt(String path) {
String name = getFileName(path);
int p = name.lastIndexOf('.');
if (p == -1) {
return name;
}
return name.substring(0, p);
}
/**
* Retrieves the total path of a resource from the Thread Context.
*
* @param resource the resource name to be retrieved.
* @return the decoded path.
*/
public static String getResourcePathFromContext(String resource) {
URL url = Thread.currentThread().getContextClassLoader().getResource(
resource);
return getFilePathFromUrl(url);
}
public static File getResourceFileFromContext(String resource) {
File file;
String filename = getResourcePathFromContext(resource);
if (filename.equals("")) {
file = null;
} else {
file = new File(filename);
}
return file;
}
public static File[] findFiles(File root, String pattern, boolean recurse) {
List<File> result = new ArrayList<File>();
if (pattern == null) {
if (recurse) {
collectFiles(root, result);
} else {
return root.listFiles();
}
} else {
FileNamePattern pat = new FileNamePattern(pattern);
if (recurse) {
collectFiles(root, pat, result);
} else {
File[] files = root.listFiles();
for (File file : files) {
if (pat.match(file.getName())) {
result.add(file);
}
}
}
}
return result.toArray(new File[result.size()]);
}
public static void collectFiles(File root, FileNamePattern pattern,
List<File> result) {
File[] files = root.listFiles();
for (File file : files) {
if (pattern.match(file.getName())) {
result.add(file);
if (file.isDirectory()) {
collectFiles(file, pattern, result);
}
}
}
}
public static void collectFiles(File root, List<File> result) {
File[] files = root.listFiles();
for (File file : files) {
result.add(file);
if (file.isDirectory()) {
collectFiles(file, result);
}
}
}
public static void close(InputStream in) {
if (in != null) {
try {
in.close();
} catch (IOException e) {
log.error(e);
}
}
}
public static void close(OutputStream out) {
if (out != null) {
try {
out.close();
} catch (IOException e) {
log.error(e);
}
}
}
/**
* Create a file handler (this doesn't create a real file) given a file URI.
* This method can be used to create files from invalid URL strings (e.g.
* containing spaces ..)
*
* @return a file object
*/
public static File urlToFile(String url) throws MalformedURLException {
return urlToFile(new URL(url));
}
public static File urlToFile(URL url) {
try {
return new File(url.toURI());
} catch (URISyntaxException e) {
return new File(url.getPath());
}
}
public static List<String> readLines(InputStream in) throws IOException {
List<String> lines = new ArrayList<String>();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
}
}
}
return lines;
}
/**
* Compares two files content as String even if their EOL are differents
*
* @param expected a file content with Windows or Unix like EOL
* @param source another file content with Windows or Unix like EOL
* @return the result of equals after replacing their EOL
*/
public static boolean areFilesContentEquals(String expected, String source) {
if (expected == source) {
return true;
}
if (expected == null || source == null) {
return false;
}
if (expected.length() != source.length()) {
// Prevent from comparing files with Windows EOL
return expected.replace("\r\n", "\n").equals(
source.replace("\r\n", "\n"));
} else {
return expected.equals(source);
}
}
}