FileOperations.java
/*
* Copyright (c) 2007-2017 MetaSolutions AB
*
* 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 org.entrystore.repository.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* Commonly used file operations.
*
* @author Hannes Ebner
* @version $Id$
*/
public class FileOperations {
private final static Logger log = LoggerFactory.getLogger(FileOperations.class);
private final static int BUFFER_SIZE = 8192;
// Noninstantiable utility class
private FileOperations() {
throw new AssertionError();
}
/**
* Moves a file from one location to another. If the file cannot be moved
* (e.g. because source and destination are in a different file system), it
* is copied instead. The source file is removed then.
*
* @param source
* File to move
* @param destination
* Destination file
* @throws IOException
*/
public static void moveFile(File source, File destination) throws IOException {
if (source == null || destination == null) {
throw new IllegalArgumentException("Parameters cannot be null");
}
if (source.equals(destination)) {
throw new IllegalArgumentException("Cannot move file to itself");
}
log.debug("Moving file " + source + " to " + destination);
if (!source.renameTo(destination)) {
copyFile(source, destination);
if (!source.delete()) {
log.warn("Unable to delete source file");
}
}
}
/**
* Copies a File using Java NIO Channels.
*
* @param src
* Source file
* @param dst
* Destination file
* @throws IOException
*/
public static void copyFile(File src, File dst) throws IOException {
FileChannel sourceChannel = null;
FileChannel destinationChannel = null;
boolean copied = false;
try {
sourceChannel = new FileInputStream(src).getChannel();
destinationChannel = new FileOutputStream(dst).getChannel();
long copiedSize = sourceChannel.transferTo(0, sourceChannel.size(), destinationChannel);
if (sourceChannel.size() == copiedSize) {
copied = true;
}
} finally {
if (sourceChannel != null) {
sourceChannel.close();
}
if (destinationChannel != null) {
destinationChannel.close();
}
}
if (!copied) {
log.warn("File copying failed using NIO, performing traditional copy using streams instead");
copyFile(Files.newInputStream(src.toPath()), Files.newOutputStream(dst.toPath()));
}
}
public static void fastChannelCopy(final ReadableByteChannel src, final WritableByteChannel dest) throws IOException {
final ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
while (src.read(buffer) != -1) {
buffer.flip();
dest.write(buffer);
buffer.compact();
}
buffer.flip();
while (buffer.hasRemaining()) {
dest.write(buffer);
}
}
/**
* Copies the contents of an InputStream to an OutputStream. Closes both
* streams after everything has gone well. Uses buffered streams internally.
*
* @param is
* Source InputStream
* @param os
* Destination OutputStream
* @throws IOException
*/
public static long copyFile(InputStream is, OutputStream os) throws IOException {
BufferedInputStream bis = new BufferedInputStream(is, BUFFER_SIZE);
BufferedOutputStream bos = new BufferedOutputStream(os, BUFFER_SIZE);
long bytesCopied = 0;
byte[] b = new byte[BUFFER_SIZE];
int s;
try {
while ((s = bis.read(b)) != -1) {
bos.write(b, 0, s);
bytesCopied += s;
}
} catch (NullPointerException npe) {
throw new IOException(npe);
}
bis.close();
bos.close();
return bytesCopied;
}
/**
* Deletes all files in a given directory, but does not remove the directory
* itself. Does not delete files in subdirectories.
*
* @param dir
* Directory to be cleaned.
* @return True if successful for all files.
*/
public static boolean deleteAllFilesInDir(File dir) {
File file;
if (dir.isDirectory()) {
String[] children = dir.list();
for (int i = 0; i < children.length; i++) {
file = new File(dir, children[i]);
if (file.isFile()) {
if (!file.delete()) {
log.warn("Could not delete file " + file);
return false;
}
log.debug("Deleted file " + file);
}
}
}
return true;
}
/**
* Deletes all files and subdirectories in a given directory.
*
* @param path Directory to be deleted.
* @return True if successful.
*/
public static boolean deleteDirectory(File path) {
if (path.exists()) {
File[] files = path.listFiles();
for (int i=0; i<files.length; i++) {
if (files[i].isDirectory()) {
deleteDirectory(files[i]);
} else {
files[i].delete();
}
}
}
return path.delete();
}
/**
* Lists all files from a directory and its subdirectories.
*
* @param folder
* Folder
* @return Returns a list of files. Does not contain folders.
*/
public static List<File> listFiles(File folder) {
List<File> result = new ArrayList<File>();
if (!folder.isDirectory()) {
result.add(folder);
} else {
File[] fileArray = folder.listFiles();
if (fileArray != null) {
List<File> fileList = Arrays.asList(fileArray);
for (File file : fileList) {
if (file.isFile()) {
result.add(file);
} else if (file.isDirectory()) {
result.addAll(listFiles(file));
}
}
}
}
return result;
}
/**
* Lists all subdirectories (and their subdirectories; recursively) of a
* directory.
*
* @param folder
* Folder
* @return Returns a list of folders. Does not contain files.
*/
public static List<File> listDirectories(File folder) {
if (!folder.isDirectory()) {
throw new IllegalArgumentException("Parameter is not a folder");
}
List<File> result = new ArrayList<File>();
File[] fileArray = folder.listFiles();
if ((fileArray != null) && (fileArray.length > 0)) {
List<File> fileList = Arrays.asList(fileArray);
for (File file : fileList) {
if (file.isDirectory()) {
result.add(file);
result.addAll(listFiles(file));
}
}
}
return result;
}
/**
* Zips a folder and its subfolders.
*
* @param zipFile
* Destination ZIP file. Is created if it does not exist.
* @param directory
* The directory to be zipped.
* @param base
* Base File path to strip off the zipped files. May be null.
* @return Returns a CRC32 checksum of the zipped file.
* @throws IOException
*/
public static long zipDirectory(File zipFile, File directory, File base) throws IOException {
return zipFiles(zipFile, listFiles(directory), base);
}
/**
* Zips a set of files.
*
* @param zipFile
* Destination ZIP file. Is created if it does not exist.
* @param files
* List of files to add to the ZIP file.
* @param base
* Base File path to strip off the zipped files. May be null.
* @return Returns a CRC32 checksum of the zipped file.
* @throws IOException
*/
public static long zipFiles(File zipFile, List<File> files, File base) throws IOException {
OutputStream fos = Files.newOutputStream(zipFile.toPath());
CheckedOutputStream cos = new CheckedOutputStream(fos, new CRC32());
BufferedOutputStream bos = new BufferedOutputStream(cos, BUFFER_SIZE);
ZipOutputStream zos = new ZipOutputStream(bos);
byte data[] = new byte[BUFFER_SIZE];
for (File file : files) {
InputStream fis = Files.newInputStream(file.toPath());
BufferedInputStream source = new BufferedInputStream(fis);
String entryPath = file.getPath();
if (base != null) {
entryPath = entryPath.replace(base.getPath(), "");
}
ZipEntry entry = new ZipEntry(entryPath);
log.debug("Adding file: " + file.getPath());
zos.putNextEntry(entry);
int count;
while ((count = source.read(data, 0, BUFFER_SIZE)) != -1) {
zos.write(data, 0, count);
}
source.close();
}
zos.close();
return cos.getChecksum().getValue();
}
/**
* Unzips a file into a directory. Subfolders are created if necessary.
*
* @param zipFile
* ZIP file to be used as source.
* @param destination
* Destination folder
* @return Returns a CRC32 checksum of the ZIP file.
* @throws IOException
* @throws IllegalArgumentException
* If destination is not a folder.
*/
public static long unzipFile(File zipFile, File destination) throws IOException {
if (!destination.isDirectory()) {
throw new IllegalArgumentException("Destination is not a folder");
}
InputStream fis = Files.newInputStream(zipFile.toPath());
CheckedInputStream cis = new CheckedInputStream(fis, new CRC32());
BufferedInputStream bis = new BufferedInputStream(cis, BUFFER_SIZE);
ZipInputStream zis = new ZipInputStream(bis);
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
log.debug("Extracting file: {}", entry.getName());
int count;
byte data[] = new byte[BUFFER_SIZE];
File unzippedFile = new File(destination, entry.getName());
File parentDir = unzippedFile.getParentFile();
if (!parentDir.exists()) {
log.debug("Creating directory: {}", parentDir);
parentDir.mkdirs();
}
OutputStream fos = Files.newOutputStream(unzippedFile.toPath());
BufferedOutputStream bos = new BufferedOutputStream(fos, BUFFER_SIZE);
while ((count = zis.read(data, 0, BUFFER_SIZE)) != -1) {
bos.write(data, 0, count);
}
bos.flush();
bos.close();
}
zis.close();
return cis.getChecksum().getValue();
}
/**
* Creates an empty file in the default temporary-file directory, using the
* given prefix and suffix to generate its name.
*
* @param prefix
* The prefix string to be used in generating the folder's name;
* must be at least three characters long
* @param suffix
* The suffix string to be used in generating the folder's name;
* may be <code>null</code>, in which case the suffix
* <code>".tmp"</code> will be used
* @return An abstract pathname denoting a newly-created empty folder
* @throws IllegalArgumentException
* If the <code>prefix</code> argument contains fewer than
* three characters
* @throws IOException
* If a folder could not be created
*/
public static File createTempDirectory(String prefix, String suffix) throws IOException {
File tempFile = File.createTempFile(prefix, suffix);
tempFile.delete();
tempFile.mkdir();
return tempFile;
}
public static void copyPath(Path src, Path dst) throws IOException {
Files.walkFileTree(src, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
Files.createDirectories(dst.resolve(src.relativize(dir)));
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.copy(file, dst.resolve(src.relativize(file)), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
return FileVisitResult.CONTINUE;
}
});
}
@Deprecated
public static void copyDirectory(File srcPath, File dstPath) throws IOException {
if (srcPath.isDirectory()) {
if (!dstPath.exists()) {
dstPath.mkdir();
}
String files[] = srcPath.list();
for (int i = 0; i < files.length; i++) {
copyDirectory(new File(srcPath, files[i]), new File(dstPath, files[i]));
}
} else {
if (!srcPath.exists()) {
log.error(srcPath + ": file or directory does not exist.");
} else {
InputStream in = null;
OutputStream out = null;
try {
in = Files.newInputStream(srcPath.toPath());
out = Files.newOutputStream(dstPath.toPath());
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
}
public static void writeStringToFile(File file, String content) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
writer.write(content);
} catch (IOException e) {
log.error(e.getMessage());
}
}
}