//******************************************************************************************************
// BlockAllocatedMemoryStream.cs - Gbtc
//
// Copyright © 2010, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the Eclipse Public License -v 1.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.opensource.org/licenses/eclipse-1.0.php
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 06/14/2013 - J. Ritchie Carroll
// Adapted from the "MemoryTributary" class written by Sebastian Friston:
// Source Code: http://memorytributary.codeplex.com/
// Article: http://www.codeproject.com/Articles/348590/A-replacement-for-MemoryStream
//
//******************************************************************************************************
#region [ Contributor License Agreements ]
/******************************************************************************\
Copyright (c) 2012 Sebastian Friston
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
\******************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
namespace GSF.IO
{
///
/// Defines a stream whose backing store is memory. Externally this class operates like , internally
/// it uses dynamically allocated buffer blocks instead of one large contiguous array of data.
///
///
///
/// The has two primary benefits over a normal , first, the
/// allocation of a large contiguous array of data in can fail when the requested amount of contiguous
/// memory is unavailable - the prevents this; second, a will
/// constantly reallocate the buffer size as the stream grows and shrinks and then copy all the data from the old buffer to the
/// new - the maintains its blocks over its lifecycle unless manually cleared thus
/// eliminating unnecessary allocations and garbage collections when growing and reusing a stream.
///
///
/// Note that the will maintain all allocated blocks for stream use until the
/// method is called or the class is disposed.
///
///
/// No members in the are guaranteed to be thread safe. Make sure any calls are
/// synchronized when simultaneously accessed from different threads.
///
///
public class BlockAllocatedMemoryStream : Stream
{
#region [ Members ]
// Constants
///
/// Default value for property.
///
public const int DefaultBlockSize = 65536;
// Fields
private long m_length;
private long m_position;
private readonly int m_blockSize;
private readonly List m_blocks = new List();
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Initializes a new instance of .
///
public BlockAllocatedMemoryStream()
{
m_blockSize = DefaultBlockSize;
}
///
/// Initializes a new instance of from specified .
///
/// Initial buffer to copy into stream.
/// is null.
public BlockAllocatedMemoryStream(byte[] buffer)
: this(buffer, 0, (object)buffer == null ? 0 : buffer.Length, DefaultBlockSize)
{
}
///
/// Initializes a new instance of from specified
/// and desired block size.
///
/// Initial buffer to copy into stream.
/// Desired size of memory blocks.
/// is null.
/// Block size must be greater than zero.
public BlockAllocatedMemoryStream(byte[] buffer, int blockSize)
: this(buffer, 0, (object)buffer == null ? 0 : buffer.Length, blockSize)
{
}
///
/// Initializes a new instance of from specified region of .
///
/// Initial buffer to copy into stream.
/// 0-based start index into the .
/// Valid number of bytes within from .
/// is null.
///
/// or is less than 0 -or-
/// and will exceed length.
///
public BlockAllocatedMemoryStream(byte[] buffer, int startIndex, int length)
: this(buffer, startIndex, length, DefaultBlockSize)
{
}
///
/// Initializes a new instance of from specified region of
/// and desired block size.
///
/// Initial buffer to copy into stream.
/// 0-based start index into the .
/// Valid number of bytes within from .
/// Desired size of memory blocks.
/// is null.
///
/// or is less than 0 -or-
/// and will exceed length.
///
/// Block size must be greater than zero.
public BlockAllocatedMemoryStream(byte[] buffer, int startIndex, int length, int blockSize)
{
buffer.ValidateParameters(startIndex, length);
if (blockSize <= 0)
throw new ArgumentOutOfRangeException("blockSize", "Block size must be greater than zero.");
m_blockSize = blockSize;
Write(buffer, startIndex, length);
}
///
/// Initializes a new instance of for specified .
///
/// Initial length of the stream.
public BlockAllocatedMemoryStream(int capacity)
: this(capacity, DefaultBlockSize)
{
}
///
/// Initializes a new instance of for specified
/// and desired block size.
///
/// Initial length of the stream.
/// Desired size of memory blocks.
/// Block size must be greater than zero.
public BlockAllocatedMemoryStream(int capacity, int blockSize)
{
if (blockSize <= 0)
throw new ArgumentOutOfRangeException("blockSize", "Block size must be greater than zero.");
m_blockSize = blockSize;
SetLength(capacity);
// Access last block to pre-allocate memory at desired capacity
m_position = capacity;
byte[] lastBlock = Block;
m_position = 0;
}
#endregion
#region [ Properties ]
///
/// Gets a value that indicates whether the object supports reading.
/// This is always true.
///
public override bool CanRead
{
get
{
return true;
}
}
///
/// Gets a value that indicates whether the object supports seeking.
/// This is always true.
///
public override bool CanSeek
{
get
{
return true;
}
}
///
/// Gets a value that indicates whether the object supports writing.
/// This is always true.
///
public override bool CanWrite
{
get
{
return true;
}
}
///
/// Gets current block size defined for this instance.
///
public int BlockSize
{
get
{
return m_blockSize;
}
}
///
/// Gets current stream length for this instance.
///
/// The stream is closed.
public override long Length
{
get
{
if (m_disposed)
throw new ObjectDisposedException("BlockAllocatedMemoryStream", "The stream is closed.");
return m_length;
}
}
///
/// Gets current stream position for this instance.
///
/// Seeking was attempted before the beginning of the stream.
/// The stream is closed.
public override long Position
{
get
{
if (m_disposed)
throw new ObjectDisposedException("BlockAllocatedMemoryStream", "The stream is closed.");
return m_position;
}
set
{
if (m_disposed)
throw new ObjectDisposedException("BlockAllocatedMemoryStream", "The stream is closed.");
if (value < 0L)
throw new IOException("Seek was attempted before the beginning of the stream.");
m_position = value;
}
}
///
/// Gets the block of memory currently addressed by .
///
///
/// The buffer returned by the property is owned and managed by this ,
/// make sure direct access to this buffer is maintained within this class and not exposed externally.
///
protected byte[] Block
{
get
{
while (m_blocks.Count <= BlockIndex)
m_blocks.Add(BufferPool.TakeBuffer(m_blockSize));
return m_blocks[BlockIndex];
}
}
///
/// Gets the index of the block currently addressed by .
///
protected int BlockIndex
{
get
{
return (int)(m_position / m_blockSize);
}
}
///
/// Gets the offset of the byte currently addressed by relative to the block that contains it.
///
protected int BlockOffset
{
get
{
return (int)(m_position % m_blockSize);
}
}
#endregion
#region [ Methods ]
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected override void Dispose(bool disposing)
{
if (!m_disposed)
{
try
{
if (disposing)
{
// Make sure buffer blocks get returned to the pool
Clear();
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
base.Dispose(disposing); // Call base class Dispose().
}
}
}
///
/// Clears the entire contents and releases any allocated memory blocks.
///
public void Clear()
{
m_position = 0;
m_length = 0;
// Return buffer blocks to the pool
foreach (byte[] buffer in m_blocks)
{
BufferPool.ReturnBuffer(buffer);
}
m_blocks.Clear();
}
///
/// Sets the within the current stream to the specified value relative the .
///
///
/// The new position within the stream, calculated by combining the initial reference point and the offset.
///
/// The new position within the stream. This is relative to the parameter, and can be positive or negative.
/// A value of type , which acts as the seek reference point.
/// Seeking was attempted before the beginning of the stream.
/// The stream is closed.
public override long Seek(long offset, SeekOrigin origin)
{
if (m_disposed)
throw new ObjectDisposedException("BlockAllocatedMemoryStream", "The stream is closed.");
switch (origin)
{
case SeekOrigin.Begin:
if (offset < 0L)
throw new IOException("Seek was attempted before the beginning of the stream.");
m_position = offset;
break;
case SeekOrigin.Current:
if (m_position + offset < 0L)
throw new IOException("Seek was attempted before the beginning of the stream.");
m_position += offset;
break;
case SeekOrigin.End:
if (m_length + offset < 0L)
throw new IOException("Seek was attempted before the beginning of the stream.");
// Note change from original code which forced negative offset - following code
// matches normal MemoryStream operation for end of stream origin:
m_position = m_length + offset;
break;
}
return m_position;
}
///
/// Sets the length of the current stream to the specified value.
///
/// The value at which to set the length.
public override void SetLength(long value)
{
m_length = value;
// Note change from original code which did not perform the following operation - code
// matches MemoryStream operation when position exceeds length for this function:
if (m_position > m_length)
m_position = m_length;
}
///
/// Reads a block of bytes from the current stream and writes the data to .
///
/// When this method returns, contains the specified byte array with the values between and ( + - 1) replaced by the characters read from the current stream.
/// The byte offset in at which to begin reading.
/// The maximum number of bytes to read.
///
/// The total number of bytes written into the buffer. This can be less than the number of bytes requested if that number of bytes are not currently available, or zero if the end of the stream is reached before any bytes are read.
///
/// is null.
///
/// or is less than 0 -or-
/// and will exceed length.
///
/// The stream is closed.
public override int Read(byte[] buffer, int startIndex, int length)
{
if (m_disposed)
throw new ObjectDisposedException("BlockAllocatedMemoryStream", "The stream is closed.");
buffer.ValidateParameters(startIndex, length);
// Only read as far as end of buffer regardless of how much data was requested
int remaining = (int)(m_length - m_position);
if (remaining <= 0)
return 0;
if (length > remaining)
length = remaining;
int read = 0;
int count;
do
{
count = Math.Min(length, m_blockSize - BlockOffset);
Buffer.BlockCopy(Block, BlockOffset, buffer, startIndex, count);
length -= count;
startIndex += count;
read += count;
m_position += count;
}
while (length > 0);
return read;
}
///
/// Reads a byte from the current stream.
///
///
/// The current byte cast to an , or -1 if the end of the stream has been reached.
///
/// The stream is closed.
public override int ReadByte()
{
if (m_disposed)
throw new ObjectDisposedException("BlockAllocatedMemoryStream", "The stream is closed.");
if (m_position >= m_length)
return -1;
byte value = Block[BlockOffset];
m_position++;
return value;
}
///
/// Writes a block of bytes to the current stream using data read from .
///
/// The buffer to write data from.
/// The byte offset in at which to begin writing from.
/// The maximum number of bytes to write.
/// is null.
///
/// or is less than 0 -or-
/// and will exceed length.
///
/// The stream is closed.
public override void Write(byte[] buffer, int startIndex, int length)
{
if (m_disposed)
throw new ObjectDisposedException("BlockAllocatedMemoryStream", "The stream is closed.");
buffer.ValidateParameters(startIndex, length);
long originalPosition = m_position;
int count;
try
{
do
{
count = Math.Min(length, m_blockSize - BlockOffset);
EnsureCapacity(m_position + count);
Buffer.BlockCopy(buffer, startIndex, Block, BlockOffset, count);
length -= count;
startIndex += count;
m_position += count;
}
while (length > 0);
}
catch (Exception)
{
m_position = originalPosition;
throw;
}
}
///
/// Writes a byte to the current stream at the current position.
///
/// The byte to write.
/// The stream is closed.
public override void WriteByte(byte value)
{
if (m_disposed)
throw new ObjectDisposedException("BlockAllocatedMemoryStream", "The stream is closed.");
EnsureCapacity(m_position + 1);
Block[BlockOffset] = value;
m_position++;
}
///
/// Writes the stream contents to a byte array, regardless of the property.
///
/// A [] containing the current data in the stream
///
/// This may fail if there is not enough contiguous memory available to hold current size of stream.
/// When possible use methods which operate on streams directly instead.
///
/// Cannot create a byte array larger than 2,147,483,591.
/// The stream is closed.
public byte[] ToArray()
{
if (m_disposed)
throw new ObjectDisposedException("BlockAllocatedMemoryStream", "The stream is closed.");
if (m_length > 0x7FFFFFC7L)
throw new InvalidOperationException("Cannot create a byte array of size " + m_length);
byte[] destination = new byte[m_length];
long originalPosition = m_position;
m_position = 0;
Read(destination, 0, (int)m_length);
m_position = originalPosition;
return destination;
}
///
/// Reads specified number of bytes from source stream into this
/// starting at the current position.
///
/// The stream containing the data to copy
/// The number of bytes to copy
/// The stream is closed.
public void ReadFrom(Stream source, long length)
{
if (m_disposed)
throw new ObjectDisposedException("BlockAllocatedMemoryStream", "The stream is closed.");
const int bufferSize = 8192;
byte[] buffer = BufferPool.TakeBuffer(bufferSize);
int read;
try
{
do
{
read = source.Read(buffer, 0, (int)Math.Min(bufferSize, length));
length -= read;
Write(buffer, 0, read);
}
while (length > 0);
}
finally
{
if ((object)buffer != null)
BufferPool.ReturnBuffer(buffer);
}
}
///
/// Writes the entire stream into destination, regardless of , which remains unchanged.
///
/// The stream onto which to write the current contents.
/// The stream is closed.
public void WriteTo(Stream destination)
{
if (m_disposed)
throw new ObjectDisposedException("BlockAllocatedMemoryStream", "The stream is closed.");
long originalPosition = m_position;
m_position = 0;
CopyTo(destination);
m_position = originalPosition;
}
///
/// Overrides the method so that no action is performed.
///
///
///
/// This method overrides the method.
///
///
/// Because any data written to a object is
/// written into RAM, this method is superfluous.
///
///
public override void Flush()
{
// Nothing to flush...
}
///
/// Makes sure desired can be accommodated by future data accesses.
///
/// Minimum desired stream capacity.
protected void EnsureCapacity(long length)
{
if (length > m_length)
m_length = length;
}
#endregion
}
}