diff options
Diffstat (limited to 'src/System.Private.CoreLib/shared/System/IO/BinaryReader.cs')
-rw-r--r-- | src/System.Private.CoreLib/shared/System/IO/BinaryReader.cs | 717 |
1 files changed, 717 insertions, 0 deletions
diff --git a/src/System.Private.CoreLib/shared/System/IO/BinaryReader.cs b/src/System.Private.CoreLib/shared/System/IO/BinaryReader.cs new file mode 100644 index 000000000..1f1b9218f --- /dev/null +++ b/src/System.Private.CoreLib/shared/System/IO/BinaryReader.cs @@ -0,0 +1,717 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: Wraps a stream and provides convenient read functionality +** for strings and primitive types. +** +** +============================================================*/ + +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace System.IO +{ + public class BinaryReader : IDisposable + { + private const int MaxCharBytesSize = 128; + + private Stream _stream; + private byte[] _buffer; + private Decoder _decoder; + private byte[] _charBytes; + private char[] _singleChar; + private char[] _charBuffer; + private int _maxCharsSize; // From MaxCharBytesSize & Encoding + + // Performance optimization for Read() w/ Unicode. Speeds us up by ~40% + private bool _2BytesPerChar; + private bool _isMemoryStream; // "do we sit on MemoryStream?" for Read/ReadInt32 perf + private bool _leaveOpen; + + public BinaryReader(Stream input) : this(input, Encoding.UTF8, false) + { + } + + public BinaryReader(Stream input, Encoding encoding) : this(input, encoding, false) + { + } + + public BinaryReader(Stream input, Encoding encoding, bool leaveOpen) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + if (encoding == null) + { + throw new ArgumentNullException(nameof(encoding)); + } + if (!input.CanRead) + { + throw new ArgumentException(SR.Argument_StreamNotReadable); + } + + _stream = input; + _decoder = encoding.GetDecoder(); + _maxCharsSize = encoding.GetMaxCharCount(MaxCharBytesSize); + int minBufferSize = encoding.GetMaxByteCount(1); // max bytes per one char + if (minBufferSize < 16) + { + minBufferSize = 16; + } + + _buffer = new byte[minBufferSize]; + // _charBuffer and _charBytes will be left null. + + // For Encodings that always use 2 bytes per char (or more), + // special case them here to make Read() & Peek() faster. + _2BytesPerChar = encoding is UnicodeEncoding; + // check if BinaryReader is based on MemoryStream, and keep this for it's life + // we cannot use "as" operator, since derived classes are not allowed + _isMemoryStream = (_stream.GetType() == typeof(MemoryStream)); + _leaveOpen = leaveOpen; + + Debug.Assert(_decoder != null, "[BinaryReader.ctor]_decoder!=null"); + } + + public virtual Stream BaseStream + { + get + { + return _stream; + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Stream copyOfStream = _stream; + _stream = null; + if (copyOfStream != null && !_leaveOpen) + { + copyOfStream.Close(); + } + } + _stream = null; + _buffer = null; + _decoder = null; + _charBytes = null; + _singleChar = null; + _charBuffer = null; + } + + public void Dispose() + { + Dispose(true); + } + + /// <remarks> + /// Override Dispose(bool) instead of Close(). This API exists for compatibility purposes. + /// </remarks> + public virtual void Close() + { + Dispose(true); + } + + public virtual int PeekChar() + { + if (_stream == null) + { + throw Error.GetFileNotOpen(); + } + + if (!_stream.CanSeek) + { + return -1; + } + + long origPos = _stream.Position; + int ch = Read(); + _stream.Position = origPos; + return ch; + } + + public virtual int Read() + { + if (_stream == null) + { + throw Error.GetFileNotOpen(); + } + + int charsRead = 0; + int numBytes = 0; + long posSav = posSav = 0; + + if (_stream.CanSeek) + { + posSav = _stream.Position; + } + + if (_charBytes == null) + { + _charBytes = new byte[MaxCharBytesSize]; //REVIEW: We need at most 2 bytes/char here? + } + if (_singleChar == null) + { + _singleChar = new char[1]; + } + + while (charsRead == 0) + { + // We really want to know what the minimum number of bytes per char + // is for our encoding. Otherwise for UnicodeEncoding we'd have to + // do ~1+log(n) reads to read n characters. + // Assume 1 byte can be 1 char unless _2BytesPerChar is true. + numBytes = _2BytesPerChar ? 2 : 1; + + int r = _stream.ReadByte(); + _charBytes[0] = (byte)r; + if (r == -1) + { + numBytes = 0; + } + if (numBytes == 2) + { + r = _stream.ReadByte(); + _charBytes[1] = (byte)r; + if (r == -1) + { + numBytes = 1; + } + } + + if (numBytes == 0) + { + return -1; + } + + Debug.Assert(numBytes == 1 || numBytes == 2, "BinaryReader::ReadOneChar assumes it's reading one or 2 bytes only."); + + try + { + charsRead = _decoder.GetChars(_charBytes, 0, numBytes, _singleChar, 0); + } + catch + { + // Handle surrogate char + + if (_stream.CanSeek) + { + _stream.Seek((posSav - _stream.Position), SeekOrigin.Current); + } + // else - we can't do much here + + throw; + } + + Debug.Assert(charsRead < 2, "BinaryReader::ReadOneChar - assuming we only got 0 or 1 char, not 2!"); + } + Debug.Assert(charsRead > 0); + return _singleChar[0]; + } + + public virtual bool ReadBoolean() + { + FillBuffer(1); + return (_buffer[0] != 0); + } + + public virtual byte ReadByte() + { + // Inlined to avoid some method call overhead with FillBuffer. + if (_stream == null) + { + throw Error.GetFileNotOpen(); + } + + int b = _stream.ReadByte(); + if (b == -1) + { + throw Error.GetEndOfFile(); + } + + return (byte)b; + } + + [CLSCompliant(false)] + public virtual sbyte ReadSByte() + { + FillBuffer(1); + return (sbyte)(_buffer[0]); + } + + public virtual char ReadChar() + { + int value = Read(); + if (value == -1) + { + throw Error.GetEndOfFile(); + } + return (char)value; + } + + public virtual short ReadInt16() + { + FillBuffer(2); + return (short)(_buffer[0] | _buffer[1] << 8); + } + + [CLSCompliant(false)] + public virtual ushort ReadUInt16() + { + FillBuffer(2); + return (ushort)(_buffer[0] | _buffer[1] << 8); + } + + public virtual int ReadInt32() + { + if (_isMemoryStream) + { + if (_stream == null) + { + throw Error.GetFileNotOpen(); + } + + // read directly from MemoryStream buffer + MemoryStream mStream = _stream as MemoryStream; + Debug.Assert(mStream != null, "_stream as MemoryStream != null"); + + return mStream.InternalReadInt32(); + } + else + { + FillBuffer(4); + return (int)(_buffer[0] | _buffer[1] << 8 | _buffer[2] << 16 | _buffer[3] << 24); + } + } + + [CLSCompliant(false)] + public virtual uint ReadUInt32() + { + FillBuffer(4); + return (uint)(_buffer[0] | _buffer[1] << 8 | _buffer[2] << 16 | _buffer[3] << 24); + } + + public virtual long ReadInt64() + { + FillBuffer(8); + uint lo = (uint)(_buffer[0] | _buffer[1] << 8 | + _buffer[2] << 16 | _buffer[3] << 24); + uint hi = (uint)(_buffer[4] | _buffer[5] << 8 | + _buffer[6] << 16 | _buffer[7] << 24); + return (long)((ulong)hi) << 32 | lo; + } + + [CLSCompliant(false)] + public virtual ulong ReadUInt64() + { + FillBuffer(8); + uint lo = (uint)(_buffer[0] | _buffer[1] << 8 | + _buffer[2] << 16 | _buffer[3] << 24); + uint hi = (uint)(_buffer[4] | _buffer[5] << 8 | + _buffer[6] << 16 | _buffer[7] << 24); + return ((ulong)hi) << 32 | lo; + } + + public virtual unsafe float ReadSingle() + { + FillBuffer(4); + uint tmpBuffer = (uint)(_buffer[0] | _buffer[1] << 8 | _buffer[2] << 16 | _buffer[3] << 24); + return *((float*)&tmpBuffer); + } + + public virtual unsafe double ReadDouble() + { + FillBuffer(8); + uint lo = (uint)(_buffer[0] | _buffer[1] << 8 | + _buffer[2] << 16 | _buffer[3] << 24); + uint hi = (uint)(_buffer[4] | _buffer[5] << 8 | + _buffer[6] << 16 | _buffer[7] << 24); + + ulong tmpBuffer = ((ulong)hi) << 32 | lo; + return *((double*)&tmpBuffer); + } + + public virtual decimal ReadDecimal() + { + FillBuffer(16); + try + { + return decimal.ToDecimal(_buffer); + } + catch (ArgumentException e) + { + // ReadDecimal cannot leak out ArgumentException + throw new IOException(SR.Arg_DecBitCtor, e); + } + } + + public virtual string ReadString() + { + if (_stream == null) + { + throw Error.GetFileNotOpen(); + } + + int currPos = 0; + int n; + int stringLength; + int readLength; + int charsRead; + + // Length of the string in bytes, not chars + stringLength = Read7BitEncodedInt(); + if (stringLength < 0) + { + throw new IOException(SR.Format(SR.IO_InvalidStringLen_Len, stringLength)); + } + + if (stringLength == 0) + { + return string.Empty; + } + + if (_charBytes == null) + { + _charBytes = new byte[MaxCharBytesSize]; + } + + if (_charBuffer == null) + { + _charBuffer = new char[_maxCharsSize]; + } + + StringBuilder sb = null; + do + { + readLength = ((stringLength - currPos) > MaxCharBytesSize) ? MaxCharBytesSize : (stringLength - currPos); + + n = _stream.Read(_charBytes, 0, readLength); + if (n == 0) + { + throw Error.GetEndOfFile(); + } + + charsRead = _decoder.GetChars(_charBytes, 0, n, _charBuffer, 0); + + if (currPos == 0 && n == stringLength) + { + return new string(_charBuffer, 0, charsRead); + } + + if (sb == null) + { + sb = StringBuilderCache.Acquire(stringLength); // Actual string length in chars may be smaller. + } + + sb.Append(_charBuffer, 0, charsRead); + currPos += n; + } while (currPos < stringLength); + + return StringBuilderCache.GetStringAndRelease(sb); + } + + public virtual int Read(char[] buffer, int index, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); + } + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); + } + if (buffer.Length - index < count) + { + throw new ArgumentException(SR.Argument_InvalidOffLen); + } + if (_stream == null) + { + throw Error.GetFileNotOpen(); + } + + // SafeCritical: index and count have already been verified to be a valid range for the buffer + return InternalReadChars(new Span<char>(buffer, index, count)); + } + + public virtual int Read(Span<char> buffer) + { + if (_stream == null) + { + throw Error.GetFileNotOpen(); + } + + return InternalReadChars(buffer); + } + + private int InternalReadChars(Span<char> buffer) + { + Debug.Assert(_stream != null); + + int numBytes = 0; + int index = 0; + int charsRemaining = buffer.Length; + + if (_charBytes == null) + { + _charBytes = new byte[MaxCharBytesSize]; + } + + while (charsRemaining > 0) + { + int charsRead = 0; + // We really want to know what the minimum number of bytes per char + // is for our encoding. Otherwise for UnicodeEncoding we'd have to + // do ~1+log(n) reads to read n characters. + numBytes = charsRemaining; + + if (_2BytesPerChar) + { + numBytes <<= 1; + } + if (numBytes > MaxCharBytesSize) + { + numBytes = MaxCharBytesSize; + } + + int position = 0; + byte[] byteBuffer = null; + if (_isMemoryStream) + { + MemoryStream mStream = _stream as MemoryStream; + Debug.Assert(mStream != null, "_stream as MemoryStream != null"); + + position = mStream.InternalGetPosition(); + numBytes = mStream.InternalEmulateRead(numBytes); + byteBuffer = mStream.InternalGetBuffer(); + } + else + { + numBytes = _stream.Read(_charBytes, 0, numBytes); + byteBuffer = _charBytes; + } + + if (numBytes == 0) + { + return (buffer.Length - charsRemaining); + } + + Debug.Assert(byteBuffer != null, "expected byteBuffer to be non-null"); + checked + { + if (position < 0 || numBytes < 0 || position > byteBuffer.Length - numBytes) + { + throw new ArgumentOutOfRangeException(nameof(numBytes)); + } + if (index < 0 || charsRemaining < 0 || index > buffer.Length - charsRemaining) + { + throw new ArgumentOutOfRangeException(nameof(charsRemaining)); + } + unsafe + { + fixed (byte* pBytes = byteBuffer) + fixed (char* pChars = &MemoryMarshal.GetReference(buffer)) + { + charsRead = _decoder.GetChars(pBytes + position, numBytes, pChars + index, charsRemaining, flush: false); + } + } + } + + charsRemaining -= charsRead; + index += charsRead; + } + + // this should never fail + Debug.Assert(charsRemaining >= 0, "We read too many characters."); + + // we may have read fewer than the number of characters requested if end of stream reached + // or if the encoding makes the char count too big for the buffer (e.g. fallback sequence) + return (buffer.Length - charsRemaining); + } + + public virtual char[] ReadChars(int count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); + } + if (_stream == null) + { + throw Error.GetFileNotOpen(); + } + + if (count == 0) + { + return Array.Empty<char>(); + } + + // SafeCritical: we own the chars buffer, and therefore can guarantee that the index and count are valid + char[] chars = new char[count]; + int n = InternalReadChars(new Span<char>(chars)); + if (n != count) + { + char[] copy = new char[n]; + Buffer.BlockCopy(chars, 0, copy, 0, 2 * n); // sizeof(char) + chars = copy; + } + + return chars; + } + + public virtual int Read(byte[] buffer, int index, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); + } + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); + } + if (buffer.Length - index < count) + { + throw new ArgumentException(SR.Argument_InvalidOffLen); + } + if (_stream == null) + { + throw Error.GetFileNotOpen(); + } + + return _stream.Read(buffer, index, count); + } + + public virtual int Read(Span<byte> buffer) + { + if (_stream == null) + { + throw Error.GetFileNotOpen(); + } + + return _stream.Read(buffer); + } + + public virtual byte[] ReadBytes(int count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); + } + if (_stream == null) + { + throw new ObjectDisposedException(null, SR.ObjectDisposed_FileClosed); + } + + if (count == 0) + { + return Array.Empty<byte>(); + } + + byte[] result = new byte[count]; + int numRead = 0; + do + { + int n = _stream.Read(result, numRead, count); + if (n == 0) + { + break; + } + + numRead += n; + count -= n; + } while (count > 0); + + if (numRead != result.Length) + { + // Trim array. This should happen on EOF & possibly net streams. + byte[] copy = new byte[numRead]; + Buffer.BlockCopy(result, 0, copy, 0, numRead); + result = copy; + } + + return result; + } + + protected virtual void FillBuffer(int numBytes) + { + if (_buffer != null && (numBytes < 0 || numBytes > _buffer.Length)) + { + throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_BinaryReaderFillBuffer); + } + + int bytesRead = 0; + int n = 0; + + if (_stream == null) + { + throw Error.GetFileNotOpen(); + } + + // Need to find a good threshold for calling ReadByte() repeatedly + // vs. calling Read(byte[], int, int) for both buffered & unbuffered + // streams. + if (numBytes == 1) + { + n = _stream.ReadByte(); + if (n == -1) + { + throw Error.GetEndOfFile(); + } + + _buffer[0] = (byte)n; + return; + } + + do + { + n = _stream.Read(_buffer, bytesRead, numBytes - bytesRead); + if (n == 0) + { + throw Error.GetEndOfFile(); + } + bytesRead += n; + } while (bytesRead < numBytes); + } + + protected internal int Read7BitEncodedInt() + { + // Read out an Int32 7 bits at a time. The high bit + // of the byte when on means to continue reading more bytes. + int count = 0; + int shift = 0; + byte b; + do + { + // Check for a corrupted stream. Read a max of 5 bytes. + // In a future version, add a DataFormatException. + if (shift == 5 * 7) // 5 bytes max per Int32, shift += 7 + { + throw new FormatException(SR.Format_Bad7BitInt32); + } + + // ReadByte handles end of stream cases for us. + b = ReadByte(); + count |= (b & 0x7F) << shift; + shift += 7; + } while ((b & 0x80) != 0); + return count; + } + } +} |