/* 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); } } } }