1 // -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 // System.Text.StringBuilder
6 // Marcin Szczepanski (marcins@zipworld.com.au)
7 // Paolo Molaro (lupus@ximian.com)
10 // NOTE: In the case the buffer is only filled by 50% a new string
11 // will be returned by ToString() is cached in the '_cached_str'
12 // cache_string will also control if a string has been handed out
13 // to via ToString(). If you are chaning the code make sure that
14 // if you modify the string data set the cache_string to null.
18 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
20 // Permission is hereby granted, free of charge, to any person obtaining
21 // a copy of this software and associated documentation files (the
22 // "Software"), to deal in the Software without restriction, including
23 // without limitation the rights to use, copy, modify, merge, publish,
24 // distribute, sublicense, and/or sell copies of the Software, and to
25 // permit persons to whom the Software is furnished to do so, subject to
26 // the following conditions:
28 // The above copyright notice and this permission notice shall be
29 // included in all copies or substantial portions of the Software.
31 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
34 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
35 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
36 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
37 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
39 using System.Runtime.CompilerServices;
41 namespace System.Text {
44 [MonoTODO ("Fix serialization compatibility with MS.NET")]
45 public sealed class StringBuilder
48 private string _str = null;
49 private string _cached_str = null;
51 private int _maxCapacity = Int32.MaxValue;
52 private const int constDefaultCapacity = 16;
54 public StringBuilder(string value, int startIndex, int length, int capacity)
56 // first, check the parameters and throw appropriate exceptions if needed
60 // make sure startIndex is zero or positive
62 throw new System.ArgumentOutOfRangeException ("startIndex", startIndex, "StartIndex cannot be less than zero.");
64 // make sure length is zero or positive
66 throw new System.ArgumentOutOfRangeException ("length", length, "Length cannot be less than zero.");
69 throw new System.ArgumentOutOfRangeException ("capacity", capacity, "capacity must be greater than zero.");
71 // make sure startIndex and length give a valid substring of value
72 // re-ordered to avoid possible integer overflow
73 if (startIndex > value.Length - length)
74 throw new System.ArgumentOutOfRangeException ("startIndex", startIndex, "StartIndex and length must refer to a location within the string.");
77 capacity = constDefaultCapacity;
79 _str = String.InternalAllocateStr ((length > capacity) ? length : capacity);
81 String.InternalStrcpy(_str, 0, value, startIndex, length);
86 public StringBuilder () : this (null) {}
88 public StringBuilder(int capacity) : this (String.Empty, 0, 0, capacity) {}
90 public StringBuilder(int capacity, int maxCapacity) : this (String.Empty, 0, 0, capacity) {
92 throw new System.ArgumentOutOfRangeException ("maxCapacity", "maxCapacity is less than one.");
93 if (capacity > maxCapacity)
94 throw new System.ArgumentOutOfRangeException ("capacity", "Capacity exceeds maximum capacity.");
96 _maxCapacity = maxCapacity;
99 public StringBuilder (string value)
104 _length = value.Length;
105 _str = _cached_str = value;
108 public StringBuilder( string value, int capacity) : this(value, 0, value.Length, capacity) {}
110 public int MaxCapacity {
112 // MS runtime always returns Int32.MaxValue.
117 public int Capacity {
119 if (_str.Length == 0)
120 return constDefaultCapacity;
127 throw new ArgumentException( "Capacity must be larger than length" );
129 InternalEnsureCapacity(value);
139 if( value < 0 || value > _maxCapacity)
140 throw new ArgumentOutOfRangeException();
142 if (value == _length)
145 if (value < _length) {
146 // LAMESPEC: The spec is unclear as to what to do
147 // with the capacity when truncating the string.
149 // Do as MS, keep the capacity
151 // Make sure that we invalidate any cached string.
152 InternalEnsureCapacity (value);
155 // Expand the capacity to the new length and
156 // pad the string with spaces.
158 // LAMESPEC: The spec says to put the spaces on the
159 // left of the string however the MS implementation
160 // puts them on the right. We'll do that for
162 Append(' ', value - _length);
167 [IndexerName("Chars")]
168 public char this [int index] {
170 if (index >= _length || index < 0)
171 throw new IndexOutOfRangeException();
177 if (index >= _length || index < 0)
178 throw new IndexOutOfRangeException();
180 if (null != _cached_str)
181 InternalEnsureCapacity (_length);
183 _str.InternalSetChar (index, value);
187 public override string ToString ()
192 if (null != _cached_str)
195 // If we only have a half-full buffer we return a new string.
196 if (_length < (_str.Length >> 1))
198 _cached_str = _str.Substring(0, _length);
203 _str.InternalSetLength(_length);
208 public string ToString (int startIndex, int length)
210 // re-ordered to avoid possible integer overflow
211 if (startIndex < 0 || length < 0 || startIndex > _length - length)
212 throw new ArgumentOutOfRangeException();
214 return _str.Substring (startIndex, length);
217 public int EnsureCapacity (int capacity)
220 throw new ArgumentOutOfRangeException ("Capacity must be greater than 0." );
222 if( capacity <= _str.Length )
225 InternalEnsureCapacity (capacity);
230 public bool Equals (StringBuilder sb)
232 if (_length == sb.Length && _str == sb._str )
238 public StringBuilder Remove (int startIndex, int length)
240 // re-ordered to avoid possible integer overflow
241 if (startIndex < 0 || length < 0 || startIndex > _length - length)
242 throw new ArgumentOutOfRangeException();
244 if (null != _cached_str)
245 InternalEnsureCapacity (_length);
247 // Copy everything after the 'removed' part to the start
248 // of the removed part and truncate the sLength
249 if (_length - (startIndex + length) > 0)
250 String.InternalStrcpy (_str, startIndex, _str, startIndex + length, _length - (startIndex + length));
257 public StringBuilder Replace (char oldChar, char newChar)
259 return Replace( oldChar, newChar, 0, _length);
262 public StringBuilder Replace (char oldChar, char newChar, int startIndex, int count)
264 // re-ordered to avoid possible integer overflow
265 if (startIndex > _length - count || startIndex < 0 || count < 0)
266 throw new ArgumentOutOfRangeException();
268 if (null != _cached_str)
269 InternalEnsureCapacity (_str.Length);
271 for (int replaceIterate = startIndex; replaceIterate < startIndex + count; replaceIterate++ ) {
272 if( _str [replaceIterate] == oldChar )
273 _str.InternalSetChar (replaceIterate, newChar);
279 public StringBuilder Replace( string oldValue, string newValue ) {
280 return Replace (oldValue, newValue, 0, _length);
283 public StringBuilder Replace( string oldValue, string newValue, int startIndex, int count )
285 if (oldValue == null)
286 throw new ArgumentNullException ("The old value cannot be null.");
288 if (startIndex < 0 || count < 0 || startIndex > _length - count)
289 throw new ArgumentOutOfRangeException ();
291 if (oldValue.Length == 0)
292 throw new ArgumentException ("The old value cannot be zero length.");
295 string replace = _str.Substring(startIndex, count).Replace(oldValue, newValue);
297 InternalEnsureCapacity (replace.Length + (_length - count));
299 String.InternalStrcpy (_str, startIndex, replace);
301 _length = replace.Length + (_length - count);
307 /* The Append Methods */
308 public StringBuilder Append (char[] value)
313 int needed_cap = _length + value.Length;
314 if (null != _cached_str || _str.Length < needed_cap)
315 InternalEnsureCapacity (needed_cap);
317 String.InternalStrcpy (_str, _length, value);
318 _length += value.Length;
323 public StringBuilder Append (string value)
328 if (_length == 0 && value.Length < _maxCapacity && value.Length > _str.Length) {
329 _length = value.Length;
330 _str = _cached_str = value;
334 int needed_cap = _length + value.Length;
335 if (null != _cached_str || _str.Length < needed_cap)
336 InternalEnsureCapacity (needed_cap);
338 String.InternalStrcpy (_str, _length, value);
339 _length += value.Length;
343 public StringBuilder Append (bool value) {
344 return Append (value.ToString());
347 public StringBuilder Append (byte value) {
348 return Append (value.ToString());
351 public StringBuilder Append (decimal value) {
352 return Append (value.ToString());
355 public StringBuilder Append (double value) {
356 return Append (value.ToString());
359 public StringBuilder Append (short value) {
360 return Append (value.ToString());
363 public StringBuilder Append (int value) {
364 return Append (value.ToString());
367 public StringBuilder Append (long value) {
368 return Append (value.ToString());
371 public StringBuilder Append (object value) {
375 return Append (value.ToString());
378 [CLSCompliant(false)]
379 public StringBuilder Append (sbyte value) {
380 return Append (value.ToString());
383 public StringBuilder Append (float value) {
384 return Append (value.ToString());
387 [CLSCompliant(false)]
388 public StringBuilder Append (ushort value) {
389 return Append (value.ToString());
392 [CLSCompliant(false)]
393 public StringBuilder Append (uint value) {
394 return Append (value.ToString());
397 [CLSCompliant(false)]
398 public StringBuilder Append (ulong value) {
399 return Append (value.ToString());
402 public StringBuilder Append (char value)
404 int needed_cap = _length + 1;
405 if (null != _cached_str || _str.Length < needed_cap)
406 InternalEnsureCapacity (needed_cap);
408 _str.InternalSetChar(_length, value);
414 public StringBuilder Append (char value, int repeatCount)
416 if( repeatCount < 0 )
417 throw new ArgumentOutOfRangeException();
419 InternalEnsureCapacity (_length + repeatCount);
421 for (int i = 0; i < repeatCount; i++)
422 _str.InternalSetChar (_length++, value);
427 public StringBuilder Append( char[] value, int startIndex, int charCount )
430 if (!(startIndex == 0 && charCount == 0))
431 throw new ArgumentNullException ("value");
436 if ((charCount < 0 || startIndex < 0) || (startIndex > value.Length - charCount))
437 throw new ArgumentOutOfRangeException();
440 InternalEnsureCapacity (_length + charCount);
442 String.InternalStrcpy (_str, _length, value, startIndex, charCount);
443 _length += charCount;
448 public StringBuilder Append (string value, int startIndex, int count)
451 if (startIndex != 0 && count != 0)
452 throw new ArgumentNullException ("value");
457 if ((count < 0 || startIndex < 0) || (startIndex > value.Length - count))
458 throw new ArgumentOutOfRangeException();
460 int needed_cap = _length + count;
461 if (null != _cached_str || _str.Length < needed_cap)
462 InternalEnsureCapacity (needed_cap);
464 String.InternalStrcpy (_str, _length, value, startIndex, count);
472 public StringBuilder AppendLine ()
474 return Append (System.Environment.NewLine);
477 public StringBuilder AppendLine (string value)
479 return Append (value).Append (System.Environment.NewLine);
483 public StringBuilder AppendFormat (string format, object arg0)
485 return AppendFormat (null, format, new object [] { arg0 });
488 public StringBuilder AppendFormat (string format, params object[] args)
490 return AppendFormat (null, format, args);
493 public StringBuilder AppendFormat (IFormatProvider provider,
495 params object[] args)
497 String.FormatHelper (this, provider, format, args);
501 public StringBuilder AppendFormat (string format, object arg0, object arg1)
503 return AppendFormat (null, format, new object [] { arg0, arg1 });
506 public StringBuilder AppendFormat (string format, object arg0, object arg1, object arg2)
508 return AppendFormat (null, format, new object [] { arg0, arg1, arg2 });
511 /* The Insert Functions */
513 public StringBuilder Insert (int index, char[] value)
515 return Insert (index, new string (value));
518 public StringBuilder Insert (int index, string value)
520 if( index > _length || index < 0)
521 throw new ArgumentOutOfRangeException();
523 if (value == null || value.Length == 0)
526 InternalEnsureCapacity (_length + value.Length);
528 // Move everything to the right of the insert point across
529 String.InternalStrcpy (_str, index + value.Length, _str, index, _length - index);
531 // Copy in stuff from the insert buffer
532 String.InternalStrcpy (_str, index, value);
534 _length += value.Length;
539 public StringBuilder Insert( int index, bool value ) {
540 return Insert (index, value.ToString());
543 public StringBuilder Insert( int index, byte value ) {
544 return Insert (index, value.ToString());
547 public StringBuilder Insert( int index, char value)
549 if (index > _length || index < 0)
550 throw new ArgumentOutOfRangeException ("index");
552 InternalEnsureCapacity (_length + 1);
554 // Move everything to the right of the insert point across
555 String.InternalStrcpy (_str, index + 1, _str, index, _length - index);
557 _str.InternalSetChar (index, value);
563 public StringBuilder Insert( int index, decimal value ) {
564 return Insert (index, value.ToString());
567 public StringBuilder Insert( int index, double value ) {
568 return Insert (index, value.ToString());
571 public StringBuilder Insert( int index, short value ) {
572 return Insert (index, value.ToString());
575 public StringBuilder Insert( int index, int value ) {
576 return Insert (index, value.ToString());
579 public StringBuilder Insert( int index, long value ) {
580 return Insert (index, value.ToString());
583 public StringBuilder Insert( int index, object value ) {
584 return Insert (index, value.ToString());
587 [CLSCompliant(false)]
588 public StringBuilder Insert( int index, sbyte value ) {
589 return Insert (index, value.ToString() );
592 public StringBuilder Insert (int index, float value) {
593 return Insert (index, value.ToString() );
596 [CLSCompliant(false)]
597 public StringBuilder Insert (int index, ushort value) {
598 return Insert (index, value.ToString() );
601 [CLSCompliant(false)]
602 public StringBuilder Insert (int index, uint value) {
603 return Insert ( index, value.ToString() );
606 [CLSCompliant(false)]
607 public StringBuilder Insert (int index, ulong value) {
608 return Insert ( index, value.ToString() );
611 public StringBuilder Insert (int index, string value, int count)
613 // LAMESPEC: The spec says to throw an exception if
614 // count < 0, while MS throws even for count < 1!
616 throw new ArgumentOutOfRangeException();
618 if (value != null && value != String.Empty)
619 for (int insertCount = 0; insertCount < count; insertCount++)
620 Insert( index, value );
625 public StringBuilder Insert (int index, char [] value, int startIndex, int charCount)
628 if (startIndex == 0 && charCount == 0)
631 throw new ArgumentNullException ("value");
634 if (charCount < 0 || startIndex < 0 || startIndex > value.Length - charCount)
635 throw new ArgumentOutOfRangeException ();
637 return Insert (index, new String (value, startIndex, charCount));
640 private void InternalEnsureCapacity (int size)
642 if (size > _str.Length || _cached_str == _str)
644 int capacity = _str.Length;
646 // Try double buffer, if that doesn't work, set the length as capacity
650 // The first time a string is appended, we just set _cached_str
651 // and _str to it. This allows us to do some optimizations.
652 // Below, we take this into account.
653 if (_cached_str == _str && capacity < constDefaultCapacity)
654 capacity = constDefaultCapacity;
656 capacity = capacity << 1;
660 if (capacity >= Int32.MaxValue || capacity < 0)
661 capacity = Int32.MaxValue;
663 if (capacity > _maxCapacity)
664 throw new ArgumentOutOfRangeException ("size", "capacity was less than the current size.");
667 string tmp = String.InternalAllocateStr (capacity);
669 String.InternalStrcpy (tmp, 0, _str, 0, _length);