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