2006-11-16 Miguel de Icaza <miguel@novell.com>
[mono-project.git] / mcs / class / corlib / System.Text / StringBuilder.cs
blob4dcc0dd797d169935090b98bbc56b484d83fded4
1 // -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 //
3 // System.Text.StringBuilder
4 //
5 // Authors:
6 // Marcin Szczepanski (marcins@zipworld.com.au)
7 // Paolo Molaro (lupus@ximian.com)
8 // Patrik Torstensson
9 //
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:
27 //
28 // The above copyright notice and this permission notice shall be
29 // included in all copies or substantial portions of the Software.
30 //
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 {
45 [Serializable]
46 #if NET_2_0
47 [ComVisible (true)]
48 #endif
49 [MonoTODO ("Serialization format not compatible with .NET")]
50 public sealed class StringBuilder
51 #if NET_2_0
52 : ISerializable
53 #endif
55 private int _length;
56 private string _str;
57 private string _cached_str;
59 private int _maxCapacity = Int32.MaxValue;
60 private const int constDefaultCapacity = 16;
62 public StringBuilder(string value, int startIndex, int length, int capacity)
64 // first, check the parameters and throw appropriate exceptions if needed
65 if (null == value)
66 value = "";
68 // make sure startIndex is zero or positive
69 if (startIndex < 0)
70 throw new System.ArgumentOutOfRangeException ("startIndex", startIndex, "StartIndex cannot be less than zero.");
72 // make sure length is zero or positive
73 if(length < 0)
74 throw new System.ArgumentOutOfRangeException ("length", length, "Length cannot be less than zero.");
76 if (capacity < 0)
77 throw new System.ArgumentOutOfRangeException ("capacity", capacity, "capacity must be greater than zero.");
79 // make sure startIndex and length give a valid substring of value
80 // re-ordered to avoid possible integer overflow
81 if (startIndex > value.Length - length)
82 throw new System.ArgumentOutOfRangeException ("startIndex", startIndex, "StartIndex and length must refer to a location within the string.");
84 if (capacity == 0)
85 capacity = constDefaultCapacity;
87 _str = String.InternalAllocateStr ((length > capacity) ? length : capacity);
88 if (length > 0)
89 String.InternalStrcpy(_str, 0, value, startIndex, length);
91 _length = length;
94 public StringBuilder () : this (null) {}
96 public StringBuilder(int capacity) : this (String.Empty, 0, 0, capacity) {}
98 public StringBuilder(int capacity, int maxCapacity) : this (String.Empty, 0, 0, capacity) {
99 if (maxCapacity < 1)
100 throw new System.ArgumentOutOfRangeException ("maxCapacity", "maxCapacity is less than one.");
101 if (capacity > maxCapacity)
102 throw new System.ArgumentOutOfRangeException ("capacity", "Capacity exceeds maximum capacity.");
104 _maxCapacity = maxCapacity;
107 public StringBuilder (string value)
110 * This is an optimization to avoid allocating the internal string
111 * until the first Append () call.
112 * The runtime pinvoke marshalling code needs to be aware of this.
114 if (null == value)
115 value = "";
117 _length = value.Length;
118 _str = _cached_str = value;
121 public StringBuilder( string value, int capacity) : this(value, 0, value.Length, capacity) {}
123 public int MaxCapacity {
124 get {
125 // MS runtime always returns Int32.MaxValue.
126 return _maxCapacity;
130 public int Capacity {
131 get {
132 if (_str.Length == 0)
133 return constDefaultCapacity;
135 return _str.Length;
138 set {
139 if (value < _length)
140 throw new ArgumentException( "Capacity must be larger than length" );
142 InternalEnsureCapacity(value);
146 public int Length {
147 get {
148 return _length;
151 set {
152 if( value < 0 || value > _maxCapacity)
153 throw new ArgumentOutOfRangeException();
155 if (value == _length)
156 return;
158 if (value < _length) {
159 // LAMESPEC: The spec is unclear as to what to do
160 // with the capacity when truncating the string.
162 // Do as MS, keep the capacity
164 // Make sure that we invalidate any cached string.
165 InternalEnsureCapacity (value);
166 _length = value;
167 } else {
168 // Expand the capacity to the new length and
169 // pad the string with NULL characters.
170 Append('\0', value - _length);
175 [IndexerName("Chars")]
176 public char this [int index] {
177 get {
178 if (index >= _length || index < 0)
179 throw new IndexOutOfRangeException();
181 return _str [index];
184 set {
185 if (index >= _length || index < 0)
186 throw new IndexOutOfRangeException();
188 if (null != _cached_str)
189 InternalEnsureCapacity (_length);
191 _str.InternalSetChar (index, value);
195 public override string ToString ()
197 if (_length == 0)
198 return String.Empty;
200 if (null != _cached_str)
201 return _cached_str;
203 // If we only have a half-full buffer we return a new string.
204 if (_length < (_str.Length >> 1))
206 _cached_str = _str.Substring(0, _length);
207 return _cached_str;
210 _cached_str = _str;
211 _str.InternalSetLength(_length);
213 return _str;
216 public string ToString (int startIndex, int length)
218 // re-ordered to avoid possible integer overflow
219 if (startIndex < 0 || length < 0 || startIndex > _length - length)
220 throw new ArgumentOutOfRangeException();
222 return _str.Substring (startIndex, length);
225 public int EnsureCapacity (int capacity)
227 if (capacity < 0)
228 throw new ArgumentOutOfRangeException ("Capacity must be greater than 0." );
230 if( capacity <= _str.Length )
231 return _str.Length;
233 InternalEnsureCapacity (capacity);
235 return _str.Length;
238 public bool Equals (StringBuilder sb)
240 if (_length == sb.Length && _str == sb._str )
241 return true;
243 return false;
246 public StringBuilder Remove (int startIndex, int length)
248 // re-ordered to avoid possible integer overflow
249 if (startIndex < 0 || length < 0 || startIndex > _length - length)
250 throw new ArgumentOutOfRangeException();
252 if (null != _cached_str)
253 InternalEnsureCapacity (_length);
255 // Copy everything after the 'removed' part to the start
256 // of the removed part and truncate the sLength
257 if (_length - (startIndex + length) > 0)
258 String.InternalStrcpy (_str, startIndex, _str, startIndex + length, _length - (startIndex + length));
260 _length -= length;
262 return this;
265 public StringBuilder Replace (char oldChar, char newChar)
267 return Replace( oldChar, newChar, 0, _length);
270 public StringBuilder Replace (char oldChar, char newChar, int startIndex, int count)
272 // re-ordered to avoid possible integer overflow
273 if (startIndex > _length - count || startIndex < 0 || count < 0)
274 throw new ArgumentOutOfRangeException();
276 if (null != _cached_str)
277 InternalEnsureCapacity (_str.Length);
279 for (int replaceIterate = startIndex; replaceIterate < startIndex + count; replaceIterate++ ) {
280 if( _str [replaceIterate] == oldChar )
281 _str.InternalSetChar (replaceIterate, newChar);
284 return this;
287 public StringBuilder Replace( string oldValue, string newValue ) {
288 return Replace (oldValue, newValue, 0, _length);
291 public StringBuilder Replace( string oldValue, string newValue, int startIndex, int count )
293 if (oldValue == null)
294 throw new ArgumentNullException ("The old value cannot be null.");
296 if (startIndex < 0 || count < 0 || startIndex > _length - count)
297 throw new ArgumentOutOfRangeException ();
299 if (oldValue.Length == 0)
300 throw new ArgumentException ("The old value cannot be zero length.");
302 // TODO: OPTIMIZE!
303 string replace = _str.Substring(startIndex, count).Replace(oldValue, newValue);
305 InternalEnsureCapacity (replace.Length + (_length - count));
307 string end = _str.Substring (startIndex + count, _length - startIndex - count );
309 String.InternalStrcpy (_str, startIndex, replace);
310 String.InternalStrcpy (_str, startIndex + replace.Length, end);
312 _length = replace.Length + (_length - count);
314 return this;
318 /* The Append Methods */
319 public StringBuilder Append (char[] value)
321 if (value == null)
322 return this;
324 int needed_cap = _length + value.Length;
325 if (null != _cached_str || _str.Length < needed_cap)
326 InternalEnsureCapacity (needed_cap);
328 String.InternalStrcpy (_str, _length, value);
329 _length = needed_cap;
331 return this;
334 public StringBuilder Append (string value)
336 if (value == null)
337 return this;
339 if (_length == 0 && value.Length < _maxCapacity && value.Length > _str.Length) {
340 _length = value.Length;
341 _str = _cached_str = value;
342 return this;
345 int needed_cap = _length + value.Length;
346 if (null != _cached_str || _str.Length < needed_cap)
347 InternalEnsureCapacity (needed_cap);
349 String.InternalStrcpy (_str, _length, value);
350 _length = needed_cap;
351 return this;
354 public StringBuilder Append (bool value) {
355 return Append (value.ToString());
358 public StringBuilder Append (byte value) {
359 return Append (value.ToString());
362 public StringBuilder Append (decimal value) {
363 return Append (value.ToString());
366 public StringBuilder Append (double value) {
367 return Append (value.ToString());
370 public StringBuilder Append (short value) {
371 return Append (value.ToString());
374 public StringBuilder Append (int value) {
375 return Append (value.ToString());
378 public StringBuilder Append (long value) {
379 return Append (value.ToString());
382 public StringBuilder Append (object value) {
383 if (value == null)
384 return this;
386 return Append (value.ToString());
389 [CLSCompliant(false)]
390 public StringBuilder Append (sbyte value) {
391 return Append (value.ToString());
394 public StringBuilder Append (float value) {
395 return Append (value.ToString());
398 [CLSCompliant(false)]
399 public StringBuilder Append (ushort value) {
400 return Append (value.ToString());
403 [CLSCompliant(false)]
404 public StringBuilder Append (uint value) {
405 return Append (value.ToString());
408 [CLSCompliant(false)]
409 public StringBuilder Append (ulong value) {
410 return Append (value.ToString());
413 public StringBuilder Append (char value)
415 int needed_cap = _length + 1;
416 if (null != _cached_str || _str.Length < needed_cap)
417 InternalEnsureCapacity (needed_cap);
419 _str.InternalSetChar(_length, value);
420 _length = needed_cap;
422 return this;
425 public StringBuilder Append (char value, int repeatCount)
427 if( repeatCount < 0 )
428 throw new ArgumentOutOfRangeException();
430 InternalEnsureCapacity (_length + repeatCount);
432 for (int i = 0; i < repeatCount; i++)
433 _str.InternalSetChar (_length++, value);
435 return this;
438 public StringBuilder Append( char[] value, int startIndex, int charCount )
440 if (value == null) {
441 if (!(startIndex == 0 && charCount == 0))
442 throw new ArgumentNullException ("value");
444 return this;
447 if ((charCount < 0 || startIndex < 0) || (startIndex > value.Length - charCount))
448 throw new ArgumentOutOfRangeException();
450 int needed_cap = _length + charCount;
451 InternalEnsureCapacity (needed_cap);
453 String.InternalStrcpy (_str, _length, value, startIndex, charCount);
454 _length = needed_cap;
456 return this;
459 public StringBuilder Append (string value, int startIndex, int count)
461 if (value == null) {
462 if (startIndex != 0 && count != 0)
463 throw new ArgumentNullException ("value");
465 return this;
468 if ((count < 0 || startIndex < 0) || (startIndex > value.Length - count))
469 throw new ArgumentOutOfRangeException();
471 int needed_cap = _length + count;
472 if (null != _cached_str || _str.Length < needed_cap)
473 InternalEnsureCapacity (needed_cap);
475 String.InternalStrcpy (_str, _length, value, startIndex, count);
477 _length = needed_cap;
479 return this;
482 #if NET_2_0
483 [ComVisible (false)]
484 public StringBuilder AppendLine ()
486 return Append (System.Environment.NewLine);
489 [ComVisible (false)]
490 public StringBuilder AppendLine (string value)
492 return Append (value).Append (System.Environment.NewLine);
494 #endif
496 public StringBuilder AppendFormat (string format, object arg0)
498 return AppendFormat (null, format, new object [] { arg0 });
501 public StringBuilder AppendFormat (string format, params object[] args)
503 return AppendFormat (null, format, args);
506 public StringBuilder AppendFormat (IFormatProvider provider,
507 string format,
508 params object[] args)
510 String.FormatHelper (this, provider, format, args);
511 return this;
514 public StringBuilder AppendFormat (string format, object arg0, object arg1)
516 return AppendFormat (null, format, new object [] { arg0, arg1 });
519 public StringBuilder AppendFormat (string format, object arg0, object arg1, object arg2)
521 return AppendFormat (null, format, new object [] { arg0, arg1, arg2 });
524 /* The Insert Functions */
526 public StringBuilder Insert (int index, char[] value)
528 return Insert (index, new string (value));
531 public StringBuilder Insert (int index, string value)
533 if( index > _length || index < 0)
534 throw new ArgumentOutOfRangeException();
536 if (value == null || value.Length == 0)
537 return this;
539 InternalEnsureCapacity (_length + value.Length);
541 // Move everything to the right of the insert point across
542 String.InternalStrcpy (_str, index + value.Length, _str, index, _length - index);
544 // Copy in stuff from the insert buffer
545 String.InternalStrcpy (_str, index, value);
547 _length += value.Length;
549 return this;
552 public StringBuilder Insert( int index, bool value ) {
553 return Insert (index, value.ToString());
556 public StringBuilder Insert( int index, byte value ) {
557 return Insert (index, value.ToString());
560 public StringBuilder Insert( int index, char value)
562 if (index > _length || index < 0)
563 throw new ArgumentOutOfRangeException ("index");
565 InternalEnsureCapacity (_length + 1);
567 // Move everything to the right of the insert point across
568 String.InternalStrcpy (_str, index + 1, _str, index, _length - index);
570 _str.InternalSetChar (index, value);
571 _length++;
573 return this;
576 public StringBuilder Insert( int index, decimal value ) {
577 return Insert (index, value.ToString());
580 public StringBuilder Insert( int index, double value ) {
581 return Insert (index, value.ToString());
584 public StringBuilder Insert( int index, short value ) {
585 return Insert (index, value.ToString());
588 public StringBuilder Insert( int index, int value ) {
589 return Insert (index, value.ToString());
592 public StringBuilder Insert( int index, long value ) {
593 return Insert (index, value.ToString());
596 public StringBuilder Insert( int index, object value ) {
597 return Insert (index, value.ToString());
600 [CLSCompliant(false)]
601 public StringBuilder Insert( int index, sbyte value ) {
602 return Insert (index, value.ToString() );
605 public StringBuilder Insert (int index, float value) {
606 return Insert (index, value.ToString() );
609 [CLSCompliant(false)]
610 public StringBuilder Insert (int index, ushort value) {
611 return Insert (index, value.ToString() );
614 [CLSCompliant(false)]
615 public StringBuilder Insert (int index, uint value) {
616 return Insert ( index, value.ToString() );
619 [CLSCompliant(false)]
620 public StringBuilder Insert (int index, ulong value) {
621 return Insert ( index, value.ToString() );
624 public StringBuilder Insert (int index, string value, int count)
626 // LAMESPEC: The spec says to throw an exception if
627 // count < 0, while MS throws even for count < 1!
628 if ( count < 0 )
629 throw new ArgumentOutOfRangeException();
631 if (value != null && value != String.Empty)
632 for (int insertCount = 0; insertCount < count; insertCount++)
633 Insert( index, value );
635 return this;
638 public StringBuilder Insert (int index, char [] value, int startIndex, int charCount)
640 if (value == null) {
641 if (startIndex == 0 && charCount == 0)
642 return this;
644 throw new ArgumentNullException ("value");
647 if (charCount < 0 || startIndex < 0 || startIndex > value.Length - charCount)
648 throw new ArgumentOutOfRangeException ();
650 return Insert (index, new String (value, startIndex, charCount));
653 private void InternalEnsureCapacity (int size)
655 if (size > _str.Length || (object) _cached_str == (object) _str) {
656 int capacity = _str.Length;
658 // Try double buffer, if that doesn't work, set the length as capacity
659 if (size > capacity) {
661 // The first time a string is appended, we just set _cached_str
662 // and _str to it. This allows us to do some optimizations.
663 // Below, we take this into account.
664 if ((object) _cached_str == (object) _str && capacity < constDefaultCapacity)
665 capacity = constDefaultCapacity;
667 capacity = capacity << 1;
668 if (size > capacity)
669 capacity = size;
671 if (capacity >= Int32.MaxValue || capacity < 0)
672 capacity = Int32.MaxValue;
674 if (capacity > _maxCapacity && size <= _maxCapacity)
675 capacity = _maxCapacity;
677 if (capacity > _maxCapacity)
678 throw new ArgumentOutOfRangeException ("size", "capacity was less than the current size.");
681 string tmp = String.InternalAllocateStr (capacity);
682 if (_length > 0)
683 String.InternalStrcpy (tmp, 0, _str, 0, _length);
685 _str = tmp;
688 _cached_str = null;
691 #if NET_2_0
692 [ComVisible (false)]
693 public void CopyTo (int sourceIndex, char [] destination, int destinationIndex, int count)
695 if (destination == null)
696 throw new ArgumentNullException ("destination");
697 if ((Length - count < sourceIndex) ||
698 (destination.Length -count < destinationIndex) ||
699 (sourceIndex < 0 || destinationIndex < 0 || count < 0))
700 throw new ArgumentOutOfRangeException ();
702 for (int i = 0; i < count; i++)
703 destination [destinationIndex+i] = _str [sourceIndex+i];
706 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
708 info.AddValue ("m_MaxCapacity", _maxCapacity);
709 info.AddValue ("Capacity", Capacity);
710 info.AddValue ("m_StringValue", ToString ());
711 info.AddValue ("m_currentThread", 0);
714 StringBuilder (SerializationInfo info, StreamingContext context)
716 string s = info.GetString ("m_StringValue");
717 if (s == null)
718 s = "";
719 _length = s.Length;
720 _str = _cached_str = s;
722 _maxCapacity = info.GetInt32 ("m_MaxCapacity");
723 if (_maxCapacity < 0)
724 _maxCapacity = Int32.MaxValue;
725 Capacity = info.GetInt32 ("Capacity");
727 #endif