/*
* This program is free software; you can redistribute it and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation; either version 3 of the License,
* or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package com.meidusa.amoeba.net.io;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
/**
*
*
* @author <a href=mailto:piratebase@sina.com>Struct chen</a>
*
*/
public abstract class PacketInputStream extends InputStream
{
/**
* ���ڶ�ȡ�ɶ�ͨ���Ļ������ʼ������={@link #INITIAL_BUFFER_CAPACITY}������ʱ��ÿ��������һ�ε�һ��
*/
protected ByteBuffer _buffer;
/** ����������ȣ�������ͷ */
protected int _length = -1;
/**
* ��ǰbuffer �е��ֽڳ���
*/
protected int _have = 0;
/**
* ��ʼ��buffer ����,Ĭ��32�ֽ�
*/
protected static final int INITIAL_BUFFER_CAPACITY = 32;
/** ������� */
protected static final int MAX_BUFFER_CAPACITY = 512 * 1024;
/**
* Creates a new framed input stream.
*/
public PacketInputStream ()
{
_buffer = ByteBuffer.allocate(INITIAL_BUFFER_CAPACITY);
}
/**
* Reads a packet from the provided channel, appending to any partially
* read packet. If the entire packet data is not yet available,
* <code>readPacket</code> will return false, otherwise true.
*
* <p> <em>Note:</em> when this method returns true, it is required
* that the caller read <em>all</em> of the packet data from the stream
* before again calling {@link #readPacket} as the previous packet's
* data will be elimitated upon the subsequent call.
*
* @return true if the entire packet has been read, false if the buffer
* contains only a partial packet.
*/
public boolean readPacket (ReadableByteChannel source)
throws IOException
{
// flush data from any previous frame from the buffer
if (_buffer.limit() == _length) {
// this will remove the old frame's bytes from the buffer,
// shift our old data to the start of the buffer, position the
// buffer appropriately for appending new data onto the end of
// our existing data, and set the limit to the capacity
_buffer.limit(_have);
_buffer.position(_length);
_buffer.compact();
_have -= _length;
// we may have picked up the next frame in a previous read, so
// try decoding the length straight away
_length = decodeLength();
}
// we may already have the next frame entirely in the buffer from
// a previous read
if (checkForCompletePacket()) {
return true;
}
// read whatever data we can from the source
do {
int got = source.read(_buffer);
if (got == -1) {
throw new EOFException();
}
_have += got;
if (_length == -1) {
// if we didn't already have our length, see if we now
// have enough data to obtain it
_length = decodeLength();
}
// if there's room remaining in the buffer, that means we've
// read all there is to read, so we can move on to inspecting
// what we've got
if (_buffer.remaining() > 0) {
break;
}
// additionally, if the buffer happened to be exactly as long
// as we needed, we need to break as well
if ((_length > 0) && (_have >= _length)) {
break;
}
// otherwise, we've filled up our buffer as a result of this
// read, expand it and try reading some more
int newSize = _buffer.capacity() << 1;
newSize = newSize>_length ? newSize:_length+16;
ByteBuffer newbuf = ByteBuffer.allocate(newSize);
newbuf.put((ByteBuffer)_buffer.flip());
_buffer = newbuf;
// don't let things grow without bounds
} while (_buffer.capacity() < MAX_BUFFER_CAPACITY);
// finally check to see if there's a complete frame in the buffer
// and prepare to serve it up if there is
return checkForCompletePacket();
}
/**
* Decodes and returns the length of the current packet from the buffer
* if possible. Returns -1 otherwise.
*/
protected abstract int decodeLength ();
/**
* Returns true if a complete frame is in the buffer, false otherwise.
* If a complete packet is in the buffer, the buffer will be prepared
* to deliver that frame via our {@link InputStream} interface.
*/
protected boolean checkForCompletePacket ()
{
if (_length == -1 || _have < _length || _length < getHeaderSize()) {
return false;
}
// prepare the buffer such that this frame can be read
_buffer.position(getHeaderSize());
_buffer.limit(_length);
return true;
}
/**
* Reads the next byte of data from this input stream. The value byte
* is returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the
* stream has been reached, the value <code>-1</code> is returned.
*
* <p>This <code>read</code> method cannot block.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream has been reached.
*/
public int read ()
{
return (_buffer.remaining() > 0) ? (_buffer.get() & 0xFF) : -1;
}
/**
* Reads up to <code>len</code> bytes of data into an array of bytes
* from this input stream. If <code>pos</code> equals
* <code>count</code>, then <code>-1</code> is returned to indicate
* end of file. Otherwise, the number <code>k</code> of bytes read is
* equal to the smaller of <code>len</code> and
* <code>count-pos</code>. If <code>k</code> is positive, then bytes
* <code>buf[pos]</code> through <code>buf[pos+k-1]</code> are copied
* into <code>b[off]</code> through <code>b[off+k-1]</code> in the
* manner performed by <code>System.arraycopy</code>. The value
* <code>k</code> is added into <code>pos</code> and <code>k</code> is
* returned.
*
* <p>This <code>read</code> method cannot block.
*
* @param b the buffer into which the data is read.
* @param off the start offset of the data.
* @param len the maximum number of bytes read.
*
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of the
* stream has been reached.
*/
public int read (byte[] b, int off, int len)
{
// if they want no bytes, we give them no bytes; this is
// purportedly the right thing to do regardless of whether we're
// at EOF or not
if (len == 0) {
return 0;
}
// trim the amount to be read to what is available; if they wanted
// bytes and we have none, return -1 to indicate EOF
if ((len = Math.min(len, _buffer.remaining())) == 0) {
return -1;
}
_buffer.get(b, off, len);
return len;
}
/**
* Skips <code>n</code> bytes of input from this input stream. Fewer
* bytes might be skipped if the end of the input stream is reached.
* The actual number <code>k</code> of bytes to be skipped is equal to
* the smaller of <code>n</code> and <code>count-pos</code>. The value
* <code>k</code> is added into <code>pos</code> and <code>k</code> is
* returned.
*
* @param n the number of bytes to be skipped.
*
* @return the actual number of bytes skipped.
*/
public long skip (long n)
{
throw new UnsupportedOperationException();
}
public int available ()
{
return _buffer.remaining();
}
/**
* Always returns false as framed input streams do not support
* marking.
*/
public boolean markSupported ()
{
return false;
}
public int getLength(){
return _length;
}
/**
* Does nothing, as marking is not supported.
*/
public void mark (int readAheadLimit)
{
// not supported; do nothing
}
/**
* Resets the buffer to the beginning of the buffered frames.
*/
public void reset ()
{
// position our buffer at the beginning of the frame data
_buffer.position(getHeaderSize());
}
public String toString(){
StringBuffer buffer = new StringBuffer();
buffer.append("buffer:").append(_buffer).append(",length:").append(_length).append(",have:").append(_have);
return buffer.toString();
}
/**
*
* @return packet header size
*/
public abstract int getHeaderSize();
}