blob: a36a9513c72a474997d12e38317fa26b8915e5be [file] [log] [blame]
Jan Tattermusch90d49692020-05-29 14:24:26 +02001#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>
James Newton-King89324462020-12-16 12:56:14 +130045 internal sealed class TestArrayBufferWriter<T> : IBufferWriter<T>
Jan Tattermusch90d49692020-05-29 14:24:26 +020046 {
47 private T[] _buffer;
48 private int _index;
49
50 private const int DefaultInitialBufferSize = 256;
51
52 /// <summary>
James Newton-King89324462020-12-16 12:56:14 +130053 /// Creates an instance of an <see cref="TestArrayBufferWriter{T}"/>, in which data can be written to,
Jan Tattermusch90d49692020-05-29 14:24:26 +020054 /// with the default initial capacity.
55 /// </summary>
James Newton-King89324462020-12-16 12:56:14 +130056 public TestArrayBufferWriter()
Jan Tattermusch90d49692020-05-29 14:24:26 +020057 {
58 _buffer = new T[0];
59 _index = 0;
60 }
61
62 /// <summary>
Peter Newmane2cc2de2020-08-10 19:08:25 +010063 /// Useful for testing writing to buffer writer with a lot of small segments.
Jan Tattermusch56372892020-06-03 16:58:02 +020064 /// 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>
James Newton-King89324462020-12-16 12:56:14 +130069 /// Creates an instance of an <see cref="TestArrayBufferWriter{T}"/>, in which data can be written to,
Jan Tattermusch90d49692020-05-29 14:24:26 +020070 /// 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>
James Newton-King89324462020-12-16 12:56:14 +130076 public TestArrayBufferWriter(int initialCapacity)
Jan Tattermusch90d49692020-05-29 14:24:26 +020077 {
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>
James Newton-King89324462020-12-16 12:56:14 +1300114 /// You must clear the <see cref="TestArrayBufferWriter{T}"/> before trying to re-use it.
Jan Tattermusch90d49692020-05-29 14:24:26 +0200115 /// </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
Jan Tattermusch56372892020-06-03 16:58:02 +0200211 // enable tests that write to small buffer segments
212 if (MaxGrowBy.HasValue && growBy > MaxGrowBy.Value)
213 {
214 growBy = MaxGrowBy.Value;
215 }
216
Jan Tattermusch90d49692020-05-29 14:24:26 +0200217 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}