Wed Oct 6 12:37:54 CEST 2004 Paolo Molaro <lupus@ximian.com>
[mono.git] / mcs / class / corlib / System / String.cs
1 //
2 // System.String.cs
3 //
4 // Authors:
5 //   Patrik Torstensson
6 //   Jeffrey Stedfast (fejj@ximian.com)
7 //   Dan Lewis (dihlewis@yahoo.co.uk)
8 //   Sebastien Pouliot  <sebastien@ximian.com>
9 //
10 // (C) 2001 Ximian, Inc.  http://www.ximian.com
11 // Copyright (C) 2004 Novell (http://www.novell.com)
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 using System;
34 using System.Text;
35 using System.Collections;
36 using System.Globalization;
37 using System.Runtime.CompilerServices;
38
39 namespace System
40 {
41         [Serializable]
42         public sealed class String : IConvertible, ICloneable, IEnumerable,
43 #if NET_2_0
44                 IComparable, IComparable<String>
45 #else
46                 IComparable
47 #endif
48         {
49                 [NonSerialized] private int length;
50                 [NonSerialized] private char start_char;
51
52                 private const int COMPARE_CASE = 0;
53                 private const int COMPARE_INCASE = 1;
54                 private const int COMPARE_ORDINAL = 2;
55
56                 public static readonly String Empty = "";
57
58                 public static unsafe bool Equals (string a, string b)
59                 {
60                         if ((a as object) == (b as object))
61                                 return true;
62
63                         if (a == null || b == null)
64                                 return false;
65
66                         int len = a.length;
67
68                         if (len != b.length)
69                                 return false;
70
71                         if (len == 0)
72                                 return true;
73
74                         fixed (char * s1 = &a.start_char, s2 = &b.start_char) {
75                                 // it must be one char, because 0 len is done above
76                                 if (len < 2)
77                                         return *s1 == *s2;
78
79                                 // check by twos
80                                 int * sint1 = (int *) s1, sint2 = (int *) s2;
81                                 int n2 = len >> 1;
82                                 do {
83                                         if (*sint1++ != *sint2++)
84                                                 return false;
85                                 } while (--n2 != 0);
86
87                                 // nothing left
88                                 if ((len & 1) == 0)
89                                         return true;
90
91                                 // check the last one
92                                 return *(char *) sint1 == *(char *) sint2;
93                         }
94                 }
95
96                 public static bool operator == (String a, String b)
97                 {
98                         return Equals (a, b);
99                 }
100
101                 public static bool operator != (String a, String b)
102                 {
103                         return !Equals (a, b);
104                 }
105
106                 public override bool Equals (Object obj)
107                 {
108                         return Equals (this, obj as String);
109                 }
110
111                 public bool Equals (String value)
112                 {
113                         return Equals (this, value);
114                 }
115
116                 [IndexerName ("Chars")]
117                 public extern char this [int index] {
118                         [MethodImplAttribute (MethodImplOptions.InternalCall)]
119                         get;
120                 }
121
122                 public Object Clone ()
123                 {
124                         return this;
125                 }
126
127                 public TypeCode GetTypeCode ()
128                 {
129                         return TypeCode.String;
130                 }
131
132                 public void CopyTo (int sourceIndex, char[] destination, int destinationIndex, int count)
133                 {
134                         // LAMESPEC: should I null-terminate?
135                         if (destination == null)
136                                 throw new ArgumentNullException ("destination");
137
138                         if (sourceIndex < 0 || destinationIndex < 0 || count < 0)
139                                 throw new ArgumentOutOfRangeException (); 
140
141                         // re-ordered to avoid possible integer overflow
142                         if (sourceIndex > Length - count)
143                                 throw new ArgumentOutOfRangeException ("sourceIndex + count > Length");
144                         // re-ordered to avoid possible integer overflow
145                         if (destinationIndex > destination.Length - count)
146                                 throw new ArgumentOutOfRangeException ("destinationIndex + count > destination.Length");
147
148                         InternalCopyTo (sourceIndex, destination, destinationIndex, count);
149                 }
150
151                 public char[] ToCharArray ()
152                 {
153                         return ToCharArray (0, length);
154                 }
155
156                 public char[] ToCharArray (int startIndex, int length)
157                 {
158                         if (startIndex < 0)
159                                 throw new ArgumentOutOfRangeException ("startIndex", "< 0"); 
160                         if (length < 0)
161                                 throw new ArgumentOutOfRangeException ("length", "< 0"); 
162                         // re-ordered to avoid possible integer overflow
163                         if (startIndex > this.length - length)
164                                 throw new ArgumentOutOfRangeException ("startIndex + length > this.length"); 
165
166                         char[] tmp = new char [length];
167
168                         InternalCopyTo (startIndex, tmp, 0, length);
169
170                         return tmp;
171                 }
172
173                 public String [] Split (params char [] separator)
174                 {
175                         return Split (separator, Int32.MaxValue);
176                 }
177
178                 public String[] Split (char[] separator, int count)
179                 {
180                         if (separator == null || separator.Length == 0)
181                                 separator = WhiteChars;
182
183                         if (count < 0)
184                                 throw new ArgumentOutOfRangeException ("count");
185
186                         if (count == 0) 
187                                 return new String[0];
188
189                         if (count == 1) 
190                                 return new String[1] { ToString() };
191
192                         return InternalSplit (separator, count);
193                 }
194
195                 public String Substring (int startIndex)
196                 {
197                         if (startIndex < 0 || startIndex > this.length)
198                                 throw new ArgumentOutOfRangeException ("startIndex");
199
200                         string tmp = InternalAllocateStr (this.length - startIndex);
201                         InternalStrcpy (tmp, 0, this, startIndex, length - startIndex);
202
203                         return tmp;
204                 }
205
206                 public String Substring (int startIndex, int length)
207                 {
208                         if (length < 0)
209                                 throw new ArgumentOutOfRangeException ("length", "< 0");
210                         if (startIndex < 0)
211                                 throw new ArgumentOutOfRangeException ("startIndex", "< 0");
212                         // re-ordered to avoid possible integer overflow
213                         if (startIndex > this.length - length)
214                                 throw new ArgumentOutOfRangeException ("startIndex + length > this.length");
215
216                         if (length == 0)
217                                 return String.Empty;
218
219                         string tmp = InternalAllocateStr (length);
220                         InternalStrcpy (tmp, 0, this, startIndex, length);
221
222                         return tmp;
223                 }       
224
225                 private static readonly char[] WhiteChars = { (char) 0x9, (char) 0xA, (char) 0xB, (char) 0xC, (char) 0xD,
226                         (char) 0x20, (char) 0xA0, (char) 0x2000, (char) 0x2001, (char) 0x2002, (char) 0x2003, (char) 0x2004,
227                         (char) 0x2005, (char) 0x2006, (char) 0x2007, (char) 0x2008, (char) 0x2009, (char) 0x200A, (char) 0x200B,
228                         (char) 0x3000, (char) 0xFEFF };
229
230                 public String Trim (params char[] trimChars)
231                 {
232                         if (trimChars == null || trimChars.Length == 0)
233                                 trimChars = WhiteChars;
234
235                         return InternalTrim (trimChars, 0);
236                 }
237
238                 public String TrimStart (params char[] trimChars)
239                 {
240                         if (trimChars == null || trimChars.Length == 0)
241                                 trimChars = WhiteChars;
242
243                         return InternalTrim (trimChars, 1);
244                 }
245
246                 public String TrimEnd (params char[] trimChars)
247                 {
248                         if (trimChars == null || trimChars.Length == 0)
249                                 trimChars = WhiteChars;
250
251                         return InternalTrim (trimChars, 2);
252                 }
253
254                 public static int Compare (String strA, String strB)
255                 {
256                         return Compare (strA, strB, false, CultureInfo.CurrentCulture);
257                 }
258
259                 public static int Compare (String strA, String strB, bool ignoreCase)
260                 {
261                         return Compare (strA, strB, ignoreCase, CultureInfo.CurrentCulture);
262                 }
263
264                 public static int Compare (String strA, String strB, bool ignoreCase, CultureInfo culture)
265                 {
266                         if (culture == null)
267                                 throw new ArgumentNullException ("culture");
268
269                         if (strA == null) {
270                                 if (strB == null)
271                                         return 0;
272                                 else
273                                         return -1;
274
275                         }
276                         else if (strB == null) {
277                                 return 1;
278                         }
279
280                         CompareOptions compopts;
281
282                         if (ignoreCase)
283                                 compopts = CompareOptions.IgnoreCase;
284                         else
285                                 compopts = CompareOptions.None;
286
287                         return culture.CompareInfo.Compare (strA, strB, compopts);
288                 }
289
290                 public static int Compare (String strA, int indexA, String strB, int indexB, int length)
291                 {
292                         return Compare (strA, indexA, strB, indexB, length, false, CultureInfo.CurrentCulture);
293                 }
294
295                 public static int Compare (String strA, int indexA, String strB, int indexB, int length, bool ignoreCase)
296                 {
297                         return Compare (strA, indexA, strB, indexB, length, ignoreCase, CultureInfo.CurrentCulture);
298                 }
299                 
300                 public static int Compare (String strA, int indexA, String strB, int indexB, int length, bool ignoreCase, CultureInfo culture)
301                 {
302                         if (culture == null)
303                                 throw new ArgumentNullException ("culture");
304
305                         if ((indexA > strA.Length) || (indexB > strB.Length) || (indexA < 0) || (indexB < 0) || (length < 0))
306                                 throw new ArgumentOutOfRangeException ();
307
308                         if (length == 0)
309                                 return 0;
310                         
311                         if (strA == null) {
312                                 if (strB == null) {
313                                         return 0;
314                                 } else {
315                                         return -1;
316                                 }
317                         }
318                         else if (strB == null) {
319                                 return 1;
320                         }
321
322                         CompareOptions compopts;
323
324                         if (ignoreCase)
325                                 compopts = CompareOptions.IgnoreCase;
326                         else
327                                 compopts = CompareOptions.None;
328
329                         /* Need to cap the requested length to the
330                          * length of the string, because
331                          * CompareInfo.Compare will insist that length
332                          * <= (string.Length - offset)
333                          */
334                         int len1 = length;
335                         int len2 = length;
336                         
337                         if (length > (strA.Length - indexA)) {
338                                 len1 = strA.Length - indexA;
339                         }
340
341                         if (length > (strB.Length - indexB)) {
342                                 len2 = strB.Length - indexB;
343                         }
344
345                         return culture.CompareInfo.Compare (strA, indexA, len1, strB, indexB, len2, compopts);
346                 }
347
348                 public int CompareTo (Object value)
349                 {
350                         if (value == null)
351                                 return 1;
352
353                         if (!(value is String))
354                                 throw new ArgumentException ();
355
356                         return String.Compare (this, (String) value, false);
357                 }
358
359                 public int CompareTo (String strB)
360                 {
361                         if (strB == null)
362                                 return 1;
363
364                         return Compare (this, strB, false);
365                 }
366
367                 public static int CompareOrdinal (String strA, String strB)
368                 {
369                         if (strA == null) {
370                                 if (strB == null)
371                                         return 0;
372                                 else
373                                         return -1;
374                         }
375                         else if (strB == null) {
376                                 return 1;
377                         }
378
379                         /* Invariant, because that is cheaper to
380                          * instantiate (and chances are it already has
381                          * been.)
382                          */
383                         return CultureInfo.InvariantCulture.CompareInfo.Compare (strA, strB, CompareOptions.Ordinal);
384                 }
385
386                 public static int CompareOrdinal (String strA, int indexA, String strB, int indexB, int length)
387                 {
388                         if ((indexA > strA.Length) || (indexB > strB.Length) || (indexA < 0) || (indexB < 0) || (length < 0))
389                                 throw new ArgumentOutOfRangeException ();
390
391                         if (strA == null) {
392                                 if (strB == null)
393                                         return 0;
394                                 else
395                                         return -1;
396                         }
397                         else if (strB == null) {
398                                 return 1;
399                         }
400
401                         /* Need to cap the requested length to the
402                          * length of the string, because
403                          * CompareInfo.Compare will insist that length
404                          * <= (string.Length - offset)
405                          */
406                         int len1 = length;
407                         int len2 = length;
408
409                         if (length > (strA.Length - indexA)) {
410                                 len1 = strA.Length - indexA;
411                         }
412
413                         if (length > (strB.Length - indexB)) {
414                                 len2 = strB.Length - indexB;
415                         }
416
417                         return CultureInfo.InvariantCulture.CompareInfo.Compare (strA, indexA, len1, strB, indexB, len2, CompareOptions.Ordinal);
418                 }
419
420                 public bool EndsWith (String value)
421                 {
422                         if (value == null)
423                                 throw new ArgumentNullException ("value");
424
425                         if (value == String.Empty)
426                                 return true;
427
428                         if (value.length > this.length)
429                                 return false;
430
431                         return (0 == Compare (this, length - value.length, value, 0, value.length));
432                 }
433
434                 public int IndexOfAny (char [] anyOf)
435                 {
436                         if (anyOf == null)
437                                 throw new ArgumentNullException ("anyOf");
438
439                         return InternalIndexOfAny (anyOf, 0, this.length);
440                 }
441
442                 public int IndexOfAny (char [] anyOf, int startIndex)
443                 {
444                         if (anyOf == null)
445                                 throw new ArgumentNullException ("anyOf");
446                         if (startIndex < 0 || startIndex >= this.length)
447                                 throw new ArgumentOutOfRangeException ("sourceIndex");
448
449                         return InternalIndexOfAny (anyOf, startIndex, this.length - startIndex);
450                 }
451
452                 public int IndexOfAny (char [] anyOf, int startIndex, int count)
453                 {
454                         if (anyOf == null)
455                                 throw new ArgumentNullException ("anyOf");
456                         if (startIndex < 0)
457                                 throw new ArgumentOutOfRangeException ("startIndex", "< 0");
458                         if (count < 0)
459                                 throw new ArgumentOutOfRangeException ("count", "< 0");
460                         // re-ordered to avoid possible integer overflow
461                         if (startIndex > this.length - count)
462                                 throw new ArgumentOutOfRangeException ("startIndex + count > this.length");
463
464                         return InternalIndexOfAny (anyOf, startIndex, count);
465                 }
466
467                 public int IndexOf (char value)
468                 {
469                         return IndexOf (value, 0, this.length);
470                 }
471
472                 public int IndexOf (String value)
473                 {
474                         return IndexOf (value, 0, this.length);
475                 }
476
477                 public int IndexOf (char value, int startIndex)
478                 {
479                         return IndexOf (value, startIndex, this.length - startIndex);
480                 }
481
482                 public int IndexOf (String value, int startIndex)
483                 {
484                         return IndexOf (value, startIndex, this.length - startIndex);
485                 }
486
487                 /* This method is culture-insensitive */
488                 public int IndexOf (char value, int startIndex, int count)
489                 {
490                         if (startIndex < 0)
491                                 throw new ArgumentOutOfRangeException ("startIndex", "< 0");
492                         if (count < 0)
493                                 throw new ArgumentOutOfRangeException ("count", "< 0");
494                         // re-ordered to avoid possible integer overflow
495                         if (startIndex > this.length - count)
496                                 throw new ArgumentOutOfRangeException ("startIndex + count > this.length");
497
498                         if ((startIndex == 0 && this.length == 0) || (startIndex == this.length) || (count == 0))
499                                 return -1;
500
501                         for (int pos = startIndex; pos < startIndex + count; pos++) {
502                                 if (this[pos] == value)
503                                         return(pos);
504                         }
505                         return -1;
506                 }
507
508                 /* But this one is culture-sensitive */
509                 public int IndexOf (String value, int startIndex, int count)
510                 {
511                         if (value == null)
512                                 throw new ArgumentNullException ("value");
513                         if (startIndex < 0)
514                                 throw new ArgumentOutOfRangeException ("startIndex", "< 0");
515                         if (count < 0)
516                                 throw new ArgumentOutOfRangeException ("count", "< 0");
517                         // re-ordered to avoid possible integer overflow
518                         if (startIndex > this.length - count)
519                                 throw new ArgumentOutOfRangeException ("startIndex + count > this.length");
520
521                         if (value.length == 0)
522                                 return startIndex;
523
524                         if (startIndex == 0 && this.length == 0)
525                                 return -1;
526
527                         if (count == 0)
528                                 return -1;
529
530                         return CultureInfo.CurrentCulture.CompareInfo.IndexOf (this, value, startIndex, count);
531                 }
532
533                 public int LastIndexOfAny (char [] anyOf)
534                 {
535                         if (anyOf == null)
536                                 throw new ArgumentNullException ("anyOf");
537
538                         return InternalLastIndexOfAny (anyOf, this.length - 1, this.length);
539                 }
540
541                 public int LastIndexOfAny (char [] anyOf, int startIndex)
542                 {
543                         if (anyOf == null) 
544                                 throw new ArgumentNullException ("anyOf");
545
546                         if (startIndex < 0 || startIndex > this.length)
547                                 throw new ArgumentOutOfRangeException ();
548
549                         if (this.length == 0)
550                                 return -1;
551
552                         return InternalLastIndexOfAny (anyOf, startIndex, startIndex + 1);
553                 }
554
555                 public int LastIndexOfAny (char [] anyOf, int startIndex, int count)
556                 {
557                         if (anyOf == null) 
558                                 throw new ArgumentNullException ("anyOf");
559
560                         if ((startIndex < 0) || (startIndex > this.Length))
561                                 throw new ArgumentOutOfRangeException ("startIndex", "< 0 || > this.Length");
562                         if ((count < 0) || (count > this.Length))
563                                 throw new ArgumentOutOfRangeException ("count", "< 0 || > this.Length");
564                         if (startIndex - count + 1 < 0)
565                                 throw new ArgumentOutOfRangeException ("startIndex - count + 1 < 0");
566
567                         if (this.length == 0)
568                                 return -1;
569
570                         return InternalLastIndexOfAny (anyOf, startIndex, count);
571                 }
572
573                 public int LastIndexOf (char value)
574                 {
575                         if (this.length == 0)
576                                 return -1;
577                         else
578                                 return LastIndexOf (value, this.length - 1, this.length);
579                 }
580
581                 public int LastIndexOf (String value)
582                 {
583                         if (this.length == 0)
584                                 /* This overload does additional checking */
585                                 return LastIndexOf (value, 0, 0);
586                         else
587                                 return LastIndexOf (value, this.length - 1, this.length);
588                 }
589
590                 public int LastIndexOf (char value, int startIndex)
591                 {
592                         return LastIndexOf (value, startIndex, startIndex + 1);
593                 }
594
595                 public int LastIndexOf (String value, int startIndex)
596                 {
597                         if (value == null)
598                                 throw new ArgumentNullException ("value");
599                         int max = startIndex;
600                         if (max < this.Length)
601                                 max++;
602                         return LastIndexOf (value, startIndex, max);
603                 }
604
605                 /* This method is culture-insensitive */
606                 public int LastIndexOf (char value, int startIndex, int count)
607                 {
608                         if (startIndex == 0 && this.length == 0)
609                                 return -1;
610
611                         // >= for char (> for string)
612                         if ((startIndex < 0) || (startIndex >= this.Length))
613                                 throw new ArgumentOutOfRangeException ("startIndex", "< 0 || >= this.Length");
614                         if ((count < 0) || (count > this.Length))
615                                 throw new ArgumentOutOfRangeException ("count", "< 0 || > this.Length");
616                         if (startIndex - count + 1 < 0)
617                                 throw new ArgumentOutOfRangeException ("startIndex - count + 1 < 0");
618
619                         for(int pos = startIndex; pos > startIndex - count; pos--) {
620                                 if (this [pos] == value)
621                                         return pos;
622                         }
623                         return -1;
624                 }
625
626                 /* But this one is culture-sensitive */
627                 public int LastIndexOf (String value, int startIndex, int count)
628                 {
629                         if (value == null)
630                                 throw new ArgumentNullException ("value");
631                         // -1 > startIndex > for string (0 > startIndex >= for char)
632                         if ((startIndex < -1) || (startIndex > this.Length))
633                                 throw new ArgumentOutOfRangeException ("startIndex", "< 0 || > this.Length");
634                         if ((count < 0) || (count > this.Length))
635                                 throw new ArgumentOutOfRangeException ("count", "< 0 || > this.Length");
636                         if (startIndex - count + 1 < 0)
637                                 throw new ArgumentOutOfRangeException ("startIndex - count + 1 < 0");
638
639                         if (value == String.Empty)
640                                 return 0;
641
642                         if (startIndex == 0 && this.length == 0)
643                                 return -1;
644
645                         // This check is needed to match undocumented MS behaviour
646                         if (this.length == 0 && value.length > 0)
647                                 return -1;
648
649                         if (value.length > startIndex)
650                                 return -1;
651
652                         if (count == 0)
653                                 return -1;
654
655                         if (startIndex == this.Length)
656                                 startIndex--;
657                         return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf (this, value, startIndex, count);
658                 }
659
660                 public String PadLeft (int totalWidth)
661                 {
662                         return PadLeft (totalWidth, ' ');
663                 }
664
665                 public String PadLeft (int totalWidth, char paddingChar)
666                 {
667                         if (totalWidth < 0)
668                                 throw new ArgumentOutOfRangeException ("totalWidth", "< 0");
669
670                         if (totalWidth < this.length)
671                                 return String.Copy (this);
672
673                         return InternalPad (totalWidth, paddingChar, false);
674                 }
675
676                 public String PadRight (int totalWidth)
677                 {
678                         return PadRight (totalWidth, ' ');
679                 }
680
681                 public String PadRight (int totalWidth, char paddingChar)
682                 {
683                         if (totalWidth < 0)
684                                 throw new ArgumentOutOfRangeException ("totalWidth", "< 0");
685
686                         if (totalWidth < this.length)
687                                 return String.Copy (this);
688
689                         return InternalPad (totalWidth, paddingChar, true);
690                 }
691
692                 public bool StartsWith (String value)
693                 {
694                         if (value == null)
695                                 throw new ArgumentNullException ("value");
696                         
697                         if (value == String.Empty)
698                                 return true;
699
700                         if (this.length < value.length)
701                                 return false;
702
703                         return (0 == Compare (this, 0, value, 0 , value.length));
704                 }
705
706                 /* This method is culture insensitive */
707                 public String Replace (char oldChar, char newChar)
708                 {
709                         return InternalReplace (oldChar, newChar);
710                 }
711
712                 /* This method is culture sensitive */
713                 public String Replace (String oldValue, String newValue)
714                 {
715                         if (oldValue == null)
716                                 throw new ArgumentNullException ("oldValue");
717
718                         if (oldValue == String.Empty)
719                                 throw new ArgumentException ("oldValue is the empty string.");
720
721                         if (this == String.Empty)
722                                 return this;
723
724                         if (oldValue.Length == 0) {
725                                 return(this);
726                         }
727                         
728                         if (newValue == null)
729                                 newValue = String.Empty;
730
731                         return InternalReplace (oldValue, newValue, CultureInfo.CurrentCulture.CompareInfo);
732                 }
733
734                 public String Remove (int startIndex, int count)
735                 {
736                         if (startIndex < 0)
737                                 throw new ArgumentOutOfRangeException ("startIndex", "< 0");
738                         if (count < 0)
739                                 throw new ArgumentOutOfRangeException ("count", "< 0");
740                         // re-ordered to avoid possible integer overflow
741                         if (startIndex > this.length - count)
742                                 throw new ArgumentOutOfRangeException ("startIndex + count > this.length");
743
744                         return InternalRemove (startIndex, count);
745                 }
746
747                 public String ToLower ()
748                 {
749                         // CurrentCulture can never be invariant or null
750                         return InternalToLower (CultureInfo.CurrentCulture);
751                 }
752
753                 public String ToLower (CultureInfo culture)
754                 {
755                         if (culture == null)
756                                 throw new ArgumentNullException ("culture");
757
758                         if (culture.LCID == 0x007F) { // Invariant
759                                 return ToLowerInvariant ();
760                         }
761                         return InternalToLower (culture);
762                 }
763
764                 internal unsafe String ToLowerInvariant ()
765                 {
766                         string tmp = InternalAllocateStr (length);
767                         fixed (char* source = &start_char, dest = tmp) {
768
769                                 char* destPtr = (char*)dest;
770                                 char* sourcePtr = (char*)source;
771
772                                 for (int n = 0; n < length; n++) {
773                                         *destPtr = Char.ToLowerInvariant (*sourcePtr);
774                                         sourcePtr++;
775                                         destPtr++;
776                                 }
777                         }
778                         return tmp;
779                 }
780
781                 public String ToUpper ()
782                 {
783                         // CurrentCulture can never be invariant or null
784                         return InternalToUpper (CultureInfo.CurrentCulture);
785                 }
786
787                 public String ToUpper (CultureInfo culture)
788                 {
789                         if (culture == null)
790                                 throw new ArgumentNullException ("culture");
791
792                         if (culture.LCID == 0x007F) { // Invariant
793                                 return ToUpperInvariant ();
794                         }
795                         return InternalToUpper (culture);
796                 }
797
798                 internal unsafe String ToUpperInvariant ()
799                 {
800                         string tmp = InternalAllocateStr (length);
801                         fixed (char* source = &start_char, dest = tmp) {
802
803                                 char* destPtr = (char*)dest;
804                                 char* sourcePtr = (char*)source;
805
806                                 for (int n = 0; n < length; n++) {
807                                         *destPtr = Char.ToUpperInvariant (*sourcePtr);
808                                         sourcePtr++;
809                                         destPtr++;
810                                 }
811                         }
812                         return tmp;
813                 }
814
815                 public override String ToString ()
816                 {
817                         return this;
818                 }
819
820                 public String ToString (IFormatProvider provider)
821                 {
822                         return this;
823                 }
824
825                 public String Trim ()
826                 {
827                         return Trim (null);
828                 }
829
830                 public static String Format (String format, Object arg0)
831                 {
832                         return Format (null, format, new Object[] {arg0});
833                 }
834
835                 public static String Format (String format, Object arg0, Object arg1)
836                 {
837                         return Format (null, format, new Object[] {arg0, arg1});
838                 }
839
840                 public static String Format (String format, Object arg0, Object arg1, Object arg2)
841                 {
842                         return Format (null, format, new Object[] {arg0, arg1, arg2});
843                 }
844
845                 public static string Format (string format, params object[] args)
846                 {
847                         return Format (null, format, args);
848                 }
849         
850                 public static string Format (IFormatProvider provider, string format, params object[] args)
851                 {
852                         StringBuilder b = new StringBuilder ();
853                         FormatHelper (b, provider, format, args);
854                         return b.ToString ();
855                 }
856                 
857                 internal static void FormatHelper (StringBuilder result, IFormatProvider provider, string format, params object[] args)
858                 {
859                         if (format == null || args == null)
860                                 throw new ArgumentNullException ();
861
862                         int ptr = 0;
863                         int start = ptr;
864                         while (ptr < format.length) {
865                                 char c = format[ptr ++];
866
867                                 if (c == '{') {
868                                         result.Append (format, start, ptr - start - 1);
869
870                                         // check for escaped open bracket
871
872                                         if (format[ptr] == '{') {
873                                                 start = ptr ++;
874                                                 continue;
875                                         }
876
877                                         // parse specifier
878                                 
879                                         int n, width;
880                                         bool left_align;
881                                         string arg_format;
882
883                                         ParseFormatSpecifier (format, ref ptr, out n, out width, out left_align, out arg_format);
884                                         if (n >= args.Length)
885                                                 throw new FormatException ("Index (zero based) must be greater than or equal to zero and less than the size of the argument list.");
886
887                                         // format argument
888
889                                         object arg = args[n];
890
891                                         string str;
892                                         if (arg == null)
893                                                 str = "";
894                                         else if (arg is IFormattable)
895                                                 str = ((IFormattable)arg).ToString (arg_format, provider);
896                                         else
897                                                 str = arg.ToString ();
898
899                                         // pad formatted string and append to result
900
901                                         if (width > str.length) {
902                                                 string pad = new String (' ', width - str.length);
903
904                                                 if (left_align) {
905                                                         result.Append (str);
906                                                         result.Append (pad);
907                                                 }
908                                                 else {
909                                                         result.Append (pad);
910                                                         result.Append (str);
911                                                 }
912                                         }
913                                         else
914                                                 result.Append (str);
915
916                                         start = ptr;
917                                 }
918                                 else if (c == '}' && ptr < format.length && format[ptr] == '}') {
919                                         result.Append (format, start, ptr - start - 1);
920                                         start = ptr ++;
921                                 }
922                                 else if (c == '}') {
923                                         throw new FormatException ("Input string was not in a correct format.");
924                                 }
925                         }
926
927                         if (start < format.length)
928                                 result.Append (format.Substring (start));
929                 }
930
931                 public static String Copy (String str)
932                 {
933                         if (str == null)
934                                 throw new ArgumentNullException ("str");
935
936                         int length = str.length;
937
938                         String tmp = InternalAllocateStr (length);
939                         InternalStrcpy (tmp, 0, str);
940                         return tmp;
941                 }
942
943                 public static String Concat (Object obj)
944                 {
945                         if (obj == null)
946                                 return String.Empty;
947
948                         return obj.ToString ();
949                 }
950
951                 public static String Concat (Object obj1, Object obj2)
952                 {
953                         string s1, s2;
954
955                         s1 = (obj1 != null) ? obj1.ToString () : null;
956                         s2 = (obj2 != null) ? obj2.ToString () : null;
957                         
958                         if (s1 == null) {
959                                 if (s2 == null)
960                                         return String.Empty;
961                                 else
962                                         return s2;
963                         } else if (s2 == null)
964                                 return s1;
965
966                         String tmp = InternalAllocateStr (s1.Length + s2.Length);
967                         InternalStrcpy (tmp, 0, s1);
968                         InternalStrcpy (tmp, s1.length, s2);
969
970                         return tmp;
971                 }
972
973                 public static String Concat (Object obj1, Object obj2, Object obj3)
974                 {
975                         string s1, s2, s3;
976                         if (obj1 == null)
977                                 s1 = String.Empty;
978                         else
979                                 s1 = obj1.ToString ();
980
981                         if (obj2 == null)
982                                 s2 = String.Empty;
983                         else
984                                 s2 = obj2.ToString ();
985
986                         if (obj3 == null)
987                                 s3 = String.Empty;
988                         else
989                                 s3 = obj3.ToString ();
990
991                         return Concat (s1, s2, s3);
992                 }
993
994 #if ! BOOTSTRAP_WITH_OLDLIB
995                 [CLSCompliant(false)]
996                 public static String Concat (Object obj1, Object obj2, Object obj3,
997                                              Object obj4, __arglist)
998                 {
999                         string s1, s2, s3, s4;
1000
1001                         if (obj1 == null)
1002                                 s1 = String.Empty;
1003                         else
1004                                 s1 = obj1.ToString ();
1005
1006                         if (obj2 == null)
1007                                 s2 = String.Empty;
1008                         else
1009                                 s2 = obj2.ToString ();
1010
1011                         if (obj3 == null)
1012                                 s3 = String.Empty;
1013                         else
1014                                 s3 = obj3.ToString ();
1015
1016                         ArgIterator iter = new ArgIterator (__arglist);
1017                         int argCount = iter.GetRemainingCount();
1018
1019                         StringBuilder sb = new StringBuilder ();
1020                         if (obj4 != null)
1021                                 sb.Append (obj4.ToString ());
1022
1023                         for (int i = 0; i < argCount; i++) {
1024                                 TypedReference typedRef = iter.GetNextArg ();
1025                                 sb.Append (TypedReference.ToObject (typedRef));
1026                         }
1027
1028                         s4 = sb.ToString ();
1029
1030                         return Concat (s1, s2, s3, s4);                 
1031                 }
1032 #endif
1033
1034                 public static String Concat (String s1, String s2)
1035                 {
1036                         if (s1 == null) {
1037                                 if (s2 == null)
1038                                         return String.Empty;
1039                                 return s2;
1040                         }
1041
1042                         if (s2 == null)
1043                                 return s1; 
1044
1045                         String tmp = InternalAllocateStr (s1.length + s2.length);
1046
1047                         InternalStrcpy (tmp, 0, s1);
1048                         InternalStrcpy (tmp, s1.length, s2);
1049
1050                         return tmp;
1051                 }
1052
1053                 public static String Concat (String s1, String s2, String s3)
1054                 {
1055                         if (s1 == null){
1056                                 if (s2 == null){
1057                                         if (s3 == null)
1058                                                 return String.Empty;
1059                                         return s3;
1060                                 } else {
1061                                         if (s3 == null)
1062                                                 return s2;
1063                                 }
1064                                 s1 = String.Empty;
1065                         } else {
1066                                 if (s2 == null){
1067                                         if (s3 == null)
1068                                                 return s1;
1069                                         else
1070                                                 s2 = String.Empty;
1071                                 } else {
1072                                         if (s3 == null)
1073                                                 s3 = String.Empty;
1074                                 }
1075                         }
1076
1077                         //return InternalConcat (s1, s2, s3);
1078                         String tmp = InternalAllocateStr (s1.length + s2.length + s3.length);
1079
1080                         InternalStrcpy (tmp, 0, s1);
1081                         InternalStrcpy (tmp, s1.length, s2);
1082                         InternalStrcpy (tmp, s1.length + s2.length, s3);
1083
1084                         return tmp;
1085                 }
1086
1087                 public static String Concat (String s1, String s2, String s3, String s4)
1088                 {
1089                         if (s1 == null && s2 == null && s3 == null && s4 == null)
1090                                 return String.Empty;
1091
1092                         if (s1 == null)
1093                                 s1 = String.Empty;
1094                         if (s2 == null)
1095                                 s2 = String.Empty;
1096                         if (s3 == null)
1097                                 s3 = String.Empty;
1098                         if (s4 == null)
1099                                 s4 = String.Empty;
1100
1101                         String tmp = InternalAllocateStr (s1.length + s2.length + s3.length + s4.length);
1102
1103                         InternalStrcpy (tmp, 0, s1);
1104                         InternalStrcpy (tmp, s1.length, s2);
1105                         InternalStrcpy (tmp, s1.length + s2.length, s3);
1106                         InternalStrcpy (tmp, s1.length + s2.length + s3.length, s4);
1107
1108                         return tmp;
1109                 }
1110
1111                 public static String Concat (params Object[] args)
1112                 {
1113                         if (args == null)
1114                                 throw new ArgumentNullException ("args");
1115
1116                         int i = args.Length;
1117                         if (i == 0)
1118                                 return String.Empty;
1119
1120                         string [] strings = new string [i];
1121                         i = 0;
1122                         int len = 0;
1123                         foreach (object arg in args) {
1124                                 if (arg == null) {
1125                                         strings[i] = String.Empty;
1126                                 } else {
1127                                         strings[i] = arg.ToString ();
1128                                         len += strings[i].length;
1129                                 }
1130                                 i++;
1131                         }
1132
1133                         if (len == 0)
1134                                 return String.Empty;
1135
1136                         return InternalJoin (String.Empty, strings, 0, strings.Length);
1137                 }
1138
1139                 public static String Concat (params String[] values)
1140                 {
1141                         if (values == null)
1142                                 throw new ArgumentNullException ("values");
1143
1144                         return InternalJoin (String.Empty, values, 0, values.Length);
1145                 }
1146
1147                 public String Insert (int startIndex, String value)
1148                 {
1149                         if (value == null)
1150                                 throw new ArgumentNullException ("value");
1151
1152                         if (startIndex < 0 || startIndex > this.length)
1153                                 throw new ArgumentOutOfRangeException ();
1154
1155                         return InternalInsert (startIndex, value);
1156                 }
1157
1158
1159                 public static string Intern (string str)
1160                 {
1161                         if (str == null)
1162                                 throw new ArgumentNullException ("str");
1163
1164                         return InternalIntern (str);
1165                 }
1166
1167                 public static string IsInterned (string str)
1168                 {
1169                         if (str == null)
1170                                 throw new ArgumentNullException ("str");
1171
1172                         return InternalIsInterned (str);
1173                 }
1174         
1175                 public static string Join (string separator, string [] value)
1176                 {
1177                         if (value == null)
1178                                 throw new ArgumentNullException ("value");
1179
1180                         return Join (separator, value, 0, value.Length);
1181                 }
1182
1183                 public static string Join (string separator, string[] value, int startIndex, int count)
1184                 {
1185                         if (value == null)
1186                                 throw new ArgumentNullException ("value");
1187                         if (startIndex < 0)
1188                                 throw new ArgumentOutOfRangeException ("startIndex", "< 0");
1189                         if (count < 0)
1190                                 throw new ArgumentOutOfRangeException ("count", "< 0");
1191                         // re-ordered to avoid possible integer overflow
1192                         if (startIndex > value.Length - count)
1193                                 throw new ArgumentOutOfRangeException ("startIndex + count > value.length");
1194
1195                         if (startIndex == value.Length)
1196                                 return String.Empty;
1197                         if (separator == null)
1198                                 separator = String.Empty;
1199
1200                         return InternalJoin (separator, value, startIndex, count);
1201                 }
1202
1203                 bool IConvertible.ToBoolean (IFormatProvider provider)
1204                 {
1205                         return Convert.ToBoolean (this, provider);
1206                 }
1207
1208                 byte IConvertible.ToByte (IFormatProvider provider)
1209                 {
1210                         return Convert.ToByte (this, provider);
1211                 }
1212
1213                 char IConvertible.ToChar (IFormatProvider provider)
1214                 {
1215                         return Convert.ToChar (this, provider);
1216                 }
1217
1218                 DateTime IConvertible.ToDateTime (IFormatProvider provider)
1219                 {
1220                         return Convert.ToDateTime (this, provider);
1221                 }
1222
1223                 decimal IConvertible.ToDecimal (IFormatProvider provider)
1224                 {
1225                         return Convert.ToDecimal (this, provider);
1226                 }
1227
1228                 double IConvertible.ToDouble (IFormatProvider provider)
1229                 {
1230                         return Convert.ToDouble (this, provider);
1231                 }
1232
1233                 short IConvertible.ToInt16 (IFormatProvider provider)
1234                 {
1235                         return Convert.ToInt16 (this, provider);
1236                 }
1237
1238                 int IConvertible.ToInt32 (IFormatProvider provider)
1239                 {
1240                         return Convert.ToInt32 (this, provider);
1241                 }
1242
1243                 long IConvertible.ToInt64 (IFormatProvider provider)
1244                 {
1245                         return Convert.ToInt64 (this, provider);
1246                 }
1247         
1248                 sbyte IConvertible.ToSByte (IFormatProvider provider)
1249                 {
1250                         return Convert.ToSByte (this, provider);
1251                 }
1252
1253                 float IConvertible.ToSingle (IFormatProvider provider)
1254                 {
1255                         return Convert.ToSingle (this, provider);
1256                 }
1257
1258                 string IConvertible.ToString (IFormatProvider format)
1259                 {
1260                         return this;
1261                 }
1262
1263                 object IConvertible.ToType (Type conversionType, IFormatProvider provider)
1264                 {
1265                         return Convert.ToType (this, conversionType,  provider);
1266                 }
1267
1268                 ushort IConvertible.ToUInt16 (IFormatProvider provider)
1269                 {
1270                         return Convert.ToUInt16 (this, provider);
1271                 }
1272
1273                 uint IConvertible.ToUInt32 (IFormatProvider provider)
1274                 {
1275                         return Convert.ToUInt32 (this, provider);
1276                 }
1277
1278                 ulong IConvertible.ToUInt64 (IFormatProvider provider)
1279                 {
1280                         return Convert.ToUInt64 (this, provider);
1281                 }
1282
1283                 TypeCode IConvertible.GetTypeCode ()
1284                 {
1285                         return TypeCode.String;
1286                 }
1287
1288                 public int Length {
1289                         get {
1290                                 return length;
1291                         }
1292                 }
1293
1294                 public CharEnumerator GetEnumerator ()
1295                 {
1296                         return new CharEnumerator (this);
1297                 }
1298
1299                 IEnumerator IEnumerable.GetEnumerator ()
1300                 {
1301                         return new CharEnumerator (this);
1302                 }
1303
1304                 private static void ParseFormatSpecifier (string str, ref int ptr, out int n, out int width,
1305                                                           out bool left_align, out string format)
1306                 {
1307                         // parses format specifier of form:
1308                         //   N,[\ +[-]M][:F]}
1309                         //
1310                         // where:
1311
1312                         try {
1313                                 // N = argument number (non-negative integer)
1314
1315                                 n = ParseDecimal (str, ref ptr);
1316                                 if (n < 0)
1317                                         throw new FormatException ("Input string was not in a correct format.");
1318
1319                                 // M = width (non-negative integer)
1320
1321                                 if (str[ptr] == ',') {
1322                                         // White space between ',' and number or sign.
1323                                         int start = ++ptr;
1324                                         while (Char.IsWhiteSpace (str [ptr]))
1325                                                 ++ptr;
1326
1327                                         format = str.Substring (start, ptr - start);
1328
1329                                         left_align = (str [ptr] == '-');
1330                                         if (left_align)
1331                                                 ++ ptr;
1332
1333                                         width = ParseDecimal (str, ref ptr);
1334                                         if (width < 0)
1335                                                 throw new FormatException ("Input string was not in a correct format.");
1336                                 }
1337                                 else {
1338                                         width = 0;
1339                                         left_align = false;
1340                                         format = "";
1341                                 }
1342
1343                                 // F = argument format (string)
1344
1345                                 if (str[ptr] == ':') {
1346                                         int start = ++ ptr;
1347                                         while (str[ptr] != '}')
1348                                                 ++ ptr;
1349
1350                                         format += str.Substring (start, ptr - start);
1351                                 }
1352                                 else
1353                                         format = null;
1354
1355                                 if (str[ptr ++] != '}')
1356                                         throw new FormatException ("Input string was not in a correct format.");
1357                         }
1358                         catch (IndexOutOfRangeException) {
1359                                 throw new FormatException ("Input string was not in a correct format.");
1360                         }
1361                 }
1362
1363                 private static int ParseDecimal (string str, ref int ptr)
1364                 {
1365                         int p = ptr;
1366                         int n = 0;
1367                         while (true) {
1368                                 char c = str[p];
1369                                 if (c < '0' || '9' < c)
1370                                         break;
1371
1372                                 n = n * 10 + c - '0';
1373                                 ++ p;
1374                         }
1375
1376                         if (p == ptr)
1377                                 return -1;
1378
1379                         ptr = p;
1380                         return n;
1381                 }
1382
1383                 internal unsafe void InternalSetChar (int idx, char val)
1384                 {
1385                         if ((uint) idx >= (uint) Length)
1386                                 throw new ArgumentOutOfRangeException ("idx");
1387
1388                         fixed (char * pStr = &start_char) 
1389                         {
1390                                 pStr [idx] = val;
1391                         }
1392                 }
1393
1394                 internal unsafe void InternalSetLength (int newLength)
1395                 {
1396                         if (newLength > length)
1397                                 throw new ArgumentOutOfRangeException ("newLength", "newLength as to be <= length");
1398
1399                         length = newLength;
1400
1401                         // zero terminate, we can pass string objects directly via pinvoke
1402                         fixed (char * pStr = &start_char) {
1403                                 pStr [length] = '\0';
1404                         }
1405                 }
1406
1407                 public unsafe override int GetHashCode ()
1408                 {
1409                         fixed (char * c = this) {
1410                                 char * cc = c;
1411                                 char * end = cc + length - 1;
1412                                 int h = 0;
1413                                 for (;cc < end; cc += 2) {
1414                                         h = (h << 5) - h + *cc;
1415                                         h = (h << 5) - h + cc [1];
1416                                 }
1417                                 ++end;
1418                                 if (cc < end)
1419                                         h = (h << 5) - h + *cc;
1420                                 return h;
1421                         }
1422                 }
1423
1424                 [CLSCompliant (false), MethodImplAttribute (MethodImplOptions.InternalCall)]
1425                 unsafe public extern String (char *value);
1426
1427                 [CLSCompliant (false), MethodImplAttribute (MethodImplOptions.InternalCall)]
1428                 unsafe public extern String (char *value, int startIndex, int length);
1429
1430                 [CLSCompliant (false), MethodImplAttribute (MethodImplOptions.InternalCall)]
1431                 unsafe public extern String (sbyte *value);
1432
1433                 [CLSCompliant (false), MethodImplAttribute (MethodImplOptions.InternalCall)]
1434                 unsafe public extern String (sbyte *value, int startIndex, int length);
1435
1436                 [CLSCompliant (false), MethodImplAttribute (MethodImplOptions.InternalCall)]
1437                 unsafe public extern String (sbyte *value, int startIndex, int length, Encoding enc);
1438
1439                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1440                 public extern String (char [] val, int startIndex, int length);
1441
1442                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1443                 public extern String (char [] val);
1444
1445                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1446                 public extern String (char c, int count);
1447
1448                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1449                 private extern static string InternalJoin (string separator, string[] value, int sIndex, int count);
1450
1451                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1452                 private extern String InternalInsert (int sourceIndex, String value);
1453
1454                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1455                 private extern String InternalReplace (char oldChar, char newChar);
1456
1457                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1458                 private extern String InternalReplace (String oldValue, string newValue, CompareInfo comp);
1459
1460                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1461                 private extern String InternalRemove (int sIndex, int count);
1462
1463                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1464                 private extern void InternalCopyTo (int sIndex, char[] dest, int destIndex, int count);
1465
1466                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1467                 private extern String[] InternalSplit (char[] separator, int count);
1468
1469                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1470                 private extern String InternalTrim (char[] chars, int typ);
1471
1472                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1473                 private extern int InternalIndexOfAny (char [] arr, int sIndex, int count);
1474
1475                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1476                 private extern int InternalLastIndexOfAny (char [] anyOf, int sIndex, int count);
1477
1478                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1479                 private extern String InternalPad (int width, char chr, bool right);
1480
1481                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1482                 private extern String InternalToLower (CultureInfo culture);
1483
1484                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1485                 private extern String InternalToUpper (CultureInfo culture);
1486
1487                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1488                 internal extern static String InternalAllocateStr (int length);
1489
1490                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1491                 internal extern static void InternalStrcpy (String dest, int destPos, String src);
1492
1493                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1494                 internal extern static void InternalStrcpy (String dest, int destPos, char[] chars);
1495
1496                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1497                 internal extern static void InternalStrcpy (String dest, int destPos, String src, int sPos, int count);
1498
1499                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1500                 internal extern static void InternalStrcpy (String dest, int destPos, char[] chars, int sPos, int count);
1501
1502                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1503                 private extern static string InternalIntern (string str);
1504
1505                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
1506                 private extern static string InternalIsInterned (string str);
1507         }
1508 }