/******************************************************************************* * $Id$ * $Author$ * $Date$ * * Copyright 2002 - YAJUL Developers, Joshua Davis, Kent Vogel. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * ******************************************************************************/ package org.yajul.io; import org.yajul.util.Copier; import java.io.*; import java.util.ArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; import static org.yajul.juli.LogHelper.unexpected; import static org.yajul.util.Copier.UNLIMITED; /** * Provides stream copying capability in a Runnable class. This can be used to * redirect streams from a spawned JVM, or to 'pump' a one side of * PipedInputStream / PipedOutputStream pair.<br> * Also provides a static method that copies an entire input stream into * an output stream. * * @author Joshua Davis */ public class StreamCopier implements Runnable { private static final Logger log = Logger.getLogger(StreamCopier.class.getName()); /** * The default buffer size. */ public static final int DEFAULT_BUFFER_SIZE = 256; /** * The input stream. */ private InputStream in; /** * The output stream. */ private OutputStream out; /** * The buffer size to use while copying. */ private final int bufsz; /** * The number of bytes to copy. */ private final int limit; /** * Progress callback functions. */ private final Copier.Callback callback; /** * If an exception was thrown in the run() method, this will be set. */ private IOException exception; /** * Lock that guards the conditions. */ private final Lock lock = new ReentrantLock(); /** * Signaled when the copying loop is complete. */ private final Condition complete = lock.newCondition(); private static final int DEFAULT_BYTE_ARRAY_BUFSZ = 128; /** * Copies the input stream into the output stream in a thread safe and * efficient manner. * * @param in The input stream. * @param out The output stream. If this is null, the input will be * discarded, similar to piping to /dev/null on UN*X. * @param bufsz The size of the buffer to use. * @return int The number of bytes copied. * @throws IOException When the stream could not be copied. */ @SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter"}) public static int copy(InputStream in, OutputStream out, int bufsz) throws IOException { // From Java I/O, page 43 // Do not allow other threads to read from the input or write to the // output while the copying is taking place. synchronized (in) { if (out != null) { synchronized (out) { return unsyncCopy(in, out, bufsz); } // synchronized (out) } else { return unsyncCopy(in, out, bufsz); } } // synchronized (in) } /** * Copies the input stream into the output stream in an efficient manner. * This version does not synchronize on the streams, so it is not safe * to use when the streams are being accessed by multiple threads. * * @param in The input stream. * @param out The output stream. If this is null, the input will be * discarded, similar to piping to /dev/null on UN*X. * @param bufsz The size of the buffer to use. * @return int The number of bytes copied. * @throws IOException When the stream could not be copied. */ public static int unsyncCopy(InputStream in, OutputStream out, int bufsz) throws IOException { return unsyncCopy(in, out, bufsz, UNLIMITED); } /** * Copies the reader into the writer in an efficient manner. * This version does not synchronize on the streams, so it is not safe * to use when the streams are being accessed by multiple threads. * * @param in The reader. * @param out The writer. If this is null, the input will be * discarded, similar to piping to /dev/null on UN*X. * @param bufsz The size of the buffer to use. * @return int The number of bytes copied. * @throws IOException When the stream could not be copied. */ public static int unsyncCopy(Reader in, Writer out, int bufsz) throws IOException { return unsyncCopy(in, out, bufsz, UNLIMITED); } /** * Copies the input stream into the output stream in an efficient manner. * This version does not synchronize on the streams, so it is not safe * to use when the streams are being accessed by multiple threads. * * @param in The input stream. * @param out The output stream. If this is null, the input will be * discarded, similar to piping to /dev/null on UN*X. * @param bufsz The size of the buffer to use. * @param limit The number of bytes to copy, or UNLIMITED (-1) to copy * until the end of the input stream. * @return int The number of bytes copied. * @throws IOException When the stream could not be copied. */ public static int unsyncCopy(InputStream in, OutputStream out, int bufsz, int limit) throws IOException { return Copier.copy(in, out, bufsz, limit); } /** * Copies the input stream (reader) into the output stream (writer) in an efficient manner. * This version does not synchronize on the streams, so it is not safe * to use when the streams are being accessed by multiple threads. * * @param in The input reader * @param out The output writer. If this is null, the input will be * discarded, similar to piping to /dev/null on UN*X. * @param bufsz The size of the buffer to use. * @param limit The number of bytes to copy, or UNLIMITED (-1) to copy * until the end of the input stream. * @return int The number of bytes copied. * @throws IOException When the stream could not be copied. */ public static int unsyncCopy(Reader in, Writer out, int bufsz, int limit) throws IOException { return Copier.copy(in, out, bufsz, limit); } /** * Copies the input stream into the output stream in a thread safe and * efficient manner. * * @param in The input stream. * @param out The output stream. If this is null, the input will be * discarded, similar to piping to /dev/null on UN*X. * @return int The number of bytes copied. * @throws IOException When the stream could not be copied. */ public static int copy(InputStream in, OutputStream out) throws IOException { return copy(in, out, DEFAULT_BUFFER_SIZE); } /** * Reads the entire input stream into an array list of byte arrays, each * byte array being a maximum of 'blocksz' bytes long. * * @param blocksz The block size. Byte arrays in the list will not * be longer than this. * @param in The input stream. * @return ArrayList - An array list of byte arrays. * @throws IOException When something happens while reading the stream. */ public static ArrayList readBlocks(InputStream in, int blocksz) throws IOException { ArrayList<byte[]> list = new ArrayList<byte[]>(); byte[] chunk; byte[] buf = new byte[blocksz]; int bytesRead; while (true) { bytesRead = in.read(buf); if (bytesRead == -1) break; // Add a new chunk to the list. chunk = new byte[bytesRead]; System.arraycopy(buf, 0, chunk, 0, bytesRead); list.add(chunk); } // while return list; } /** * Reads the entire input stream into a byte array. * * @param in The input stream. * @return the byte array * @throws IOException When something happens while reading the stream. */ public static byte[] readByteArray(InputStream in) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); unsyncCopy(in, baos, DEFAULT_BYTE_ARRAY_BUFSZ); return baos.toByteArray(); } /** * Reads the entire input stream into a byte array with a limit. * * @param in The input reader * @param limit The number of bytes to read. * @return An array of bytes read from the input. * @throws IOException Thrown if there was an error while copying. */ public static byte[] readByteArray(InputStream in, int limit) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); unsyncCopy(in, baos, DEFAULT_BUFFER_SIZE, limit); return baos.toByteArray(); } /** * Reads the entire input stream into a byte array with a limit. * * @param in The input reader * @param limit The number of bytes to read. * @return An array of bytes read from the input. * @throws IOException Thrown if there was an error while copying. */ public static byte[] readByteArray(Reader in, int limit) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); unsyncCopy(in, new OutputStreamWriter(baos), DEFAULT_BUFFER_SIZE, limit); return baos.toByteArray(); } /** * Reads the entire input stream into a byte array. * * @param in The input reader * @return An array of bytes read from the input. * @throws IOException Thrown if there was an error while copying. */ public static byte[] readByteArray(Reader in) throws IOException { return readByteArray(in, -1); } /** * Reads the specified file into a byte array. * * @param file The file to read. * @return An array of bytes read from the input. * @throws IOException When something happens while reading the stream. */ public static byte[] readByteArray(File file) throws IOException { return readByteArray(new BufferedInputStream( new FileInputStream(file), DEFAULT_BUFFER_SIZE)); } /** * Reads the specified file into a byte array. * * @param fileName The file name to read. * @return An array of bytes read from the input. * @throws IOException When something happens while reading the stream. */ public static byte[] readFileIntoByteArray(String fileName) throws IOException { return readByteArray(new File(fileName)); } /** * Serializes the object into an array of bytes. * * @param o The object to serialize. * @return An array of bytes that contiains the serialized object. * @throws java.io.IOException if something goes wrong */ public static byte[] serializeObject(Object o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(o); oos.flush(); return baos.toByteArray(); } /** * Reads a serialized object from the array of bytes. * * @param bytes The array of bytes. * @return The unserialized object. * @throws IOException if there was a problem reading the input. * @throws ClassNotFoundException if the class of the object in the input was not found. */ public static Object unserializeObject(byte[] bytes) throws IOException, ClassNotFoundException { ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais); return ois.readObject(); } /** * Creates a new stream copier, that will copy the input stream into the * output stream when the run() method is caled. * * @param in The input stream to read from. * @param out The output stream. If this is null, the input will be * discarded, similar to piping to /dev/null on UN*X. */ public StreamCopier(InputStream in, OutputStream out) { this(in,out,DEFAULT_BUFFER_SIZE,UNLIMITED, Copier.NO_CALLBACK); } /** * Creates a new stream copier, that will copy the input stream into the * output stream when the run() method is caled. * * @param in The input stream to read from. * @param out The output stream. If this is null, the input will be * discarded, similar to piping to /dev/null on UN*X. * @param bufsz the buffer size to use * @param limit limit the copy to this number of bytes (-1 for unlimited) * @param callback optional progress callback */ public StreamCopier(InputStream in,OutputStream out,int bufsz,int limit,Copier.Callback callback) { this.in = in; this.out = out; this.bufsz = bufsz; this.limit = limit; this.callback = callback; } /** * This method will copy the input into the output until there is no more * input. Since this method is typically run by a thread, exceptions * are not thrown from it. Instead, the exception can be read using * the getException() method. * <p/> * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p/> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public void run() { try { // Copy, using the a buffer. Copier.copy(in, out, bufsz, limit, callback); // Flush the output. if (out != null) out.flush(); } catch (IOException e) { // Log the exception! unexpected(log, e); setException(e); } finally { signalComplete(); } } private void setException(IOException e) { // Remember the exception, just in case anyone cares. synchronized (this) { exception = e; } } private void signalComplete() { lock.lock(); try { complete.signalAll(); } finally { lock.unlock(); } } /** * Waits for the copy operation to complete. * * @param millis milliseconds to wait, < 0 to wait forever * @return {@code false} if the waiting time detectably elapsed * before return from the method, else {@code true}. * If millis is < 0, this always returns true after waiting forever. * @throws InterruptedException if the thread was interrupted. */ public boolean waitForComplete(long millis) throws InterruptedException { boolean rv = true; lock.lock(); try { if (millis >= 0) { rv = complete.await(millis, TimeUnit.MILLISECONDS); } else complete.await(); return rv; } finally { lock.unlock(); } } /** * Returns the exception thrown in the run() method, if any. * * @return IOException - The exception thrown during the run() method, * or null if there were no errors. */ public IOException getException() { synchronized (this) { return exception; } } }