File: Text\SourceTextStream.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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.Diagnostics;
using System.IO;
using System.Text;
 
namespace Microsoft.CodeAnalysis.Text
{
    /// <summary>
    /// A read-only, non-seekable <see cref="Stream"/> over a <see cref="SourceText"/>.
    /// </summary>
    internal sealed class SourceTextStream : Stream
    {
        private readonly SourceText _source;
        private readonly Encoding _encoding;
        private readonly Encoder _encoder;
 
        private readonly int _minimumTargetBufferCount;
        private int _position;
        private int _sourceOffset;
        private readonly char[] _charBuffer;
        private int _bufferOffset;
        private int _bufferUnreadChars;
        private bool _preambleWritten;
 
        private static readonly Encoding s_utf8EncodingWithNoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);
 
        public SourceTextStream(SourceText source, int bufferSize = 2048, bool useDefaultEncodingIfNull = false)
        {
            Debug.Assert(source.Encoding != null || useDefaultEncodingIfNull);
 
            _source = source;
            _encoding = source.Encoding ?? s_utf8EncodingWithNoBOM;
            _encoder = _encoding.GetEncoder();
            _minimumTargetBufferCount = _encoding.GetMaxByteCount(charCount: 1);
            _sourceOffset = 0;
            _position = 0;
            _charBuffer = new char[Math.Min(bufferSize, _source.Length)];
            _bufferOffset = 0;
            _bufferUnreadChars = 0;
            _preambleWritten = false;
        }
 
        public override bool CanRead
        {
            get { return true; }
        }
 
        public override bool CanSeek
        {
            get { return false; }
        }
 
        public override bool CanWrite
        {
            get { return false; }
        }
 
        public override void Flush()
        {
            throw new NotSupportedException();
        }
 
        public override long Length
        {
            get { throw new NotSupportedException(); }
        }
 
        public override long Position
        {
            get { return _position; }
            set { throw new NotSupportedException(); }
        }
 
        public override int Read(byte[] buffer, int offset, int count)
        {
            if (count < _minimumTargetBufferCount)
            {
                // The buffer must be able to hold at least one character from the 
                // SourceText stream.  Returning 0 for that case isn't correct because
                // that indicates end of stream vs. insufficient buffer. 
                throw new ArgumentException($"{nameof(count)} must be greater than or equal to {_minimumTargetBufferCount}", nameof(count));
            }
 
            int originalCount = count;
 
            if (!_preambleWritten)
            {
                int bytesWritten = WritePreamble(buffer, offset, count);
                offset += bytesWritten;
                count -= bytesWritten;
            }
 
            while (count >= _minimumTargetBufferCount && _position < _source.Length)
            {
                if (_bufferUnreadChars == 0)
                {
                    FillBuffer();
                }
 
                int charsUsed, bytesUsed;
                bool ignored;
                _encoder.Convert(_charBuffer, _bufferOffset, _bufferUnreadChars, buffer, offset, count, flush: false, charsUsed: out charsUsed, bytesUsed: out bytesUsed, completed: out ignored);
                _position += charsUsed;
                _bufferOffset += charsUsed;
                _bufferUnreadChars -= charsUsed;
                offset += bytesUsed;
                count -= bytesUsed;
            }
 
            // Return value is the number of bytes read
            return originalCount - count;
        }
 
        private int WritePreamble(byte[] buffer, int offset, int count)
        {
            _preambleWritten = true;
            byte[] preambleBytes = _encoding.GetPreamble();
            if (preambleBytes == null)
            {
                return 0;
            }
 
            int length = Math.Min(count, preambleBytes.Length);
            Array.Copy(preambleBytes, 0, buffer, offset, length);
            return length;
        }
 
        private void FillBuffer()
        {
            int charsToRead = Math.Min(_charBuffer.Length, _source.Length - _sourceOffset);
            _source.CopyTo(_sourceOffset, _charBuffer, 0, charsToRead);
            _sourceOffset += charsToRead;
            _bufferOffset = 0;
            _bufferUnreadChars = charsToRead;
        }
 
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException();
        }
 
        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }
 
        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotSupportedException();
        }
    }
}