/*
* Copyright (c) 2011, Cloudera, Inc. All Rights Reserved.
*
* Cloudera, Inc. licenses this file to you 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
*
* This software 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 com.cloudera.lib.io;
import com.cloudera.lib.util.Check;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.text.MessageFormat;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public abstract class IOUtils {
/**
* Deletes the specified path recursively.
* <p/>
* As a safety mechanism, it converts the specified file to an absolute
* path and it throws an exception if the path is shorter than 5 character
* (you dont want to delete things like /tmp, /usr, /etc, /var, /opt, /sbin, /dev).
*
* @param file path to delete.
* @throws IOException thrown if an IO error occurred.
*/
public static void delete(File file) throws IOException {
if (file.getAbsolutePath().length() < 5) {
throw new IllegalArgumentException(
MessageFormat.format("Path [{0}] is too short, not deleting", file.getAbsolutePath()));
}
if (file.exists()) {
if (file.isDirectory()) {
File[] children = file.listFiles();
if (children != null) {
for (File child : children) {
delete(child);
}
}
}
if (!file.delete()) {
throw new RuntimeException(MessageFormat.format("Could not delete path [{0}]", file.getAbsolutePath()));
}
}
}
/**
* Copies an inputstream to an outputstream.
*
* @param is inputstream to copy.
* @param os target outputstream.
* @throws IOException thrown if an IO error occurred.
*/
public static void copy(InputStream is, OutputStream os) throws IOException {
Check.notNull(is, "is");
Check.notNull(os, "os");
copy(is, os, 0, -1);
}
/**
* Copies a range of bytes from an inputstream to an outputstream.
*
* @param is inputstream to copy.
* @param os target outputstream.
* @param offset of the inputstream to start the copy from, the offset
* is relative to the current position.
* @param len length of the inputstream to copy, <code>-1</code> means
* until the end of the inputstream.
* @throws IOException thrown if an IO error occurred.
*/
public static void copy(InputStream is, OutputStream os, long offset, long len) throws IOException {
Check.notNull(is, "is");
Check.notNull(os, "os");
byte[] buffer = new byte[1024];
long skipped = is.skip(offset);
if (skipped == offset) {
if (len == -1) {
int read = is.read(buffer);
while (read > -1) {
os.write(buffer, 0, read);
read = is.read(buffer);
}
is.close();
}
else {
long count = 0;
int read = is.read(buffer);
while (read > -1 && count < len) {
count += read;
if (count < len) {
os.write(buffer, 0, read);
read = is.read(buffer);
}
else if (count == len) {
os.write(buffer, 0, read);
}
else {
int leftToWrite = read - (int) (count - len);
os.write(buffer, 0, leftToWrite);
}
}
}
os.flush();
}
else {
throw new IOException(MessageFormat.format("InputStream ended before offset [{0}]", offset));
}
}
/**
* Copies a reader to a writer.
*
* @param reader reader to copy.
* @param writer target writer.
* @throws IOException thrown if an IO error occurred.
*/
public static void copy(Reader reader, Writer writer) throws IOException {
Check.notNull(reader, "reader");
Check.notNull(writer, "writer");
copy(reader, writer, 0, -1);
}
/**
* Copies a range of chars from a reader to a writer.
*
* @param reader reader to copy.
* @param writer target writer.
* @param offset of the reader to start the copy from, the offset
* is relative to the current position.
* @param len length of the reader to copy, <code>-1</code> means
* until the end of the reader.
* @throws IOException thrown if an IO error occurred.
*/
public static void copy(Reader reader, Writer writer, long offset, long len) throws IOException {
Check.notNull(reader, "reader");
Check.notNull(writer, "writer");
Check.ge0(offset, "offset");
char[] buffer = new char[1024];
long skipped = reader.skip(offset);
if (skipped == offset) {
if (len == -1) {
int read = reader.read(buffer);
while (read > -1) {
writer.write(buffer, 0, read);
read = reader.read(buffer);
}
reader.close();
}
else {
long count = 0;
int read = reader.read(buffer);
while (read > -1 && count < len) {
count += read;
if (count < len) {
writer.write(buffer, 0, read);
read = reader.read(buffer);
}
else if (count == len) {
writer.write(buffer, 0, read);
}
else {
int leftToWrite = read - (int) (count - len);
writer.write(buffer, 0, leftToWrite);
}
}
}
writer.flush();
}
else {
throw new IOException(MessageFormat.format("Reader ended before offset [{0}]", offset));
}
}
/**
* Reads a reader into a string.
* <p/>
*
* @param reader reader to read.
* @return string with the contents of the reader.
* @throws IOException thrown if an IO error occurred.
*/
public static String toString(Reader reader) throws IOException {
Check.notNull(reader, "reader");
StringWriter writer = new StringWriter();
copy(reader, writer);
return writer.toString();
}
/**
* Zips the contents of a directory into a zip outputstream.
*
* @param dir directory contents to zip.
* @param relativePath relative path top prepend to all files in the zip.
* @param zos zip output stream
* @throws IOException thrown if an IO error occurred.
*/
public static void zipDir(File dir, String relativePath, ZipOutputStream zos) throws IOException {
Check.notNull(dir, "dir");
Check.notNull(relativePath, "relativePath");
Check.notNull(zos, "zos");
zipDir(dir, relativePath, zos, true);
zos.close();
}
/**
* This recursive method is used by the {@link #zipDir(File, String, ZipOutputStream)} method.
* <p/>
* A special handling is required for the start of the zip (via the start parameter).
*
* @param dir directory contents to zip.
* @param relativePath relative path top prepend to all files in the zip.
* @param zos zip output stream
* @param start indicates if this invocation is the start of the zip.
* @throws IOException thrown if an IO error occurred.
*/
private static void zipDir(File dir, String relativePath, ZipOutputStream zos, boolean start) throws IOException {
String[] dirList = dir.list();
for (String aDirList : dirList) {
File f = new File(dir, aDirList);
if (!f.isHidden()) {
if (f.isDirectory()) {
if (!start) {
ZipEntry dirEntry = new ZipEntry(relativePath + f.getName() + "/");
zos.putNextEntry(dirEntry);
zos.closeEntry();
}
String filePath = f.getPath();
File file = new File(filePath);
zipDir(file, relativePath + f.getName() + "/", zos, false);
}
else {
ZipEntry anEntry = new ZipEntry(relativePath + f.getName());
zos.putNextEntry(anEntry);
InputStream is = new FileInputStream(f);
byte[] arr = new byte[4096];
int read = is.read(arr);
while (read > -1) {
zos.write(arr, 0, read);
read = is.read(arr);
}
is.close();
zos.closeEntry();
}
}
}
}
}