summaryrefslogtreecommitdiff
path: root/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs
blob: 29cef08b6c8711e1744a84e22ce6ff8a3ec610b0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
// 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.

namespace System.Runtime.InteropServices
{
    /// <summary>
    /// Native buffer that deals in char size increments. Dispose to free memory. Allows buffers larger
    /// than a maximum size string to enable working with very large string arrays.  Always makes ordinal
    /// comparisons.
    ///
    /// A more performant replacement for StringBuilder when performing native interop.
    /// </summary>
    /// <remarks>
    /// Suggested use through P/Invoke: define DllImport arguments that take a character buffer as SafeHandle and pass StringBuffer.GetHandle().
    /// </remarks>
    internal class StringBuffer : NativeBuffer
    {
        private uint _length;

        /// <summary>
        /// Instantiate the buffer with capacity for at least the specified number of characters. Capacity
        /// includes the trailing null character.
        /// </summary>
        public StringBuffer(uint initialCapacity = 0)
            : base(initialCapacity * (ulong)sizeof(char))
        {
        }

        /// <summary>
        /// Get/set the character at the given index.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if attempting to index outside of the buffer length.</exception>
        public unsafe char this[uint index]
        {
            get
            {
                if (index >= _length) throw new ArgumentOutOfRangeException(nameof(index));
                return CharPointer[index];
            }
            set
            {
                if (index >= _length) throw new ArgumentOutOfRangeException(nameof(index));
                CharPointer[index] = value;
            }
        }

        /// <summary>
        /// Character capacity of the buffer. Includes the count for the trailing null character.
        /// </summary>
        public uint CharCapacity
        {
            get
            {
                ulong byteCapacity = ByteCapacity;
                ulong charCapacity = byteCapacity == 0 ? 0 : byteCapacity / sizeof(char);
                return charCapacity > uint.MaxValue ? uint.MaxValue : (uint)charCapacity;
            }
        }

        /// <summary>
        /// Ensure capacity in characters is at least the given minimum.
        /// </summary>
        /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception>
        public void EnsureCharCapacity(uint minCapacity)
        {
            EnsureByteCapacity(minCapacity * (ulong)sizeof(char));
        }

        /// <summary>
        /// The logical length of the buffer in characters. (Does not include the final null.) Will automatically attempt to increase capacity.
        /// This is where the usable data ends.
        /// </summary>
        /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if the set size in bytes is uint.MaxValue (as space is implicitly reserved for the trailing null).</exception>
        public unsafe uint Length
        {
            get { return _length; }
            set
            {
                if (value == uint.MaxValue) throw new ArgumentOutOfRangeException(nameof(Length));

                // Null terminate
                EnsureCharCapacity(value + 1);
                CharPointer[value] = '\0';

                _length = value;
            }
        }

        /// <summary>
        /// For use when the native api null terminates but doesn't return a length.
        /// If no null is found, the length will not be changed.
        /// </summary>
        public unsafe void SetLengthToFirstNull()
        {
            char* buffer = CharPointer;
            uint capacity = CharCapacity;
            for (uint i = 0; i < capacity; i++)
            {
                if (buffer[i] == '\0')
                {
                    _length = i;
                    break;
                }
            }
        }

        internal unsafe char* CharPointer
        {
            get
            {
                return (char*)VoidPointer;
            }
        }

        /// <summary>
        /// True if the buffer contains the given character.
        /// </summary>
        public unsafe bool Contains(char value)
        {
            char* start = CharPointer;
            uint length = _length;

            for (uint i = 0; i < length; i++)
            {
                if (*start++ == value) return true;
            }

            return false;
        }

        /// <summary>
        /// Returns true if the buffer starts with the given string.
        /// </summary>
        public bool StartsWith(string value)
        {
            if (value == null) throw new ArgumentNullException(nameof(value));
            if (_length < (uint)value.Length) return false;
            return SubstringEquals(value, startIndex: 0, count: value.Length);
        }

