New tests, updates.
[mono.git] / mcs / class / corlib / System.Text / StringBuilder.cs
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.
15 //
16
17 //
18 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
19 //
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.
38 //
39 using System.Runtime.Serialization;
40 using System.Runtime.CompilerServices;
41 using System.Runtime.InteropServices;
42
43 namespace System.Text {
44         
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
54         {
55                 private int _length;
56                 private string _str;
57                 private string _cached_str;
58                 
59                 private int _maxCapacity = Int32.MaxValue;
60                 private const int constDefaultCapacity = 16;
61
62                 public StringBuilder(string value, int startIndex, int length, int capacity) 
63                 {
64                         // first, check the parameters and throw appropriate exceptions if needed
65                         if (null == value)
66                                 value = "";
67
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.");
71
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.");
75
76                         if (capacity < 0)
77                                 throw new System.ArgumentOutOfRangeException ("capacity", capacity, "capacity must be greater than zero.");
78
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.");
83
84                         if (capacity == 0)
85                                 capacity = constDefaultCapacity;
86
87                         _str = String.InternalAllocateStr ((length > capacity) ? length : capacity);
88                         if (length > 0)
89                                 String.CharCopy (_str, 0, value, startIndex, length);
90                         
91                         _length = length;
92                 }
93
94                 public StringBuilder () : this (null) {}
95
96                 public StringBuilder(int capacity) : this (String.Empty, 0, 0, capacity) {}
97
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.");
103
104                         _maxCapacity = maxCapacity;
105                 }
106
107                 public StringBuilder (string value)
108                 {
109                         /*
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.
113                          */
114                         if (null == value)
115                                 value = "";
116                         
117                         _length = value.Length;
118                         _str = _cached_str = value;
119                 }
120         
121                 public StringBuilder( string value, int capacity) : this(value, 0, value.Length, capacity) {}
122         
123                 public int MaxCapacity {
124                         get {
125                                 // MS runtime always returns Int32.MaxValue.
126                                 return _maxCapacity;
127                         }
128                 }
129
130                 public int Capacity {
131                         get {
132                                 if (_str.Length == 0)
133                                         return constDefaultCapacity;
134                                 
135                                 return _str.Length;
136                         }
137
138                         set {
139                                 if (value < _length)
140                                         throw new ArgumentException( "Capacity must be larger than length" );
141
142                                 InternalEnsureCapacity(value);
143                         }
144                 }
145
146                 public int Length {
147                         get {
148                                 return _length;
149                         }
150
151                         set {
152                                 if( value < 0 || value > _maxCapacity)
153                                         throw new ArgumentOutOfRangeException();
154
155                                 if (value == _length)
156                                         return;
157
158                                 if (value < _length) {
159                                         // LAMESPEC:  The spec is unclear as to what to do
160                                         // with the capacity when truncating the string.
161
162                                         // Do as MS, keep the capacity
163                                         
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);
171                                 }
172                         }
173                 }
174
175                 [IndexerName("Chars")]
176                 public char this [int index] {
177                         get {
178                                 if (index >= _length || index < 0)
179                                         throw new IndexOutOfRangeException();
180
181                                 return _str [index];
182                         } 
183
184                         set {
185                                 if (index >= _length || index < 0)
186                                         throw new IndexOutOfRangeException();
187
188                                 if (null != _cached_str)
189                                         InternalEnsureCapacity (_length);
190                                 
191                                 _str.InternalSetChar (index, value);
192                         }
193                 }
194
195                 public override string ToString () 
196                 {
197                         if (_length == 0)
198                                 return String.Empty;
199
200                         if (null != _cached_str)
201                                 return _cached_str;
202
203                         // If we only have a half-full buffer we return a new string.
204                         if (_length < (_str.Length >> 1)) 
205                         {
206                                 // use String.SubstringUnchecked instead of String.Substring
207                                 // as the former is guaranteed to create a new string object
208                                 _cached_str = _str.SubstringUnchecked (0, _length);
209                                 return _cached_str;
210                         }
211
212                         _cached_str = _str;
213                         _str.InternalSetLength(_length);
214
215                         return _str;
216                 }
217
218                 public string ToString (int startIndex, int length) 
219                 {
220                         // re-ordered to avoid possible integer overflow
221                         if (startIndex < 0 || length < 0 || startIndex > _length - length)
222                                 throw new ArgumentOutOfRangeException();
223
224                         // use String.SubstringUnchecked instead of String.Substring
225                         // as the former is guaranteed to create a new string object
226                         if (startIndex == 0 && length == _length)
227                                 return ToString ();
228                         else
229                                 return _str.SubstringUnchecked (startIndex, length);
230                 }
231
232                 public int EnsureCapacity (int capacity) 
233                 {
234                         if (capacity < 0)
235                                 throw new ArgumentOutOfRangeException ("Capacity must be greater than 0." );
236
237                         if( capacity <= _str.Length )
238                                 return _str.Length;
239
240                         InternalEnsureCapacity (capacity);
241
242                         return _str.Length;
243                 }
244
245                 public bool Equals (StringBuilder sb) 
246                 {
247                         if (((object)sb) == null)
248                                 return false;
249                         
250                         if (_length == sb.Length && _str == sb._str )
251                                 return true;
252
253                         return false;
254                 }
255
256                 public StringBuilder Remove (int startIndex, int length)
257                 {
258                         // re-ordered to avoid possible integer overflow
259                         if (startIndex < 0 || length < 0 || startIndex > _length - length)
260                                 throw new ArgumentOutOfRangeException();
261                         
262                         if (null != _cached_str)
263                                 InternalEnsureCapacity (_length);
264                         
265                         // Copy everything after the 'removed' part to the start 
266                         // of the removed part and truncate the sLength
267                         if (_length - (startIndex + length) > 0)
268                                 String.CharCopy (_str, startIndex, _str, startIndex + length, _length - (startIndex + length));
269
270                         _length -= length;
271
272                         return this;
273                 }                              
274
275                 public StringBuilder Replace (char oldChar, char newChar) 
276                 {
277                         return Replace( oldChar, newChar, 0, _length);
278                 }
279
280                 public StringBuilder Replace (char oldChar, char newChar, int startIndex, int count) 
281                 {
282                         // re-ordered to avoid possible integer overflow
283                         if (startIndex > _length - count || startIndex < 0 || count < 0)
284                                 throw new ArgumentOutOfRangeException();
285
286                         if (null != _cached_str)
287                                 InternalEnsureCapacity (_str.Length);
288
289                         for (int replaceIterate = startIndex; replaceIterate < startIndex + count; replaceIterate++ ) {
290                                 if( _str [replaceIterate] == oldChar )
291                                         _str.InternalSetChar (replaceIterate, newChar);
292                         }
293
294                         return this;
295                 }
296
297                 public StringBuilder Replace( string oldValue, string newValue ) {
298                         return Replace (oldValue, newValue, 0, _length);
299                 }
300
301                 public StringBuilder Replace( string oldValue, string newValue, int startIndex, int count ) 
302                 {
303                         if (oldValue == null)
304                                 throw new ArgumentNullException ("The old value cannot be null.");
305
306                         if (startIndex < 0 || count < 0 || startIndex > _length - count)
307                                 throw new ArgumentOutOfRangeException ();
308
309                         if (oldValue.Length == 0)
310                                 throw new ArgumentException ("The old value cannot be zero length.");
311
312                         string substr = _str.Substring(startIndex, count);
313                         string replace = substr.Replace(oldValue, newValue);
314                         // return early if no oldValue was found
315                         if ((object) replace == (object) substr)
316                                 return this;
317
318                         InternalEnsureCapacity (replace.Length + (_length - count));
319
320                         // shift end part
321                         if (replace.Length < count)
322                                 String.CharCopy (_str, startIndex + replace.Length, _str, startIndex + count, _length - startIndex  - count);
323                         else if (replace.Length > count)
324                                 String.CharCopyReverse (_str, startIndex + replace.Length, _str, startIndex + count, _length - startIndex  - count);
325
326                         // copy middle part back into _str
327                         String.CharCopy (_str, startIndex, replace, 0, replace.Length);
328                         
329                         _length = replace.Length + (_length - count);
330
331                         return this;
332                 }
333
334                       
335                 /* The Append Methods */
336                 public StringBuilder Append (char[] value) 
337                 {
338                         if (value == null)
339                                 return this;
340
341                         int needed_cap = _length + value.Length;
342                         if (null != _cached_str || _str.Length < needed_cap)
343                                 InternalEnsureCapacity (needed_cap);
344                         
345                         String.CharCopy (_str, _length, value, 0, value.Length);
346                         _length = needed_cap;
347
348                         return this;
349                 } 
350                 
351                 public StringBuilder Append (string value) 
352                 {
353                         if (value == null)
354                                 return this;
355                         
356                         if (_length == 0 && value.Length < _maxCapacity && value.Length > _str.Length) {
357                                 _length = value.Length;
358                                 _str = _cached_str = value;
359                                 return this;
360                         }
361
362                         int needed_cap = _length + value.Length;
363                         if (null != _cached_str || _str.Length < needed_cap)
364                                 InternalEnsureCapacity (needed_cap);
365
366                         String.CharCopy (_str, _length, value, 0, value.Length);
367                         _length = needed_cap;
368                         return this;
369                 }
370
371                 public StringBuilder Append (bool value) {
372                         return Append (value.ToString());
373                 }
374                 
375                 public StringBuilder Append (byte value) {
376                         return Append (value.ToString());
377                 }
378
379                 public StringBuilder Append (decimal value) {
380                         return Append (value.ToString());
381                 }
382
383                 public StringBuilder Append (double value) {
384                         return Append (value.ToString());
385                 }
386
387                 public StringBuilder Append (short value) {
388                         return Append (value.ToString());
389                 }
390
391                 public StringBuilder Append (int value) {
392                         return Append (value.ToString());
393                 }
394
395                 public StringBuilder Append (long value) {
396                         return Append (value.ToString());
397                 }
398
399                 public StringBuilder Append (object value) {
400                         if (value == null)
401                                 return this;
402
403                         return Append (value.ToString());
404                 }
405
406                 [CLSCompliant(false)]
407                 public StringBuilder Append (sbyte value) {
408                         return Append (value.ToString());
409                 }
410
411                 public StringBuilder Append (float value) {
412                         return Append (value.ToString());
413                 }
414
415                 [CLSCompliant(false)]
416                 public StringBuilder Append (ushort value) {
417                         return Append (value.ToString());
418                 }       
419                 
420                 [CLSCompliant(false)]
421                 public StringBuilder Append (uint value) {
422                         return Append (value.ToString());
423                 }
424
425                 [CLSCompliant(false)]
426                 public StringBuilder Append (ulong value) {
427                         return Append (value.ToString());
428                 }
429
430                 public StringBuilder Append (char value) 
431                 {
432                         int needed_cap = _length + 1;
433                         if (null != _cached_str || _str.Length < needed_cap)
434                                 InternalEnsureCapacity (needed_cap);
435
436                         _str.InternalSetChar(_length, value);
437                         _length = needed_cap;
438
439                         return this;
440                 }
441
442                 public StringBuilder Append (char value, int repeatCount) 
443                 {
444                         if( repeatCount < 0 )
445                                 throw new ArgumentOutOfRangeException();
446
447                         InternalEnsureCapacity (_length + repeatCount);
448                         
449                         for (int i = 0; i < repeatCount; i++)
450                                 _str.InternalSetChar (_length++, value);
451
452                         return this;
453                 }
454
455                 public StringBuilder Append( char[] value, int startIndex, int charCount ) 
456                 {
457                         if (value == null) {
458                                 if (!(startIndex == 0 && charCount == 0))
459                                         throw new ArgumentNullException ("value");
460
461                                 return this;
462                         }
463
464                         if ((charCount < 0 || startIndex < 0) || (startIndex > value.Length - charCount)) 
465                                 throw new ArgumentOutOfRangeException();
466                         
467                         int needed_cap = _length + charCount;
468                         InternalEnsureCapacity (needed_cap);
469
470                         String.CharCopy (_str, _length, value, startIndex, charCount);
471                         _length = needed_cap;
472
473                         return this;
474                 }
475
476                 public StringBuilder Append (string value, int startIndex, int count) 
477                 {
478                         if (value == null) {
479                                 if (startIndex != 0 && count != 0)
480                                         throw new ArgumentNullException ("value");
481                                         
482                                 return this;
483                         }
484
485                         if ((count < 0 || startIndex < 0) || (startIndex > value.Length - count))
486                                 throw new ArgumentOutOfRangeException();
487                         
488                         int needed_cap = _length + count;
489                         if (null != _cached_str || _str.Length < needed_cap)
490                                 InternalEnsureCapacity (needed_cap);
491
492                         String.CharCopy (_str, _length, value, startIndex, count);
493                         
494                         _length = needed_cap;
495
496                         return this;
497                 }
498
499 #if NET_2_0
500                 [ComVisible (false)]
501                 public StringBuilder AppendLine ()
502                 {
503                         return Append (System.Environment.NewLine);
504                 }
505
506                 [ComVisible (false)]
507                 public StringBuilder AppendLine (string value)
508                 {
509                         return Append (value).Append (System.Environment.NewLine);
510                 }
511 #endif
512
513                 public StringBuilder AppendFormat (string format, params object[] args)
514                 {
515                         return AppendFormat (null, format, args);
516                 }
517
518                 public StringBuilder AppendFormat (IFormatProvider provider,
519                                                    string format,
520                                                    params object[] args)
521                 {
522                         String.FormatHelper (this, provider, format, args);
523                         return this;
524                 }
525
526 #if NET_2_1
527                 internal
528 #else
529                 public
530 #endif
531                 StringBuilder AppendFormat (string format, object arg0)
532                 {
533                         return AppendFormat (null, format, new object [] { arg0 });
534                 }
535
536 #if NET_2_1
537                 internal
538 #else
539                 public
540 #endif
541                 StringBuilder AppendFormat (string format, object arg0, object arg1)
542                 {
543                         return AppendFormat (null, format, new object [] { arg0, arg1 });
544                 }
545
546 #if NET_2_1
547                 internal
548 #else
549                 public
550 #endif
551                 StringBuilder AppendFormat (string format, object arg0, object arg1, object arg2)
552                 {
553                         return AppendFormat (null, format, new object [] { arg0, arg1, arg2 });
554                 }
555
556                 /*  The Insert Functions */
557                 
558                 public StringBuilder Insert (int index, char[] value) 
559                 {
560                         return Insert (index, new string (value));
561                 }
562                                 
563                 public StringBuilder Insert (int index, string value) 
564                 {
565                         if( index > _length || index < 0)
566                                 throw new ArgumentOutOfRangeException();
567
568                         if (value == null || value.Length == 0)
569                                 return this;
570
571                         InternalEnsureCapacity (_length + value.Length);
572
573                         // Move everything to the right of the insert point across
574                         String.CharCopyReverse (_str, index + value.Length, _str, index, _length - index);
575                         
576                         // Copy in stuff from the insert buffer
577                         String.CharCopy (_str, index, value, 0, value.Length);
578                         
579                         _length += value.Length;
580
581                         return this;
582                 }
583
584                 public StringBuilder Insert( int index, bool value ) {
585                         return Insert (index, value.ToString());
586                 }
587                 
588                 public StringBuilder Insert( int index, byte value ) {
589                         return Insert (index, value.ToString());
590                 }
591
592                 public StringBuilder Insert( int index, char value) 
593                 {
594                         if (index > _length || index < 0)
595                                 throw new ArgumentOutOfRangeException ("index");
596
597                         InternalEnsureCapacity (_length + 1);
598                         
599                         // Move everything to the right of the insert point across
600                         String.CharCopyReverse (_str, index + 1, _str, index, _length - index);
601                         
602                         _str.InternalSetChar (index, value);
603                         _length++;
604
605                         return this;
606                 }
607
608                 public StringBuilder Insert( int index, decimal value ) {
609                         return Insert (index, value.ToString());
610                 }
611
612                 public StringBuilder Insert( int index, double value ) {
613                         return Insert (index, value.ToString());
614                 }
615                 
616                 public StringBuilder Insert( int index, short value ) {
617                         return Insert (index, value.ToString());
618                 }
619
620                 public StringBuilder Insert( int index, int value ) {
621                         return Insert (index, value.ToString());
622                 }
623
624                 public StringBuilder Insert( int index, long value ) {
625                         return Insert (index, value.ToString());
626                 }
627         
628                 public StringBuilder Insert( int index, object value ) {
629                         return Insert (index, value.ToString());
630                 }
631                 
632                 [CLSCompliant(false)]
633                 public StringBuilder Insert( int index, sbyte value ) {
634                         return Insert (index, value.ToString() );
635                 }
636
637                 public StringBuilder Insert (int index, float value) {
638                         return Insert (index, value.ToString() );
639                 }
640
641                 [CLSCompliant(false)]
642                 public StringBuilder Insert (int index, ushort value) {
643                         return Insert (index, value.ToString() );
644                 }
645
646                 [CLSCompliant(false)]
647                 public StringBuilder Insert (int index, uint value) {
648                         return Insert ( index, value.ToString() );
649                 }
650                 
651                 [CLSCompliant(false)]
652                 public StringBuilder Insert (int index, ulong value) {
653                         return Insert ( index, value.ToString() );
654                 }
655
656                 public StringBuilder Insert (int index, string value, int count) 
657                 {
658                         // LAMESPEC: The spec says to throw an exception if 
659                         // count < 0, while MS throws even for count < 1!
660                         if ( count < 0 )
661                                 throw new ArgumentOutOfRangeException();
662
663                         if (value != null && value != String.Empty)
664                                 for (int insertCount = 0; insertCount < count; insertCount++)
665                                         Insert( index, value );
666
667                         return this;
668                 }
669
670                 public StringBuilder Insert (int index, char [] value, int startIndex, int charCount)
671                 {
672                         if (value == null) {
673                                 if (startIndex == 0 && charCount == 0)
674                                         return this;
675
676                                 throw new ArgumentNullException ("value");
677                         }
678
679                         if (charCount < 0 || startIndex < 0 || startIndex > value.Length - charCount)
680                                 throw new ArgumentOutOfRangeException ();
681
682                         return Insert (index, new String (value, startIndex, charCount));
683                 }
684         
685                 private void InternalEnsureCapacity (int size) 
686                 {
687                         if (size > _str.Length || (object) _cached_str == (object) _str) {
688                                 int capacity = _str.Length;
689
690                                 // Try double buffer, if that doesn't work, set the length as capacity
691                                 if (size > capacity) {
692                                         
693                                         // The first time a string is appended, we just set _cached_str
694                                         // and _str to it. This allows us to do some optimizations.
695                                         // Below, we take this into account.
696                                         if ((object) _cached_str == (object) _str && capacity < constDefaultCapacity)
697                                                 capacity = constDefaultCapacity;
698                                         
699                                         capacity = capacity << 1;
700                                         if (size > capacity)
701                                                 capacity = size;
702
703                                         if (capacity >= Int32.MaxValue || capacity < 0)
704                                                 capacity = Int32.MaxValue;
705
706                                         if (capacity > _maxCapacity && size <= _maxCapacity)
707                                                 capacity = _maxCapacity;
708                                         
709                                         if (capacity > _maxCapacity)
710                                                 throw new ArgumentOutOfRangeException ("size", "capacity was less than the current size.");
711                                 }
712
713                                 string tmp = String.InternalAllocateStr (capacity);
714                                 if (_length > 0)
715                                         String.CharCopy (tmp, 0, _str, 0, _length);
716
717                                 _str = tmp;
718                         }
719
720                         _cached_str = null;
721                 }
722
723 #if NET_2_0
724                 [ComVisible (false)]
725                 public void CopyTo (int sourceIndex, char [] destination, int destinationIndex, int count)
726                 {
727                         if (destination == null)
728                                 throw new ArgumentNullException ("destination");
729                         if ((Length - count < sourceIndex) ||
730                             (destination.Length -count < destinationIndex) ||
731                             (sourceIndex < 0 || destinationIndex < 0 || count < 0))
732                                 throw new ArgumentOutOfRangeException ();
733
734                         for (int i = 0; i < count; i++)
735                                 destination [destinationIndex+i] = _str [sourceIndex+i];
736                 }
737
738                 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
739                 {
740                         info.AddValue ("m_MaxCapacity", _maxCapacity);
741                         info.AddValue ("Capacity", Capacity);
742                         info.AddValue ("m_StringValue", ToString ());
743                         info.AddValue ("m_currentThread", 0);
744                 }
745
746                 StringBuilder (SerializationInfo info, StreamingContext context)
747                 {
748                         string s = info.GetString ("m_StringValue");
749                         if (s == null)
750                                 s = "";
751                         _length = s.Length;
752                         _str = _cached_str = s;
753                         
754                         _maxCapacity = info.GetInt32 ("m_MaxCapacity");
755                         if (_maxCapacity < 0)
756                                 _maxCapacity = Int32.MaxValue;
757                         Capacity = info.GetInt32 ("Capacity");
758                 }
759 #endif
760         }
761 }