/*
* Copyright (C) 2012 Google 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 interactivespaces.util.io;
import static com.google.common.io.Closeables.closeQuietly;
import interactivespaces.InteractiveSpacesException;
import interactivespaces.SimpleInteractiveSpacesException;
import com.google.common.collect.Lists;
import com.google.common.io.Closeables;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* Various useful file routines.
*
* <p>
* This class maintains no state.
*
* @author Keith M. Hughes
*/
public class FileSupportImpl implements FileSupport {
/**
* An instance everyone can use if they chose.
*/
public static final FileSupport INSTANCE = new FileSupportImpl();
/**
* File path separator for ZIP files.
*/
public static final String ZIP_PATH_SEPARATOR = "/";
/**
* Default buffer size for copy operations.
*/
private static final int COPY_DEFAULT_BUFFER_SIZE = 4096;
/**
* File prefix to use for creating unique temporary files.
*/
private static final String TEMP_FILE_PREFIX = "tmp";
@Override
public void zip(File target, File sourceDirectory) {
zip(target, sourceDirectory, false);
}
@Override
public void zip(File target, File sourceDirectory, boolean overwrite) {
if (exists(target) && !overwrite) {
throw new SimpleInteractiveSpacesException("Cannot overwrite existing output file " + target);
}
ZipOutputStream zipOutputStream = null;
try {
zipOutputStream = createZipOutputStream(target);
File relPath = new File(".");
addFileToZipStream(zipOutputStream, sourceDirectory, relPath, null);
zipOutputStream.close();
zipOutputStream = null;
} catch (Exception e) {
throw new InteractiveSpacesException("Error while zipping directory " + sourceDirectory, e);
} finally {
close(zipOutputStream, true);
}
}
@Override
public ZipOutputStream createZipOutputStream(File outputFile) {
try {
return new ZipOutputStream(new FileOutputStream(outputFile));
} catch (IOException e) {
throw new SimpleInteractiveSpacesException("Error creating zip output file " + getAbsolutePath(outputFile), e);
}
}
@Override
public void addFileToZipStream(ZipOutputStream zipOutputStream, File baseFile, File relFile, String prefixPath) {
FileInputStream fileStream = null;
try {
File target = new File(baseFile, getPath(relFile));
String relPath = getPath(relFile);
String entryPath = prefixPath == null ? relPath : getPath(new File(prefixPath, relPath));
if (isFile(target)) {
zipOutputStream.putNextEntry(new ZipEntry(entryPath));
fileStream = new FileInputStream(target);
copyStream(fileStream, zipOutputStream, false);
fileStream.close();
} else if (isDirectory(target)) {
// Zip requires trailing / for directory.
zipOutputStream.putNextEntry(new ZipEntry(entryPath + ZIP_PATH_SEPARATOR));
File[] dirFiles = listFiles(target);
if (dirFiles != null) {
for (File childPath : dirFiles) {
File childRelPath = new File(relFile, childPath.getName());
addFileToZipStream(zipOutputStream, baseFile, childRelPath, prefixPath);
}
}
} else {
throw new SimpleInteractiveSpacesException("File source not found/recognized: " + getAbsolutePath(target));
}
} catch (Exception e) {
throw new SimpleInteractiveSpacesException("Error adding file to zip stream " + getAbsolutePath(baseFile), e);
} finally {
closeQuietly(fileStream);
}
}
@Override
public void unzip(File source, File baseLocation) {
unzip(source, baseLocation, null);
}
@Override
public void unzip(File source, File baseLocation, FileCollector fileCollector) {
java.util.zip.ZipFile zipFile = null;
try {
zipFile = new java.util.zip.ZipFile(source);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.isDirectory()) {
File newDir = newFile(baseLocation, entry.getName());
if (!exists(newDir) && !mkdirs(newDir)) {
throw new SimpleInteractiveSpacesException("Could not create directory: " + newDir);
}
} else {
File file = newFile(baseLocation, entry.getName());
File parentFile = getParentFile(file);
if (!exists(parentFile) && !mkdirs(parentFile)) {
throw new SimpleInteractiveSpacesException("Could not create parent directory: " + parentFile);
}
copyInputStream(zipFile.getInputStream(entry), new BufferedOutputStream(new FileOutputStream(file)));
if (fileCollector != null) {
fileCollector.put(file, source);
}
}
}
zipFile.close();
} catch (IOException ioe) {
throw new SimpleInteractiveSpacesException(String.format("Error while unzipping file %s",
getAbsolutePath(source)), ioe);
} finally {
// ZipFile does not implement Closeable, so can't use utility function.
if (zipFile != null) {
try {
zipFile.close();
} catch (IOException e) {
// Don't care.
}
}
}
}
@Override
public final void cleanDuplicateDirectory(File srcDir, File destDir) {
directoryExists(destDir);
deleteDirectoryContents(destDir);
copyDirectory(srcDir, destDir, true);
}
@Override
public void copyDirectory(File sourceDir, File destDir, boolean overwrite) {
copyDirectory(sourceDir, destDir, overwrite, null);
}
@Override
public void copyDirectory(File sourceDir, File destDir, boolean overwrite, FileCollector fileCollector) {
copyDirectory(sourceDir, null, destDir, overwrite, fileCollector);
}
@Override
public void
copyDirectory(File sourceDir, FileFilter filter, File destDir, boolean overwrite, FileCollector fileCollector) {
directoryExists(destDir);
File[] sourceFiles = listFiles(sourceDir);
if (sourceFiles == null) {
throw new SimpleInteractiveSpacesException("Missing source directory " + getAbsolutePath(sourceDir));
}
for (File src : sourceFiles) {
if (filter != null && !filter.accept(src)) {
continue;
}
try {
File dst = new File(destDir, src.getName());
if (isDirectory(src)) {
copyDirectory(src, filter, dst, overwrite, fileCollector);
} else {
if (!exists(dst) || overwrite) {
if (fileCollector != null) {
fileCollector.put(dst, src);
}
copyFile(src, dst);
}
}
} catch (Exception e) {
throw new InteractiveSpacesException("While copying file " + getAbsolutePath(src), e);
}
}
}
@Override
public void copyFile(File source, File destination) {
try {
createNewFile(destination);
} catch (IOException e) {
throw new InteractiveSpacesException(
String.format("Could not create new file %s", getAbsolutePath(destination)), e);
}
FileChannel in = null;
FileChannel out = null;
try {
in = new FileInputStream(source).getChannel();
out = new FileOutputStream(destination).getChannel();
out.transferFrom(in, 0, in.size());
} catch (IOException e) {
throw new InteractiveSpacesException(String.format("Could not copy file %s to %s", getAbsoluteFile(source),
getAbsolutePath(destination)), e);
} finally {
try {
if (out != null) {
try {
out.close();
} catch (IOException e) {
throw new InteractiveSpacesException(
String.format("Could not close file %s", getAbsolutePath(destination)), e);
}
}
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// Don't care.
}
}
}
}
}
@Override
public void copyInputStream(InputStream in, File file) throws IOException {
copyInputStream(in, new FileOutputStream(file));
}
@Override
public void copyInputStream(InputStream in, OutputStream out) throws IOException {
copyStream(in, out, true);
}
@Override
public void copyStream(InputStream in, OutputStream out, boolean closeOnCompletion) throws IOException {
try {
byte[] buffer = new byte[COPY_DEFAULT_BUFFER_SIZE];
int len;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
out.flush();
} finally {
if (closeOnCompletion) {
try {
in.close();
} catch (IOException e) {
// Don't care
}
try {
out.close();
} catch (IOException e) {
// Don't care
}
}
}
}
@Override
public void copyFileToStream(File in, OutputStream out, boolean closeOnCompletion) throws IOException {
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(in);
copyStream(inputStream, out, closeOnCompletion);
} finally {
Closeables.closeQuietly(inputStream);
}
}
@Override
public String inputStreamAsString(InputStream in) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
copyInputStream(in, bos);
return bos.toString();
}
@Override
public String readAvailableToString(InputStream in) throws IOException {
if (in == null) {
return null;
}
byte[] buffer = new byte[COPY_DEFAULT_BUFFER_SIZE];
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while (in.available() > 0) {
int length = in.read(buffer);
bos.write(buffer, 0, length);
}
return bos.toString();
}
@Override
public final void delete(File file) {
if (exists(file)) {
if (isDirectory(file)) {
deleteDirectoryContents(file);
}
file.delete();
}
}
@Override
public void deleteDirectoryContents(File file) {
File[] files = listFiles(file);
if (files != null) {
for (File contained : files) {
delete(contained);
}
}
}
@Override
public String readFile(File file) {
final StringBuilder builder = new StringBuilder();
FileLineReader reader = new FileLineReader();
reader.process(file, new LineReaderHandler() {
@Override
public void processLine(String line) {
builder.append(line).append('\n');
}
});
return builder.toString();
}
@Override
public void writeFile(File file, String contents) {
FileWriter writer = null;
try {
writer = new FileWriter(file);
writer.append(contents);
writer.flush();
} catch (Exception e) {
throw new InteractiveSpacesException(String.format("Unable to write contents out to file %s", file), e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
// Don't care
}
}
}
}
@Override
public void directoryExists(File dir, String message) {
if (exists(dir)) {
if (!isDirectory(dir)) {
String emessage =
message != null ? String.format("%s: %s is not a directory", message, getAbsolutePath(dir)) : String
.format("%s is not a directory", getAbsolutePath(dir));
throw new SimpleInteractiveSpacesException(emessage);
}
} else {
if (!mkdirs(dir)) {
String emessage =
message != null ? String.format("%s: Could not create directory %s", message, getAbsolutePath(dir))
: String.format("Could not create directory %s", getAbsolutePath(dir));
throw new SimpleInteractiveSpacesException(emessage);
}
}
}
@Override
public boolean createNewFile(File file) throws IOException {
return file.createNewFile();
}
@Override
public void directoryExists(File dir) {
directoryExists(dir, null);
}
@Override
public boolean exists(File file) {
return file.exists();
}
@Override
public File newFile(String path) {
// TODO(keith): Handle Windows
return new File(path);
}
@Override
public File newFile(URI uri) {
return new File(uri);
}
@Override
public File newFile(File parent, String subpath) {
// TODO(keith): Handle Windows
return new File(parent, subpath);
}
@Override
public File resolveFile(File parent, String subpath) {
File subFile = newFile(subpath);
if (subFile.isAbsolute()) {
return subFile;
} else {
return newFile(parent, subpath);
}
}
@Override
public File getAbsoluteFile(File file) {
return file.getAbsoluteFile();
}
@Override
public String getAbsolutePath(File file) {
return file.getAbsolutePath();
}
@Override
public String getName(File file) {
return file.getName();
}
@Override
public String getParent(File file) {
return file.getParent();
}
@Override
public File getParentFile(File file) {
return file.getParentFile();
}
@Override
public String getPath(File file) {
return file.getPath();
}
@Override
public boolean isDirectory(File dir) {
return dir.isDirectory();
}
@Override
public boolean isFile(File file) {
return file.isFile();
}
@Override
public File[] listFiles(File dir) {
return dir.listFiles();
}
@Override
public File[] listFiles(File dir, FileFilter fileFilter) {
return dir.listFiles(fileFilter);
}
@Override
public File[] listFiles(File dir, FilenameFilter fileFilter) {
return dir.listFiles(fileFilter);
}
@Override
public boolean mkdir(File dir) {
return dir.mkdir();
}
@Override
public boolean mkdirs(File dir) {
return dir.mkdirs();
}
@Override
public boolean rename(File from, File to) {
return from.renameTo(to);
}
@Override
public File createTempFile(File baseDir) {
return createTempFile(baseDir, TEMP_FILE_PREFIX, "");
}
@Override
public File createTempFile(File baseDir, String prefix, String suffix) {
try {
return File.createTempFile(prefix, suffix, baseDir);
} catch (IOException e) {
throw new SimpleInteractiveSpacesException("Error creating temp file in " + baseDir.getAbsolutePath(), e);
}
}
@Override
public File createTempFile(String prefix, String suffix) {
try {
return File.createTempFile(prefix, suffix);
} catch (IOException e) {
throw new SimpleInteractiveSpacesException("Error creating temp file", e);
}
}
@Override
public File createTempDirectory(File baseDir) {
return createTempDirectory(baseDir, TEMP_FILE_PREFIX);
}
@Override
public File createTempDirectory(File baseDir, String prefix) {
File dir = null;
try {
dir = Files.createTempDirectory(baseDir.toPath(), prefix).toFile();
} catch (IOException e) {
throw InteractiveSpacesException.newFormattedException(e, "Error creating temp directory in %s",
baseDir.getAbsolutePath(), e);
}
return dir;
}
@Override
public void close(Closeable closeable, boolean throwException) throws InteractiveSpacesException {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
if (throwException) {
throw new InteractiveSpacesException("Exception while closing closeable", e);
}
}
}
}
@Override
public boolean isParent(File candidateParent, File file) {
try {
// We need to make sure that /foo/bar is not considered a parent for /foo/bart/spam, hence taking on
// the separator
return file.getCanonicalPath().startsWith(candidateParent.getCanonicalPath() + File.separator);
} catch (IOException e) {
throw InteractiveSpacesException.newFormattedException(e, "Error checking that %s is a parent of %s",
candidateParent.getPath(), file.getPath());
}
}
@Override
public boolean setExecutable(File file, boolean executable) {
return file.setExecutable(executable);
}
@Override
public List<File> collectFiles(File baseDir, FileFilter filter, boolean recurse) {
List<File> files = Lists.newArrayList();
collectFiles(baseDir, filter, recurse, files);
return files;
}
/**
* Collect files from the current directory.
*
* @param currentDir
* the current directory
* @param filter
* the file filter
* @param recurse
* {@code true} if should recurse
* @param files
* the collection of files being added to
*/
private void collectFiles(File currentDir, FileFilter filter, boolean recurse, List<File> files) {
File[] contents = listFiles(currentDir);
if (contents != null) {
for (File file : contents) {
if (filter.accept(file)) {
files.add(file);
}
if (recurse && file.isDirectory()) {
collectFiles(file, filter, true, files);
}
}
}
}
@Override
public FileInputStream newFileInputStream(File file) {
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
throw SimpleInteractiveSpacesException.newFormattedException(e,
"Could not create a new file input stream for file %s", file.getAbsolutePath());
}
}
@Override
public FileOutputStream newFileOutputStream(File file) {
try {
return new FileOutputStream(file);
} catch (FileNotFoundException e) {
throw SimpleInteractiveSpacesException.newFormattedException(e,
"Could not create a new file output stream for file %s", file.getAbsolutePath());
}
}
}