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