package com.limegroup.gnutella.udpconnect;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Handle reading from a UDP Connection in the form of a stream.
* This class tries to minimize byte array allocations by using the
* data directly as it comes out of messages.
*/
public class UDPBufferedInputStream extends InputStream {
private static final Log LOG =
LogFactory.getLog(UDPBufferedInputStream.class);
/**
* The maximum blocking time of a read.
*/
private static final int FOREVER = 10 * 60 * 60 * 1000;
/**
* A cached chunk of data that hasn't been completely written
* to the stream.
*/
protected Chunk _activeChunk;
/**
* The reader of information coming into this output stream.
*/
private UDPConnectionProcessor _processor;
/**
* Create the InputStream with a handle to the connection processor
* to be used as an input source.
*/
public UDPBufferedInputStream(UDPConnectionProcessor p) {
_processor = p;
_activeChunk = null;
}
/**
* Read the next byte of data from the input source. As normal, return -1
* if there is no more data.
*/
public int read() throws IOException {
boolean timedOut=false;
synchronized(_processor) { // Lock on the ConnectionProcessor
while (true) {
// Try to fetch some data if necessary
checkForData();
if ( _activeChunk != null && _activeChunk.length > 0 ) {
// return a byte of data
_activeChunk.length--;
return (_activeChunk.data[_activeChunk.start++] & 0xff);
} else if ( _activeChunk == null && _processor.isConnected() ) {
// Wait for some data to become available
if (timedOut) {
InterruptedIOException e = new InterruptedIOException();
if (LOG.isDebugEnabled()) {
LOG.debug("read() timed out for timeout "+
_processor.getReadTimeout(),e);
}
throw e;
}
waitOnData();
timedOut=true;
} else {
// This connection is closed
return -1;
}
}
}
}
/**
* Just ensure that this call is passed to the detailed local read method.
*/
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
/**
* Read the next len byte of data from the input source. Return how much
* was really read.
*/
public int read(byte b[], int off, int len)
throws IOException {
int origLen = len;
int origOff = off;
int wlength;
boolean timedOut= false;
synchronized(_processor) { // Lock on the ConnectionProcessor
while (true) {
// Try to fetch some data if necessary
checkForData();
if ( _activeChunk != null && _activeChunk.length > 0 ) {
timedOut=false;
// Load some data
wlength = Math.min(_activeChunk.length, len);
System.arraycopy( _activeChunk.data, _activeChunk.start,
b, off, wlength);
len -= wlength;
off += wlength;
_activeChunk.start += wlength;
_activeChunk.length -= wlength;
if ( len <= 0 )
return origLen;
} else if ( origLen != len ){
// Return whatever was available
return(origLen - len);
} else if ( _activeChunk == null && _processor.isConnected() ) {
// Wait for some data to become available
if (timedOut) {
InterruptedIOException e = new InterruptedIOException();
if (LOG.isDebugEnabled()) {
LOG.debug("read(byte [], int, int) timed out for timeout "+
_processor.getReadTimeout(),e);
}
throw e;
}
waitOnData();
timedOut=true;
} else {
// This connection is closed
return -1;
}
}
}
}
/**
* Throw away n bytes of data. Return the true amount of bytes skipped.
* Note that I am downgrading the long input to an int.
*/
public long skip(long n)
throws IOException {
int len = (int) n;
int origLen = len;
int wlength;
boolean timedOut=false;
// Just like reading a chunk of data above but the bytes get ignored
synchronized(_processor) { // Lock on the ConnectionProcessor
while (true) {
// Try to fetch some data if necessary
checkForData();
if ( _activeChunk != null && _activeChunk.length > 0 ) {
timedOut=false;
// Load some data
wlength = Math.min(_activeChunk.length, len);
len -= wlength;
_activeChunk.start += wlength;
_activeChunk.length -= wlength;
if ( len <= 0 )
return origLen;
} else if ( origLen != len ){
// Return whatever was available
return(origLen - len);
} else if ( _activeChunk == null && _processor.isConnected() ) {
// Wait for some data to become available
if (timedOut) {
InterruptedIOException e = new InterruptedIOException();
if (LOG.isDebugEnabled()) {
LOG.debug("skip() timed out for timeout "+
_processor.getReadTimeout(),e);
}
throw e;
}
waitOnData();
timedOut=true;
} else {
// This connection is closed
return -1;
}
}
}
}
/**
* Returns how many bytes I know are immediately available.
* This can be optimized later but these things never seem to be accurate.
*/
public int available() {
synchronized(_processor) { // Lock on the ConnectionProcessor
if ( _activeChunk == null )
return 0;
return _activeChunk.length;
}
}
/**
* I hope that I don't need to support this.
*/
public boolean markSupported() {
return false;
}
/**
* I hope that I don't need to support this.
*/
public void mark(int readAheadLimit) {
}
/**
* I hope that I don't need to support this.
*/
public void reset() {
}
/**
* This does nothing for now.
*/
public void close() throws IOException {
_processor.close();
}
/**
* If no pending data then try to get some.
*/
private void checkForData() {
synchronized(_processor) { // Lock on the ConnectionProcessor
if ( _activeChunk == null || _activeChunk.length <= 0 ) {
_activeChunk = _processor.getIncomingChunk();
}
}
}
/**
* Wait for a new chunk to become available.
*/
private void waitOnData() throws InterruptedIOException {
synchronized(_processor) { // Lock on the ConnectionProcessor
try {
_processor.wait(_processor.getReadTimeout());
} catch(InterruptedException e) {
throw new InterruptedIOException(e.getMessage());
}
}
}
/**
* Package accessor for notifying readers that data is available.
*/
void wakeup() {
synchronized(_processor) { // Lock on the ConnectionProcessor
// Wakeup any read operation waiting for data
_processor.notify();
}
}
}