/*
* Copyright Myrrix Ltd
*
* 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 net.myrrix.common.io;
import java.io.BufferedReader;
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.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.InflaterInputStream;
import java.util.zip.ZipInputStream;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import net.myrrix.common.ClassUtils;
/**
* Simple utility methods related to I/O.
*
* @author Sean Owen
* @since 1.0
*/
public final class IOUtils {
private IOUtils() {
}
/**
* @param raw string to URL-encode
* @return the URL encoding of the argument, using the UTF-8 encoding if necessary to interpret
* characters as bytes
*/
public static String urlEncode(String raw) {
try {
return URLEncoder.encode(raw, Charsets.UTF_8.name());
} catch (UnsupportedEncodingException uee) {
// Can't happen for UTF-8
throw new AssertionError(uee);
}
}
/**
* Attempts to recursively delete a directory. This may not work across symlinks.
*
* @param dir directory to delete along with contents
* @return {@code true} if all files and dirs were deleted successfully
*/
public static boolean deleteRecursively(File dir) {
if (dir == null) {
return false;
}
Deque<File> stack = new ArrayDeque<File>();
stack.push(dir);
boolean result = true;
while (!stack.isEmpty()) {
File topElement = stack.peek();
if (topElement.isDirectory()) {
File[] directoryContents = topElement.listFiles();
if (directoryContents != null && directoryContents.length > 0) {
for (File fileOrSubDirectory : directoryContents) {
stack.push(fileOrSubDirectory);
}
} else {
result = result && stack.pop().delete();
}
} else {
result = result && stack.pop().delete();
}
}
return result;
}
/**
* Opens an {@link InputStream} to the file. If it appears to be compressed, because its file name ends in
* ".gz" or ".zip" or ".deflate", then it will be decompressed accordingly
*
* @param file file, possibly compressed, to open
* @return {@link InputStream} on uncompressed contents
* @throws IOException if the stream can't be opened or is invalid or can't be read
*/
public static InputStream openMaybeDecompressing(File file) throws IOException {
String name = file.getName();
InputStream in = new FileInputStream(file);
if (name.endsWith(".gz")) {
return new GZIPInputStream(in);
}
if (name.endsWith(".zip")) {
return new ZipInputStream(in);
}
if (name.endsWith(".deflate")) {
return new InflaterInputStream(in);
}
if (name.endsWith(".bz2") || name.endsWith(".bzip2")) {
return new BZip2CompressorInputStream(in);
}
return in;
}
/**
* @param file file, possibly compressed, to open
* @return {@link Reader} on uncompressed contents
* @throws IOException if the stream can't be opened or is invalid or can't be read
* @see #openMaybeDecompressing(File)
*/
public static Reader openReaderMaybeDecompressing(File file) throws IOException {
return new InputStreamReader(openMaybeDecompressing(file), Charsets.UTF_8);
}
/**
* @param in stream to read and copy
* @param file file to write stream's contents to
* @throws IOException if the stream can't be read or the file can't be written
*/
public static void copyStreamToFile(InputStream in, File file) throws IOException {
FileOutputStream out = new FileOutputStream(file);
try {
ByteStreams.copy(in, out);
} finally {
out.close();
}
}
/**
* @param url URL whose contents are to be read and copied
* @param file file to write contents to
* @throws IOException if the URL can't be read or the file can't be written
*/
public static void copyURLToFile(URL url, File file) throws IOException {
InputStream in = url.openStream();
try {
copyStreamToFile(in, file);
} finally {
in.close();
}
}
/**
* @param url URL whose contents are to be read
* @return the contents of the URL, interpreted as a UTF-8 encoded string
* @throws IOException if the URL can't be read or the file can't be written
*/
public static String readSmallTextFromURL(URL url) throws IOException {
Reader in = new InputStreamReader(url.openStream(), Charsets.UTF_8);
try {
return CharStreams.toString(in);
} finally {
in.close();
}
}
/**
* @param delegate {@link OutputStream} to wrap
* @return a {@link GZIPOutputStream} wrapping the given {@link OutputStream}. It attempts to use the new
* Java 7 version that actually responds to {@link OutputStream#flush()} as expected. If not available,
* uses the previous version ({@link GZIPOutputStream#GZIPOutputStream(OutputStream)})
*/
public static GZIPOutputStream buildGZIPOutputStream(OutputStream delegate) throws IOException {
// In Java 7, GZIPOutputStream's flush() behavior can be made more as expected. Use it if possible
// but fall back if not to the usual version
try {
return ClassUtils.loadInstanceOf(GZIPOutputStream.class,
new Class<?>[] {OutputStream.class, boolean.class},
new Object[] {delegate, true});
} catch (IllegalStateException ignored) {
return new GZIPOutputStream(delegate);
}
}
/**
* @see #buildGZIPOutputStream(OutputStream)
*/
public static GZIPOutputStream buildGZIPOutputStream(File file) throws IOException {
return buildGZIPOutputStream(new FileOutputStream(file));
}
/**
* @param delegate {@link OutputStream} to wrap
* @return the result of {@link #buildGZIPOutputStream(OutputStream)} as a {@link Writer} that encodes
* using UTF-8 encoding
*/
public static Writer buildGZIPWriter(OutputStream delegate) throws IOException {
return new OutputStreamWriter(buildGZIPOutputStream(delegate), Charsets.UTF_8);
}
/**
* @see #buildGZIPWriter(OutputStream)
*/
public static Writer buildGZIPWriter(File file) throws IOException {
return buildGZIPWriter(new FileOutputStream(file, false));
}
/**
* Wraps its argument in {@link BufferedReader} if not already one.
*/
public static BufferedReader buffer(Reader maybeBuffered) {
return maybeBuffered instanceof BufferedReader
? (BufferedReader) maybeBuffered
: new BufferedReader(maybeBuffered);
}
/**
* @return a {@link BufferedReader} on the stream, using UTF-8 encoding
*/
public static BufferedReader bufferStream(InputStream in) {
return new BufferedReader(new InputStreamReader(in, Charsets.UTF_8));
}
/**
* @return true iff the given file is a gzip-compressed file with no content; the file itself may not
* be empty because it contains gzip headers and footers
* @throws IOException if the file is not a gzip file or can't be read
*/
public static boolean isGZIPFileEmpty(File f) throws IOException {
InputStream in = new GZIPInputStream(new FileInputStream(f));
try {
return in.read() == -1;
} finally {
in.close();
}
}
/**
* @return object of type T that was serialized into the given file
*/
public static <T extends Serializable> T readObjectFromFile(File f, Class<T> clazz) throws IOException {
ObjectInputStream in = new ObjectInputStream(openMaybeDecompressing(f));
try {
@SuppressWarnings("unchecked")
T result = (T) in.readObject();
return result;
} catch (ClassNotFoundException cnfe) {
throw new IllegalStateException(cnfe);
} finally {
in.close();
}
}
/**
* Serializes an object, with gzip compression, to a given file.
*/
public static <T extends Serializable> void writeObjectToFile(File f, T t) throws IOException {
Preconditions.checkArgument(f.getName().endsWith(".gz"), "File should end in .gz: %s", f);
ObjectOutputStream out = new ObjectOutputStream(buildGZIPOutputStream(f));
try {
out.writeObject(t);
} finally {
out.close();
}
}
}