1#region Copyright notice and license
2// Protocol Buffers - Google's data interchange format
3// Copyright 2008 Google Inc.  All rights reserved.
4// https://developers.google.com/protocol-buffers/
5//
6// Redistribution and use in source and binary forms, with or without
7// modification, are permitted provided that the following conditions are
8// met:
9//
10//     * Redistributions of source code must retain the above copyright
11// notice, this list of conditions and the following disclaimer.
12//     * Redistributions in binary form must reproduce the above
13// copyright notice, this list of conditions and the following disclaimer
14// in the documentation and/or other materials provided with the
15// distribution.
16//     * Neither the name of Google Inc. nor the names of its
17// contributors may be used to endorse or promote products derived from
18// this software without specific prior written permission.
19//
20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31#endregion
32
33using System;
34using System.Buffers;
35using System.Diagnostics;
36
37namespace Google.Protobuf.Buffers
38{
39    /// <summary>
40    /// Represents a heap-based, array-backed output sink into which <typeparam name="T"/> data can be written.
41    ///
42    /// ArrayBufferWriter is originally from corefx, and has been contributed to Protobuf
43    /// https://github.com/dotnet/runtime/blob/071da4c41aa808c949a773b92dca6f88de9d11f3/src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs
44    /// </summary>
45    internal sealed class ArrayBufferWriter<T> : IBufferWriter<T>
46    {
47        private T[] _buffer;
48        private int _index;
49
50        private const int DefaultInitialBufferSize = 256;
51
52        /// <summary>
53        /// Creates an instance of an <see cref="ArrayBufferWriter{T}"/>, in which data can be written to,
54        /// with the default initial capacity.
55        /// </summary>
56        public ArrayBufferWriter()
57        {
58            _buffer = new T[0];
59            _index = 0;
60        }
61
62        /// <summary>
63        /// Userful for testing writing to buffer writer with a lot of small segments.
64        /// If set, it limits the max number of bytes by which the buffer grows by at once.
65        /// </summary>
66        public int? MaxGrowBy { get; set; }
67
68        /// <summary>
69        /// Creates an instance of an <see cref="ArrayBufferWriter{T}"/>, in which data can be written to,
70        /// with an initial capacity specified.
71        /// </summary>
72        /// <param name="initialCapacity">The minimum capacity with which to initialize the underlying buffer.</param>
73        /// <exception cref="ArgumentException">
74        /// Thrown when <paramref name="initialCapacity"/> is not positive (i.e. less than or equal to 0).
75        /// </exception>
76        public ArrayBufferWriter(int initialCapacity)
77        {
78            if (initialCapacity <= 0)
79                throw new ArgumentException(nameof(initialCapacity));
80
81            _buffer = new T[initialCapacity];
82            _index = 0;
83        }
84
85        /// <summary>
86        /// Returns the data written to the underlying buffer so far, as a <see cref="ReadOnlyMemory{T}"/>.
87        /// </summary>
88        public ReadOnlyMemory<T> WrittenMemory => _buffer.AsMemory(0, _index);
89
90        /// <summary>
91        /// Returns the data written to the underlying buffer so far, as a <see cref="ReadOnlySpan{T}"/>.
92        /// </summary>
93        public ReadOnlySpan<T> WrittenSpan => _buffer.AsSpan(0, _index);
94
95        /// <summary>
96        /// Returns the amount of data written to the underlying buffer so far.
97        /// </summary>
98        public int WrittenCount => _index;
99
100        /// <summary>
101        /// Returns the total amount of space within the underlying buffer.
102        /// </summary>
103        public int Capacity => _buffer.Length;
104
105        /// <summary>
106        /// Returns the amount of space available that can still be written into without forcing the underlying buffer to grow.
107        /// </summary>
108        public int FreeCapacity => _buffer.Length - _index;
109
110        /// <summary>
111        /// Clears the data written to the underlying buffer.
112        /// </summary>
113        /// <remarks>
114        /// You must clear the <see cref="ArrayBufferWriter{T}"/> before trying to re-use it.
115        /// </remarks>
116        public void Clear()
117        {
118            Debug.Assert(_buffer.Length >= _index);
119            _buffer.AsSpan(0, _index).Clear();
120            _index = 0;
121        }
122
123        /// <summary>
124        /// Notifies <see cref="IBufferWriter{T}"/> that <paramref name="count"/> amount of data was written to the output <see cref="Span{T}"/>/<see cref="Memory{T}"/>
125        /// </summary>
126        /// <exception cref="ArgumentException">
127        /// Thrown when <paramref name="count"/> is negative.
128        /// </exception>
129        /// <exception cref="InvalidOperationException">
130        /// Thrown when attempting to advance past the end of the underlying buffer.
131        /// </exception>
132        /// <remarks>
133        /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer.
134        /// </remarks>
135        public void Advance(int count)
136        {
137            if (count < 0)
138                throw new ArgumentException(nameof(count));
139
140            if (_index > _buffer.Length - count)
141                throw new InvalidOperationException("Advanced past capacity.");
142
143            _index += count;
144        }
145
146        /// <summary>
147        /// Returns a <see cref="Memory{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>).
148        /// If no <paramref name="sizeHint"/> is provided (or it's equal to <code>0</code>), some non-empty buffer is returned.
149        /// </summary>
150        /// <exception cref="ArgumentException">
151        /// Thrown when <paramref name="sizeHint"/> is negative.
152        /// </exception>
153        /// <remarks>
154        /// This will never return an empty <see cref="Memory{T}"/>.
155        /// </remarks>
156        /// <remarks>
157        /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer.
158        /// </remarks>
159        /// <remarks>
160        /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer.
161        /// </remarks>
162        public Memory<T> GetMemory(int sizeHint = 0)
163        {
164            CheckAndResizeBuffer(sizeHint);
165            Debug.Assert(_buffer.Length > _index);
166            return _buffer.AsMemory(_index);
167        }
168
169        /// <summary>
170        /// Returns a <see cref="Span{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>).
171        /// If no <paramref name="sizeHint"/> is provided (or it's equal to <code>0</code>), some non-empty buffer is returned.
172        /// </summary>
173        /// <exception cref="ArgumentException">
174        /// Thrown when <paramref name="sizeHint"/> is negative.
175        /// </exception>
176        /// <remarks>
177        /// This will never return an empty <see cref="Span{T}"/>.
178        /// </remarks>
179        /// <remarks>
180        /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer.
181        /// </remarks>
182        /// <remarks>
183        /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer.
184        /// </remarks>
185        public Span<T> GetSpan(int sizeHint = 0)
186        {
187            CheckAndResizeBuffer(sizeHint);
188            Debug.Assert(_buffer.Length > _index);
189            return _buffer.AsSpan(_index);
190        }
191
192        private void CheckAndResizeBuffer(int sizeHint)
193        {
194            if (sizeHint < 0)
195                throw new ArgumentException(nameof(sizeHint));
196
197            if (sizeHint == 0)
198            {
199                sizeHint = 1;
200            }
201
202            if (sizeHint > FreeCapacity)
203            {
204                int growBy = Math.Max(sizeHint, _buffer.Length);
205
206                if (_buffer.Length == 0)
207                {
208                    growBy = Math.Max(growBy, DefaultInitialBufferSize);
209                }
210
211                // enable tests that write to small buffer segments
212                if (MaxGrowBy.HasValue && growBy > MaxGrowBy.Value)
213                {
214                    growBy = MaxGrowBy.Value;
215                }
216
217                int newSize = checked(_buffer.Length + growBy);
218
219                Array.Resize(ref _buffer, newSize);
220            }
221
222            Debug.Assert(FreeCapacity > 0 && FreeCapacity >= sizeHint);
223        }
224    }
225}