/* * * 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; namespace Apache.Qpid.Buffer { /// /// Abstract class implementing a byte buffer /// public abstract class ByteBuffer { private int _position; private int _limit; private bool _isAutoExpand; private static IByteBufferAllocator _allocator = new SimpleByteBufferAllocator(); #region Properties // // Properties // /// /// The maximum number of bytes the buffer can hold /// public abstract int Capacity { get; } /// /// Return the backing array of this buffer /// public abstract byte[] Array { get; } /// /// The current position inside this buffer /// public int Position { get { return _position; } set { Seek(value); } } /// /// Index of the first element that should not be read or written. /// A buffer's limit is never negative and is never greater than the its capacity. /// public int Limit { get { return _limit; } set { SetLimit(value); } } /// /// Number of bytes remaining in the buffer from the current position /// public int Remaining { get { return Limit - Position; } } /// /// True if there are bytes remaining in the buffer /// public bool HasRemaining { get { return Remaining > 0; } } /// /// If true, the buffer will be resized as necessary /// to allow space for writing. By default is false. /// public bool IsAutoExpand { get { return _isAutoExpand; } set { _isAutoExpand = value; } } #endregion // Properties #region Buffer Manipulation // // Buffer Manipulation // /// /// Move the buffer to Position 0 /// /// This instance public ByteBuffer Rewind() { Seek(0); return this; } /// /// Prepare the buffer to read back what's been written /// /// This instance public ByteBuffer Flip() { Limit = Position; Position = 0; return this; } /// /// Compact this buffer. /// /// This instance /// /// The bytes between the buffer's current position and its limit, if any, /// are copied to the beginning of the buffer. /// public ByteBuffer Compact() { DoCompact(); return this; } /// /// Clears this buffer. The position is set to zero, the limit is set to the capacity /// /// This instance public ByteBuffer Clear() { Limit = Capacity; Position = 0; return this; } /// /// Expands this buffer's capacity so that /// Remaining == expectedRemaining /// /// Number of bytes that should be accessable after resizing /// This instance public ByteBuffer Expand(int expectedRemaining) { return Expand(Position, expectedRemaining); } /// /// Expands this buffer's capacity so that /// Remaining == expectedRemaining /// /// Position from which to start the resize /// Number of bytes that should be accessable after resizing /// This instance public ByteBuffer Expand(int position, int expectedRemaining) { if ( expectedRemaining <= 0 ) throw new ArgumentException("expectedRemaining must be greater than 0"); int end = position + expectedRemaining; if ( end > Capacity ) { DoResize(end); } if ( end > Limit ) Limit = end; return this; } /// /// Creates a new byte buffer whose content is a shared /// subsequence of this buffer's content. /// /// /// The content of the new buffer will start at this buffer's current position. /// Changes to this buffer's content will be visible in the new buffer, /// and vice versa; the two buffers' position and limit values will be independent. /// /// The new buffer's position will be zero, its capacity and its limit will /// be the number of bytes remaining in this buffer. /// /// /// A view on top of this instance public ByteBuffer Slice() { return new SlicedByteBuffer(this); } /// /// Skip the specified number of bytes /// /// Number of bytes to move forward by /// This instance public ByteBuffer Skip(int numBytes) { Position += numBytes; return this; } /// /// Acquire this buffer to keep it alive. /// public virtual void Acquire() { // override in subclass if supported } /// /// Release this buffer instance /// public virtual void Release() { // override in subclass if supported } /// /// Return a string with a Hex Dump of this buffer's contents /// /// The hex dump public string GetHexDump() { return ByteBufferHexDumper.GetHexDump(this); } public override string ToString() { return GetHexDump(); } #endregion // Buffer Manipulation #region Static Operations // // Static Operations // /// /// Replaces the default allocator with your own implementation /// /// New allocator public static void SetAllocator(IByteBufferAllocator allocator) { if ( allocator == null ) throw new ArgumentNullException("allocator"); _allocator = allocator; } /// /// Allocate a new buffer with the specified capacity /// using the default allocator /// /// Desired capacity /// The new buffer public static ByteBuffer Allocate(int capacity) { return _allocator.Allocate(capacity); } /// /// Wraps the specified arrat into a new buffer /// /// /// public static ByteBuffer Wrap(byte[] buffer) { return _allocator.Wrap(buffer); } #endregion // Static Operations #region Data Accessors // // Data Accessors // // Byte Stuff /// /// Read the next byte in the buffer /// /// The next byte available public byte GetByte() { byte value = GetByte(Position); Position += 1; return value; } /// /// Read the byte at the specified position /// /// Position to read from /// The value at the position public byte GetByte(int position) { CheckSpaceForReading(position, 1); return ReadByte(position); } /// /// Write a byte at the current position /// /// Value to write /// This instance public ByteBuffer Put(byte value) { Put(Position, value); Position++; return this; } /// /// Write a byte at the specified position /// /// Position to write to /// Value to write /// This instance public ByteBuffer Put(int position, byte value) { CheckSpaceForWriting(position, 1); Write(position, value); return this; } // SByte Stuff /// /// Read the next signed byte in the buffer /// /// The next signed byte available public sbyte GetSByte() { sbyte value = GetSByte(Position); Position += 1; return value; } /// /// Read the signed byte at the specified position /// /// Position to read from /// The value at the position public sbyte GetSByte(int position) { CheckSpaceForReading(position, 1); return (sbyte)ReadByte(position); } /// /// Write a signed byte at the current position /// /// Value to write /// This instance public ByteBuffer Put(sbyte value) { Put(Position, value); Position += 1; return this; } /// /// Write a signed byte at the specified position /// /// Position to write to /// Value to write /// This instance public ByteBuffer Put(int position, sbyte value) { CheckSpaceForWriting(position, 1); Write(position, (byte)value); return this; } // UInt16 Stuff /// /// Read the next uint16 in the buffer /// /// The next uint16 available public ushort GetUInt16() { ushort value = GetUInt16(Position); Position += 2; return value; } /// /// Read the uint16 at the specified position /// /// Position to read from /// The value at the position public ushort GetUInt16(int position) { CheckSpaceForReading(position, 2); byte upper = ReadByte(position); byte lower = ReadByte(position+1); return (ushort)(((ushort)upper << 8) + lower); } /// /// Write a uint16 at the current position /// /// Value to write /// This instance public ByteBuffer Put(ushort value) { Put(Position, value); Position += 2; return this; } /// /// Write a uint16 at the specified position /// /// Position to write to /// Value to write /// This instance public ByteBuffer Put(int position, ushort value) { CheckSpaceForWriting(position, 2); Write(position, (byte)(value >> 8)); Write(position+1, (byte)(value)); return this; } // Int16 Stuff /// /// Read the next int16 in the buffer /// /// The next int16 available public short GetInt16() { return (short) GetUInt16(); } /// /// Read the int16 at the specified position /// /// Position to read from /// The value at the position public short GetInt16(int position) { return (short)GetUInt16(position); } /// /// Write a int16 at the current position /// /// Value to write /// This instance public ByteBuffer Put(short value) { return Put((ushort) value); } /// /// Write a int16 at the specified position /// /// Position to write to /// Value to write /// This instance public ByteBuffer Put(int position, short value) { return Put(position, (ushort)value); } // UInt32 Stuff /// /// Read the next uint32 in the buffer /// /// The next uint32 available public uint GetUInt32() { uint value = GetUInt32(Position); Position += 4; return value; } /// /// Read the uint32 at the specified position /// /// Position to read from /// The value at the position public uint GetUInt32(int position) { CheckSpaceForReading(position, 4); byte b1 = ReadByte(position); byte b2 = ReadByte(position + 1); byte b3 = ReadByte(position + 2); byte b4 = ReadByte(position + 3); return (uint)((b1 << 24) + (b2 << 16) + (b3 << 8) + b4); } /// /// Write a uint32 at the current position /// /// Value to write /// This instance public ByteBuffer Put(uint value) { Put(Position, value); Position += 4; return this; } /// /// Write a uint32 at the specified position /// /// Position to write to /// Value to write /// This instance public ByteBuffer Put(int position, uint value) { CheckSpaceForWriting(position, 4); Write(position, (byte)(value >> 24)); Write(position + 1, (byte)(value >> 16)); Write(position + 2, (byte)(value >> 8)); Write(position + 3, (byte)(value)); return this; } // Int32 Stuff /// /// Read the next int32 in the buffer /// /// The next int32 available public int GetInt32() { return (int)GetUInt32(); } /// /// Read the int32 at the specified position /// /// Position to read from /// The value at the position public int GetInt32(int position) { return (int)GetUInt32(position); } /// /// Write a int32 at the current position /// /// Value to write /// This instance public ByteBuffer Put(int value) { return Put((uint)value); } /// /// Write a int32 at the specified position /// /// Position to write to /// Value to write /// This instance public ByteBuffer Put(int position, int value) { return Put(position, (uint)value); } // UInt64 Stuff /// /// Read the next uint64 in the buffer /// /// The next uint64 available public ulong GetUInt64() { ulong value = GetUInt64(Position); Position += 8; return value; } /// /// Read the uint64 at the specified position /// /// Position to read from /// The value at the position public ulong GetUInt64(int position) { CheckSpaceForReading(position, 8); byte b1 = ReadByte(position); byte b2 = ReadByte(position + 1); byte b3 = ReadByte(position + 2); byte b4 = ReadByte(position + 3); byte b5 = ReadByte(position + 4); byte b6 = ReadByte(position + 5); byte b7 = ReadByte(position + 6); byte b8 = ReadByte(position + 7); // all the casts necessary because otherwise each subexpression // only gets promoted to uint and cause incorrect results return (((ulong)b1 << 56) + ((ulong)b2 << 48) + ((ulong)b3 << 40) + ((ulong)b4 << 32) + ((ulong)b5 << 24) + ((ulong)b6 << 16) + ((ulong)b7 << 8) + b8); } /// /// Write a uint64 at the current position /// /// Value to write /// This instance public ByteBuffer Put(ulong value) { Put(Position, value); Position += 8; return this; } /// /// Write a uint64 at the specified position /// /// Position to write to /// Value to write /// This instance public ByteBuffer Put(int position, ulong value) { CheckSpaceForWriting(position, 8); Write(position, (byte)(value >> 56)); Write(position + 1, (byte)(value >> 48)); Write(position + 2, (byte)(value >> 40)); Write(position + 3, (byte)(value >> 32)); Write(position + 4, (byte)(value >> 24)); Write(position + 5, (byte)(value >> 16)); Write(position + 6, (byte)(value >> 8)); Write(position + 7, (byte)(value)); return this; } // Int64 Stuff /// /// Read the next int64 in the buffer /// /// The next int64 available public long GetInt64() { return (long)GetUInt64(); } /// /// Read the int64 at the specified position /// /// Position to read from /// The value at the position public long GetInt64(int position) { return (long)GetUInt64(position); } /// /// Write a int64 at the current position /// /// Value to write /// This instance public ByteBuffer Put(long value) { return Put((ulong)value); } /// /// Write a int64 at the specified position /// /// Position to write to /// Value to write /// This instance public ByteBuffer Put(int position, long value) { return Put(position, (ulong)value); } // Float Stuff /// /// Read the next float in the buffer /// /// The next float available public float GetFloat() { unsafe { uint val = GetUInt32(); return *((float*)&val); } } /// /// Read the float at the specified position /// /// Position to read from /// The value at the position public float GetFloat(int position) { unsafe { uint val = GetUInt32(position); return *((float*)&val); } } /// /// Write a float at the current position /// /// Value to write /// This instance public ByteBuffer Put(float value) { unsafe { uint val = *((uint*)&value); return Put(val); } } /// /// Write a float at the specified position /// /// Position to write to /// Value to write /// This instance public ByteBuffer Put(int position, float value) { unsafe { uint val = *((uint*)&value); return Put(position, val); } } // Double Stuff /// /// Read the next double in the buffer /// /// The next double available public double GetDouble() { unsafe { ulong val = GetUInt64(); return *((double*)&val); } } /// /// Read the double at the specified position /// /// Position to read from /// The value at the position public double GetDouble(int position) { unsafe { ulong val = GetUInt64(position); return *((double*)&val); } } /// /// Write a double at the current position /// /// Value to write /// This instance public ByteBuffer Put(double value) { unsafe { ulong val = *((ulong*)&value); return Put(val); } } /// /// Write a double at the specified position /// /// Position to write to /// Value to write /// This instance public ByteBuffer Put(int position, double value) { unsafe { ulong val = *((ulong*)&value); return Put(position, val); } } // Char Stuff /// /// Read the next char in the buffer /// /// The next char available public char GetChar() { return (char)GetUInt16(); } /// /// Read the char at the specified position /// /// Position to read from /// The value at the position public char GetChar(int position) { return (char)GetUInt16(position); } /// /// Write a char at the current position /// /// Value to write /// This instance public ByteBuffer Put(char value) { return Put((ushort) value); } /// /// Write a char at the specified position /// /// Position to write to /// Value to write /// This instance public ByteBuffer Put(int position, char value) { return Put(position, (ushort)value); } // Byte[] stuff public void GetBytes(byte[] buffer) { GetBytes(buffer, 0, buffer.Length); } public void GetBytes(byte[] buffer, int offset, int length) { GetBytes(Position, buffer, offset, length); Position += length; } public void GetBytes(int position, byte[] buffer, int offset, int length) { CheckSpaceForReading(position, length); if ( offset + length > buffer.Length ) throw new ArgumentException("Invalid offset + length"); ReadBytes(position, buffer, offset, length); } public ByteBuffer Put(byte[] buffer) { return Put(buffer, 0, buffer.Length); } public ByteBuffer Put(byte[] buffer, int offset, int length) { Put(Position, buffer, offset, length); Position += length; return this; } public ByteBuffer Put(int position, byte[] buffer, int offset, int length) { CheckSpaceForWriting(position, length); if ( offset + length > buffer.Length ) throw new ArgumentException("Invalid offset + length"); Write(position, buffer, offset, length); return this; } public ByteBuffer Put(ByteBuffer data) { Put(Position, data); Position += data.Remaining; return this; } public ByteBuffer Put(int position, ByteBuffer data) { CheckSpaceForWriting(position, data.Remaining); Write(position, data.Array, data.Position, data.Remaining); return this; } #endregion // Data Accessors #region Core Overrides // // Core Overrides // protected abstract void DoWrite(int position, byte value); protected abstract void DoWrite(int position, byte[] src, int offset, int length); protected abstract byte DoReadByte(int position); protected abstract void DoReadBytes(int position, byte[] dest, int offset, int length); protected abstract void DoCompact(); protected abstract void DoResize(int newSize); #endregion // Core Overrides #region Private Methods // // Private Methods // private void Seek(int offset) { if ( offset > Capacity ) throw new ArgumentException("Cannot position beyond end of buffer"); _position = offset; AdjustLimit(); } private void SetLimit(int newLimit) { if ( newLimit < 0 ) throw new ArgumentOutOfRangeException("The new limit must be a positive value"); if ( newLimit > Capacity ) throw new ArgumentOutOfRangeException("The new limit must not be greater than the capacity"); _limit = newLimit; if ( _position > newLimit ) _position = newLimit; } private void AdjustLimit() { if ( _limit < _position ) _limit = _position; } private void CheckSpaceForReading(int position, int length) { if ( position + length > Limit ) { throw new BufferUnderflowException("Attempt to read " + length + " byte(s) to buffer where position is " + position + " and limit is " + Limit); } } private void CheckSpaceForWriting(int position, int length) { if ( IsAutoExpand ) { Expand(position, length); } if ( position + length > Limit ) { throw new BufferOverflowException("Attempt to write " + length + " byte(s) to buffer where position is " + position + " and limit is " + Limit); } } private void Write(int position, byte value) { DoWrite(position, value); } private void Write(int position, byte[] src, int offset, int length) { DoWrite(position, src, offset, length); } private byte ReadByte(int position) { return DoReadByte(position); } private void ReadBytes(int position, byte[] dest, int offset, int length) { DoReadBytes(position, dest, offset, length); } #endregion // Private Methods } // class ByteBuffer }