| File: Utilities\TextReaderStream.cs | Web Access |
| Project: src\src\Microsoft.ML.Core\Microsoft.ML.Core.csproj (Microsoft.ML.Core) |
// 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. using System; using System.IO; using System.Text; using Microsoft.ML.Runtime; namespace Microsoft.ML.Internal.Utilities { /// <summary> /// A readable <see cref="Stream"/> that is backed by a <see cref="TextReader"/>. /// Because text readers strip line breaks from the end of their lines, this /// compensates by inserting <c>\n</c> line feed characters at the end of every /// input line, including the last one. /// </summary> [BestFriend] internal sealed class TextReaderStream : Stream { private readonly TextReader _baseReader; private readonly Encoding _encoding; // Position in the stream. private long _position; private const int _charBlockSize = 1024; private string _line; private int _lineCur; private readonly byte[] _buff; private int _buffCur; private int _buffLim; private bool _eof; public override bool CanRead => true; public override bool CanSeek => false; public override bool CanWrite => false; public override long Length => throw Contracts.ExceptNotSupp("Stream cannot determine length."); public override long Position { get => _position; set { if (value != Position) { throw Contracts.ExceptNotSupp("Stream cannot seek."); } } } /// <summary> /// Create a stream wrapping the given text reader, using the <see cref="Encoding.UTF8"/> /// encoding. /// </summary> /// <param name="baseReader">the reader to wrap</param> public TextReaderStream(TextReader baseReader) : this(baseReader, Encoding.UTF8) { } /// <summary> /// Create a stream wrapping the given text reader, using the given encoding. The class /// assumes that the encoding is distributive, that is, the concatenation of the byte /// encodings of different strings, is a valid byte encoding of the single encoding of /// the concatenation of those strings. (I believe all standard encodings obey this /// property.) /// </summary> /// <param name="baseReader">The reader to wrap.</param> /// <param name="encoding">The encoding to use.</param> public TextReaderStream(TextReader baseReader, Encoding encoding) { _baseReader = baseReader; _encoding = encoding; _buff = new byte[Math.Max(_encoding.GetByteCount("\n"), _encoding.GetMaxByteCount(_charBlockSize))]; } public override void Close() { _baseReader.Close(); _eof = true; } protected override void Dispose(bool disposing) { _baseReader.Dispose(); base.Dispose(disposing); } public override void Flush() { } /// <summary> /// A helper method that will either ensure that <see cref="_buffCur"/> is less /// than <see cref="_buffLim"/> (so there are at least some characters), or that /// <see cref="_eof"/> is set. /// </summary> private void EnsureBytes() { // If we're at the end of the file, or if there are still bytes available in // the buffer, just return. if (_eof || _buffCur < _buffLim) return; Contracts.Assert(_buffCur == _buffLim); // There are no bytes available in the buffer, and we have not reached the // end of the file. If we don't have a line pending, get one. if (_line == null) { Contracts.Assert(_lineCur == 0); _line = _baseReader.ReadLine(); if (_line == null) { _eof = true; return; } } Contracts.AssertValue(_line); Contracts.Assert(_lineCur <= _line.Length); _buffCur = 0; if (_lineCur == _line.Length) { // There are no more chars, and we are at the end of a string. Encode the // linefeed, then set the line to null so we know, when we run out of bytes // on the linefeed character, that we have no line "pending." _buffLim = _encoding.GetBytes("\n", 0, 1, _buff, 0); // I am assuming it's impossible for an encoding to encode "\n" as 0 bytes. :) Contracts.Assert(0 < _buffLim && _buffLim <= _buff.Length); _line = null; _lineCur = 0; return; } // There are still more characters in _line to encode. int charCount = Math.Min(_line.Length - _lineCur, _charBlockSize); Contracts.Assert(charCount > 0); _buffLim = _encoding.GetBytes(_line, _lineCur, charCount, _buff, 0); Contracts.Assert(0 < _buffLim && _buffLim <= _buff.Length); _lineCur += charCount; Contracts.Assert(_lineCur <= _line.Length); } public override int Read(byte[] buffer, int offset, int count) { Contracts.CheckValue(buffer, nameof(buffer)); Contracts.CheckParam(0 <= offset && offset <= buffer.Length, nameof(offset), "invalid for this sized array"); Contracts.CheckParam(0 <= count && count <= buffer.Length - offset, nameof(count), "invalid for this sized array"); int readCount = 0; while (readCount < count) { EnsureBytes(); if (_eof) break; Contracts.Assert(_buffCur < _buffLim); int toCopy = Math.Min(count - readCount, _buffLim - _buffCur); Contracts.Assert(toCopy > 0); Buffer.BlockCopy(_buff, _buffCur, buffer, offset, toCopy); offset += toCopy; readCount += toCopy; _buffCur += toCopy; } _position += readCount; return readCount; } public override int ReadByte() { EnsureBytes(); Contracts.Assert(_eof || _buffCur < _buffLim); return _eof ? -1 : (int)_buff[_buffCur++]; } public override long Seek(long offset, SeekOrigin origin) => throw Contracts.ExceptNotSupp("Stream cannot seek."); public override void Write(byte[] buffer, int offset, int count) => throw Contracts.ExceptNotSupp("Stream is not writable."); public override void SetLength(long value) => throw Contracts.ExceptNotSupp("Stream is not writable."); } }