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