2002-01-14 Miguel de Icaza <miguel@ximian.com>
[mono.git] / mcs / class / corlib / System.Text / StringBuilder.cs
1 // -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-\r
2 //\r
3 // System.Text.StringBuilder\r
4 //\r
5 // Author: Marcin Szczepanski (marcins@zipworld.com.au)\r
6 //\r
7 // TODO: Implement the AppendFormat methods.  Wasn't sure how\r
8 // best to do this at this early stage, might want to see\r
9 // how the String class and the IFormatProvider / IFormattable interfaces\r
10 // pan out first.\r
11 //  \r
12 // TODO: Make sure the coding complies to the ECMA draft, there's some\r
13 // variable names that probably don't (like sString)\r
14 //\r
15 namespace System.Text {\r
16         \r
17         [MonoTODO ("Implement AppendFormat methods and IFormatProvider, IFormattable")]\r
18         public sealed class StringBuilder {\r
19 \r
20                 private const int defaultCapacity = 16;\r
21 \r
22                 private int sCapacity;\r
23                 private int sLength;\r
24                 private char[] sString;\r
25                 private int sMaxCapacity = Int32.MaxValue;\r
26 \r
27                 public StringBuilder(string value, int startIndex, int length, int capacity) {\r
28                         // first, check the parameters and throw appropriate exceptions if needed\r
29                         if(null==value) {\r
30                                 throw new System.ArgumentNullException("value");\r
31                         }\r
32 \r
33                         // make sure startIndex is zero or positive\r
34                         if(startIndex < 0) {\r
35                                 throw new System.ArgumentOutOfRangeException("startIndex", startIndex, "StartIndex cannot be less than zero.");\r
36                         }\r
37 \r
38                         // make sure length is zero or positive\r
39                         if(length < 0) {\r
40                                 throw new System.ArgumentOutOfRangeException("length", length, "Length cannot be less than zero.");\r
41                         }\r
42 \r
43                         // make sure startIndex and length give a valid substring of value\r
44                         if(startIndex + (length -1) > (value.Length - 1) ) {\r
45                                 throw new System.ArgumentOutOfRangeException("startIndex", startIndex, "StartIndex and length must refer to a location within the string.");\r
46                         }\r
47                         \r
48                         // the capacity must be at least as big as the default capacity\r
49                         sCapacity = Math.Max(capacity, defaultCapacity);\r
50 \r
51                         // LAMESPEC: what to do if capacity is too small to hold the substring?\r
52                         // Like the MS implementation, double the capacity until it is large enough\r
53                         while (sCapacity < length) {\r
54                                 // However, take care not to double if that would make the number\r
55                                 // larger than what an int can hold\r
56                                 if (sCapacity <= Int32.MaxValue / 2) {\r
57                                         sCapacity *= 2;\r
58                                 }\r
59                                 else{\r
60                                         sCapacity = Int32.MaxValue;\r
61                                 }\r
62                         }\r
63 \r
64                         sString = new char[sCapacity];\r
65                         sLength = length;\r
66 \r
67                         // if the length is not zero, then we have to copy some characters\r
68                         if (sLength > 0) {\r
69                                 // Copy the correct number of characters into the internal array\r
70                                 char[] tString = value.ToCharArray(startIndex, sLength);\r
71                                 Array.Copy( tString, sString, sLength);\r
72                         }\r
73                 }\r
74 \r
75                 public StringBuilder() : this(String.Empty, 0, 0, 0) {}\r
76 \r
77                 public StringBuilder( int capacity ) : this("", 0, 0, capacity) {}\r
78 \r
79                 public StringBuilder( int capacity, int maxCapacity ) : this("", 0, 0, capacity) {\r
80                         if(capacity > maxCapacity) {\r
81                                 throw new System.ArgumentOutOfRangeException("capacity", "Capacity exceeds maximum capacity.");\r
82                         }\r
83                         sMaxCapacity = maxCapacity;\r
84                 }\r
85 \r
86                 public StringBuilder( string value ) : this(value, 0, value == null ? 0 : value.Length, value == null? 0 : value.Length) {\r
87                 }\r
88         \r
89                 public StringBuilder( string value, int capacity) : this(value, 0, value.Length, capacity) {}\r
90         \r
91                 [MonoTODO]\r
92                 public int MaxCapacity {\r
93                         get {\r
94                                 // TODO: Need to look at the memory of the system to return a useful value here\r
95                                 return sMaxCapacity;\r
96                         }\r
97                 }\r
98 \r
99                 public int Capacity {\r
100                         get {\r
101                                 return sCapacity;\r
102                         }\r
103 \r
104                         set {\r
105                                 if( value < sLength ) {\r
106                                         throw new ArgumentException( "Capacity must be > length" );\r
107                                 } else {\r
108                                         char[] tString = new char[value];              \r
109                                         Array.Copy( sString, tString, sLength );\r
110                                         sString = tString;\r
111                                         sCapacity = sString.Length;\r
112                                 }\r
113                         }\r
114                 }\r
115 \r
116 \r
117                 public int Length {\r
118                         get {\r
119                                 return sLength;\r
120                         }\r
121 \r
122                         set {\r
123                                 if( value < 0 || value > MaxCapacity) {\r
124                                         throw new ArgumentOutOfRangeException();\r
125                                 } else {\r
126                                         if( value < sLength ) {\r
127                                                 // Truncate current string at value\r
128 \r
129                                                 // LAMESPEC:  The spec is unclear as to what to do\r
130                                                 // with the capacity when truncating the string.\r
131                                                 //\r
132                                                 // Don't change the capacity, as this is what\r
133                                                 // the MS implementation does.\r
134 \r
135                                                 sLength = value;\r
136                                         } else {\r
137                                                 // Expand the capacity to the new length and\r
138                                                 // pad the string with spaces.\r
139                                                 \r
140                                                 // LAMESPEC: The spec says to put the spaces on the\r
141                                                 // left of the string however the MS implementation\r
142                                                 // puts them on the right.  We'll do that for \r
143                                                 // compatibility (!)\r
144 \r
145                                                 char[] tString = new char[ value ];\r
146                                                 int padLength = value - sLength;\r
147                                                 \r
148                                                 string padding = new String( ' ', padLength );\r
149                                                 Array.Copy( sString, tString, sLength );\r
150                                                 Array.Copy( padding.ToCharArray(), 0, tString, sLength, padLength );\r
151                                                 sString = tString;\r
152                                                 sLength = sString.Length;\r
153                                                 sCapacity = value;\r
154                                         }\r
155                                 }\r
156                         }\r
157                 }\r
158 \r
159                 public char this[ int index ] {\r
160                         get {\r
161 \r
162                                 if( index >= sLength || index < 0 ) {\r
163                                         throw new IndexOutOfRangeException();\r
164                                 }\r
165                                 return sString[ index ];\r
166                         } \r
167 \r
168                         set {\r
169                                 if( index >= sLength || index < 0 ) {\r
170                                         throw new IndexOutOfRangeException();\r
171                                 }\r
172                                 sString[ index ] = value;\r
173                         }\r
174                 }\r
175 \r
176                 public override string ToString() {\r
177                         return ToString(0, sLength);\r
178                 }\r
179 \r
180                 public string ToString( int startIndex, int length ) {\r
181                         if( startIndex < 0 || length < 0 || startIndex + length > sLength ) {\r
182                                 throw new ArgumentOutOfRangeException();\r
183                         }\r
184         \r
185                         return new String( sString, startIndex, length );\r
186                 }\r
187 \r
188                 public int EnsureCapacity( int capacity ) {\r
189                         if( capacity < 0 ) {\r
190                                 throw new ArgumentOutOfRangeException( \r
191                                         "Capacity must be greater than 0." );\r
192                         }\r
193 \r
194                         if( capacity <= sCapacity ) {\r
195                                 return sCapacity;\r
196                         } else {\r
197                                 Capacity = capacity;\r
198                                 return sCapacity;\r
199                         }\r
200                 }\r
201 \r
202                 public bool Equals( StringBuilder sb ) {\r
203                         if( this.ToString() == sb.ToString() ) {\r
204                                 return true;\r
205                         } else {\r
206                                 return false;\r
207                         }\r
208                 }\r
209 \r
210                 public StringBuilder Remove( int startIndex, int length ) {\r
211                         if( startIndex < 0 || length < 0 || startIndex + length > sLength ) {\r
212                                 throw new ArgumentOutOfRangeException();\r
213                         }\r
214 \r
215                         // Copy everything after the 'removed' part to the start \r
216                         // of the removed part and truncate the sLength\r
217 \r
218                         Array.Copy( sString, startIndex + length, sString, \r
219                                 startIndex, length );\r
220 \r
221                         sLength -= length;\r
222                         return this;\r
223                 }                              \r
224 \r
225                 public StringBuilder Replace( char oldChar, char newChar ) {\r
226                 \r
227                         return Replace( oldChar, newChar, 0, sLength);\r
228                 }\r
229 \r
230                 public StringBuilder Replace( char oldChar, char newChar, int startIndex, int count ) {\r
231                         if( startIndex + count > sLength || startIndex < 0 || count < 0 ) {\r
232                                 throw new ArgumentOutOfRangeException();\r
233                         }\r
234 \r
235                         for( int replaceIterate = startIndex; replaceIterate < startIndex + count; replaceIterate++ ) {\r
236                                 if( this[replaceIterate] == oldChar ) {\r
237                                         this[replaceIterate] = newChar;\r
238                                 }\r
239                         }\r
240 \r
241                         return this;\r
242                 }\r
243 \r
244                 public StringBuilder Replace( string oldValue, string newValue ) {\r
245                         return Replace( oldValue, newValue, 0, sLength );\r
246                 }\r
247 \r
248                 public StringBuilder Replace( string oldValue, string newValue, int startIndex, int count ) {\r
249                         string startString = this.ToString();\r
250                         StringBuilder newStringB = new StringBuilder();\r
251                         string newString;\r
252 \r
253                         if( oldValue == null ) { \r
254                                 throw new ArgumentNullException(\r
255                                         "The old value cannot be null.");\r
256                         }\r
257 \r
258                         if( startIndex < 0 || count < 0 || startIndex + count > sLength ) {\r
259                                 throw new ArgumentOutOfRangeException();\r
260                         }\r
261 \r
262                         if( oldValue.Length == 0 ) {\r
263                                 throw new ArgumentException(\r
264                                         "The old value cannot be zero length.");\r
265                         }\r
266 \r
267                         int nextIndex = startIndex; // Where to start the next search\r
268                         int lastIndex = nextIndex;  // Where the last search finished\r
269 \r
270                         while( nextIndex != -1 ) {\r
271                                 nextIndex = startString.IndexOf( oldValue, lastIndex);                            \r
272                                 if( nextIndex != -1 ) {\r
273                                         // The MS implementation won't replace a substring \r
274                                         // if that substring goes over the "count"\r
275                                         // boundary, so we'll make sure the behaviour \r
276                                         // here is the same.\r
277 \r
278                                         if( nextIndex + oldValue.Length <= startIndex + count ) {\r
279 \r
280                                                 // Add everything to the left of the old \r
281                                                 // string\r
282                                                 newStringB.Append( startString.Substring( lastIndex, nextIndex - lastIndex ) );\r
283         \r
284                                                 // Add the replacement string\r
285                                                 newStringB.Append( newValue );\r
286                                                 \r
287                                                 // Set the next start point to the \r
288                                                 // end of the last match\r
289                                                 lastIndex = nextIndex + oldValue.Length;\r
290                                         } else {\r
291                                                 // We're past the "count" we're supposed to replace within\r
292                                                 nextIndex = -1;\r
293                                                 newStringB.Append( \r
294                                                         startString.Substring( lastIndex ) );\r
295                                         }\r
296 \r
297                                 } else {\r
298                                         // Append everything left over\r
299                                         newStringB.Append( startString.Substring( lastIndex ) );\r
300                                 }\r
301                         } \r
302 \r
303                         newString = newStringB.ToString();\r
304 \r
305                         EnsureCapacity( newString.Length );\r
306                         sString = newString.ToCharArray();\r
307                         sLength = newString.Length;\r
308                         return this;\r
309                 }\r
310 \r
311                       \r
312                 /* The Append Methods */\r
313 \r
314                 // TODO: Currently most of these methods convert the \r
315                 // parameter to a CharArray (via a String) and then pass\r
316                 // it to Append( char[] ).  There might be a faster way\r
317                 // of doing this, but it's probably adequate and anything else\r
318                 // would make it too messy.\r
319                 //\r
320                 // As an example, a sample test run of appending a 100 character\r
321                 // string to the StringBuilder, and loooping this 50,000 times\r
322                 // results in an elapsed time of 2.4s using the MS StringBuilder\r
323                 // and 2.7s using this StringBuilder.  Note that this results\r
324                 // in a 5 million character string.  I believe MS uses a lot\r
325                 // of "native" DLLs for the "meat" of the base classes.\r
326 \r
327                 [MonoTODO ("Look at all Append methods and complete them if necessary")]\r
328                 public StringBuilder Append( char[] value ) {\r
329                         if( sLength + value.Length > sCapacity ) {\r
330                                 // Need more capacity, double the capacity StringBuilder \r
331                                 // and make sure we have at least enough for the value \r
332                                 // if that's going to go over double. \r
333                                          \r
334                                 Capacity = value.Length + ( sCapacity + sCapacity);\r
335                         }\r
336 \r
337                         Array.Copy( value, 0, sString, sLength, value.Length );\r
338                         sLength += value.Length;\r
339 \r
340                         return this;\r
341                 } \r
342                 \r
343                 public StringBuilder Append( string value ) {\r
344                         if( value != null ) {\r
345                                 return Append( value.ToCharArray() );\r
346                         } else {\r
347                                 return null;\r
348                         }\r
349                 }\r
350 \r
351                 public StringBuilder Append( bool value ) {\r
352                         return Append( value.ToString().ToCharArray() );\r
353                 }\r
354                 \r
355                 public StringBuilder Append( byte value ) {\r
356                         return Append( value.ToString().ToCharArray() );\r
357                 }\r
358 \r
359                 public StringBuilder Append( int index, char value) {\r
360                         char[] appendChar = new char[1];\r
361                         \r
362                         appendChar[0] = value;\r
363                         return Append( appendChar );\r
364                 }\r
365 \r
366 \r
367                 public StringBuilder Append( decimal value ) {\r
368                         return Append( value.ToString().ToCharArray() );\r
369                 }\r
370 \r
371                 public StringBuilder Append( double value ) {\r
372                         return Append( value.ToString().ToCharArray() );\r
373                 }\r
374 \r
375                 public StringBuilder Append( short value ) {\r
376                         return Append( value.ToString().ToCharArray() );\r
377                 }\r
378 \r
379                 public StringBuilder Append( int value ) {\r
380                         return Append( value.ToString().ToCharArray() );\r
381                 }\r
382 \r
383                 public StringBuilder Append( long value ) {\r
384                         return Append( value.ToString().ToCharArray() );\r
385                 }\r
386 \r
387                 public StringBuilder Append( object value ) {\r
388                         return Append( value.ToString().ToCharArray() );\r
389                 }\r
390 \r
391                 [CLSCompliant(false)]\r
392                 public StringBuilder Append( sbyte value ) {\r
393                         return Append( value.ToString().ToCharArray() );\r
394                 }\r
395 \r
396                 public StringBuilder Append( float value ) {\r
397                         return Append( value.ToString().ToCharArray() );\r
398                 }\r
399 \r
400                 [CLSCompliant(false)]\r
401                 public StringBuilder Append( ushort value ) {\r
402                         return Append( value.ToString().ToCharArray() );\r
403                 }       \r
404                 \r
405                 [CLSCompliant(false)]\r
406                 public StringBuilder Append( uint value ) {\r
407                         return Append( value.ToString().ToCharArray() );\r
408                 }\r
409 \r
410                 [CLSCompliant(false)]\r
411                 public StringBuilder Append( ulong value ) {\r
412                         return Append( value.ToString().ToCharArray() );\r
413                 }\r
414 \r
415                 public StringBuilder Append( char value ) {\r
416                         return Append (value, 1);\r
417                 }\r
418 \r
419                 public StringBuilder Append( char value, int repeatCount ) {\r
420                         if( repeatCount < 0 ) {\r
421                                 throw new ArgumentOutOfRangeException();\r
422                         }\r
423 \r
424                         return Append( new String( value, repeatCount) );\r
425                 }\r
426 \r
427                 public StringBuilder Append( char[] value, int startIndex, int charCount ) {\r
428 \r
429                         if( (charCount < 0 || startIndex < 0) || \r
430                                 ( charCount + startIndex > value.Length ) ) {\r
431                                 throw new ArgumentOutOfRangeException();\r
432                         }\r
433                         \r
434                         if( value == null ) {\r
435                                 if( !(startIndex == 0 && charCount == 0) ) {\r
436                                         throw new ArgumentNullException();\r
437                                 } else {\r
438                                         return this;\r
439                                 }\r
440                         } else {\r
441                                 char[] appendChars = new char[ charCount ];\r
442                         \r
443                                 Array.Copy( value, startIndex, appendChars, 0, charCount );\r
444                                 return Append( appendChars );\r
445                         }\r
446                 }\r
447 \r
448                 public StringBuilder Append( string value, int startIndex, int count ) {\r
449                         if( (count < 0 || startIndex < 0) || \r
450                                 ( startIndex + count > value.Length ) ) { \r
451                                 throw new ArgumentOutOfRangeException();\r
452                         }\r
453 \r
454                         return Append( value.Substring( startIndex, count ).ToCharArray() );\r
455                 }\r
456 \r
457                 [MonoTODO]\r
458                 public StringBuilder AppendFormat( string format, object arg0 ) {\r
459                         // TODO: Implement\r
460                         return this;\r
461                 }\r
462 \r
463                 [MonoTODO]\r
464                 public StringBuilder AppendFormat( string format, params object[] args ) {\r
465                         // TODO: Implement\r
466                         return this;\r
467                 }\r
468 \r
469                 [MonoTODO]\r
470                 public StringBuilder AppendFormat( IFormatProvider provider, string format,\r
471                         params object[] args ) {\r
472                         // TODO: Implement\r
473                         return this;\r
474                 }\r
475 \r
476                 [MonoTODO]\r
477                 public StringBuilder AppendFormat( string format, object arg0, object arg1 ) {\r
478                         // TODO: Implement;\r
479                         return this;\r
480                 }\r
481 \r
482                 [MonoTODO]\r
483                 public StringBuilder AppendFormat( string format, object arg0, object arg1, object arg2 ) {\r
484                         // TODO Implement\r
485                         return this;\r
486                 }\r
487 \r
488                 /*  The Insert Functions */\r
489                 \r
490                 // Similarly to the Append functions, get everything down to a CharArray \r
491                 // and insert that.\r
492                 \r
493                 public StringBuilder Insert( int index, char[] value ) {\r
494                         if( index > sLength || index < 0) {\r
495                                 throw new ArgumentOutOfRangeException();\r
496                         }\r
497 \r
498                         if( value == null || value.Length == 0 ) {\r
499                                 return this;\r
500                         } else {\r
501                                 // Check we have the capacity to insert this array\r
502                                 if( sCapacity < sLength + value.Length ) {\r
503                                         Capacity = value.Length + ( sCapacity + sCapacity );\r
504                                 }\r
505 \r
506                                 // Move everything to the right of the insert point across\r
507                                 Array.Copy( sString, index, sString, index + value.Length, sLength - index);\r
508                                 \r
509                                 // Copy in stuff from the insert buffer\r
510                                 Array.Copy( value, 0, sString, index, value.Length );\r
511                                 \r
512                                 sLength += value.Length;\r
513                                 return this;\r
514                         }\r
515                 }\r
516                                 \r
517                 public StringBuilder Insert( int index, string value ) {\r
518                         return Insert( index, value.ToCharArray() );\r
519                 }\r
520 \r
521                 public StringBuilder Insert( int index, bool value ) {\r
522                         return Insert( index, value.ToString().ToCharArray() );\r
523                 }\r
524                 \r
525                 public StringBuilder Insert( int index, byte value ) {\r
526                         return Insert( index, value.ToString().ToCharArray() );\r
527                 }\r
528 \r
529                 public StringBuilder Insert( int index, char value) {\r
530                         char[] insertChar = new char[1];\r
531                         \r
532                         insertChar[0] = value;\r
533                         return Insert( index, insertChar );\r
534                 }\r
535 \r
536                 public StringBuilder Insert( int index, decimal value ) {\r
537                         return Insert( index, value.ToString().ToCharArray() );\r
538                 }\r
539 \r
540                 public StringBuilder Insert( int index, double value ) {\r
541                         return Insert( index, value.ToString().ToCharArray() );\r
542                 }\r
543                 \r
544                 public StringBuilder Insert( int index, short value ) {\r
545                         return Insert( index, value.ToString().ToCharArray() );\r
546                 }\r
547 \r
548                 public StringBuilder Insert( int index, int value ) {\r
549                         return Insert( index, value.ToString().ToCharArray() );\r
550                 }\r
551 \r
552                 public StringBuilder Insert( int index, long value ) {\r
553                         return Insert( index, value.ToString().ToCharArray() );\r
554                 }\r
555         \r
556                 public StringBuilder Insert( int index, object value ) {\r
557                         return Insert( index, value.ToString().ToCharArray() );\r
558                 }\r
559                 \r
560                 [CLSCompliant(false)]\r
561                 public StringBuilder Insert( int index, sbyte value ) {\r
562                         return Insert( index, value.ToString().ToCharArray() );\r
563                 }\r
564 \r
565                 public StringBuilder Insert( int index, float value ) {\r
566                         return Insert( index, value.ToString().ToCharArray() );\r
567                 }\r
568 \r
569                 [CLSCompliant(false)]\r
570                 public StringBuilder Insert( int index, ushort value ) {\r
571                         return Insert( index, value.ToString().ToCharArray() );\r
572                 }\r
573 \r
574                 [CLSCompliant(false)]\r
575                 public StringBuilder Insert( int index, uint value ) {\r
576                         return Insert( index, value.ToString().ToCharArray() );\r
577                 }\r
578                 \r
579                 [CLSCompliant(false)]\r
580                 public StringBuilder Insert( int index, ulong value ) {\r
581                         return Insert( index, value.ToString().ToCharArray() );\r
582                 }\r
583 \r
584                 public StringBuilder Insert( int index, string value, int count ) {\r
585                         if ( count < 0 ) {\r
586                                 throw new ArgumentOutOfRangeException();\r
587                         }\r
588 \r
589                         if( value != null ) {\r
590                                 if( value != "" ) {\r
591                                         for( int insertCount = 0; insertCount < count; \r
592                                                 insertCount++ ) {\r
593                                                 Insert( index, value.ToCharArray() );      \r
594                                         }\r
595                                 }\r
596                         }\r
597                         return this;\r
598                 }\r
599 \r
600                 public StringBuilder Insert( int index, char[] value, int startIndex, \r
601                         int charCount ) {\r
602 \r
603                         if( value != null ) {\r
604                                 if( charCount < 0 || startIndex < 0 || startIndex + charCount > value.Length ) {\r
605                                         throw new ArgumentOutOfRangeException();\r
606                                 }\r
607                                         \r
608                                 char[] insertChars = new char[ charCount  ];\r
609                                 Array.Copy( value, startIndex, insertChars, 0, charCount );\r
610                                 return Insert( index, insertChars );\r
611                         } else {\r
612                                 return this;\r
613                         }\r
614                 }\r
615         }\r
616 }       \r