        /// <summary>
        /// Returns true if the specified StringBuffer substring equals the given value.
        /// </summary>
        /// <param name="value">The value to compare against the specified substring.</param>
        /// <param name="startIndex">Start index of the sub string.</param>
        /// <param name="count">Length of the substring, or -1 to check all remaining.</param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range
        /// of the buffer's length.
        /// </exception>
        public unsafe bool SubstringEquals(string value, uint startIndex = 0, int count = -1)
        {
            if (value == null) return false;
            if (count < -1) throw new ArgumentOutOfRangeException(nameof(count));
            if (startIndex > _length) throw new ArgumentOutOfRangeException(nameof(startIndex));

            uint realCount = count == -1 ? _length - startIndex : (uint)count;
            if (checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException(nameof(count));

            int length = value.Length;

            // Check the substring length against the input length
            if (realCount != (uint)length) return false;

            fixed (char* valueStart = value)
            {
                char* bufferStart = CharPointer + startIndex;
                for (int i = 0; i < length; i++)
                {
                    // Note that indexing in this case generates faster code than trying to copy the pointer and increment it
                    if (*bufferStart++ != valueStart[i]) return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Append the given string.
        /// </summary>
        /// <param name="value">The string to append.</param>
        /// <param name="startIndex">The index in the input string to start appending from.</param>
        /// <param name="count">The count of characters to copy from the input string, or -1 for all remaining.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range
        /// of <paramref name="value"/> characters.
        /// </exception>
        public void Append(string value, int startIndex = 0, int count = -1)
        {
            CopyFrom(
                bufferIndex: _length,
                source: value,
                sourceIndex: startIndex,
                count: count);
        }

        /// <summary>
        /// Append the given buffer.
        /// </summary>
        /// <param name="value">The buffer to append.</param>
        /// <param name="startIndex">The index in the input buffer to start appending from.</param>
        /// <param name="count">The count of characters to copy from the buffer string.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range
        /// of <paramref name="value"/> characters.
        /// </exception>
        public void Append(StringBuffer value, uint startIndex = 0)
        {
            if (value == null) throw new ArgumentNullException(nameof(value));
            if (value.Length == 0) return;

            value.CopyTo(
                bufferIndex: startIndex,
                destination: this,
                destinationIndex: _length,
                count: value.Length);
        }

        /// <summary>
        /// Append the given buffer.
        /// </summary>
        /// <param name="value">The buffer to append.</param>
        /// <param name="startIndex">The index in the input buffer to start appending from.</param>
        /// <param name="count">The count of characters to copy from the buffer string.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range
        /// of <paramref name="value"/> characters.
        /// </exception>
        public void Append(StringBuffer value, uint startIndex, uint count)
        {
            if (value == null) throw new ArgumentNullException(nameof(value));
            if (count == 0) return;

            value.CopyTo(
                bufferIndex: startIndex,
                destination: this,
                destinationIndex: _length,
                count: count);
        }

        /// <summary>
        /// Copy contents to the specified buffer. Destination index must be within current destination length.
        /// Will grow the destination buffer if needed.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown if <paramref name="bufferIndex"/> or <paramref name="destinationIndex"/> or <paramref name="count"/> are outside the range
        /// of <paramref name="value"/> characters.
        /// </exception>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="destination"/> is null.</exception>
        public unsafe void CopyTo(uint bufferIndex, StringBuffer destination, uint destinationIndex, uint count)
        {
            if (destination == null) throw new ArgumentNullException(nameof(destination));
            if (destinationIndex > destination._length) throw new ArgumentOutOfRangeException(nameof(destinationIndex));
            if (bufferIndex >= _length) throw new ArgumentOutOfRangeException(nameof(bufferIndex));
            if (_length < checked(bufferIndex + count)) throw new ArgumentOutOfRangeException(nameof(count));

            if (count == 0) return;
            uint lastIndex = checked(destinationIndex + count);
            if (destination._length < lastIndex) destination.Length = lastIndex;

            Buffer.MemoryCopy(
                source: CharPointer + bufferIndex,
                destination: destination.CharPointer + destinationIndex,
                destinationSizeInBytes: checked((long)(destination.ByteCapacity - (destinationIndex * sizeof(char)))),
                sourceBytesToCopy: checked((long)count * sizeof(char)));
        }

        /// <summary>
        /// Copy contents from the specified string into the buffer at the given index. Start index must be within the current length of
        /// the buffer, will grow as necessary.
        /// </summary>
        public unsafe void CopyFrom(uint bufferIndex, string source, int sourceIndex = 0, int count = -1)
        {
            if (source == null) throw new ArgumentNullException(nameof(source));
            if (bufferIndex > _length) throw new ArgumentOutOfRangeException(nameof(bufferIndex));
            if (sourceIndex < 0 || sourceIndex > source.Length) throw new ArgumentOutOfRangeException(nameof(sourceIndex));
            if (count == -1) count = source.Length - sourceIndex;
            if (count < 0 || source.Length - count < sourceIndex) throw new ArgumentOutOfRangeException(nameof(count));

            if (count == 0) return;
            uint lastIndex = bufferIndex + (uint)count;
            if (_length < lastIndex) Length = lastIndex;

            fixed (char* content = source)
            {
                Buffer.MemoryCopy(
                    source: content + sourceIndex,
                    destination: CharPointer + bufferIndex,
                    destinationSizeInBytes: checked((long)(ByteCapacity - (bufferIndex * sizeof(char)))),
                    sourceBytesToCopy: (long)count * sizeof(char));
            }
        }

        /// <summary>
        /// Trim the specified values from the end of the buffer. If nothing is specified, nothing is trimmed.
        /// </summary>
        public unsafe void TrimEnd(char[] values)
        {
            if (values == null || values.Length == 0 || _length == 0) return;

            char* end = CharPointer + _length - 1;

            while (_length > 0 && Array.IndexOf(values, *end) >= 0)
            {
                Length = _length - 1;
                end--;
            }
        }

        /// <summary>
        /// String representation of the entire buffer. If the buffer is larger than the maximum size string (int.MaxValue) this will throw.
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if the buffer is too big to fit into a string.</exception>
        public unsafe override string ToString()
        {
            if (_length == 0) return string.Empty;
            if (_length > int.MaxValue) throw new InvalidOperationException();
            return new string(CharPointer, startIndex: 0, length: (int)_length);
        }

        /// <summary>
        /// Get the given substring in the buffer.
        /// </summary>
        /// <param name="count">Count of characters to take, or remaining characters from <paramref name="startIndex"/> if -1.</param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range of the buffer's length
        /// or count is greater than the maximum string size (int.MaxValue).
        /// </exception>
        public unsafe string Substring(uint startIndex, int count = -1)
        {
            if (startIndex > (_length == 0 ? 0 : _length - 1)) throw new ArgumentOutOfRangeException(nameof(startIndex));
            if (count < -1) throw new ArgumentOutOfRangeException(nameof(count));

            uint realCount = count == -1 ? _length - startIndex : (uint)count;
            if (realCount > int.MaxValue || checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException(nameof(count));
            if (realCount == 0) return string.Empty;

            // The buffer could be bigger than will fit into a string, but the substring might fit. As the starting
            // index might be bigger than int we need to index ourselves.
            return new string(value: CharPointer + startIndex, startIndex: 0, length: (int)realCount);
        }

        public override void Free()
        {
            base.Free();
            _length = 0;
        }
    }
}