/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.engine.io;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.nio.channels.Channel;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.WritableByteChannel;
import java.util.logging.Level;
import org.restlet.Context;
import org.restlet.data.CharacterSet;
import org.restlet.data.Range;
import org.restlet.engine.Edition;
import org.restlet.engine.Engine;
import org.restlet.representation.Representation;
/**
* IO manipulation utilities.
*
* @author Thierry Boileau
*/
public class IoUtils {
/**
* The size to use when instantiating buffered items such as instances of
* the {@link BufferedReader} class. It looks for the System property
* "org.restlet.engine.io.bufferSize" and if not defined, uses the "8192"
* default value.
*/
public static final int BUFFER_SIZE = getProperty(
"org.restlet.engine.io.bufferSize", 8192);
// [ifndef gwt] member
/** Support for byte to hexa conversions. */
private static final char[] HEXDIGITS = "0123456789ABCDEF".toCharArray();
/**
* The number of milliseconds after which IO operation will time out. It
* looks for the System property "org.restlet.engine.io.timeoutMs" and if
* not defined, uses the "60000" default value.
*/
public final static int TIMEOUT_MS = getProperty(
"org.restlet.engine.io.timeoutMs", 60000);
// [ifndef gwt] method
/**
* Copies an input stream to an output stream. When the reading is done, the
* input stream is closed.
*
* @param inputStream
* The input stream.
* @param outputStream
* The output stream.
* @throws IOException
*/
public static void copy(InputStream inputStream,
java.io.OutputStream outputStream) throws IOException {
if (inputStream != null) {
if (outputStream != null) {
int bytesRead;
byte[] buffer = new byte[2048];
while ((bytesRead = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
inputStream.close();
} else {
Context.getCurrentLogger()
.log(Level.FINE,
"Unable to copy input to output stream. Output stream is null.");
}
} else {
Context.getCurrentLogger()
.log(Level.FINE,
"Unable to copy input to output stream. Input stream is null.");
}
}
// [ifndef gwt] method
/**
* Copies an input stream to a random access file. When the reading is done,
* the input stream is closed.
*
* @param inputStream
* The input stream.
* @param randomAccessFile
* The random access file.
* @throws IOException
*/
public static void copy(InputStream inputStream,
java.io.RandomAccessFile randomAccessFile) throws IOException {
int bytesRead;
byte[] buffer = new byte[2048];
while ((bytesRead = inputStream.read(buffer)) > 0) {
randomAccessFile.write(buffer, 0, bytesRead);
}
inputStream.close();
}
// [ifndef gwt] method
/**
* Writes a readable channel to a writable channel.
*
* @param readableChannel
* The readable channel.
* @param writableChannel
* The writable channel.
* @throws IOException
*/
public static void copy(ReadableByteChannel readableChannel,
WritableByteChannel writableChannel) throws IOException {
if ((readableChannel != null) && (writableChannel != null)) {
copy(getStream(readableChannel), getStream(writableChannel));
}
}
// [ifndef gwt] method
/**
* Copies characters from a reader to a writer. When the reading is done,
* the reader is closed.
*
* @param reader
* The reader.
* @param writer
* The writer.
* @throws IOException
*/
public static void copy(Reader reader, java.io.Writer writer)
throws IOException {
int charsRead;
char[] buffer = new char[2048];
while ((charsRead = reader.read(buffer)) > 0) {
writer.write(buffer, 0, charsRead);
}
writer.flush();
reader.close();
}
// [ifndef gwt] method
/**
* Deletes an individual file or an empty directory.
*
* @param file
* The individual file or directory to delete.
* @return True if the deletion was successful.
*/
public static boolean delete(java.io.File file) {
return IoUtils.delete(file, false);
}
// [ifndef gwt] method
/**
* Deletes an individual file or a directory. A recursive deletion can be
* forced as well. Under Windows operating systems, the garbage collector
* will be invoked once before attempting to delete in order to prevent
* locking issues.
*
* @param file
* The individual file or directory to delete.
* @param recursive
* Indicates if directory with content should be deleted
* recursively as well.
* @return True if the deletion was successful or if the file or directory
* didn't exist.
*/
public static boolean delete(java.io.File file, boolean recursive) {
String osName = System.getProperty("os.name").toLowerCase();
return IoUtils.delete(file, recursive, osName.startsWith("windows"));
}
// [ifndef gwt] method
/**
* Deletes an individual file or a directory. A recursive deletion can be
* forced as well. The garbage collector can be run once before attempting
* to delete, to workaround lock issues under Windows operating systems.
*
* @param file
* The individual file or directory to delete.
* @param recursive
* Indicates if directory with content should be deleted
* recursively as well.
* @param garbageCollect
* Indicates if the garbage collector should be run.
* @return True if the deletion was successful or if the file or directory
* didn't exist.
*/
public static boolean delete(java.io.File file, boolean recursive,
boolean garbageCollect) {
boolean result = true;
boolean runGC = garbageCollect;
if (file.exists()) {
if (file.isDirectory()) {
java.io.File[] entries = file.listFiles();
// Check if the directory is empty
if (entries.length > 0) {
if (recursive) {
for (int i = 0; result && (i < entries.length); i++) {
if (runGC) {
System.gc();
runGC = false;
}
result = delete(entries[i], true, false);
}
} else {
result = false;
}
}
}
if (runGC) {
System.gc();
runGC = false;
}
result = result && file.delete();
}
return result;
}
// [ifndef gwt] method
/**
* Exhaust the content of the representation by reading it and silently
* discarding anything read.
*
* @param input
* The input stream to exhaust.
* @return The number of bytes consumed or -1 if unknown.
*/
public static long exhaust(InputStream input) throws IOException {
long result = -1L;
if (input != null) {
byte[] buf = new byte[2048];
int read = input.read(buf);
result = (read == -1) ? -1 : 0;
while (read != -1) {
result += read;
read = input.read(buf);
}
}
return result;
}
/**
* Returns the size effectively available. This returns the same value as
* {@link Representation#getSize()} if no range is defined, otherwise it
* returns the size of the range using {@link Range#getSize()}.
*
* @param representation
* The representation to evaluate.
* @return The available size.
*/
public static long getAvailableSize(Representation representation) {
// [ifndef gwt]
if (representation.getRange() == null) {
return representation.getSize();
} else if (representation.getRange().getSize() != Range.SIZE_MAX) {
if (representation.hasKnownSize()) {
return Math.min(representation.getRange().getIndex()
+ representation.getRange().getSize(),
representation.getSize())
- representation.getRange().getIndex();
} else {
return Representation.UNKNOWN_SIZE;
}
} else if (representation.hasKnownSize()) {
if (representation.getRange().getIndex() != Range.INDEX_LAST) {
return representation.getSize()
- representation.getRange().getIndex();
}
return representation.getSize();
}
return Representation.UNKNOWN_SIZE;
// [enddef]
// [ifdef gwt] line uncomment
// return representation.getSize();
}
// [ifndef gwt] method
/**
* Returns a readable byte channel based on a given input stream. If it is
* supported by a file a read-only instance of FileChannel is returned.
*
* @param inputStream
* The input stream to convert.
* @return A readable byte channel.
*/
public static ReadableByteChannel getChannel(InputStream inputStream)
throws IOException {
ReadableByteChannel result = null;
if (inputStream instanceof FileInputStream) {
result = ((FileInputStream) inputStream).getChannel();
} else if (inputStream != null) {
result = new InputStreamChannel(inputStream);
}
return result;
}
// [ifndef gwt] method
/**
* Returns a writable byte channel based on a given output stream.
*
* @param outputStream
* The output stream.
* @return A writable byte channel.
*/
public static WritableByteChannel getChannel(OutputStream outputStream) {
return (outputStream != null) ? Channels.newChannel(outputStream)
: null;
}
// [ifndef gwt] method
/**
* Returns a readable byte channel based on the given representation's
* content and its write(WritableByteChannel) method. Internally, it uses a
* writer thread and a pipe channel.
*
* @param representation
* the representation to get the {@link OutputStream} from.
* @return A readable byte channel.
* @throws IOException
*/
public static ReadableByteChannel getChannel(
final Representation representation) throws IOException {
ReadableByteChannel result = null;
if (Edition.CURRENT != Edition.GAE) {
// [ifndef gae]
final java.nio.channels.Pipe pipe = java.nio.channels.Pipe.open();
// Get a thread that will handle the task of continuously
// writing the representation into the input side of the pipe
Runnable task = new Runnable() {
public void run() {
WritableByteChannel wbc = null;
try {
wbc = pipe.sink();
representation.write(wbc);
} catch (IOException ioe) {
Context.getCurrentLogger().log(Level.WARNING,
"Error while writing to the piped channel.",
ioe);
} finally {
if (wbc != null)
try {
wbc.close();
} catch (IOException e) {
Context.getCurrentLogger()
.log(Level.WARNING,
"Error while closing to the piped channel.",
e);
}
}
}
};
org.restlet.Context context = org.restlet.Context.getCurrent();
if (context != null && context.getExecutorService() != null) {
context.getExecutorService().execute(task);
} else {
Engine.createThreadWithLocalVariables(task, "Restlet-IoUtils")
.start();
}
result = pipe.source();
// [enddef]
} else {
Context.getCurrentLogger()
.log(Level.WARNING,
"The GAE edition is unable to return a channel for a representation given its write(WritableByteChannel) method.");
}
return result;
}
private static int getProperty(String name, int defaultValue) {
int result = defaultValue;
// [ifndef gwt]
try {
result = Integer.parseInt(System.getProperty(name));
} catch (NumberFormatException nfe) {
result = defaultValue;
}
// [enddef]
return result;
}
/**
* Returns a reader from an input stream and a character set.
*
* @param stream
* The input stream.
* @param characterSet
* The character set. May be null.
* @return The equivalent reader.
* @throws UnsupportedEncodingException
* if a character set is given, but not supported
*/
public static Reader getReader(InputStream stream, CharacterSet characterSet)
throws UnsupportedEncodingException {
if (characterSet != null) {
return new InputStreamReader(stream, characterSet.getName());
}
return new InputStreamReader(stream);
}
// [ifndef gwt] method
/**
* Returns a reader from a writer representation.Internally, it uses a
* writer thread and a pipe stream.
*
* @param representation
* The representation to read from.
* @return The character reader.
* @throws IOException
*/
public static Reader getReader(
final org.restlet.representation.WriterRepresentation representation)
throws IOException {
Reader result = null;
final java.io.PipedWriter pipedWriter = new java.io.PipedWriter();
java.io.PipedReader pipedReader = new java.io.PipedReader(pipedWriter);
// Gets a thread that will handle the task of continuously
// writing the representation into the input side of the pipe
Runnable task = new org.restlet.engine.util.ContextualRunnable() {
public void run() {
try {
representation.write(pipedWriter);
pipedWriter.flush();
} catch (IOException ioe) {
Context.getCurrentLogger().log(Level.WARNING,
"Error while writing to the piped reader.", ioe);
} finally {
try {
pipedWriter.close();
} catch (IOException ioe2) {
Context.getCurrentLogger().log(Level.WARNING,
"Error while closing the pipe.", ioe2);
}
}
}
};
org.restlet.Context context = org.restlet.Context.getCurrent();
if (context != null && context.getExecutorService() != null) {
context.getExecutorService().execute(task);
} else {
Engine.createThreadWithLocalVariables(task, "Restlet-IoUtils")
.start();
}
result = pipedReader;
return result;
}
// [ifndef gwt] method
/**
* Returns an output stream based on a given writer.
*
* @param writer
* The writer.
* @param characterSet
* The character set used to write on the output stream.
* @return the output stream of the writer
*/
public static java.io.OutputStream getStream(java.io.Writer writer,
CharacterSet characterSet) {
return new WriterOutputStream(writer, characterSet);
}
// [ifndef gwt] method
/**
* Returns an input stream based on a given readable byte channel.
*
* @param readableChannel
* The readable byte channel.
* @return An input stream based on a given readable byte channel.
*/
public static InputStream getStream(ReadableByteChannel readableChannel) {
InputStream result = null;
if (readableChannel != null) {
result = isBlocking(readableChannel) ? Channels
.newInputStream(readableChannel) : null;
}
return result;
}
// [ifndef gwt] method
/**
* Returns an input stream based on a given character reader.
*
* @param reader
* The character reader.
* @param characterSet
* The stream character set.
* @return An input stream based on a given character reader.
*/
public static InputStream getStream(Reader reader, CharacterSet characterSet) {
InputStream result = null;
try {
result = new ReaderInputStream(reader, characterSet);
} catch (IOException e) {
Context.getCurrentLogger().log(Level.WARNING,
"Unable to create the reader input stream", e);
}
return result;
}
// [ifndef gwt] method
/**
* Returns an input stream based on the given representation's content and
* its write(OutputStream) method. Internally, it uses a writer thread and a
* pipe stream.
*
* @param representation
* the representation to get the {@link java.io.OutputStream}
* from.
* @return A stream with the representation's content.
*/
public static InputStream getStream(final Representation representation) {
InputStream result = null;
if (representation == null) {
return null;
}
final PipeStream pipe = new PipeStream();
final java.io.OutputStream os = pipe.getOutputStream();
// Creates a thread that will handle the task of continuously
// writing the representation into the input side of the pipe
Runnable task = new org.restlet.engine.util.ContextualRunnable() {
public void run() {
try {
representation.write(os);
os.flush();
} catch (IOException ioe) {
Context.getCurrentLogger().log(Level.WARNING,
"Error while writing to the piped input stream.",
ioe);
} finally {
try {
os.close();
} catch (IOException ioe2) {
Context.getCurrentLogger().log(Level.WARNING,
"Error while closing the pipe.", ioe2);
}
}
}
};
org.restlet.Context context = org.restlet.Context.getCurrent();
if (context != null && context.getExecutorService() != null) {
context.getExecutorService().execute(task);
} else {
Engine.createThreadWithLocalVariables(task, "Restlet-IoUtils")
.start();
}
result = pipe.getInputStream();
return result;
}
// [ifndef gwt] method
/**
* Returns an output stream based on a given writable byte channel.
*
* @param writableChannel
* The writable byte channel.
* @return An output stream based on a given writable byte channel.
*/
public static OutputStream getStream(WritableByteChannel writableChannel) {
OutputStream result = null;
if (writableChannel != null) {
result = isBlocking(writableChannel) ? Channels
.newOutputStream(writableChannel) : null;
}
return result;
}
// [ifndef gwt] method
/**
* Converts the representation to a string value. Be careful when using this
* method as the conversion of large content to a string fully stored in
* memory can result in OutOfMemoryErrors being thrown.
*
* @param representation
* The representation to convert.
* @return The representation as a string value.
*/
public static String getText(Representation representation)
throws IOException {
String result = null;
if (representation.isAvailable()) {
if (representation.getSize() == 0) {
result = "";
} else {
java.io.StringWriter sw = new java.io.StringWriter();
representation.write(sw);
sw.flush();
result = sw.toString();
}
}
return result;
}
// [ifndef gwt] method
/**
* Returns a writer to the given output stream, using the given character
* set for encoding to bytes.
*
* @param outputStream
* The target output stream.
* @param characterSet
* The character set for encoding.
* @return The wrapping writer.
*/
public static Writer getWriter(OutputStream outputStream,
CharacterSet characterSet) {
Writer result = null;
if (characterSet != null) {
result = new OutputStreamWriter(outputStream,
characterSet.toCharset());
} else {
// Use the default HTTP character set
result = new OutputStreamWriter(outputStream,
CharacterSet.ISO_8859_1.toCharset());
}
return result;
}
// [ifndef gwt] method
/**
* Indicates if the channel is in blocking mode. It returns false when the
* channel is selectable and configured to be non blocking.
*
* @param channel
* The channel to test.
* @return True if the channel is in blocking mode.
*/
public static boolean isBlocking(Channel channel) {
boolean result = true;
if (channel instanceof SelectableChannel) {
SelectableChannel selectableChannel = (SelectableChannel) channel;
result = selectableChannel.isBlocking();
}
return result;
}
// [ifndef gwt] method
/**
* Converts a char array into a byte array using the default character set.
*
* @param chars
* The source characters.
* @return The result bytes.
*/
public static byte[] toByteArray(char[] chars) {
return IoUtils.toByteArray(chars, java.nio.charset.Charset
.defaultCharset().name());
}
// [ifndef gwt] method
/**
* Converts a char array into a byte array using the provided character set.
*
* @param chars
* The source characters.
* @param charsetName
* The character set to use.
* @return The result bytes.
*/
public static byte[] toByteArray(char[] chars, String charsetName) {
java.nio.CharBuffer cb = java.nio.CharBuffer.wrap(chars);
java.nio.ByteBuffer bb = java.nio.charset.Charset.forName(charsetName)
.encode(cb);
byte[] r = new byte[bb.remaining()];
bb.get(r);
return r;
}
// [ifndef gwt] method
/**
* Converts a byte array into a character array using the default character
* set.
*
* @param bytes
* The source bytes.
* @return The result characters.
*/
public static char[] toCharArray(byte[] bytes) {
return IoUtils.toCharArray(bytes, java.nio.charset.Charset
.defaultCharset().name());
}
// [ifndef gwt] method
/**
* Converts a byte array into a character array using the default character
* set.
*
* @param bytes
* The source bytes.
* @param charsetName
* The character set to use.
* @return The result characters.
*/
public static char[] toCharArray(byte[] bytes, String charsetName) {
java.nio.ByteBuffer bb = java.nio.ByteBuffer.wrap(bytes);
java.nio.CharBuffer cb = java.nio.charset.Charset.forName(charsetName)
.decode(bb);
char[] r = new char[cb.remaining()];
cb.get(r);
return r;
}
// [ifndef gwt] method
/**
* Converts a byte array into an hexadecimal string.
*
* @param byteArray
* The byte array to convert.
* @return The hexadecimal string.
*/
public static String toHexString(byte[] byteArray) {
final char[] hexChars = new char[2 * byteArray.length];
int i = 0;
for (final byte b : byteArray) {
hexChars[i++] = HEXDIGITS[(b >> 4) & 0xF];
hexChars[i++] = HEXDIGITS[b & 0xF];
}
return new String(hexChars);
}
/**
* Converts an input stream to a string.<br>
* As this method uses the InputstreamReader class, the default character
* set is used for decoding the input stream.
*
* @see InputStreamReader
* @see IoUtils#toString(InputStream, CharacterSet)
* @param inputStream
* The input stream.
* @return The converted string.
*/
public static String toString(InputStream inputStream) {
return IoUtils.toString(inputStream, null);
}
/**
* Converts an input stream to a string using the specified character set
* for decoding the input stream. Once read, the input stream is closed.
*
* @see InputStreamReader
* @param inputStream
* The input stream.
* @param characterSet
* The character set
* @return The converted string.
*/
public static String toString(InputStream inputStream,
CharacterSet characterSet) {
String result = null;
if (inputStream != null) {
// [ifndef gwt]
try {
if (characterSet != null) {
result = IoUtils.toString(new InputStreamReader(
inputStream, characterSet.getName()));
} else {
result = IoUtils
.toString(new InputStreamReader(inputStream));
}
inputStream.close();
} catch (Exception e) {
// Returns an empty string
}
// [enddef]
// [ifdef gwt] uncomment
// if (inputStream instanceof StringInputStream) {
// return ((StringInputStream) inputStream).getText();
// } else {
// try {
// if (characterSet != null) {
// result = toString(new InputStreamReader(inputStream,
// characterSet.getName()));
// } else {
// result = toString(new InputStreamReader(inputStream));
// }
// } catch (Exception e) {
// // Returns an empty string
// }
// }
// [enddef]
}
return result;
}
/**
* Converts a reader to a string.
*
* @see InputStreamReader
*
* @param reader
* The characters reader.
* @return The converted string.
*/
public static String toString(Reader reader) {
String result = null;
if (reader != null) {
try {
StringBuilder sb = new StringBuilder();
BufferedReader br = (reader instanceof BufferedReader) ? (BufferedReader) reader
: new BufferedReader(reader, BUFFER_SIZE);
char[] buffer = new char[2048];
int charsRead = br.read(buffer);
while (charsRead != -1) {
sb.append(buffer, 0, charsRead);
charsRead = br.read(buffer);
}
br.close();
result = sb.toString();
} catch (Exception e) {
// Returns an empty string
}
}
return result;
}
/**
* Private constructor to ensure that the class acts as a true utility class
* i.e. it isn't instantiable and extensible.
*/
private IoUtils() {
}
}