/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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
*
* 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 org.apache.hadoop.io;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* BufferedByteInputStream uses an underlying in-memory staging buffer for
* reading bytes from the underlying input stream. The actual reading is done
* by a separate thread.
*/
public class BufferedByteInputStream extends InputStream {
static final Log LOG = LogFactory.getLog(BufferedByteInputStream.class
.getName());
// staging buffer used to store bytes read from underlying stream
private final BufferedByteInputOutput buffer;
// reader thread that reads bytes from underlying stream
// and writes to the buffer
private final ReadThread readThread;
// actual input stream to read from
private final InputStream underlyingInputStream;
private boolean closed = false;
/**
* Wrap given input stream with BufferedByteInputOutput.
* This is the only way to instantiate the buffered input stream.
* @param is underlying input stream
* @param bufferSize size of the in memory buffer
* @param readBufferSize size of the buffer used for reading from is
*/
public static DataInputStream wrapInputStream(InputStream is, int bufferSize,
int readBufferSize) {
// wrapping BufferedByteInputStream in BufferedInputStream decreases
// pressure on BBIS internal locks, and we read from the BBIS in
// bigger chunks
return new DataInputStream(new BufferedInputStream(
new BufferedByteInputStream(is, bufferSize, readBufferSize)));
}
/**
* Construct BuffredByteInputStream
* @param is underlying input stream
* @param bufferSize size of the in-memory buffer
* @param readBufferSize size of the transfer chunks
* (from input stream to staging buffer)
*/
private BufferedByteInputStream(InputStream is, int bufferSize,
int readBufferSize) {
buffer = new BufferedByteInputOutput(bufferSize);
readThread = new ReadThread(is, buffer, readBufferSize);
readThread.setDaemon(true);
readThread.start();
underlyingInputStream = is;
}
// override InputStream methods
// each of the calls will fail after the stream is closed
// after reading all bytes available in the buffer!
public int read() throws IOException {
return checkOutput(buffer.read());
}
public int read(byte[] buf) throws IOException {
return checkOutput(buffer.read(buf, 0, buf.length));
}
public int read(byte[] buf, int off, int len) throws IOException {
return checkOutput(buffer.read(buf, off, len));
}
/**
* How many bytes are available in the buffer.
* After closing this call will fail.
*/
public int available() throws IOException {
checkOutput(-1);
return buffer.available();
}
/**
* Close the input stream. Joins the thread and
* closes the underlying input stream. Can be
* called multiple times.
*/
public void close() throws IOException {
// multiple close should return with no errors
// readThread will close underlying buffer
readThread.close();
try {
readThread.join();
} catch (InterruptedException e) {
throw new IOException(e);
}
}
/////////////////////////////////////
/**
* Check if the read thread died, if so, throw an exception.
*/
private int checkOutput(int readBytes) throws IOException {
if (readBytes > -1) {
return readBytes;
}
if (closed) {
throw new IOException("The stream has been closed");
}
if (readThread.error != null) {
throw new IOException(readThread.error.getMessage());
}
return readBytes;
}
/**
* Reader thread which will copy bytes from the underlying input stream
* to the byte buffer.
*/
private class ReadThread extends Thread implements Runnable {
private final InputStream is;
private final BufferedByteInputOutput buffer;
private final int readBufferSize;
volatile Throwable error = null;
ReadThread(InputStream is, BufferedByteInputOutput os, int readBufferSize) {
this.buffer = os;
this.is = is;
this.readBufferSize = readBufferSize;
}
/**
* Close input stream.
* Closes the buffer and underlying input stream;
*/
void close() throws IOException {
closed = true;
buffer.close();
is.close();
}
public void run() {
byte[] buf = new byte[readBufferSize];
int bytesRead;
try {
while (((bytesRead = is.read(buf)) > 0)) {
buffer.write(buf, 0, bytesRead);
}
} catch (Exception e) {
// after closing we might get an error here
// but we can ignore it
if (!closed) {
error = e;
LOG.warn("Exception", e);
}
} finally {
// close buffer and input stream
buffer.close();
try {
is.close();
} catch (IOException e) {
error = e;
LOG.error("Exception when closing underlying input stream", e);
}
}
}
}
}