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;
42 namespace System.Text {
45 [MonoTODO ("Fix serialization compatibility with MS.NET")]
46 public sealed class StringBuilder
53 private string _cached_str;
55 private int _maxCapacity = Int32.MaxValue;
56 private const int constDefaultCapacity = 16;
58 public StringBuilder(string value, int startIndex, int length, int capacity)
60 // first, check the parameters and throw appropriate exceptions if needed
64 // make sure startIndex is zero or positive
66 throw new System.ArgumentOutOfRangeException ("startIndex", startIndex, "StartIndex cannot be less than zero.");
68 // make sure length is zero or positive
70 throw new System.ArgumentOutOfRangeException ("length", length, "Length cannot be less than zero.");
73 throw new System.ArgumentOutOfRangeException ("capacity", capacity, "capacity must be greater than zero.");
75 // make sure startIndex and length give a valid substring of value
76 // re-ordered to avoid possible integer overflow
77 if (startIndex > value.Length - length)
78 throw new System.ArgumentOutOfRangeException ("startIndex", startIndex, "StartIndex and length must refer to a location within the string.");
81 capacity = constDefaultCapacity;
83 _str = String.InternalAllocateStr ((length > capacity) ? length : capacity);
85 String.InternalStrcpy(_str, 0, value, startIndex, length);
90 public StringBuilder () : this (null) {}
92 public StringBuilder(int capacity) : this (String.Empty, 0, 0, capacity) {}
94 public StringBuilder(int capacity, int maxCapacity) : this (String.Empty, 0, 0, capacity) {
96 throw new System.ArgumentOutOfRangeException ("maxCapacity", "maxCapacity is less than one.");
97 if (capacity > maxCapacity)
98 throw new System.ArgumentOutOfRangeException ("capacity", "Capacity exceeds maximum capacity.");
100 _maxCapacity = maxCapacity;
103 public StringBuilder (string value)
108 _length = value.Length;
109 _str = _cached_str = value;
112 public StringBuilder( string value, int capacity) : this(value, 0, value.Length, capacity) {}
114 public int MaxCapacity {
116 // MS runtime always returns Int32.MaxValue.
121 public int Capacity {
123 if (_str.Length == 0)
124 return constDefaultCapacity;
131 throw new ArgumentException( "Capacity must be larger than length" );
133 InternalEnsureCapacity(value);
143 if( value < 0 || value > _maxCapacity)
144 throw new ArgumentOutOfRangeException();
146 if (value == _length)
149 if (value < _length) {
150 // LAMESPEC: The spec is unclear as to what to do
151 // with the capacity when truncating the string.
153 // Do as MS, keep the capacity
155 // Make sure that we invalidate any cached string.
156 InternalEnsureCapacity (value);
159 // Expand the capacity to the new length and
160 // pad the string with spaces.
162 // LAMESPEC: The spec says to put the spaces on the
163 // left of the string however the MS implementation
164 // puts them on the right. We'll do that for
166 Append(' ', value - _length);
171 [IndexerName("Chars")]
172 public char this [int index] {
174 if (index >= _length || index < 0)
175 throw new IndexOutOfRangeException();
181 if (index >= _length || index < 0)
182 throw new IndexOutOfRangeException();
184 if (null != _cached_str)
185 InternalEnsureCapacity (_length);
187 _str.InternalSetChar (index, value);
191 public override string ToString ()
196 if (null != _cached_str)
199 // If we only have a half-full buffer we return a new string.
200 if (_length < (_str.Length >> 1))
202 _cached_str = _str.Substring(0, _length);
207 _str.InternalSetLength(_length);
212 public string ToString (int startIndex, int length)
214 // re-ordered to avoid possible integer overflow
215 if (startIndex < 0 || length < 0 || startIndex > _length - length)
216 throw new ArgumentOutOfRangeException();
218 return _str.Substring (startIndex, length);
221 public int EnsureCapacity (int capacity)
224 throw new ArgumentOutOfRangeException ("Capacity must be greater than 0." );
226 if( capacity <= _str.Length )
229 InternalEnsureCapacity (capacity);
234 public bool Equals (StringBuilder sb)
236 if (_length == sb.Length && _str == sb._str )
242 public StringBuilder Remove (int startIndex, int length)
244 // re-ordered to avoid possible integer overflow
245 if (startIndex < 0 || length < 0 || startIndex > _length - length)
246 throw new ArgumentOutOfRangeException();
248 if (null != _cached_str)
249 InternalEnsureCapacity (_length);
251 // Copy everything after the 'removed' part to the start
252 // of the removed part and truncate the sLength
253 if (_length - (startIndex + length) > 0)
254 String.InternalStrcpy (_str, startIndex, _str, startIndex + length, _length - (startIndex + length));
261 public StringBuilder Replace (char oldChar, char newChar)
263 return Replace( oldChar, newChar, 0, _length);
266 public StringBuilder Replace (char oldChar, char newChar, int startIndex, int count)
268 // re-ordered to avoid possible integer overflow
269 if (startIndex > _length - count || startIndex < 0 || count < 0)
270 throw new ArgumentOutOfRangeException();
272 if (null != _cached_str)
273 InternalEnsureCapacity (_str.Length);
275 for (int replaceIterate = startIndex; replaceIterate < startIndex + count; replaceIterate++ ) {
276 if( _str [replaceIterate] == oldChar )
277 _str.InternalSetChar (replaceIterate, newChar);
283 public StringBuilder Replace( string oldValue, string newValue ) {
284 return Replace (oldValue, newValue, 0, _length);
287 public StringBuilder Replace( string oldValue, string newValue, int startIndex, int count )
289 if (oldValue == null)
290 throw new ArgumentNullException ("The old value cannot be null.");
292 if (startIndex < 0 || count < 0 || startIndex > _length - count)
293 throw new ArgumentOutOfRangeException ();
295 if (oldValue.Length == 0)
296 throw new ArgumentException ("The old value cannot be zero length.");
299 string replace = _str.Substring(startIndex, count).Replace(oldValue, newValue);
301 InternalEnsureCapacity (replace.Length + (_length - count));
303 string end = _str.Substring (startIndex + count, _length - startIndex - count );
305 String.InternalStrcpy (_str, startIndex, replace);
306 String.InternalStrcpy (_str, startIndex + replace.Length, end);
308 _length = replace.Length + (_length - count);
314 /* The Append Methods */
315 public StringBuilder Append (char[] value)
320 int needed_cap = _length + value.Length;
321 if (null != _cached_str || _str.Length < needed_cap)
322 InternalEnsureCapacity (needed_cap);
324 String.InternalStrcpy (_str, _length, value);
325 _length += value.Length;
330 public StringBuilder Append (string value)
335 if (_length == 0 && value.Length < _maxCapacity && value.Length > _str.Length) {
336 _length = value.Length;
337 _str = _cached_str = value;
341 int needed_cap = _length + value.Length;
342 if (null != _cached_str || _str.Length < needed_cap)
343 InternalEnsureCapacity (needed_cap);
345 String.InternalStrcpy (_str, _length, value);
346 _length += value.Length;
350 public StringBuilder Append (bool value) {
351 return Append (value.ToString());
354 public StringBuilder Append (byte value) {
355 return Append (value.ToString());
358 public StringBuilder Append (decimal value) {
359 return Append (value.ToString());
362 public StringBuilder Append (double value) {
363 return Append (value.ToString());
366 public StringBuilder Append (short value) {
367 return Append (value.ToString());
370 public StringBuilder Append (int value) {
371 return Append (value.ToString());
374 public StringBuilder Append (long value) {
375 return Append (value.ToString());
378 public StringBuilder Append (object value) {
382 return Append (value.ToString());
385 [CLSCompliant(false)]
386 public StringBuilder Append (sbyte value) {
387 return Append (value.ToString());
390 public StringBuilder Append (float value) {
391 return Append (value.ToString());
394 [CLSCompliant(false)]
395 public StringBuilder Append (ushort value) {
396 return Append (value.ToString());
399 [CLSCompliant(false)]
400 public StringBuilder Append (uint value) {
401 return Append (value.ToString());
404 [CLSCompliant(false)]
405 public StringBuilder Append (ulong value) {
406 return Append (value.ToString());
409 public StringBuilder Append (char value)
411 int needed_cap = _length + 1;
412 if (null != _cached_str || _str.Length < needed_cap)
413 InternalEnsureCapacity (needed_cap);
415 _str.InternalSetChar(_length, value);
421 public StringBuilder Append (char value, int repeatCount)
423 if( repeatCount < 0 )
424 throw new ArgumentOutOfRangeException();
426 InternalEnsureCapacity (_length + repeatCount);
428 for (int i = 0; i < repeatCount; i++)
429 _str.InternalSetChar (_length++, value);
434 public StringBuilder Append( char[] value, int startIndex, int charCount )
437 if (!(startIndex == 0 && charCount == 0))
438 throw new ArgumentNullException ("value");
443 if ((charCount < 0 || startIndex < 0) || (startIndex > value.Length - charCount))
444 throw new ArgumentOutOfRangeException();
447 InternalEnsureCapacity (_length + charCount);
449 String.InternalStrcpy (_str, _length, value, startIndex, charCount);
450 _length += charCount;
455 public StringBuilder Append (string value, int startIndex, int count)
458 if (startIndex != 0 && count != 0)
459 throw new ArgumentNullException ("value");
464 if ((count < 0 || startIndex < 0) || (startIndex > value.Length - count))
465 throw new ArgumentOutOfRangeException();
467 int needed_cap = _length + count;
468 if (null != _cached_str || _str.Length < needed_cap)
469 InternalEnsureCapacity (needed_cap);
471 String.InternalStrcpy (_str, _length, value, startIndex, count);
479 public StringBuilder AppendLine ()
481 return Append (System.Environment.NewLine);
484 public StringBuilder AppendLine (string value)
486 return Append (value).Append (System.Environment.NewLine);
490 public StringBuilder AppendFormat (string format, object arg0)
492 return AppendFormat (null, format, new object [] { arg0 });
495 public StringBuilder AppendFormat (string format, params object[] args)
497 return AppendFormat (null, format, args);
500 public StringBuilder AppendFormat (IFormatProvider provider,
502 params object[] args)
504 String.FormatHelper (this, provider, format, args);
508 public StringBuilder AppendFormat (string format, object arg0, object arg1)
510 return AppendFormat (null, format, new object [] { arg0, arg1 });
513 public StringBuilder AppendFormat (string format, object arg0, object arg1, object arg2)
515 return AppendFormat (null, format, new object [] { arg0, arg1, arg2 });
518 /* The Insert Functions */
520 public StringBuilder Insert (int index, char[] value)
522 return Insert (index, new string (value));
525 public StringBuilder Insert (int index, string value)
527 if( index > _length || index < 0)
528 throw new ArgumentOutOfRangeException();
530 if (value == null || value.Length == 0)
533 InternalEnsureCapacity (_length + value.Length);
535 // Move everything to the right of the insert point across
536 String.InternalStrcpy (_str, index + value.Length, _str, index, _length - index);
538 // Copy in stuff from the insert buffer
539 String.InternalStrcpy (_str, index, value);
541 _length += value.Length;
546 public StringBuilder Insert( int index, bool value ) {
547 return Insert (index, value.ToString());
550 public StringBuilder Insert( int index, byte value ) {
551 return Insert (index, value.ToString());
554 public StringBuilder Insert( int index, char value)
556 if (index > _length || index < 0)
557 throw new ArgumentOutOfRangeException ("index");
559 InternalEnsureCapacity (_length + 1);
561 // Move everything to the right of the insert point across
562 String.InternalStrcpy (_str, index + 1, _str, index, _length - index);
564 _str.InternalSetChar (index, value);
570 public StringBuilder Insert( int index, decimal value ) {
571 return Insert (index, value.ToString());
574 public StringBuilder Insert( int index, double value ) {
575 return Insert (index, value.ToString());
578 public StringBuilder Insert( int index, short value ) {
579 return Insert (index, value.ToString());
582 public StringBuilder Insert( int index, int value ) {
583 return Insert (index, value.ToString());
586 public StringBuilder Insert( int index, long value ) {
587 return Insert (index, value.ToString());
590 public StringBuilder Insert( int index, object value ) {
591 return Insert (index, value.ToString());
594 [CLSCompliant(false)]
595 public StringBuilder Insert( int index, sbyte value ) {
596 return Insert (index, value.ToString() );
599 public StringBuilder Insert (int index, float value) {
600 return Insert (index, value.ToString() );
603 [CLSCompliant(false)]
604 public StringBuilder Insert (int index, ushort value) {
605 return Insert (index, value.ToString() );
608 [CLSCompliant(false)]
609 public StringBuilder Insert (int index, uint value) {
610 return Insert ( index, value.ToString() );
613 [CLSCompliant(false)]
614 public StringBuilder Insert (int index, ulong value) {
615 return Insert ( index, value.ToString() );
618 public StringBuilder Insert (int index, string value, int count)
620 // LAMESPEC: The spec says to throw an exception if
621 // count < 0, while MS throws even for count < 1!
623 throw new ArgumentOutOfRangeException();
625 if (value != null && value != String.Empty)
626 for (int insertCount = 0; insertCount < count; insertCount++)
627 Insert( index, value );
632 public StringBuilder Insert (int index, char [] value, int startIndex, int charCount)
635 if (startIndex == 0 && charCount == 0)
638 throw new ArgumentNullException ("value");
641 if (charCount < 0 || startIndex < 0 || startIndex > value.Length - charCount)
642 throw new ArgumentOutOfRangeException ();
644 return Insert (index, new String (value, startIndex, charCount));
647 private void InternalEnsureCapacity (int size)
649 if (size > _str.Length || (object) _cached_str == (object) _str) {
650 int capacity = _str.Length;
652 // Try double buffer, if that doesn't work, set the length as capacity
653 if (size > capacity) {
655 // The first time a string is appended, we just set _cached_str
656 // and _str to it. This allows us to do some optimizations.
657 // Below, we take this into account.
658 if ((object) _cached_str == (object) _str && capacity < constDefaultCapacity)
659 capacity = constDefaultCapacity;
661 capacity = capacity << 1;
665 if (capacity >= Int32.MaxValue || capacity < 0)
666 capacity = Int32.MaxValue;
668 if (capacity > _maxCapacity && size <= _maxCapacity)
669 capacity = _maxCapacity;
671 if (capacity > _maxCapacity)
672 throw new ArgumentOutOfRangeException ("size", "capacity was less than the current size.");
675 string tmp = String.InternalAllocateStr (capacity);
677 String.InternalStrcpy (tmp, 0, _str, 0, _length);
686 public void CopyTo (int sourceIndex, char [] destination, int destinationIndex, int count)
688 if (destination == null)
689 throw new ArgumentNullException ("destination");
690 if ((Length - count < sourceIndex) ||
691 (destination.Length -count < destinationIndex) ||
692 (sourceIndex < 0 || destinationIndex < 0 || count < 0))
693 throw new ArgumentOutOfRangeException ();
695 for (int i = 0; i < count; i++)
696 destination [destinationIndex+i] = _str [sourceIndex+i];
699 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
701 info.AddValue ("m_MaxCapacity", _maxCapacity);
702 info.AddValue ("Capacity", Capacity);
703 info.AddValue ("m_StringValue", ToString ());
704 info.AddValue ("m_currentThread", 0);
707 StringBuilder (SerializationInfo info, StreamingContext context)
709 string s = info.GetString ("m_StringValue");
713 _str = _cached_str = s;
715 _maxCapacity = info.GetInt32 ("m_MaxCapacity");
716 if (_maxCapacity < 0)
717 _maxCapacity = Int32.MaxValue;
718 Capacity = info.GetInt32 ("Capacity");