All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.threerings.io.FramedInputStream Maven / Gradle / Ivy

//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.io;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;

import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;

/**
 * The framed input stream reads input that was framed by a framing output
 * stream. Framing in this case simply means writing the length of the
 * frame followed by the data associated with the frame so that an entire
 * frame can be loaded from the network layer before any higher layer
 * attempts to process it. Additionally, any failure in decoding a frame
 * won't result in the entire stream being skewed due to the remainder of
 * the undecoded frame remaining in the input stream.
 *
 * 

The framed input stream reads an entire frame worth of data into its * internal buffer when readFrame() is called. It then * behaves as if this is the only data available on the stream (meaning * that when the data in the frame is exhausted, it will behave as if the * end of the stream has been reached). The buffer can only contain a * single frame at a time, so any data left over from a previous frame * will disappear when readFrame() is called again. * *

Note: The framing input stream does not synchronize reads * from its internal buffer. It is intended to only be accessed from a * single thread. * *

Implementation note: maybe this should derive from * FilterInputStream and be tied to a single * InputStream for its lifetime. */ public class FramedInputStream extends InputStream { /** * Creates a new framed input stream. */ public FramedInputStream () { _buffer = ByteBuffer.allocate(INITIAL_BUFFER_CAPACITY); } /** * Reads a frame from the provided channel, appending to any partially * read frame. If the entire frame data is not yet available, * readFrame will return false, otherwise true. * *

Note: when this method returns true, it is required * that the caller read all of the frame data from the stream * before again calling {@link #readFrame} as the previous frame's * data will be elimitated upon the subsequent call. * * @return true if the entire frame has been read, false if the buffer * contains only a partial frame. */ public boolean readFrame (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 (checkForCompleteFrame()) { 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 ByteBuffer newbuf = ByteBuffer.allocate(_buffer.capacity() << 1); 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 checkForCompleteFrame(); } /** * Decodes and returns the length of the current frame from the buffer * if possible. Returns -1 otherwise. */ protected final int decodeLength () { // if we don't have enough bytes to determine our frame size, stop // here and let the caller know that we're not ready if (_have < HEADER_SIZE) { return -1; } // decode the frame length _buffer.rewind(); int length = (_buffer.get() & 0xFF) << 24; length += (_buffer.get() & 0xFF) << 16; length += (_buffer.get() & 0xFF) << 8; length += (_buffer.get() & 0xFF); _buffer.position(_have); return length; } /** * Returns true if a complete frame is in the buffer, false otherwise. * If a complete frame is in the buffer, the buffer will be prepared * to deliver that frame via our {@link InputStream} interface. */ protected final boolean checkForCompleteFrame () { if (_length == -1 || _have < _length) { return false; } // prepare the buffer such that this frame can be read _buffer.position(HEADER_SIZE); _buffer.limit(_length); return true; } /** * Reads the next byte of data from this input stream. The value byte * is returned as an int in the range 0 to * 255. If no byte is available because the end of the * stream has been reached, the value -1 is returned. * *

This read method cannot block. * * @return the next byte of data, or -1 if the end of the * stream has been reached. */ @Override public int read () { return (_buffer.remaining() > 0) ? (_buffer.get() & 0xFF) : -1; } /** * Reads up to len bytes of data into an array of bytes * from this input stream. If pos equals * count, then -1 is returned to indicate * end of file. Otherwise, the number k of bytes read is * equal to the smaller of len and * count-pos. If k is positive, then bytes * buf[pos] through buf[pos+k-1] are copied * into b[off] through b[off+k-1] in the * manner performed by System.arraycopy. The value * k is added into pos and k is * returned. * *

This read 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 * -1 if there is no more data because the end of the * stream has been reached. */ @Override 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 n bytes of input from this input stream. Fewer * bytes might be skipped if the end of the input stream is reached. * The actual number k of bytes to be skipped is equal to * the smaller of n and count-pos. The value * k is added into pos and k is * returned. * * @param n the number of bytes to be skipped. * * @return the actual number of bytes skipped. */ @Override public long skip (long n) { throw new UnsupportedOperationException(); } /** * Returns the number of bytes that can be read from this input stream * without blocking. * * @return the number of bytes remaining to be read from the buffered * frame. */ @Override public int available () { return _buffer.remaining(); } /** * Always returns false as framed input streams do not support * marking. */ @Override public boolean markSupported () { return false; } /** * Does nothing, as marking is not supported. */ @Override public void mark (int readAheadLimit) { // not supported; do nothing } /** * Resets the buffer to the beginning of the buffered frames. */ @Override public void reset () { // position our buffer at the beginning of the frame data _buffer.position(HEADER_SIZE); } /** The buffer in which we maintain our frame data. */ protected ByteBuffer _buffer; /** The length of the current frame being read. */ protected int _length = -1; /** The number of bytes total that we have in our buffer (these bytes * may comprise more than one frame. */ protected int _have = 0; /** The size of the frame header (a 32-bit integer). */ protected static final int HEADER_SIZE = 4; /** The default initial size of the internal buffer. */ protected static final int INITIAL_BUFFER_CAPACITY = 32; /** No need to get out of hand. */ protected static final int MAX_BUFFER_CAPACITY = 512 * 1024; }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy