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.Serialization;
40 using System.Runtime.CompilerServices;
41 using System.Runtime.InteropServices;
43 namespace System.Text {
47 [MonoLimitation ("Serialization format not compatible with .NET")]
48 public sealed class StringBuilder : ISerializable
52 private string _cached_str;
54 private int _maxCapacity;
55 private const int constDefaultCapacity = 16;
57 public StringBuilder(string value, int startIndex, int length, int capacity)
58 : this (value, startIndex, length, capacity, Int32.MaxValue)
62 private StringBuilder(string value, int startIndex, int length, int capacity, int maxCapacity)
64 // first, check the parameters and throw appropriate exceptions if needed
68 // make sure startIndex is zero or positive
70 throw new System.ArgumentOutOfRangeException ("startIndex", startIndex, "StartIndex cannot be less than zero.");
72 // make sure length is zero or positive
74 throw new System.ArgumentOutOfRangeException ("length", length, "Length cannot be less than zero.");
77 throw new System.ArgumentOutOfRangeException ("capacity", capacity, "capacity must be greater than zero.");
80 throw new System.ArgumentOutOfRangeException ("maxCapacity", "maxCapacity is less than one.");
81 if (capacity > maxCapacity)
82 throw new System.ArgumentOutOfRangeException ("capacity", "Capacity exceeds maximum capacity.");
84 // make sure startIndex and length give a valid substring of value
85 // re-ordered to avoid possible integer overflow
86 if (startIndex > value.Length - length)
87 throw new System.ArgumentOutOfRangeException ("startIndex", startIndex, "StartIndex and length must refer to a location within the string.");
90 if (maxCapacity > constDefaultCapacity)
91 capacity = constDefaultCapacity;
93 _str = _cached_str = String.Empty;
95 _maxCapacity = maxCapacity;
98 _str = String.InternalAllocateStr ((length > capacity) ? length : capacity);
100 String.CharCopy (_str, 0, value, startIndex, length);
105 public StringBuilder () : this (null) {}
107 public StringBuilder(int capacity) : this (String.Empty, 0, 0, capacity) {}
109 public StringBuilder(int capacity, int maxCapacity) : this (String.Empty, 0, 0, capacity, maxCapacity) { }
111 public StringBuilder (string value)
114 * This is an optimization to avoid allocating the internal string
115 * until the first Append () call.
116 * The runtime pinvoke marshalling code needs to be aware of this.
121 _length = value.Length;
122 _str = _cached_str = value;
123 _maxCapacity = Int32.MaxValue;
126 public StringBuilder( string value, int capacity) : this(value == null ? "" : value, 0, value == null ? 0 : value.Length, capacity) {}
128 public int MaxCapacity {
134 public int Capacity {
136 if (_str.Length == 0)
137 return Math.Min (_maxCapacity, constDefaultCapacity);
144 throw new ArgumentException( "Capacity must be larger than length" );
146 if (value > _maxCapacity)
147 throw new ArgumentOutOfRangeException ("value", "Should be less than or equal to MaxCapacity");
149 InternalEnsureCapacity(value);
159 if( value < 0 || value > _maxCapacity)
160 throw new ArgumentOutOfRangeException();
162 if (value == _length)
165 if (value < _length) {
166 // LAMESPEC: The spec is unclear as to what to do
167 // with the capacity when truncating the string.
169 // Do as MS, keep the capacity
171 // Make sure that we invalidate any cached string.
172 InternalEnsureCapacity (value);
175 // Expand the capacity to the new length and
176 // pad the string with NULL characters.
177 Append('\0', value - _length);
182 [IndexerName("Chars")]
183 public char this [int index] {
185 if (index >= _length || index < 0)
186 throw new IndexOutOfRangeException();
192 if (index >= _length || index < 0)
193 throw new IndexOutOfRangeException();
195 if (null != _cached_str)
196 InternalEnsureCapacity (_length);
198 _str.InternalSetChar (index, value);
202 public override string ToString ()
207 if (null != _cached_str)
210 // If we only have a half-full buffer we return a new string.
211 if (_length < (_str.Length >> 1) || (_str.Length > string.LOS_limit && _length <= string.LOS_limit))
213 // use String.SubstringUnchecked instead of String.Substring
214 // as the former is guaranteed to create a new string object
215 _cached_str = _str.SubstringUnchecked (0, _length);
220 _str.InternalSetLength(_length);
225 public string ToString (int startIndex, int length)
227 // re-ordered to avoid possible integer overflow
228 if (startIndex < 0 || length < 0 || startIndex > _length - length)
229 throw new ArgumentOutOfRangeException();
231 // use String.SubstringUnchecked instead of String.Substring
232 // as the former is guaranteed to create a new string object
233 if (startIndex == 0 && length == _length)
236 return _str.SubstringUnchecked (startIndex, length);
239 public int EnsureCapacity (int capacity)
242 throw new ArgumentOutOfRangeException ("Capacity must be greater than 0." );
244 if( capacity <= _str.Length )
247 InternalEnsureCapacity (capacity);
252 public bool Equals (StringBuilder sb)
254 if (((object)sb) == null)
257 if (_length == sb.Length && _str == sb._str )
263 public StringBuilder Remove (int startIndex, int length)
265 // re-ordered to avoid possible integer overflow
266 if (startIndex < 0 || length < 0 || startIndex > _length - length)
267 throw new ArgumentOutOfRangeException();
269 if (null != _cached_str)
270 InternalEnsureCapacity (_length);
272 // Copy everything after the 'removed' part to the start
273 // of the removed part and truncate the sLength
274 if (_length - (startIndex + length) > 0)
275 String.CharCopy (_str, startIndex, _str, startIndex + length, _length - (startIndex + length));
282 public StringBuilder Replace (char oldChar, char newChar)
284 return Replace( oldChar, newChar, 0, _length);
287 public StringBuilder Replace (char oldChar, char newChar, int startIndex, int count)
289 // re-ordered to avoid possible integer overflow
290 if (startIndex > _length - count || startIndex < 0 || count < 0)
291 throw new ArgumentOutOfRangeException();
293 if (null != _cached_str)
294 InternalEnsureCapacity (_str.Length);
296 for (int replaceIterate = startIndex; replaceIterate < startIndex + count; replaceIterate++ ) {
297 if( _str [replaceIterate] == oldChar )
298 _str.InternalSetChar (replaceIterate, newChar);
304 public StringBuilder Replace( string oldValue, string newValue ) {
305 return Replace (oldValue, newValue, 0, _length);
308 public StringBuilder Replace( string oldValue, string newValue, int startIndex, int count )
310 if (oldValue == null)
311 throw new ArgumentNullException ("The old value cannot be null.");
313 if (startIndex < 0 || count < 0 || startIndex > _length - count)
314 throw new ArgumentOutOfRangeException ();
316 if (oldValue.Length == 0)
317 throw new ArgumentException ("The old value cannot be zero length.");
319 string substr = _str.Substring(startIndex, count);
320 string replace = substr.Replace(oldValue, newValue);
321 // return early if no oldValue was found
322 if ((object) replace == (object) substr)
325 InternalEnsureCapacity (replace.Length + (_length - count));
328 if (replace.Length < count)
329 String.CharCopy (_str, startIndex + replace.Length, _str, startIndex + count, _length - startIndex - count);
330 else if (replace.Length > count)
331 String.CharCopyReverse (_str, startIndex + replace.Length, _str, startIndex + count, _length - startIndex - count);
333 // copy middle part back into _str
334 String.CharCopy (_str, startIndex, replace, 0, replace.Length);
336 _length = replace.Length + (_length - count);
342 /* The Append Methods */
343 public StringBuilder Append (char[] value)
348 int needed_cap = _length + value.Length;
349 if (null != _cached_str || _str.Length < needed_cap)
350 InternalEnsureCapacity (needed_cap);
352 String.CharCopy (_str, _length, value, 0, value.Length);
353 _length = needed_cap;
358 public StringBuilder Append (string value)
363 if (_length == 0 && value.Length < _maxCapacity && value.Length > _str.Length) {
364 _length = value.Length;
365 _str = _cached_str = value;
369 int needed_cap = _length + value.Length;
370 if (null != _cached_str || _str.Length < needed_cap)
371 InternalEnsureCapacity (needed_cap);
373 String.CharCopy (_str, _length, value, 0, value.Length);
374 _length = needed_cap;
378 public StringBuilder Append (bool value) {
379 return Append (value.ToString());
382 public StringBuilder Append (byte value) {
383 return Append (value.ToString());
386 public StringBuilder Append (decimal value) {
387 return Append (value.ToString());
390 public StringBuilder Append (double value) {
391 return Append (value.ToString());
394 public StringBuilder Append (short value) {
395 return Append (value.ToString());
398 public StringBuilder Append (int value) {
399 return Append (value.ToString());
402 public StringBuilder Append (long value) {
403 return Append (value.ToString());
406 public StringBuilder Append (object value) {
410 return Append (value.ToString());
413 [CLSCompliant(false)]
414 public StringBuilder Append (sbyte value) {
415 return Append (value.ToString());
418 public StringBuilder Append (float value) {
419 return Append (value.ToString());
422 [CLSCompliant(false)]
423 public StringBuilder Append (ushort value) {
424 return Append (value.ToString());
427 [CLSCompliant(false)]
428 public StringBuilder Append (uint value) {
429 return Append (value.ToString());
432 [CLSCompliant(false)]
433 public StringBuilder Append (ulong value) {
434 return Append (value.ToString());
437 public StringBuilder Append (char value)
439 int needed_cap = _length + 1;
440 if (null != _cached_str || _str.Length < needed_cap)
441 InternalEnsureCapacity (needed_cap);
443 _str.InternalSetChar(_length, value);
444 _length = needed_cap;
449 public StringBuilder Append (char value, int repeatCount)
451 if( repeatCount < 0 )
452 throw new ArgumentOutOfRangeException();
454 InternalEnsureCapacity (_length + repeatCount);
456 for (int i = 0; i < repeatCount; i++)
457 _str.InternalSetChar (_length++, value);
462 public StringBuilder Append( char[] value, int startIndex, int charCount )
465 if (!(startIndex == 0 && charCount == 0))
466 throw new ArgumentNullException ("value");
471 if ((charCount < 0 || startIndex < 0) || (startIndex > value.Length - charCount))
472 throw new ArgumentOutOfRangeException();
474 int needed_cap = _length + charCount;
475 InternalEnsureCapacity (needed_cap);
477 String.CharCopy (_str, _length, value, startIndex, charCount);
478 _length = needed_cap;
483 public StringBuilder Append (string value, int startIndex, int count)
486 if (startIndex != 0 && count != 0)
487 throw new ArgumentNullException ("value");
492 if ((count < 0 || startIndex < 0) || (startIndex > value.Length - count))
493 throw new ArgumentOutOfRangeException();
495 int needed_cap = _length + count;
496 if (null != _cached_str || _str.Length < needed_cap)
497 InternalEnsureCapacity (needed_cap);
499 String.CharCopy (_str, _length, value, startIndex, count);
501 _length = needed_cap;
506 #if NET_4_0 || MOONLIGHT || MOBILE
507 public StringBuilder Clear ()
515 public StringBuilder AppendLine ()
517 return Append (System.Environment.NewLine);
521 public StringBuilder AppendLine (string value)
523 return Append (value).Append (System.Environment.NewLine);
526 public StringBuilder AppendFormat (string format, params object[] args)
528 return AppendFormat (null, format, args);
531 public StringBuilder AppendFormat (IFormatProvider provider,
533 params object[] args)
535 String.FormatHelper (this, provider, format, args);
544 StringBuilder AppendFormat (string format, object arg0)
546 return AppendFormat (null, format, new object [] { arg0 });
554 StringBuilder AppendFormat (string format, object arg0, object arg1)
556 return AppendFormat (null, format, new object [] { arg0, arg1 });
564 StringBuilder AppendFormat (string format, object arg0, object arg1, object arg2)
566 return AppendFormat (null, format, new object [] { arg0, arg1, arg2 });
569 /* The Insert Functions */
571 public StringBuilder Insert (int index, char[] value)
573 return Insert (index, new string (value));
576 public StringBuilder Insert (int index, string value)
578 if( index > _length || index < 0)
579 throw new ArgumentOutOfRangeException();
581 if (value == null || value.Length == 0)
584 InternalEnsureCapacity (_length + value.Length);
586 // Move everything to the right of the insert point across
587 String.CharCopyReverse (_str, index + value.Length, _str, index, _length - index);
589 // Copy in stuff from the insert buffer
590 String.CharCopy (_str, index, value, 0, value.Length);
592 _length += value.Length;
597 public StringBuilder Insert( int index, bool value ) {
598 return Insert (index, value.ToString());
601 public StringBuilder Insert( int index, byte value ) {
602 return Insert (index, value.ToString());
605 public StringBuilder Insert( int index, char value)
607 if (index > _length || index < 0)
608 throw new ArgumentOutOfRangeException ("index");
610 InternalEnsureCapacity (_length + 1);
612 // Move everything to the right of the insert point across
613 String.CharCopyReverse (_str, index + 1, _str, index, _length - index);
615 _str.InternalSetChar (index, value);
621 public StringBuilder Insert( int index, decimal value ) {
622 return Insert (index, value.ToString());
625 public StringBuilder Insert( int index, double value ) {
626 return Insert (index, value.ToString());
629 public StringBuilder Insert( int index, short value ) {
630 return Insert (index, value.ToString());
633 public StringBuilder Insert( int index, int value ) {
634 return Insert (index, value.ToString());
637 public StringBuilder Insert( int index, long value ) {
638 return Insert (index, value.ToString());
641 public StringBuilder Insert( int index, object value ) {
642 return Insert (index, value.ToString());
645 [CLSCompliant(false)]
646 public StringBuilder Insert( int index, sbyte value ) {
647 return Insert (index, value.ToString() );
650 public StringBuilder Insert (int index, float value) {
651 return Insert (index, value.ToString() );
654 [CLSCompliant(false)]
655 public StringBuilder Insert (int index, ushort value) {
656 return Insert (index, value.ToString() );
659 [CLSCompliant(false)]
660 public StringBuilder Insert (int index, uint value) {
661 return Insert ( index, value.ToString() );
664 [CLSCompliant(false)]
665 public StringBuilder Insert (int index, ulong value) {
666 return Insert ( index, value.ToString() );
669 public StringBuilder Insert (int index, string value, int count)
671 // LAMESPEC: The spec says to throw an exception if
672 // count < 0, while MS throws even for count < 1!
674 throw new ArgumentOutOfRangeException();
676 if (value != null && value != String.Empty)
677 for (int insertCount = 0; insertCount < count; insertCount++)
678 Insert( index, value );
683 public StringBuilder Insert (int index, char [] value, int startIndex, int charCount)
686 if (startIndex == 0 && charCount == 0)
689 throw new ArgumentNullException ("value");
692 if (charCount < 0 || startIndex < 0 || startIndex > value.Length - charCount)
693 throw new ArgumentOutOfRangeException ();
695 return Insert (index, new String (value, startIndex, charCount));
698 private void InternalEnsureCapacity (int size)
700 if (size > _str.Length || (object) _cached_str == (object) _str) {
701 int capacity = _str.Length;
703 // Try double buffer, if that doesn't work, set the length as capacity
704 if (size > capacity) {
706 // The first time a string is appended, we just set _cached_str
707 // and _str to it. This allows us to do some optimizations.
708 // Below, we take this into account.
709 if ((object) _cached_str == (object) _str && capacity < constDefaultCapacity)
710 capacity = constDefaultCapacity;
712 capacity = capacity << 1;
716 if (capacity >= Int32.MaxValue || capacity < 0)
717 capacity = Int32.MaxValue;
719 if (capacity > _maxCapacity && size <= _maxCapacity)
720 capacity = _maxCapacity;
722 if (capacity > _maxCapacity)
723 throw new ArgumentOutOfRangeException ("size", "capacity was less than the current size.");
726 string tmp = String.InternalAllocateStr (capacity);
728 String.CharCopy (tmp, 0, _str, 0, _length);
737 public void CopyTo (int sourceIndex, char [] destination, int destinationIndex, int count)
739 if (destination == null)
740 throw new ArgumentNullException ("destination");
741 if ((Length - count < sourceIndex) ||
742 (destination.Length -count < destinationIndex) ||
743 (sourceIndex < 0 || destinationIndex < 0 || count < 0))
744 throw new ArgumentOutOfRangeException ();
746 for (int i = 0; i < count; i++)
747 destination [destinationIndex+i] = _str [sourceIndex+i];
750 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
752 info.AddValue ("m_MaxCapacity", _maxCapacity);
753 info.AddValue ("Capacity", Capacity);
754 info.AddValue ("m_StringValue", ToString ());
755 info.AddValue ("m_currentThread", 0);
758 StringBuilder (SerializationInfo info, StreamingContext context)
760 string s = info.GetString ("m_StringValue");
764 _str = _cached_str = s;
766 _maxCapacity = info.GetInt32 ("m_MaxCapacity");
767 if (_maxCapacity < 0)
768 _maxCapacity = Int32.MaxValue;
769 Capacity = info.GetInt32 ("Capacity");