/* Copyright 2010-2014 MongoDB Inc.
*
* Licensed 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.IO;
namespace MongoDB.Bson.IO
{
///
/// A BSON buffer that is backed by a byte array.
///
public class ByteArrayBuffer : IByteBuffer
{
// private fields
private bool _disposed;
private byte[] _bytes;
private int _sliceOffset;
private int _capacity;
private int _length;
private int _position;
private bool _isReadOnly;
// constructors
///
/// Initializes a new instance of the class.
///
/// The backing bytes.
/// The offset where the slice begins.
/// The length of the slice.
/// Whether the buffer is read only.
/// bytes
public ByteArrayBuffer(byte[] bytes, int sliceOffset, int length, bool isReadOnly)
{
if (bytes == null)
{
throw new ArgumentNullException("bytes");
}
_bytes = bytes;
_sliceOffset = sliceOffset;
_capacity = isReadOnly ? length : bytes.Length - _sliceOffset;
_length = length;
_isReadOnly = isReadOnly;
_position = 0;
}
// public properties
///
/// Gets or sets the capacity.
///
///
/// The capacity.
///
/// ByteArrayBuffer
/// The capacity of a ByteArrayBuffer cannot be changed.
public int Capacity
{
get
{
ThrowIfDisposed();
return _capacity;
}
set
{
ThrowIfDisposed();
throw new NotSupportedException("The capacity of a ByteArrayBuffer cannot be changed.");
}
}
///
/// Gets a value indicating whether this instance is read only.
///
///
/// true if this instance is read only; otherwise, false.
///
/// ByteArrayBuffer
public bool IsReadOnly
{
get
{
ThrowIfDisposed();
return _isReadOnly;
}
}
///
/// Gets or sets the length.
///
///
/// The length.
///
/// ByteArrayBuffer
/// The length of a read only buffer cannot be changed.
/// Length
public int Length
{
get
{
ThrowIfDisposed();
return _length;
}
set
{
ThrowIfDisposed();
EnsureIsWritable();
if (value < 0 || value > _capacity)
{
throw new ArgumentOutOfRangeException("length");
}
_length = value;
if (_position > _length)
{
_position = _length;
}
}
}
///
/// Gets or sets the position.
///
///
/// The position.
///
/// ByteArrayBuffer
/// Position
public int Position
{
get
{
ThrowIfDisposed();
return _position;
}
set
{
ThrowIfDisposed();
if (value < 0 || value > _capacity)
{
throw new ArgumentOutOfRangeException("Position");
}
_position = value;
if (_length < _position)
{
_length = _position;
}
}
}
// protected properties
///
/// Gets a value indicating whether this is disposed.
///
///
/// true if disposed; otherwise, false.
///
protected bool Disposed
{
get { return _disposed; }
}
///
/// Gets the slice offset.
///
///
/// The slice offset.
///
protected int SliceOffset
{
get { return _sliceOffset; }
}
// public methods
///
/// Clears this instance.
///
/// ByteArrayBuffer
/// Write operations are not allowed for read only buffers.
public virtual void Clear()
{
ThrowIfDisposed();
EnsureIsWritable();
_position = 0;
_length = 0;
}
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Finds the next null byte.
///
///
/// The position of the next null byte.
///
/// ByteArrayBuffer
public int FindNullByte()
{
ThrowIfDisposed();
var index = Array.IndexOf(_bytes, 0, _sliceOffset + _position, _length - _position);
return (index == -1) ? -1 : index - _sliceOffset;
}
///
/// Gets a slice of this buffer.
///
/// The position of the start of the slice.
/// The length of the slice.
///
/// A slice of this buffer.
///
/// ByteArrayBuffer
/// GetSlice can only be called for read only buffers.
///
/// position
/// or
/// length
///
public virtual IByteBuffer GetSlice(int position, int length)
{
ThrowIfDisposed();
EnsureIsReadOnly();
if (position < 0 || position >= _length)
{
throw new ArgumentOutOfRangeException("position");
}
if (length <= 0 || length > _length - position)
{
throw new ArgumentOutOfRangeException("length");
}
return new ByteArrayBuffer(_bytes, _sliceOffset + position, length, true);
}
///
/// Loads the buffer from a stream.
///
/// The stream.
/// The count.
/// ByteArrayBuffer
/// Write operations are not allowed for read only buffers.
/// stream
/// count
public void LoadFrom(Stream stream, int count)
{
ThrowIfDisposed();
EnsureIsWritable();
if (stream == null)
{
throw new ArgumentNullException("stream");
}
if (count > _capacity - _position)
{
throw new ArgumentOutOfRangeException("count");
}
EnsureSpaceAvailable(count);
var position = _position; // don't advance position
while (count > 0)
{
var bytesRead = stream.Read(_bytes, _sliceOffset + position, count);
if (bytesRead == 0)
{
throw new EndOfStreamException();
}
position += bytesRead;
count -= bytesRead;
}
if (_length < position)
{
_length = position;
}
}
///
/// Makes this buffer read only.
///
/// ByteArrayBuffer
public void MakeReadOnly()
{
ThrowIfDisposed();
_isReadOnly = true;
}
///
/// Read directly from the backing bytes. The returned ArraySegment points directly to the backing bytes for
/// the current position and you can read the bytes directly from there. If the backing bytes happen to span
/// a chunk boundary shortly after the current position there might not be enough bytes left in the current
/// chunk in which case the returned ArraySegment will have a Count of zero and you should call ReadBytes instead.
///
/// When ReadBackingBytes returns the position will have been advanced by count bytes *if and only if* there
/// were count bytes left in the current chunk.
///
/// The number of bytes you need to read.
///
/// An ArraySegment pointing directly to the backing bytes for the current position.
///
/// ByteArrayBuffer
public ArraySegment ReadBackingBytes(int count)
{
ThrowIfDisposed();
EnsureDataAvailable(count);
var offset = _sliceOffset + _position;
_position += count;
return new ArraySegment(_bytes, offset, count);
}
///
/// Reads a byte.
///
///
/// A byte.
///
/// ByteArrayBuffer
public byte ReadByte()
{
ThrowIfDisposed();
EnsureDataAvailable(1);
return _bytes[_sliceOffset + _position++];
}
///
/// Reads bytes.
///
/// The destination.
/// The destination offset.
/// The count.
/// ByteArrayBuffer
public void ReadBytes(byte[] destination, int destinationOffset, int count)
{
ThrowIfDisposed();
EnsureDataAvailable(count);
Buffer.BlockCopy(_bytes, _sliceOffset + _position, destination, destinationOffset, count);
_position += count;
}
///
/// Reads bytes.
///
/// The count.
///
/// The bytes.
///
/// ByteArrayBuffer
public byte[] ReadBytes(int count)
{
ThrowIfDisposed();
var destination = new byte[count];
ReadBytes(destination, 0, count);
return destination;
}
///
/// Write directly to the backing bytes. The returned ArraySegment points directly to the backing bytes for
/// the current position and you can write the bytes directly to there. If the backing bytes happen to span
/// a chunk boundary shortly after the current position there might not be enough bytes left in the current
/// chunk in which case the returned ArraySegment will have a Count of zero and you should call WriteBytes instead.
///
/// When WriteBackingBytes returns the position has not been advanced. After you have written up to count
/// bytes directly to the backing bytes advance the position by the number of bytes actually written.
///
/// The count.
///
/// An ArraySegment pointing directly to the backing bytes for the current position.
///
public ArraySegment WriteBackingBytes(int count)
{
ThrowIfDisposed();
EnsureSpaceAvailable(count);
var offset = _sliceOffset + _position;
return new ArraySegment(_bytes, offset, count);
}
///
/// Writes a byte.
///
/// The byte.
/// ByteArrayBuffer
/// Write operations are not allowed for read only buffers.
public void WriteByte(byte source)
{
ThrowIfDisposed();
EnsureIsWritable();
EnsureSpaceAvailable(1);
_bytes[_sliceOffset + _position++] = source;
if (_length < _position)
{
_length = _position;
}
}
///
/// Writes bytes.
///
/// The bytes.
/// ByteArrayBuffer
/// Write operations are not allowed for read only buffers.
public void WriteBytes(byte[] bytes)
{
ThrowIfDisposed();
EnsureIsWritable();
var count = bytes.Length;
EnsureSpaceAvailable(count);
Buffer.BlockCopy(bytes, 0, _bytes, _sliceOffset + _position, count);
_position += count;
if (_length < _position)
{
_length = _position;
}
}
///
/// Writes bytes.
///
/// The bytes (in the form of an IByteBuffer).
/// ByteArrayBuffer
/// Write operations are not allowed for read only buffers.
public void WriteBytes(IByteBuffer source)
{
ThrowIfDisposed();
EnsureIsWritable();
var count = source.Length;
EnsureSpaceAvailable(count);
var savedPosition = source.Position;
source.Position = 0;
source.ReadBytes(_bytes, _sliceOffset + _position, count);
_position += count;
if (_length < _position)
{
_length = _position;
}
source.Position = savedPosition;
}
///
/// Writes Length bytes from this buffer starting at Position 0 to a stream.
///
/// The stream.
/// ByteArrayBuffer
public void WriteTo(Stream stream)
{
ThrowIfDisposed();
stream.Write(_bytes, _sliceOffset, _length);
}
// protected methods
///
/// Releases unmanaged and - optionally - managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected virtual void Dispose(bool disposing)
{
// subclasses override this method if they have anything to Dispose
_disposed = true;
}
///
/// Ensures the buffer is writable.
///
/// ByteArrayBuffer is not writable.
protected void EnsureIsWritable()
{
if (_isReadOnly)
{
var message = string.Format("{0} is not writable.", GetType().Name);
throw new InvalidOperationException(message);
}
}
///
/// Ensures the buffer is read only.
///
/// ByteArrayBuffer is not read only.
protected void EnsureIsReadOnly()
{
if (!_isReadOnly)
{
var message = string.Format("{0} is not read only.", GetType().Name);
throw new InvalidOperationException(message);
}
}
///
/// Throws if disposed.
///
///
protected void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().Name);
}
}
// private methods
private void EnsureDataAvailable(int needed)
{
if (needed > _length - _position)
{
var available = _length - _position;
var message = string.Format(
"Not enough input bytes available. Needed {0}, but only {1} are available (at position {2}).",
needed, available, _position);
throw new EndOfStreamException(message);
}
}
private void EnsureSpaceAvailable(int needed)
{
if (needed > _capacity - _length)
{
var available = _capacity - _length;
var message = string.Format(
"Not enough space available. Needed {0}, but only {1} are available (at position {2}).",
needed, available, _position);
throw new EndOfStreamException(message);
}
}
}
}