package com.limegroup.gnutella.connection;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import com.limegroup.gnutella.io.ChannelReadObserver;
import com.limegroup.gnutella.io.InterestReadChannel;
import com.limegroup.gnutella.messages.BadPacketException;
import com.limegroup.gnutella.messages.Message;
/**
* Reads messages from a channel. This class is notified when more of a message
* can potentially be read by its handleRead() method being called. To change
* the channel this reads from, use setReaderChannel(ReadableByteChannel).
*
* It is possible to construct this class without an initial source channel.
* However, before handleRead is called, the channel must be set.
*
* The first time the channel returns -1 this will throw an IOException, as it
* never expects the channel to run out of data. Upon each read notification,
* as much data as possible will be read from the source channel.
*/
public class MessageReader implements ChannelReadObserver {
/** the maximum size of a message payload that we'll accept */
private static final long MAX_MESSAGE_SIZE = 64 * 1024;
/** the size of the header */
private static final int HEADER_SIZE = 23;
/** where in the header the payload is */
private static final int PAYLOAD_LENGTH_OFFSET = 19;
/** the constant buffer to use for emtpy payloads. */
private static final ByteBuffer EMPTY_PAYLOAD = ByteBuffer.allocate(0);
/** the sole buffer for parsing msg headers */
private final ByteBuffer header;
/** the buffer used for parsing the payload -- recreated for each message */
private ByteBuffer payload;
/** the sole receiver of messages */
private final MessageReceiver receiver;
/** the source channel */
private InterestReadChannel channel;
/** whether or not this reader has been shut down yet. */
private boolean shutdown = false;
/**
* Constructs a new MessageReader without an underlying source.
* Prior to handleRead() being called, setReadChannel(ReadableByteChannel)
* MUST be called.
*/
public MessageReader(MessageReceiver receiver) {
this(null, receiver);
}
/**
* Constructs a new MessageReader with the given source channel & receiver.
*/
public MessageReader(InterestReadChannel channel, MessageReceiver receiver) {
if(receiver == null)
throw new NullPointerException("null receiver");
this.channel = channel;
this.receiver = receiver;
this.header = ByteBuffer.allocate(HEADER_SIZE);
header.order(ByteOrder.LITTLE_ENDIAN);
this.payload = null;
}
/**
* Sets the new channel to be reading from.
*/
public void setReadChannel(InterestReadChannel channel) {
if(channel == null)
throw new NullPointerException("cannot set null channel!");
this.channel = channel;
}
/**
* Gets the channel that is used for reading.
*/
public InterestReadChannel getReadChannel() {
return channel;
}
/**
* Notification that a read can be performed from the given channel.
* All messages that can be read without blocking are read & dispatched.
*/
public void handleRead() throws IOException {
// Continue reading until we can't fill up the header or payload.
while(true) {
int read = 0;
// First try to fill up the header.
while(header.hasRemaining() && (read = channel.read(header)) > 0);
// If there header's not full, we can't bother reading the payload, so abort.
if(header.hasRemaining()) {
if(read == -1)
throw new IOException("EOF");
break;
}
// if we haven't set up a payload yet, set one up (if necessary).
if(payload == null) {
int payloadLength = header.getInt(PAYLOAD_LENGTH_OFFSET);
if(payloadLength < 0 || payloadLength > MAX_MESSAGE_SIZE)
throw new IOException("should i implement skipping?");
if(payloadLength == 0) {
payload = EMPTY_PAYLOAD;
} else {
try {
payload = ByteBuffer.allocate(payloadLength);
} catch(OutOfMemoryError oome) {
throw new IOException("message too large.");
}
}
}
// Okay, a payload is set up, let's read into it.
while(payload.hasRemaining() && (read = channel.read(payload)) > 0);
// If the payload's not full, we can't create a message, so abort.
if(payload.hasRemaining()) {
if(read == -1)
throw new IOException("eof");
break;
}
// Yay, we've got a full message.
try {
Message m = Message.createMessage(header.array(), payload.array(),
receiver.getSoftMax(), receiver.getNetwork());
receiver.processReadMessage(m);
} catch(BadPacketException ignored) {}
if(read == -1)
throw new IOException("eof");
payload = null;
header.clear();
}
}
/**
* Informs the receiver that the message is shutdown.
*/
public void shutdown() {
synchronized(this) {
if(shutdown)
return;
shutdown = true;
}
receiver.messagingClosed();
}
/** Unused */
public void handleIOException(IOException iox) {
throw new RuntimeException("unsupported operation", iox);
}
}