blob: 8cea09a9a0b3723c0ca0fd8672cb383a52250b31 [file] [log] [blame]
// $Id$
//
// 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.
//
using System;
using System.Diagnostics;
using System.IO;
namespace Org.Apache.Etch.Bindings.Csharp.Util
{
/// <summary>
/// A FlexBuffer wraps a byte array and manages the active region of
/// it (0..length). It also supports dynamically extending the buffer
/// as needed.
///
/// A FlexBuffer also has an index (read or write cursor). The various
/// Get and put operations always occur at the current index, with the
/// index adjusted appropriately afterward. Get will not move index past
/// length. If put needs to move index past length, length is also
/// adjusted. This may cause the byte array to be re-allocated to a
/// larger size.
/// </summary>
public sealed class FlexBuffer
{
/// <summary>
/// Constructs a FlexBuffer with initial length and index of 0.
/// </summary>
public FlexBuffer()
: this(new byte[INIT_BUFFER_LEN], 0, 0)
{
}
/// <summary>
/// Constructs the FlexBuffer out of an existing buffer, ready to
/// read, with the index set to 0 and length set to buffer.length.
/// The buffer is adopted, not copied. If you want to copy, use one
/// of the put methods instead.
///
/// Note: this is the same as FlexBuffer( buffer, 0, buffer.length ).
/// </summary>
/// <param name="buffer">the buffer to adopt.</param>
/// <see cref="put(byte[])"/>
public FlexBuffer(byte[] buffer)
: this(buffer, 0, buffer.Length)
{
}
/// <summary>
/// Constructs the FlexBuffer out of an existing buffer, ready to
/// read, with the index set to 0 and length set to buffer.length.
/// The buffer is adopted, not copied. If you want to copy, use one
/// of the put methods instead.
///
/// Note: this is the same as FlexBuffer( buffer, 0, length ).
/// </summary>
/// <param name="buffer">the buffer to adopt.</param>
/// <param name="length">the length of the data in the buffer (data presumed
/// to start at 0).</param>
/// <see cref="put(byte[], int, int)"/>
public FlexBuffer(byte[] buffer, int length)
: this(buffer, 0, length)
{
}
/// <summary>
/// Constructs the FlexBuffer out of an existing buffer, ready to
/// read, with the index set to 0 and length set to buffer.length.
/// The buffer is adopted, not copied. If you want to copy, use one
/// of the put methods instead.
///
/// Note: if you want to start off writing to the end of the buffer
/// and not reading it, specify index as the place to start writing,
/// and length as the amount of data that is valid after index ( which
/// might reasonably be 0 )
/// </summary>
/// <param name="buffer">the buffer to adopt.</param>
/// /// <param name="index">the index to start reading or writing.</param>
/// <param name="length">the length of the valid data in the buffer,
/// starting at the index.</param>
///
public FlexBuffer(byte[] buffer, int index, int length )
{
if ( buffer == null )
throw new NullReferenceException( "buffer == null" );
if ( index < 0 )
throw new ArgumentException( " index < 0 " );
if ( length < 0 )
throw new ArgumentException( "length < 0 " );
if ( index + length > buffer.Length )
throw new ArgumentException( "index + length > buffer.Length" );
this.buffer = buffer;
this.index = index;
this.length = index + length;
}
/// <summary>
///
/// </summary>
/// <returns>the current byte array. Might change if any operation
/// needs to extend length past the end of the array.</returns>
public byte[] GetBuf()
{
return buffer;
}
/// <summary>
///
/// </summary>
/// <param name="len"></param>
/// Exception:
/// throws IOException
private void EnsureLength( int len )
{
int n = buffer.Length;
if (len <= n)
return;
// the buffer is not big enough, expand it
int k = n;
if ( k < INIT_BUFFER_LEN )
k = INIT_BUFFER_LEN;
while ( len > k && k < MAX_BUFFER_LEN )
k = k << 1;
if (len > k)
throw new IOException( "buffer overflow" );
byte[] b = new byte[k];
Array.Copy( buffer, 0, b, 0, n );
buffer = b;
}
private byte[] buffer;
private const int INIT_BUFFER_LEN = 32;
private const int TRIM_BUFFER_LEN = 16*1024;
private const int MAX_BUFFER_LEN = 4*1024*1024;
/// <summary>
///
/// </summary>
/// <returns>the current value of length. This is the last
/// index of valid data in the buffer.</returns>
public int Length()
{
return length;
}
/// <summary>
///
/// </summary>
/// <param name="length">length the new value of length. Length must be >= 0.
/// If length is less than index, index is also set to length. If length
/// is larger than the current buffer, the buffer is expanded.</param>
/// <returns>this flex buffer object.</returns>
/// Exception:
/// throws ArgumentOutOfRangeException, IOException
public FlexBuffer SetLength( int length )
{
if (length < 0)
throw new ArgumentOutOfRangeException( "length < 0" );
EnsureLength( length );
this.length = length;
if (index > length)
index = length;
return this;
}
private int length;
/// <summary>
///
/// </summary>
/// <returns>the current value of index.</returns>
public int Index()
{
return index;
}
/// <summary>
///
/// </summary>
/// <param name="index">index the new value of index. Index must be >= 0.</param>
/// <returns>this flex buffer object.</returns>
/// Exception:
/// throws ArgumentOutOfRangeException
public FlexBuffer SetIndex( int index )
{
if (index < 0 || index > length)
throw new ArgumentOutOfRangeException( "index < 0 || index > length" );
this.index = index;
return this;
}
private int index;
/// <summary>
///
/// </summary>
/// <returns>length() - index(). Result is always >= 0. This is the amount
/// of data that could be read using the various forms of Get. It doesn't
/// really mean anything in relation to put.</returns>
public int Avail()
{
return length - index;
}
/// <summary>
/// Sets both length and index to 0.
///
/// Shorthand for SetLength( 0 ).
/// </summary>
/// <returns>this flex buffer object.</returns>
public FlexBuffer Reset()
{
index = 0;
length = 0;
if (buffer.Length > TRIM_BUFFER_LEN)
buffer = new byte[TRIM_BUFFER_LEN];
return this;
}
/// <summary>
/// Compacts the buffer by moving remaining data (from index to length)
/// to the front of the buffer. Sets index to 0, and sets length to
/// Avail (before index was changed).
/// </summary>
/// <returns>this flex buffer object.</returns>
public FlexBuffer Compact()
{
if(index == 0)
return this;
int n = Avail();
if(n == 0)
{
Reset();
return this;
}
Array.Copy(buffer, index, buffer, 0, n);
index = 0;
length = n;
return this;
}
/// <summary>
///
/// </summary>
/// <returns>the byte value at index, and adjusts index
/// by adding one.</returns>
/// Exception:
/// End of File exception / NullReferenceException
public int Get()
{
CheckAvail( 1 );
return buffer[index++] & 255;
}
/// <summary>
/// Copies data from the byte array to buf as if by repeated
/// calls to Get().
/// </summary>
/// <param name="buf">a buffer to receive the data. At most
/// min( buf.length, Avail() ) bytes are transferred.</param>
/// <returns>the amount of data transferred.</returns>
/// Exception:
/// End of File exception / NullReferenceException
public int Get( byte[] buf )
{
return Get( buf, 0, buf.Length );
}
/// <summary>
/// Copies data from the byte array to buf as if by repeated
/// calls to Get().
/// </summary>
/// <param name="buf">a buffer to receive the data. At most
/// min( len, Avail() ) bytes are transferred, starting
/// at off.</param>
/// <param name="off">the index in buf to receive the data.
/// off must be >= 0 && less than or equal to buf.length.</param>
/// <param name="len">the max amount of data to transfer. Len
/// must be >= 0 and less than or equal to buf.length - off.</param>
/// <returns>the amount of data transferred.</returns>
/// Exception:
/// End of File exception / NullReferenceException
public int Get( byte[] buf, int off, int len )
{
CheckBuf( buf, off, len );
if (len == 0)
return 0;
CheckAvail( 1 );
int n = Math.Min( len, Avail() );
// Array.Copy( buffer, index, buf, off, n );
Buffer.BlockCopy(buffer, index, buf, off, n);
index += n;
return n;
}
public sbyte GetByte()
{
CheckAvail( 1 );
return (sbyte)buffer[ index++ ];
}
public readonly static bool littleEndian = false;
/// <summary>
///
/// </summary>
/// <returns>a short composed from the next 2 bytes. Little-endian.</returns>
/// Exception:
/// throws Exception if avail() < 2
public short GetShort()
{
CheckAvail( 2 );
if ( littleEndian )
{
// little-endian
int value = buffer[ index++ ] & 255;
return ( short ) ( value + ( ( buffer[ index++ ] & 255 ) << 8 ) );
}
else
{
// big-endian
int value = buffer[ index++ ];
return ( short ) ( ( value << 8 ) + ( buffer[ index++ ] & 255 ) );
}
}
/// <summary>
///
/// </summary>
/// <returns>an integer composed of 4 bytes from
/// the buffer, with the first byte being the least
/// significant and the last being the most significant.</returns>
/// Exception:
/// End of File exception / NullReferenceException
public int GetInt()
{
CheckAvail( 4 );
if ( littleEndian )
{
// little-endian
int value = buffer[ index++ ] & 255;
value += ( ( buffer[ index++ ] & 255 ) << 8 );
value += ( ( buffer[ index++ ] & 255 ) << 16 );
return value + ( ( buffer[ index++ ] & 255 ) << 24 );
}
else
{
// big-endian
int value = buffer[ index++ ];
value = ( value << 8 ) + ( buffer[ index++ ] & 255 );
value = ( value << 8 ) + ( buffer[ index++ ] & 255 );
return ( value << 8 ) + ( buffer[ index++ ] & 255 );
}
}
/// <summary>
///
/// </summary>
/// <returns>a long comprised of the next 8 bytes. Little-endian</returns>
public long GetLong()
{
CheckAvail( 8 );
if ( littleEndian )
{
// little-endian
long value = buffer[ index++ ] & 255;
value += ( ( ( long ) ( buffer[ index++ ] & 255 ) ) << 8 );
value += ( ( ( long ) ( buffer[ index++ ] & 255 ) ) << 16 );
value += ( ( ( long ) ( buffer[ index++ ] & 255 ) ) << 24 );
value += ( ( ( long ) ( buffer[ index++ ] & 255 ) ) << 32 );
value += ( ( ( long ) ( buffer[ index++ ] & 255 ) ) << 40 );
value += ( ( ( long ) ( buffer[ index++ ] & 255 ) ) << 48 );
return value + ( ( ( long ) ( buffer[ index++ ] & 255 ) ) << 56 );
}
else
{
// big-endian
long value = buffer[ index++ ];
value = ( value << 8 ) + ( buffer[ index++ ] & 255 );
value = ( value << 8 ) + ( buffer[ index++ ] & 255 );
value = ( value << 8 ) + ( buffer[ index++ ] & 255 );
value = ( value << 8 ) + ( buffer[ index++ ] & 255 );
value = ( value << 8 ) + ( buffer[ index++ ] & 255 );
value = ( value << 8 ) + ( buffer[ index++ ] & 255 );
return ( value << 8 ) + ( buffer[ index++ ] & 255 );
}
}
/// <summary>
///
/// </summary>
/// <returns>a float from the next available bytes</returns>
public float GetFloat()
{
return BitConverter.ToSingle( BitConverter.GetBytes( GetInt() ), 0 );
}
/// <summary>
///
/// </summary>
/// <returns>a double from the next available bytes</returns>
public double GetDouble()
{
return BitConverter.Int64BitsToDouble( GetLong() );
}
public void GetFully( byte[] b )
{
CheckAvail( b.Length );
int n = Get( b, 0, b.Length );
Debug.Assert( n == b.Length );
}
/// <summary>
/// Puts a byte into the buffer at the current index,
/// then adjusts the index by one. Adjusts the length
/// as necessary. The buffer is expanded if needed.
/// </summary>
/// <param name="b">byte to be put</param>
/// <returns>this flex buffer object.</returns>
/// Exception:
/// IOException if the buffer overflows its max length.
public FlexBuffer Put( int b )
{
EnsureLength( index+1 );
buffer[index++] = (byte) b;
FixLength();
return this;
}
/// <summary>
/// Puts some bytes into the buffer as if by repeated
/// calls to put().
/// </summary>
/// <param name="buf">the source of the bytes to put. The entire
/// array of bytes is put.</param>
/// <returns>flex buffer object.</returns>
/// Exception:
/// IOException if the buffer overflows its max length.
public FlexBuffer Put( byte[] buf )
{
return Put( buf, 0, buf.Length );
}
/// <summary>
/// Puts some bytes into the buffer as if by repeated
/// calls to Put().
/// </summary>
/// <param name="buf">the source of the bytes to put.</param>
/// <param name="off">the index to the first byte to put.</param>
/// <param name="len">the number of bytes to put.</param>
/// <returns>flex buffer object.</returns>
/// Exception:
/// IOException if the buffer overflows its max length.
public FlexBuffer Put( byte[] buf, int off, int len )
{
CheckBuf( buf, off, len );
if (len == 0)
return this;
EnsureLength( index+len );
// Array.Copy( buf, off, buffer, index, len );
Buffer.BlockCopy(buf, off, buffer, index, len);
index += len;
FixLength();
return this;
}
/// <summary>
/// Copies the Available bytes from buf into buffer as if by
/// repeated execution of put( buf.Get() ).
/// </summary>
/// <param name="buf">the source of the bytes to put. All Available
/// bytes are copied.</param>
/// <returns>flex buffer object.</returns>
/// Exception:
/// IOException if the buffer overflows its max length.
public FlexBuffer Put( FlexBuffer buf )
{
int n = buf.Avail();
Put( buf.buffer, buf.index, n );
buf.Skip( n, false );
return this;
}
/// <summary>
/// Copies the specified number of bytes from buf into buffer
/// as if by repeated execution of Put( buf.Get() ).
/// </summary>
/// <param name="buf">the source of the bytes to put.</param>
/// <param name="len"></param>len the number of bytes to put. Len must be
/// less than or equal to buf.Avail().
/// <returns>flex buffer object.</returns>
/// Exception:
/// IOException if the buffer overflows its max length.
public FlexBuffer Put( FlexBuffer buf, int len )
{
if (len > buf.Avail())
throw new ArgumentOutOfRangeException( "len > buf.Avail()" );
Put( buf.buffer, buf.index, len );
buf.Skip( len, false );
return this;
}
public void PutByte( byte value )
{
EnsureLength( index + 1 );
buffer[ index++ ] = ( byte ) value;
FixLength();
}
public void PutByte( sbyte value )
{
EnsureLength( index + 1 );
buffer[ index++ ] = ( byte ) value;
FixLength();
}
/// <summary>
/// Put short as the next 2 bytes. Little-endian
/// </summary>
/// <param name="value"></param>
public void PutShort( short value )
{
EnsureLength( index + 2 );
if ( littleEndian )
{
buffer[ index++ ] = ( byte ) value;
buffer[ index++ ] = ( byte ) ( value >> 8 );
}
else
{
/// In C#, since we're using the byte (which is unsigned),
/// it doesn't matter whether you do logical or arithmetic
/// shift. Hence, an equivalent of the Java >>> operator is
/// not required here.
buffer[ index++ ] = ( byte ) ( value >> 8 );
buffer[ index++ ] = ( byte ) value;
}
FixLength();
}
public void PutInt( int value )
{
EnsureLength( length + 4 );
if ( littleEndian )
{
buffer[ index++ ] = ( byte ) value;
buffer[ index++ ] = ( byte ) ( value >> 8 );
buffer[ index++ ] = ( byte ) ( value >> 16 );
buffer[ index++ ] = ( byte ) ( value >> 24 );
}
else
{
buffer[ index++ ] = ( byte ) ( value >> 24 );
buffer[ index++ ] = ( byte ) ( value >> 16 );
buffer[ index++ ] = ( byte ) ( value >> 8 );
buffer[ index++ ] = ( byte ) value;
}
FixLength();
}
public void PutLong( long value )
{
EnsureLength( index+8 );
if ( littleEndian )
{
buffer[ index++ ] = ( byte ) value;
buffer[ index++ ] = ( byte ) ( value >> 8 );
buffer[ index++ ] = ( byte ) ( value >> 16 );
buffer[ index++ ] = ( byte ) ( value >> 24 );
buffer[ index++ ] = ( byte ) ( value >> 32 );
buffer[ index++ ] = ( byte ) ( value >> 40 );
buffer[ index++ ] = ( byte ) ( value >> 48 );
buffer[ index++ ] = ( byte ) ( value >> 56 );
}
else
{
buffer[ index++ ] = ( byte ) ( value >> 56 );
buffer[ index++ ] = ( byte ) ( value >> 48 );
buffer[ index++ ] = ( byte ) ( value >> 40 );
buffer[ index++ ] = ( byte ) ( value >> 32 );
buffer[ index++ ] = ( byte ) ( value >> 24 );
buffer[ index++ ] = ( byte ) ( value >> 16 );
buffer[ index++ ] = ( byte ) ( value >> 8 );
buffer[ index++ ] = ( byte ) value;
}
FixLength();
}
public void PutFloat( float value )
{
PutInt( BitConverter.ToInt32( BitConverter.GetBytes( value ), 0 ) );
}
public void PutDouble( double value )
{
PutLong( BitConverter.DoubleToInt64Bits( value ) );
}
/// <summary>
/// Adjusts index as if by a Get or put but without transferring
/// any data. This could be used to skip over a data item in an
/// input or output buffer.
/// </summary>
/// <param name="len">len the number of bytes to skip over. Len must be
/// greater than or equal to 0. If put is false, it is an error if len >Avail().
/// If put is true, the buffer
/// may be extended (and the buffer length adjusted).</param>
/// <param name="put">put if true it is ok to extend the length of the
/// buffer.</param>
/// <returns>this Flexbuffer object.</returns>
/// Exception:
/// IOException
public FlexBuffer Skip( int len, bool put )
{
if (len < 0)
throw new ArgumentException( "count < 0" );
if (len == 0)
return this;
if (put)
{
EnsureLength( index+len );
index += len;
FixLength();
return this;
}
CheckAvail( len );
index += len;
return this;
}
/// <summary>
/// If index has moved past length during a put, then adjust length
/// to track index.
/// </summary>
private void FixLength()
{
if(index > length)
length = index;
}
private void CheckBuf(byte[] buf, int off, int len)
{
if(buf == null)
throw new NullReferenceException("buf == null");
if(off < 0 || off > buf.Length)
throw new ArgumentOutOfRangeException("off < 0 || off > buf.length");
if(len < 0)
throw new ArgumentOutOfRangeException("len < 0");
if(off+len > buf.Length)
throw new ArgumentOutOfRangeException("off+len > buf.length");
}
/// <summary>
/// Return the currently Available bytes.
/// </summary>
/// <returns>the currently Available bytes.</returns>
/// Exception:
/// throws an IO Exception
public byte[] GetAvailBytes()
{
byte[] buf = new byte[Avail()];
Get( buf );
return buf;
}
/// <summary>
/// Checks that there are enough bytes to for a read.
/// </summary>
/// <param name="len">the length required by a read operation.</param>
private void CheckAvail( int len )
{
if ( len > Avail() )
throw new EndOfStreamException( " len > Avail() " );
}
}
}