|
|
- using System;
- using System.IO;
- using System.Text;
- using MiscUtil.Conversion;
-
- namespace MiscUtil.IO
- {
- /// <summary>
- /// Equivalent of System.IO.BinaryReader, but with either endianness, depending on
- /// the EndianBitConverter it is constructed with. No data is buffered in the
- /// reader; the client may seek within the stream at will.
- /// </summary>
- public class EndianBinaryReader : IDisposable
- {
- #region Fields not directly related to properties
- /// <summary>
- /// Whether or not this reader has been disposed yet.
- /// </summary>
- bool disposed=false;
- /// <summary>
- /// Decoder to use for string conversions.
- /// </summary>
- Decoder decoder;
- /// <summary>
- /// Buffer used for temporary storage before conversion into primitives
- /// </summary>
- byte[] buffer = new byte[16];
- /// <summary>
- /// Buffer used for temporary storage when reading a single character
- /// </summary>
- char[] charBuffer = new char[1];
- /// <summary>
- /// Minimum number of bytes used to encode a character
- /// </summary>
- int minBytesPerChar;
- #endregion
-
- #region Constructors
- /// <summary>
- /// Equivalent of System.IO.BinaryWriter, but with either endianness, depending on
- /// the EndianBitConverter it is constructed with.
- /// </summary>
- /// <param name="bitConverter">Converter to use when reading data</param>
- /// <param name="stream">Stream to read data from</param>
- public EndianBinaryReader (EndianBitConverter bitConverter,
- Stream stream) : this (bitConverter, stream, Encoding.UTF8)
- {
- }
-
- /// <summary>
- /// Constructs a new binary reader with the given bit converter, reading
- /// to the given stream, using the given encoding.
- /// </summary>
- /// <param name="bitConverter">Converter to use when reading data</param>
- /// <param name="stream">Stream to read data from</param>
- /// <param name="encoding">Encoding to use when reading character data</param>
- public EndianBinaryReader (EndianBitConverter bitConverter, Stream stream, Encoding encoding)
- {
- if (bitConverter==null)
- {
- throw new ArgumentNullException("bitConverter");
- }
- if (stream==null)
- {
- throw new ArgumentNullException("stream");
- }
- if (encoding==null)
- {
- throw new ArgumentNullException("encoding");
- }
- if (!stream.CanRead)
- {
- throw new ArgumentException("Stream isn't writable", "stream");
- }
- this.stream = stream;
- this.bitConverter = bitConverter;
- this.encoding = encoding;
- this.decoder = encoding.GetDecoder();
- this.minBytesPerChar = 1;
-
- if (encoding is UnicodeEncoding)
- {
- minBytesPerChar = 2;
- }
- }
- #endregion
-
- #region Properties
- EndianBitConverter bitConverter;
- /// <summary>
- /// The bit converter used to read values from the stream
- /// </summary>
- public EndianBitConverter BitConverter
- {
- get { return bitConverter; }
- }
-
- Encoding encoding;
- /// <summary>
- /// The encoding used to read strings
- /// </summary>
- public Encoding Encoding
- {
- get { return encoding; }
- }
-
- Stream stream;
- /// <summary>
- /// Gets the underlying stream of the EndianBinaryReader.
- /// </summary>
- public Stream BaseStream
- {
- get { return stream; }
- }
- #endregion
-
- #region Public methods
- /// <summary>
- /// Closes the reader, including the underlying stream..
- /// </summary>
- public void Close()
- {
- Dispose();
- }
-
- /// <summary>
- /// Seeks within the stream.
- /// </summary>
- /// <param name="offset">Offset to seek to.</param>
- /// <param name="origin">Origin of seek operation.</param>
- public void Seek (int offset, SeekOrigin origin)
- {
- CheckDisposed();
- stream.Seek (offset, origin);
- }
-
- /// <summary>
- /// Reads a single byte from the stream.
- /// </summary>
- /// <returns>The byte read</returns>
- public byte ReadByte()
- {
- ReadInternal(buffer, 1);
- return buffer[0];
- }
-
- /// <summary>
- /// Reads a single signed byte from the stream.
- /// </summary>
- /// <returns>The byte read</returns>
- public sbyte ReadSByte()
- {
- ReadInternal(buffer, 1);
- return unchecked((sbyte)buffer[0]);
- }
-
- /// <summary>
- /// Reads a boolean from the stream. 1 byte is read.
- /// </summary>
- /// <returns>The boolean read</returns>
- public bool ReadBoolean()
- {
- ReadInternal(buffer, 1);
- return bitConverter.ToBoolean(buffer, 0);
- }
-
- /// <summary>
- /// Reads a 16-bit signed integer from the stream, using the bit converter
- /// for this reader. 2 bytes are read.
- /// </summary>
- /// <returns>The 16-bit integer read</returns>
- public short ReadInt16()
- {
- ReadInternal(buffer, 2);
- return bitConverter.ToInt16(buffer, 0);
- }
-
- /// <summary>
- /// Reads a 32-bit signed integer from the stream, using the bit converter
- /// for this reader. 4 bytes are read.
- /// </summary>
- /// <returns>The 32-bit integer read</returns>
- public int ReadInt32()
- {
- ReadInternal(buffer, 4);
- return bitConverter.ToInt32(buffer, 0);
- }
-
- /// <summary>
- /// Reads a 64-bit signed integer from the stream, using the bit converter
- /// for this reader. 8 bytes are read.
- /// </summary>
- /// <returns>The 64-bit integer read</returns>
- public long ReadInt64()
- {
- ReadInternal(buffer, 8);
- return bitConverter.ToInt64(buffer, 0);
- }
-
- /// <summary>
- /// Reads a 16-bit unsigned integer from the stream, using the bit converter
- /// for this reader. 2 bytes are read.
- /// </summary>
- /// <returns>The 16-bit unsigned integer read</returns>
- public ushort ReadUInt16()
- {
- ReadInternal(buffer, 2);
- return bitConverter.ToUInt16(buffer, 0);
- }
-
- /// <summary>
- /// Reads a 32-bit unsigned integer from the stream, using the bit converter
- /// for this reader. 4 bytes are read.
- /// </summary>
- /// <returns>The 32-bit unsigned integer read</returns>
- public uint ReadUInt32()
- {
- ReadInternal(buffer, 4);
- return bitConverter.ToUInt32(buffer, 0);
- }
-
- /// <summary>
- /// Reads a 64-bit unsigned integer from the stream, using the bit converter
- /// for this reader. 8 bytes are read.
- /// </summary>
- /// <returns>The 64-bit unsigned integer read</returns>
- public ulong ReadUInt64()
- {
- ReadInternal(buffer, 8);
- return bitConverter.ToUInt64(buffer, 0);
- }
-
- /// <summary>
- /// Reads a single-precision floating-point value from the stream, using the bit converter
- /// for this reader. 4 bytes are read.
- /// </summary>
- /// <returns>The floating point value read</returns>
- public float ReadSingle()
- {
- ReadInternal(buffer, 4);
- return bitConverter.ToSingle(buffer, 0);
- }
-
- /// <summary>
- /// Reads a double-precision floating-point value from the stream, using the bit converter
- /// for this reader. 8 bytes are read.
- /// </summary>
- /// <returns>The floating point value read</returns>
- public double ReadDouble()
- {
- ReadInternal(buffer, 8);
- return bitConverter.ToDouble(buffer, 0);
- }
-
- /// <summary>
- /// Reads a decimal value from the stream, using the bit converter
- /// for this reader. 16 bytes are read.
- /// </summary>
- /// <returns>The decimal value read</returns>
- public decimal ReadDecimal()
- {
- ReadInternal(buffer, 16);
- return bitConverter.ToDecimal(buffer, 0);
- }
-
- /// <summary>
- /// Reads a single character from the stream, using the character encoding for
- /// this reader. If no characters have been fully read by the time the stream ends,
- /// -1 is returned.
- /// </summary>
- /// <returns>The character read, or -1 for end of stream.</returns>
- public int Read()
- {
- int charsRead = Read(charBuffer, 0, 1);
- if (charsRead==0)
- {
- return -1;
- }
- else
- {
- return charBuffer[0];
- }
- }
-
- /// <summary>
- /// Reads the specified number of characters into the given buffer, starting at
- /// the given index.
- /// </summary>
- /// <param name="data">The buffer to copy data into</param>
- /// <param name="index">The first index to copy data into</param>
- /// <param name="count">The number of characters to read</param>
- /// <returns>The number of characters actually read. This will only be less than
- /// the requested number of characters if the end of the stream is reached.
- /// </returns>
- public int Read(char[] data, int index, int count)
- {
- CheckDisposed();
- if (buffer==null)
- {
- throw new ArgumentNullException("buffer");
- }
- if (index < 0)
- {
- throw new ArgumentOutOfRangeException("index");
- }
- if (count < 0)
- {
- throw new ArgumentOutOfRangeException("index");
- }
- if (count+index > data.Length)
- {
- throw new ArgumentException
- ("Not enough space in buffer for specified number of characters starting at specified index");
- }
-
- int read=0;
- bool firstTime=true;
-
- // Use the normal buffer if we're only reading a small amount, otherwise
- // use at most 4K at a time.
- byte[] byteBuffer = buffer;
-
- if (byteBuffer.Length < count*minBytesPerChar)
- {
- byteBuffer = new byte[4096];
- }
-
- while (read < count)
- {
- int amountToRead;
- // First time through we know we haven't previously read any data
- if (firstTime)
- {
- amountToRead = count*minBytesPerChar;
- firstTime=false;
- }
- // After that we can only assume we need to fully read "chars left -1" characters
- // and a single byte of the character we may be in the middle of
- else
- {
- amountToRead = ((count-read-1)*minBytesPerChar)+1;
- }
- if (amountToRead > byteBuffer.Length)
- {
- amountToRead = byteBuffer.Length;
- }
- int bytesRead = TryReadInternal(byteBuffer, amountToRead);
- if (bytesRead==0)
- {
- return read;
- }
- int decoded = decoder.GetChars(byteBuffer, 0, bytesRead, data, index);
- read += decoded;
- index += decoded;
- }
- return read;
- }
-
- /// <summary>
- /// Reads the specified number of bytes into the given buffer, starting at
- /// the given index.
- /// </summary>
- /// <param name="buffer">The buffer to copy data into</param>
- /// <param name="index">The first index to copy data into</param>
- /// <param name="count">The number of bytes to read</param>
- /// <returns>The number of bytes actually read. This will only be less than
- /// the requested number of bytes if the end of the stream is reached.
- /// </returns>
- public int Read(byte[] buffer, int index, int count)
- {
- CheckDisposed();
- if (buffer==null)
- {
- throw new ArgumentNullException("buffer");
- }
- if (index < 0)
- {
- throw new ArgumentOutOfRangeException("index");
- }
- if (count < 0)
- {
- throw new ArgumentOutOfRangeException("index");
- }
- if (count+index > buffer.Length)
- {
- throw new ArgumentException
- ("Not enough space in buffer for specified number of bytes starting at specified index");
- }
- int read=0;
- while (count > 0)
- {
- int block = stream.Read(buffer, index, count);
- if (block==0)
- {
- return read;
- }
- index += block;
- read += block;
- count -= block;
- }
- return read;
- }
-
- /// <summary>
- /// Reads the specified number of bytes, returning them in a new byte array.
- /// If not enough bytes are available before the end of the stream, this
- /// method will return what is available.
- /// </summary>
- /// <param name="count">The number of bytes to read</param>
- /// <returns>The bytes read</returns>
- public byte[] ReadBytes(int count)
- {
- CheckDisposed();
- if (count < 0)
- {
- throw new ArgumentOutOfRangeException("count");
- }
- byte[] ret = new byte[count];
- int index=0;
- while (index < count)
- {
- int read = stream.Read(ret, index, count-index);
- // Stream has finished half way through. That's fine, return what we've got.
- if (read==0)
- {
- byte[] copy = new byte[index];
- Buffer.BlockCopy(ret, 0, copy, 0, index);
- return copy;
- }
- index += read;
- }
- return ret;
- }
-
- /// <summary>
- /// Reads the specified number of bytes, returning them in a new byte array.
- /// If not enough bytes are available before the end of the stream, this
- /// method will throw an IOException.
- /// </summary>
- /// <param name="count">The number of bytes to read</param>
- /// <returns>The bytes read</returns>
- public byte[] ReadBytesOrThrow(int count)
- {
- byte[] ret = new byte[count];
- ReadInternal(ret, count);
- return ret;
- }
-
- /// <summary>
- /// Reads a 7-bit encoded integer from the stream. This is stored with the least significant
- /// information first, with 7 bits of information per byte of value, and the top
- /// bit as a continuation flag. This method is not affected by the endianness
- /// of the bit converter.
- /// </summary>
- /// <returns>The 7-bit encoded integer read from the stream.</returns>
- public int Read7BitEncodedInt()
- {
- CheckDisposed();
-
- int ret=0;
- for (int shift = 0; shift < 35; shift+=7)
- {
- int b = stream.ReadByte();
- if (b==-1)
- {
- throw new EndOfStreamException();
- }
- ret = ret | ((b&0x7f) << shift);
- if ((b & 0x80) == 0)
- {
- return ret;
- }
- }
- // Still haven't seen a byte with the high bit unset? Dodgy data.
- throw new IOException("Invalid 7-bit encoded integer in stream.");
- }
-
- /// <summary>
- /// Reads a 7-bit encoded integer from the stream. This is stored with the most significant
- /// information first, with 7 bits of information per byte of value, and the top
- /// bit as a continuation flag. This method is not affected by the endianness
- /// of the bit converter.
- /// </summary>
- /// <returns>The 7-bit encoded integer read from the stream.</returns>
- public int ReadBigEndian7BitEncodedInt()
- {
- CheckDisposed();
-
- int ret=0;
- for (int i=0; i < 5; i++)
- {
- int b = stream.ReadByte();
- if (b==-1)
- {
- throw new EndOfStreamException();
- }
- ret = (ret << 7) | (b&0x7f);
- if ((b & 0x80) == 0)
- {
- return ret;
- }
- }
- // Still haven't seen a byte with the high bit unset? Dodgy data.
- throw new IOException("Invalid 7-bit encoded integer in stream.");
- }
-
- /// <summary>
- /// Reads a length-prefixed string from the stream, using the encoding for this reader.
- /// A 7-bit encoded integer is first read, which specifies the number of bytes
- /// to read from the stream. These bytes are then converted into a string with
- /// the encoding for this reader.
- /// </summary>
- /// <returns>The string read from the stream.</returns>
- public string ReadString()
- {
- int bytesToRead = Read7BitEncodedInt();
-
- byte[] data = new byte[bytesToRead];
- ReadInternal(data, bytesToRead);
- return encoding.GetString(data, 0, data.Length);
- }
-
- #endregion
-
- #region Private methods
- /// <summary>
- /// Checks whether or not the reader has been disposed, throwing an exception if so.
- /// </summary>
- void CheckDisposed()
- {
- if (disposed)
- {
- throw new ObjectDisposedException("EndianBinaryReader");
- }
- }
-
- /// <summary>
- /// Reads the given number of bytes from the stream, throwing an exception
- /// if they can't all be read.
- /// </summary>
- /// <param name="data">Buffer to read into</param>
- /// <param name="size">Number of bytes to read</param>
- void ReadInternal (byte[] data, int size)
- {
- CheckDisposed();
- int index=0;
- while (index < size)
- {
- int read = stream.Read(data, index, size-index);
- if (read==0)
- {
- throw new EndOfStreamException
- (String.Format("End of stream reached with {0} byte{1} left to read.", size-index,
- size-index==1 ? "s" : ""));
- }
- index += read;
- }
- }
-
- /// <summary>
- /// Reads the given number of bytes from the stream if possible, returning
- /// the number of bytes actually read, which may be less than requested if
- /// (and only if) the end of the stream is reached.
- /// </summary>
- /// <param name="data">Buffer to read into</param>
- /// <param name="size">Number of bytes to read</param>
- /// <returns>Number of bytes actually read</returns>
- int TryReadInternal (byte[] data, int size)
- {
- CheckDisposed();
- int index=0;
- while (index < size)
- {
- int read = stream.Read(data, index, size-index);
- if (read==0)
- {
- return index;
- }
- index += read;
- }
- return index;
- }
- #endregion
-
- #region IDisposable Members
- /// <summary>
- /// Disposes of the underlying stream.
- /// </summary>
- public void Dispose()
- {
- if (!disposed)
- {
- disposed = true;
- ((IDisposable)stream).Dispose();
- }
- }
- #endregion
- }
- }
|