2001-11-21 Miguel de Icaza <miguel@ximian.com>
[mono.git] / mcs / class / corlib / System / String.cs
1 // -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 //
3 // System.String.cs
4 //
5 // Author:
6 //   Jeffrey Stedfast (fejj@ximian.com)
7 //
8 // (C) 2001 Ximian, Inc.  http://www.ximian.com
9 //
10
11 // FIXME: from what I gather from msdn, when a function is to return an empty string
12 //        we should be returning this.Empty - some methods do this and others don't.
13
14 // FIXME: I didn't realise until later that `string' has a .Length method and so
15 //        I am missing some proper bounds-checking in some methods. Find these
16 //        instances and throw the ArgumentOutOfBoundsException at the programmer.
17 //        I like pelting programmers with ArgumentOutOfBoundsException's :-)
18
19 // FIXME: The ToLower(), ToUpper(), and Compare(..., bool ignoreCase) methods
20 //        need to be made unicode aware.
21
22 // FIXME: when you have a char carr[], does carr.Length include the terminating null char?
23
24 using System;
25 using System.Text;
26 using System.Collections;
27 using System.Globalization;
28 using System.Runtime.CompilerServices;
29
30 namespace System {
31
32         public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable {
33                 public static readonly string Empty = "";
34                 private char[] c_str;
35                 private int length;
36
37                 // Constructors
38                 [CLSCompliant(false)]
39                 unsafe public String (char *value)
40                 {
41                         int i;
42
43                         // FIXME: can I do value.Length here?
44                         if (value == null) {
45                                 this.length = 0;
46                         } else {
47                                 for (i = 0; *(value + i) != '\0'; i++);
48                                 this.length = i;
49                         }
50
51                         this.c_str = new char [this.length + 1];
52                         for (i = 0; i < this.length; i++)
53                                 this.c_str[i] = *(value + i);
54                 }
55
56                 public String (char[] value)
57                 {
58                         int i;
59
60                         // FIXME: value.Length includes the terminating null char?
61                         this.length = value != null ? strlen (value): 0;
62                         this.c_str = new char [this.length + 1];
63                         for (i = 0; i < this.length; i++)
64                                 this.c_str[i] = value[i];
65                 }
66
67                 [CLSCompliant(false)]
68                 unsafe public String (sbyte *value)
69                 {
70                         // FIXME: consider unicode?
71                         int i;
72
73                         // FIXME: can I do value.Length here? */
74                         if (value == null) {
75                                 this.length = 0;
76                         } else {
77                                 for (i = 0; *(value + i) != '\0'; i++);
78                                 this.length = i;
79                         }
80
81                         this.c_str = new char [this.length + 1];
82                         for (i = 0; i < this.length; i++)
83                                 this.c_str[i] = (char) *(value + i);
84                 }
85
86                 public String (char c, int count)
87                 {
88                         int i;
89
90                         this.length = count;
91                         this.c_str = new char [count + 1];
92                         for (i = 0; i < count; i++)
93                                 this.c_str[i] = c;
94                 }
95
96                 [CLSCompliant(false)]
97                 unsafe public String (char *value, int startIndex, int length)
98                 {
99                         int i;
100
101                         if (value == null && startIndex != 0 && length != 0)
102                                 throw new ArgumentNullException ();
103
104                         if (startIndex < 0 || length < 0)
105                                 throw new ArgumentOutOfRangeException ();
106
107                         this.length = length;
108                         this.c_str = new char [length + 1];
109                         for (i = 0; i < length; i++)
110                                 this.c_str[i] = *(value + startIndex + i);
111                 }
112
113                 public String (char[] value, int startIndex, int length)
114                 {
115                         int i;
116
117                         if (value == null && startIndex != 0 && length != 0)
118                                 throw new ArgumentNullException ();
119
120                         if (startIndex < 0 || length < 0)
121                                 throw new ArgumentOutOfRangeException ();
122
123                         this.length = length;
124                         this.c_str = new char [length + 1];
125                         for (i = 0; i < length; i++)
126                                 this.c_str[i] = value[startIndex + i];
127                 }
128
129                 [CLSCompliant(false)]
130                 unsafe public String (sbyte *value, int startIndex, int length)
131                 {
132                         // FIXME: consider unicode?
133                         int i;
134
135                         if (value == null && startIndex != 0 && length != 0)
136                                 throw new ArgumentNullException ();
137
138                         if (startIndex < 0 || length < 0)
139                                 throw new ArgumentOutOfRangeException ();
140
141                         this.length = length;
142                         this.c_str = new char [length + 1];
143                         for (i = 0; i < length; i++)
144                                 this.c_str[i] = (char) *(value + startIndex + i);
145                 }
146
147                 [CLSCompliant(false)]
148                 unsafe public String (sbyte *value, int startIndex, int length, Encoding enc)
149                 {
150                         // FIXME: implement me
151                 }
152
153                 ~String ()
154                 {
155                         // FIXME: is there anything we need to do here?
156                         /*base.Finalize ();*/
157                 }
158
159                 // Properties
160                 public int Length {
161                         get {
162                                 return this.length;
163                         }
164                 }
165
166                 // FIXME: is this correct syntax??
167                 public char this [int index] {
168                         get {
169                                 if (index >= this.length)
170                                         throw new ArgumentOutOfRangeException ();
171
172                                 return this.c_str[index];
173                         }
174                 }
175
176                 // Private helper methods
177                 private static int strlen (char[] str)
178                 {
179                         // FIXME: if str.Length includes terminating null char, then return (str.Length - 1)
180                         return str.Length;
181                 }
182
183                 private static char tolowerordinal (char c)
184                 {
185                         // FIXME: implement me
186                         return c;
187                 }
188
189                 private static bool is_lwsp (char c)
190                 {
191                         /* this comes from the msdn docs for String.Trim() */
192                         if ((c >= '\x9' && c <= '\xD') || c == '\x20' || c == '\xA0' ||
193                             (c >= '\x2000' && c <= '\x200B') || c == '\x3000' || c == '\xFEFF')
194                                 return true;
195                         else
196                                 return false;
197                 }
198
199                 private static int BoyerMoore (char[] haystack, string needle, int startIndex, int count)
200                 {
201                         /* (hopefully) Unicode-safe Boyer-Moore implementation */
202                         int[] skiptable = new int[65536];  /* our unicode-safe skip-table */
203                         int h, n, he, ne, hc, nc, i;
204
205                         if (haystack == null || needle == null)
206                                 throw new ArgumentNullException ();
207
208                         /* if the search buffer is shorter than the pattern buffer, we can't match */
209                         if (count < needle.Length)
210                                 return -1;
211
212                         /* return an instant match if the pattern is 0-length */
213                         if (needle.Length == 0)
214                                 return startIndex;
215
216                         /* set a pointer at the end of each string */
217                         ne = needle.Length - 1;      /* position of char before '\0' */
218                         he = startIndex + count;     /* position of last valid char */
219
220                         /* init the skip table with the pattern length */
221                         for (i = 0; i < 65536; i++)
222                                 skiptable[i] = needle.Length;
223
224                         /* set the skip value for the chars that *do* appear in the
225                          * pattern buffer (needle) to the distance from the index to
226                          * the end of the pattern buffer. */
227                         for (nc = 0; nc < ne; nc++)
228                                 skiptable[(int) needle[nc]] = ne - nc;
229
230                         h = startIndex;
231                         while (count >= needle.Length) {
232                                 hc = h + needle.Length - 1;  /* set the haystack compare pointer */
233                                 nc = ne;                     /* set the needle compare pointer */
234
235                                 /* work our way backwards until they don't match */
236                                 for (i = 0; nc > 0; nc--, hc--, i++)
237                                         if (needle[nc] != haystack[hc])
238                                                 break;
239
240                                 if (needle[nc] != haystack[hc]) {
241                                         n = skiptable[(int) haystack[hc]] - i;
242                                         h += n;
243                                         count -= n;
244                                 } else
245                                         return h;
246                         }
247
248                         return -1;
249                 }
250
251                 // Methods
252                 public object Clone ()
253                 {
254                         // FIXME: implement me
255                         return null;
256                 }
257
258                 public static int Compare (string strA, string strB)
259                 {
260                         int min, i;
261
262                         if (strA == null) {
263                                 if (strB == null)
264                                         return 0;
265                                 else
266                                         return -1;
267                         } else if (strB == null)
268                                 return 1;
269
270                         min = strA.Length < strB.Length ? strA.Length : strB.Length;
271
272                         for (i = 0; strA[i] == strB[i] && i < min; i++);
273
274                         return ((int) (strA[i] - strB[i]));
275                 }
276
277                 public static int Compare (string strA, string strB, bool ignoreCase)
278                 {
279                         int min, i;
280
281                         if (!ignoreCase)
282                                 return Compare (strA, strB);
283
284                         /*
285                          * And here I thought Eazel developers were on crack...
286                          * if a string is null it should pelt the programmer with
287                          * ArgumentNullExceptions, damnit!
288                          */
289                         if (strA == null) {
290                                 if (strB == null)
291                                         return 0;
292                                 else
293                                         return -1;
294                         } else if (strB == null)
295                                 return 1;
296
297                         min = strA.Length < strB.Length ? strA.Length : strB.Length;
298
299                         for (i = 0; i < min; i++) {
300                                 if (Char.ToLower (strA[i]) != Char.ToLower (strB[i]))
301                                         break;
302                         }
303
304                         return ((int) (strA[i] - strB[i]));
305                 }
306
307                 public static int Compare (string strA, string strB, bool ignoreCase, CultureInfo culture)
308                 {
309                         // FIXME: implement me
310                         return 0;
311                 }
312
313                 public static int Compare (string strA, int indexA, string strB, int indexB, int length)
314                 {
315                         int i;
316
317                         if (length < 0 || indexA < 0 || indexB < 0)
318                                 throw new ArgumentOutOfRangeException ();
319
320                         if (indexA > strA.Length || indexB > strB.Length)
321                                 throw new ArgumentOutOfRangeException ();
322
323                         /* And again with the ("" > null) logic... lord have mercy! */
324                         if (strA == null) {
325                                 if (strB == null)
326                                         return 0;
327                                 else
328                                         return -1;
329                         } else if (strB == null)
330                                 return 1;
331
332                         for (i = 0; i < length - 1; i++) {
333                                 if (strA[indexA + i] != strB[indexB + i])
334                                         break;
335                         }
336
337                         return ((int) (strA[indexA + i] - strB[indexB + i]));
338                 }
339
340                 public static int Compare (string strA, int indexA, string strB, int indexB,
341                                            int length, bool ignoreCase)
342                 {
343                         int i;
344
345                         if (!ignoreCase)
346                                 return Compare (strA, indexA, strB, indexB, length);
347
348                         if (length < 0 || indexA < 0 || indexB < 0)
349                                 throw new ArgumentOutOfRangeException ();
350
351                         if (indexA > strA.Length || indexB > strB.Length)
352                                 throw new ArgumentOutOfRangeException ();
353
354                         /* When will the hurting stop!?!? */
355                         if (strA == null) {
356                                 if (strB == null)
357                                         return 0;
358                                 else
359                                         return -1;
360                         } else if (strB == null)
361                                 return 1;
362
363                         for (i = 0; i < length - 1; i++) {
364                                 if (Char.ToLower (strA[indexA + i]) != Char.ToLower (strB[indexB + i]))
365                                         break;
366                         }
367
368                         return ((int) (strA[indexA + i] - strB[indexB + i]));
369                 }
370
371                 public static int Compare (string strA, int indexA, string strB, int indexB,
372                                            int length, bool ignoreCase, CultureInfo culture)
373                 {
374                         if (culture == null)
375                                 throw new ArgumentNullException ();
376
377                         if (length < 0 || indexA < 0 || indexB < 0)
378                                 throw new ArgumentOutOfRangeException ();
379
380                         if (indexA > strA.Length || indexB > strB.Length)
381                                 throw new ArgumentOutOfRangeException ();
382
383                         /* I can't take it anymore! */
384                         if (strA == null) {
385                                 if (strB == null)
386                                         return 0;
387                                 else
388                                         return -1;
389                         } else if (strB == null)
390                                 return 1;
391
392                         // FIXME: implement me
393                         return 0;
394                 }
395
396                 public static int CompareOrdinal (string strA, string strB)
397                 {
398                         int i;
399
400                         /* Please God, make it stop! */
401                         if (strA == null) {
402                                 if (strB == null)
403                                         return 0;
404                                 else
405                                         return -1;
406                         } else if (strB == null)
407                                 return 1;
408
409                         for (i = 0; i < strA.Length && i < strB.Length; i++) {
410                                 char cA, cB;
411
412                                 cA = tolowerordinal (strA[i]);
413                                 cB = tolowerordinal (strB[i]);
414
415                                 if (cA != cB)
416                                         break;
417                         }
418
419                         return ((int) (strA[i] - strB[i]));
420                 }
421
422                 public static int CompareOrdinal (string strA, int indexA, string strB, int indexB,
423                                                   int length)
424                 {
425                         int i;
426
427                         if (length < 0 || indexA < 0 || indexB < 0)
428                                 throw new ArgumentOutOfRangeException ();
429
430                         if (strA == null) {
431                                 if (strB == null)
432                                         return 0;
433                                 else
434                                         return -1;
435                         } else if (strB == null)
436                                 return 1;
437
438                         for (i = 0; i < length; i++) {
439                                 char cA, cB;
440
441                                 cA = tolowerordinal (strA[indexA + i]);
442                                 cB = tolowerordinal (strB[indexB + i]);
443
444                                 if (cA != cB)
445                                         break;
446                         }
447
448                         return ((int) (strA[indexA + i] - strB[indexB + i]));
449                 }
450
451                 public int CompareTo (object obj)
452                 {
453                         return Compare (this, obj == null ? null : obj.ToString ());
454                 }
455
456                 public int CompareTo (string str)
457                 {
458                         return Compare (this, str);
459                 }
460
461                 public static string Concat (object arg)
462                 {
463                         return arg != null ? arg.ToString () : String.Empty;
464                 }
465
466                 public static string Concat (params object[] args)
467                 {
468                         string[] strings;
469                         char[] str;
470                         int len, i;
471
472                         if (args == null)
473                                 throw new ArgumentNullException ();
474
475                         strings = new string [args.Length];
476                         len = 0;
477                         i = 0;
478                         foreach (object arg in args) {
479                                 /* use Empty for each null argument */
480                                 if (arg == null)
481                                         strings[i] = String.Empty;
482                                 else
483                                         strings[i] = arg.ToString ();
484                                 len += strings[i].Length;
485                                 i++;
486                         }
487
488                         if (len == 0)
489                                 return String.Empty;
490
491                         str = new char [len];
492                         i = 0;
493                         for (int j = 0; j < strings.Length; j++)
494                                 for (int k = 0; k < strings[j].Length; k++)
495                                         str[i++] = strings[j][k];
496
497                         return new String (str);
498                 }
499
500                 public static string Concat (params string[] values)
501                 {
502                         int len, i;
503                         char[] str;
504
505                         if (values == null)
506                                 throw new ArgumentNullException ();
507
508                         len = 0;
509                         foreach (string value in values)
510                                 len += value != null ? value.Length : 0;
511
512                         if (len == 0)
513                                 return String.Empty;
514
515                         str = new char [len];
516                         i = 0;
517                         foreach (string value in values) {
518                                 if (value == null)
519                                         continue;
520
521                                 for (int j = 0; j < value.Length; j++)
522                                         str[i++] = value[j];
523                         }
524
525                         return new String (str);
526                 }
527
528                 public static string Concat (object arg0, object arg1)
529                 {
530                         string str0 = arg0 != null ? arg0.ToString () : String.Empty;
531                         string str1 = arg1 != null ? arg1.ToString () : String.Empty;
532
533                         return Concat (str0, str1);
534                 }
535
536                 public static string Concat (string str0, string str1)
537                 {
538                         char[] concat;
539                         int i, j, len;
540
541                         if (str0 == null)
542                                 str0 = String.Empty;
543                         if (str1 == null)
544                                 str1 = String.Empty;
545
546                         len = str0.Length + str1.Length;
547                         if (len == 0)
548                                 return String.Empty;
549
550                         concat = new char [len];
551                         for (i = 0; i < str0.Length; i++)
552                                 concat[i] = str0[i];
553                         for (j = 0 ; j < str1.Length; j++)
554                                 concat[i + j] = str1[j];
555
556                         return new String (concat);
557                 }
558
559                 public static string Concat (object arg0, object arg1, object arg2)
560                 {
561                         string str0 = arg0 != null ? arg0.ToString () : String.Empty;
562                         string str1 = arg1 != null ? arg1.ToString () : String.Empty;
563                         string str2 = arg2 != null ? arg2.ToString () : String.Empty;
564
565                         return Concat (str0, str1, str2);
566                 }
567
568                 public static string Concat (string str0, string str1, string str2)
569                 {
570                         char[] concat;
571                         int i, j, k, len;
572
573                         if (str0 == null)
574                                 str0 = String.Empty;
575                         if (str1 == null)
576                                 str1 = String.Empty;
577                         if (str2 == null)
578                                 str2 = String.Empty;
579
580                         len = str0.Length + str1.Length + str2.Length;
581                         if (len == 0)
582                                 return String.Empty;
583
584                         concat = new char [len];
585                         for (i = 0; i < str0.Length; i++)
586                                 concat[i] = str0[i];
587                         for (j = 0; j < str1.Length; j++)
588                                 concat[i + j] = str1[j];
589                         for (k = 0; k < str2.Length; k++)
590                                 concat[i + j + k] = str2[k];
591
592                         return new String (concat);
593                 }
594
595                 public static string Concat (string str0, string str1, string str2, string str3)
596                 {
597                         char[] concat;
598                         int i, j, k, l, len;
599
600                         if (str0 == null)
601                                 str0 = String.Empty;
602                         if (str1 == null)
603                                 str1 = String.Empty;
604                         if (str2 == null)
605                                 str2 = String.Empty;
606                         if (str3 == null)
607                                 str3 = String.Empty;
608
609                         len = str0.Length + str1.Length + str2.Length + str3.Length;
610                         if (len == 0)
611                                 return String.Empty;
612
613                         concat = new char [len];
614                         for (i = 0; i < str0.Length; i++)
615                                 concat[i] = str0[i];
616                         for (j = 0; j < str1.Length; j++)
617                                 concat[i + j] = str1[j];
618                         for (k = 0; k < str2.Length; k++)
619                                 concat[i + j + k] = str2[k];
620                         for (l = 0; l < str3.Length; l++)
621                                 concat[i + j + k + l] = str3[l];
622
623                         return new String (concat);
624                 }
625
626                 public static string Copy (string str)
627                 {
628                         // FIXME: how do I *copy* a string if I can only have 1 of each?
629                         if (str == null)
630                                 throw new ArgumentNullException ();
631
632                         return str;
633                 }
634
635                 public void CopyTo (int sourceIndex, char[] destination, int destinationIndex, int count)
636                 {
637                         // LAMESPEC: should I null-terminate?
638                         int i;
639
640                         if (destination == null)
641                                 throw new ArgumentNullException ();
642
643                         if (sourceIndex < 0 || destinationIndex < 0 || count < 0)
644                                 throw new ArgumentOutOfRangeException ();
645
646                         if (sourceIndex + count > this.length)
647                                 throw new ArgumentOutOfRangeException ();
648
649                         if (destinationIndex + count > destination.Length)
650                                 throw new ArgumentOutOfRangeException ();
651
652                         for (i = 0; i < count; i++)
653                                 destination[destinationIndex + i] = this.c_str[sourceIndex + i];
654                 }
655
656                 public bool EndsWith (string value)
657                 {
658                         bool endswith = true;
659                         int start, i;
660
661                         if (value == null)
662                                 throw new ArgumentNullException ();
663
664                         start = this.length - value.Length;
665                         if (start < 0)
666                                 return false;
667
668                         for (i = start; i < this.length && endswith; i++)
669                                 endswith = this.c_str[i] == value[i - start];
670
671                         return endswith;
672                 }
673
674                 public override bool Equals (object obj)
675                 {
676                         if (!(obj is String))
677                                 return false;
678
679                         return this == (String) obj;
680                 }
681
682                 public bool Equals (string value)
683                 {
684                         return this == value;
685                 }
686
687                 public static bool Equals (string a, string b)
688                 {
689                         return a == b;
690                 }
691
692                 public static string Format (string format, object arg0)
693                 {
694                         // FIXME: implement me
695                         return null;
696                 }
697
698                 public static string Format (string format, params object[] args)
699                 {
700                         // FIXME: implement me
701                         return null;
702                 }
703
704                 public static string Format (IFormatProvider provider, string format, params object[] args)
705                 {
706                         // FIXME: implement me
707                         return null;
708                 }
709
710                 public static string Format (string format, object arg0, object arg1)
711                 {
712                         // FIXME: implement me
713                         return null;
714                 }
715
716                 public static string Format (string format, object arg0, object arg1, object arg2)
717                 {
718                         // FIXME: implement me
719                         return null;
720                 }
721
722                 //public CharEnumerator GetEnumerator ()
723                 public IEnumerator GetEnumerator ()
724                 {
725                         // FIXME: implement me
726                         return null;
727                 }
728
729                 public override int GetHashCode ()
730                 {
731                         // FIXME: implement me
732                         return 0;
733                 }
734
735                 public new Type GetType ()
736                 {
737                         // FIXME: implement me
738                         return null;
739                 }
740
741                 public TypeCode GetTypeCode ()
742                 {
743                         // FIXME: implement me
744                         return 0;
745                 }
746
747                 public int IndexOf (char value)
748                 {
749                         return IndexOf (value, 0, this.length);
750                 }
751
752                 public int IndexOf (string value)
753                 {
754                         return IndexOf (value, 0, this.length);
755                 }
756
757                 public int IndexOf (char value, int startIndex)
758                 {
759                         return IndexOf (value, startIndex, this.length - startIndex);
760                 }
761
762                 public int IndexOf (string value, int startIndex)
763                 {
764                         return IndexOf (value, startIndex, this.length - startIndex);
765                 }
766
767                 public int IndexOf (char value, int startIndex, int count)
768                 {
769                         int i;
770
771                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
772                                 throw new ArgumentOutOfRangeException ();
773
774                         for (i = startIndex; i - startIndex < count; i++)
775                                 if (this.c_str[i] == value)
776                                         return i;
777
778                         return -1;
779                 }
780
781                 public int IndexOf (string value, int startIndex, int count)
782                 {
783                         if (value == null)
784                                 throw new ArgumentNullException ();
785
786                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
787                                 throw new ArgumentOutOfRangeException ();
788
789                         return BoyerMoore (this.c_str, value, startIndex, count);
790 #if XXX
791                         int i;
792                         for (i = startIndex; i - startIndex + value.Length <= count; ) {
793                                 if (this.c_str[i] == value[0]) {
794                                         bool equal = true;
795                                         int j, nexti = 0;
796
797                                         for (j = 1; equal && j < value.Length; j++) {
798                                                 equal = this.c_str[i + j] == value[j];
799                                                 if (this.c_str[i + j] == value[0] && nexti == 0)
800                                                         nexti = i + j;
801                                         }
802
803                                         if (equal)
804                                                 return i;
805
806                                         if (nexti != 0)
807                                                 i = nexti;
808                                         else
809                                                 i += j;
810                                 } else
811                                         i++;
812                         }
813
814                         return -1;
815 #endif
816                 }
817
818                 public int IndexOfAny (char[] values)
819                 {
820                         return IndexOfAny (values, 0, this.length);
821                 }
822
823                 public int IndexOfAny (char[] values, int startIndex)
824                 {
825                         return IndexOfAny (values, startIndex, this.length - startIndex);
826                 }
827
828                 public int IndexOfAny (char[] values, int startIndex, int count)
829                 {
830                         if (values == null)
831                                 throw new ArgumentNullException ();
832
833                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
834                                 throw new ArgumentOutOfRangeException ();
835
836                         for (int i = startIndex; i < startIndex + count; i++) {
837                                 for (int j = 0; j < strlen (values); j++) {
838                                         if (this.c_str[i] == values[j])
839                                                 return i;
840                                 }
841                         }
842
843                         return -1;
844                 }
845
846                 public string Insert (int startIndex, string value)
847                 {
848                         char[] str;
849                         int i, j;
850
851                         if (value == null)
852                                 throw new ArgumentNullException ();
853
854                         if (startIndex < 0 || startIndex > this.length)
855                                 throw new ArgumentOutOfRangeException ();
856
857                         str = new char [value.Length + this.length];
858                         for (i = 0; i < startIndex; i++)
859                                 str[i] = this.c_str[i];
860                         for (j = 0; j < value.Length; j++)
861                                 str[i + j] = value[j];
862                         for ( ; i < this.length; i++)
863                                 str[i + j] = this.c_str[i];
864
865                         return new String (str);
866                 }
867
868                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
869                 public extern static string Intern (string str);
870
871                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
872                 public extern static string IsInterned (string str);
873
874                 public static string Join (string separator, string[] value)
875                 {
876                         return Join (separator, value, 0, value.Length);
877                 }
878
879                 public static string Join (string separator, string[] value, int startIndex, int count)
880                 {
881                         // LAMESPEC: msdn doesn't specify what happens when separator is null
882                         int len, i, j, used;
883                         char[] str;
884
885                         if (separator == null || value == null)
886                                 throw new ArgumentNullException ();
887
888                         if (startIndex + count > value.Length)
889                                 throw new ArgumentOutOfRangeException ();
890
891                         len = 0;
892                         for (i = startIndex, used = 0; used < count; i++, used++) {
893                                 if (i != startIndex)
894                                         len += separator.Length;
895
896                                 len += value[i].Length;
897                         }
898
899                         // We have no elements to join?
900                         if (i == startIndex)
901                                 return String.Empty;
902
903                         str = new char [len];
904                         for (i = 0; i < value[startIndex].Length; i++)
905                                 str[i] = value[startIndex][i];
906
907                         used = 1;
908                         for (j = startIndex + 1; used < count; j++, used++) {
909                                 int k;
910
911                                 for (k = 0; k < separator.Length; k++)
912                                         str[i++] = separator[k];
913                                 for (k = 0; k < value[j].Length; k++)
914                                         str[i++] = value[j][k];
915                         }
916
917                         return new String (str);
918                 }
919
920                 public int LastIndexOf (char value)
921                 {
922                         int i = this.length;
923                         if (i == 0)
924                                 return -1;
925                         --i;
926                         for (; i >= 0; i--) {
927                                 if (this.c_str[i] == value)
928                                         return i;
929                         }
930
931                         return -1;
932                 }
933
934                 public int LastIndexOf (string value)
935                 {
936                         return LastIndexOf (value, this.length, this.length);
937                 }
938
939                 public int LastIndexOf (char value, int startIndex)
940                 {
941                         if (startIndex < 0 || startIndex > this.length)
942                                 throw new ArgumentOutOfRangeException ();
943
944                         for (int i = startIndex; i >= 0; i--) {
945                                 if (this.c_str[i] == value)
946                                         return i;
947                         }
948
949                         return -1;
950                 }
951
952                 public int LastIndexOf (string value, int startIndex)
953                 {
954                         return LastIndexOf (value, startIndex, this.length);
955                 }
956
957                 public int LastIndexOf (char value, int startIndex, int count)
958                 {
959                         if (startIndex < 0 || count < 0)
960                                 throw new ArgumentOutOfRangeException ();
961
962                         if (startIndex > this.length || startIndex - count < 0)
963                                 throw new ArgumentOutOfRangeException ();
964
965                         for (int i = startIndex; i >= startIndex - count; i--) {
966                                 if (this.c_str[i] == value)
967                                         return i;
968                         }
969
970                         return -1;
971                 }
972
973                 public int LastIndexOf (string value, int startIndex, int count)
974                 {
975                         // LAMESPEC: currently I'm using startIndex as the 0-position in the comparison,
976                         //           but maybe it's the end-position in MS's implementation?
977                         //           msdn is unclear on this point. I think this is correct though.
978                         int i, len;
979
980                         if (value == null)
981                                 throw new ArgumentNullException ();
982
983                         if (startIndex < 0 || startIndex > this.length)
984                                 throw new ArgumentOutOfRangeException ();
985
986                         if (count < 0 || startIndex - count < 0)
987                                 throw new ArgumentOutOfRangeException ();
988
989                         if (value == String.Empty)
990                                 return startIndex;
991
992                         if (startIndex + value.Length > this.length) {
993                                 /* just a little optimization */
994                                 int start;
995
996                                 start = this.length - value.Length;
997                                 count -= startIndex - start;
998                                 startIndex = start;
999                         }
1000
1001                         // FIXME: use a reversed-unicode-safe-Boyer-Moore?
1002                         len = value.Length - 1;
1003                         for (i = startIndex; i >= startIndex - count; i--) {
1004                                 if (this.c_str[i + len] == value[len]) {
1005                                         bool equal = true;
1006                                         int j;
1007
1008                                         for (j = len - 1; equal && j >= 0; j--)
1009                                                 equal = this.c_str[i + j] == value[j];
1010
1011                                         if (equal)
1012                                                 return i;
1013                                 }
1014                         }
1015
1016                         return -1;
1017                 }
1018
1019                 public int LastIndexOfAny (char[] values)
1020                 {
1021                         return LastIndexOfAny (values, this.length, this.length);
1022                 }
1023
1024                 public int LastIndexOfAny (char[] values, int startIndex)
1025                 {
1026                         return LastIndexOfAny (values, startIndex, startIndex);
1027                 }
1028
1029                 public int LastIndexOfAny (char[] values, int startIndex, int count)
1030                 {
1031                         int i;
1032
1033                         if (values == null)
1034                                 throw new ArgumentNullException ();
1035
1036                         if (startIndex < 0 || count < 0 || startIndex - count < 0)
1037                                 throw new ArgumentOutOfRangeException ();
1038
1039                         for (i = startIndex; i >= startIndex - count; i--) {
1040                                 for (int j = 0; j < strlen (values); j++) {
1041                                         if (this.c_str[i] == values[j])
1042                                                 return i;
1043                                 }
1044                         }
1045
1046                         return -1;
1047                 }
1048
1049                 public string PadLeft (int totalWidth)
1050                 {
1051                         return PadLeft (totalWidth, ' ');
1052                 }
1053
1054                 public string PadLeft (int totalWidth, char padChar)
1055                 {
1056                         char[] str;
1057                         int i, j;
1058
1059                         if (totalWidth < 0)
1060                                 throw new ArgumentException ();
1061
1062                         str = new char [totalWidth > this.length ? totalWidth : this.length];
1063                         for (i = 0; i < totalWidth - this.length; i++)
1064                                 str[i] = padChar;
1065
1066                         for (j = 0; j < this.length; i++, j++)
1067                                 str[i] = this.c_str[j];
1068
1069                         return new String (str);
1070                 }
1071
1072                 public string PadRight (int totalWidth)
1073                 {
1074                         return PadRight (totalWidth, ' ');
1075                 }
1076
1077                 public string PadRight (int totalWidth, char padChar)
1078                 {
1079                         char[] str;
1080                         int i;
1081
1082                         if (totalWidth < 0)
1083                                 throw new ArgumentException ();
1084
1085                         str = new char [totalWidth > this.length ? totalWidth : this.length];
1086                         for (i = 0; i < this.length; i++)
1087                                 str[i] = this.c_str[i];
1088
1089                         for ( ; i < str.Length; i++)
1090                                 str[i] = padChar;
1091
1092                         return new String (str);
1093                 }
1094
1095                 public string Remove (int startIndex, int count)
1096                 {
1097                         char[] str;
1098                         int i, j, len;
1099
1100                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
1101                                 throw new ArgumentOutOfRangeException ();
1102
1103                         len = this.length - count;
1104                         if (len == 0)
1105                                 return String.Empty;
1106
1107                         str = new char [len];
1108                         for (i = 0; i < startIndex; i++)
1109                                 str[i] = this.c_str[i];
1110                         for (j = i + count; j < this.length; j++)
1111                                 str[i++] = this.c_str[j];
1112
1113                         return new String (str);
1114                 }
1115
1116                 public string Replace (char oldChar, char newChar)
1117                 {
1118                         char[] str;
1119                         int i;
1120
1121                         str = new char [this.length];
1122                         for (i = 0; i < this.length; i++) {
1123                                 if (this.c_str[i] == oldChar)
1124                                         str[i] = newChar;
1125                                 else
1126                                         str[i] = this.c_str[i];
1127                         }
1128
1129                         return new String (str);
1130                 }
1131
1132                 public string Replace (string oldValue, string newValue)
1133                 {
1134                         // LAMESPEC: msdn doesn't specify what to do if either args is null
1135                         int index, len, i, j;
1136                         char[] str;
1137
1138                         if (oldValue == null || newValue == null)
1139                                 throw new ArgumentNullException ();
1140
1141                         // Use IndexOf in case I later rewrite it to use Boyer-Moore
1142                         index = IndexOf (oldValue, 0);
1143                         if (index == -1) {
1144                                 // This is the easy one ;-)
1145                                 return Substring (0, this.length);
1146                         }
1147
1148                         len = this.length - oldValue.Length + newValue.Length;
1149                         if (len == 0)
1150                                 return String.Empty;
1151
1152                         str = new char [len];
1153                         for (i = 0; i < index; i++)
1154                                 str[i] = this.c_str[i];
1155                         for (j = 0; j < newValue.Length; j++)
1156                                 str[i++] = newValue[j];
1157                         for (j = index + oldValue.Length; j < this.length; j++)
1158                                 str[i++] = this.c_str[j];
1159
1160                         return new String (str);
1161                 }
1162
1163                 private int splitme (char[] separators, int startIndex)
1164                 {
1165                         /* this is basically a customized IndexOfAny() for the Split() methods */
1166                         for (int i = startIndex; i < this.length; i++) {
1167                                 if (separators != null) {
1168                                         foreach (char sep in separators) {
1169                                                 if (this.c_str[i] == sep)
1170                                                         return i - startIndex;
1171                                         }
1172                                 } else if (is_lwsp (this.c_str[i])) {
1173                                         return i - startIndex;
1174                                 }
1175                         }
1176
1177                         return -1;
1178                 }
1179
1180                 public string[] Split (params char[] separator)
1181                 {
1182                         /**
1183                          * split:
1184                          * @separator: delimiting chars or null to split on whtspc
1185                          *
1186                          * Returns: 1. An array consisting of a single
1187                          * element (@this) if none of the delimiting
1188                          * chars appear in @this. 2. An array of
1189                          * substrings which are delimited by one of
1190                          * the separator chars. 3. An array of
1191                          * substrings separated by whitespace if
1192                          * @separator is null. The Empty string should
1193                          * be returned wherever 2 delimiting chars are
1194                          * adjacent.
1195                          **/
1196                         // FIXME: would using a Queue be better?
1197                         string[] strings;
1198                         ArrayList list;
1199                         int index, len;
1200
1201                         list = new ArrayList ();
1202                         for (index = 0, len = 0; index < this.length; index += len + 1) {
1203                                 len = splitme (separator, index);
1204                                 len = len > -1 ? len : this.length - index;
1205                                 if (len == 0) {
1206                                         list.Add (String.Empty);
1207                                 } else {
1208                                         char[] str;
1209                                         int i;
1210
1211                                         str = new char [len];
1212                                         for (i = 0; i < len; i++)
1213                                                 str[i] = this.c_str[index + i];
1214
1215                                         list.Add (new String (str));
1216                                 }
1217                         }
1218
1219                         strings = new string [list.Count];
1220                         if (list.Count == 1) {
1221                                 /* special case for an array holding @this */
1222                                 strings[0] = this;
1223                         } else {
1224                                 for (index = 0; index < list.Count; index++)
1225                                         strings[index] = (string) list[index];
1226                         }
1227
1228                         return strings;
1229                 }
1230
1231                 public string[] Split (char[] separator, int maxCount)
1232                 {
1233                         // FIXME: what to do if maxCount <= 0?
1234                         // FIXME: would using Queue be better than ArrayList?
1235                         string[] strings;
1236                         ArrayList list;
1237                         int index, len, used;
1238
1239                         used = 0;
1240                         list = new ArrayList ();
1241                         for (index = 0, len = 0; index < this.length && used < maxCount; index += len + 1) {
1242                                 len = splitme (separator, index);
1243                                 len = len > -1 ? len : this.length - index;
1244                                 if (len == 0) {
1245                                         list.Add (String.Empty);
1246                                 } else {
1247                                         char[] str;
1248                                         int i;
1249
1250                                         str = new char [len];
1251                                         for (i = 0; i < len; i++)
1252                                                 str[i] = this.c_str[index + i];
1253
1254                                         list.Add (new String (str));
1255                                 }
1256                                 used++;
1257                         }
1258
1259                         /* fit the remaining chunk of the @this into it's own element */
1260                         if (index != this.length) {
1261                                 char[] str;
1262                                 int i;
1263
1264                                 str = new char [this.length - index];
1265                                 for (i = index; i < this.length; i++)
1266                                         str[i - index] = this.c_str[i];
1267
1268                                 list.Add (new String (str));
1269                         }
1270
1271                         strings = new string [list.Count];
1272                         if (list.Count == 1) {
1273                                 /* special case for an array holding @this */
1274                                 strings[0] = this;
1275                         } else {
1276                                 for (index = 0; index < list.Count; index++)
1277                                         strings[index] = (string) list[index];
1278                         }
1279
1280                         return strings;
1281                 }
1282
1283                 public bool StartsWith (string value)
1284                 {
1285                         bool startswith = true;
1286                         int i;
1287
1288                         if (value == null)
1289                                 throw new ArgumentNullException ();
1290
1291                         if (value.Length > this.length)
1292                                 return false;
1293
1294                         for (i = 0; i < value.Length && startswith; i++)
1295                                 startswith = startswith && value[i] == this.c_str[i];
1296
1297                         return startswith;
1298                 }
1299
1300                 public string Substring (int startIndex)
1301                 {
1302                         char[] str;
1303                         int i, len;
1304
1305                         if (startIndex < 0 || startIndex > this.length)
1306                                 throw new ArgumentOutOfRangeException ();
1307
1308                         len = this.length - startIndex;
1309                         if (len == 0)
1310                                 return String.Empty;
1311
1312                         str = new char [len];
1313                         for (i = startIndex; i < this.length; i++)
1314                                 str[i - startIndex] = this.c_str[i];
1315
1316                         return new String (str);
1317                 }
1318
1319                 public string Substring (int startIndex, int length)
1320                 {
1321                         char[] str;
1322                         int i;
1323
1324                         if (startIndex < 0 || length < 0 || startIndex + length > this.length)
1325                                 throw new ArgumentOutOfRangeException ();
1326
1327                         if (length == 0)
1328                                 return String.Empty;
1329
1330                         str = new char [length];
1331                         for (i = startIndex; i < startIndex + length; i++)
1332                                 str[i - startIndex] = this.c_str[i];
1333
1334                         return new String (str);
1335                 }
1336
1337                 public bool ToBoolean (IFormatProvider provider)
1338                 {
1339                         // FIXME: implement me
1340                         throw new NotImplementedException ();
1341                 }
1342
1343                 public byte ToByte (IFormatProvider provider)
1344                 {
1345                         // FIXME: implement me
1346                         throw new NotImplementedException ();
1347                 }
1348
1349                 public char ToChar (IFormatProvider provider)
1350                 {
1351                         // FIXME: implement me
1352                         throw new NotImplementedException ();
1353                 }
1354
1355                 public char[] ToCharArray ()
1356                 {
1357                         return ToCharArray (0, this.length);
1358                 }
1359
1360                 public char[] ToCharArray (int startIndex, int length)
1361                 {
1362                         char[] chars;
1363                         int i;
1364
1365                         if (startIndex < 0 || length < 0 || startIndex + length > this.length)
1366                                 throw new ArgumentOutOfRangeException ();
1367
1368                         chars = new char [length];
1369                         for (i = startIndex; i < length; i++)
1370                                 chars[i - startIndex] = this.c_str[i];
1371
1372                         return chars;
1373                 }
1374
1375                 public DateTime ToDateTime (IFormatProvider provider)
1376                 {
1377                         // FIXME: implement me
1378                         // return new DateTime (0);
1379                         throw new NotImplementedException ();
1380                 }
1381
1382                 public decimal ToDecimal (IFormatProvider provider)
1383                 {
1384                         // FIXME: implement me
1385                         throw new NotImplementedException ();
1386                 }
1387
1388                 public double ToDouble (IFormatProvider provider)
1389                 {
1390                         // FIXME: implement me
1391                         throw new NotImplementedException ();
1392                 }
1393
1394                 public short ToInt16 (IFormatProvider provider)
1395                 {
1396                         // FIXME: implement me
1397                         throw new NotImplementedException ();
1398                 }
1399
1400                 public int ToInt32 (IFormatProvider provider)
1401                 {
1402                         // FIXME: implement me
1403                         throw new NotImplementedException ();
1404                 }
1405
1406                 public long ToInt64 (IFormatProvider provider)
1407                 {
1408                         // FIXME: implement me
1409                         throw new NotImplementedException ();
1410                 }
1411
1412                 public string ToLower ()
1413                 {
1414                         char[] str;
1415                         int i;
1416
1417                         str = new char [this.length];
1418                         for (i = 0; i < this.length; i++)
1419                                 str[i] = Char.ToLower (this.c_str[i]);
1420
1421                         return new String (str);
1422                 }
1423
1424                 public string ToLower (CultureInfo culture)
1425                 {
1426                         // FIXME: implement me
1427                         throw new NotImplementedException ();
1428
1429                 }
1430
1431                 [CLSCompliant(false)]
1432                 public sbyte ToSByte (IFormatProvider provider)
1433                 {
1434                         // FIXME: implement me
1435                         throw new NotImplementedException ();
1436                 }
1437
1438                 public float ToSingle (IFormatProvider provider)
1439                 {
1440                         // FIXME: implement me
1441                         throw new NotImplementedException ();
1442                 }
1443
1444                 public override string ToString ()
1445                 {
1446                         return Substring (0, this.length);
1447                 }
1448
1449                 public string ToString (IFormatProvider format)
1450                 {
1451                         // FIXME: implement me
1452                         throw new NotImplementedException ();
1453                 }
1454
1455                 public object ToType (Type conversionType, IFormatProvider provider)
1456                 {
1457                         // FIXME: implement me
1458                         throw new NotImplementedException ();
1459                 }
1460
1461                 [CLSCompliant(false)]
1462                 public ushort ToUInt16 (IFormatProvider provider)
1463                 {
1464                         // FIXME: implement me
1465                         throw new NotImplementedException ();
1466                 }
1467
1468                 [CLSCompliant(false)]
1469                 public uint ToUInt32 (IFormatProvider provider)
1470                 {
1471                         // FIXME: implement me
1472                         throw new NotImplementedException ();
1473                 }
1474
1475                 [CLSCompliant(false)]
1476                 public ulong ToUInt64 (IFormatProvider provider)
1477                 {
1478                         // FIXME: implement me
1479                         throw new NotImplementedException ();
1480                 }
1481
1482                 public string ToUpper ()
1483                 {
1484                         char[] str;
1485                         int i;
1486
1487                         str = new char [this.length];
1488                         for (i = 0; i < this.length; i++)
1489                                 str[i] = Char.ToUpper (this.c_str[i]);
1490
1491                         return new String (str);
1492                 }
1493
1494                 public string ToUpper (CultureInfo culture)
1495                 {
1496                         // FIXME: implement me
1497                         throw new NotImplementedException ();
1498                 }
1499
1500                 public string Trim ()
1501                 {
1502                         return Trim (null);
1503                 }
1504
1505                 public string Trim (params char[] trimChars)
1506                 {
1507                         int begin, end;
1508                         bool matches;
1509
1510                         matches = true;
1511                         for (begin = 0; matches && begin < this.length; begin++) {
1512                                 if (trimChars != null) {
1513                                         matches = false;
1514                                         foreach (char c in trimChars) {
1515                                                 matches = this.c_str[begin] == c;
1516                                                 if (matches)
1517                                                         break;
1518                                         }
1519                                 } else {
1520                                         matches = is_lwsp (this.c_str[begin]);
1521                                 }
1522                         }
1523
1524                         matches = true;
1525                         for (end = this.length-1; end > begin; end--) {
1526                                 if (trimChars != null) {
1527                                         matches = false;
1528                                         foreach (char c in trimChars) {
1529                                                 matches = this.c_str[end] == c;
1530                                                 if (matches)
1531                                                         break;
1532                                         }
1533                                 } else {
1534                                         matches = is_lwsp (this.c_str[end]);
1535                                 }
1536                         }
1537
1538                         if (begin == end)
1539                                 return String.Empty;
1540
1541                         return Substring (begin, end - begin);
1542                 }
1543
1544                 public string TrimEnd (params char[] trimChars)
1545                 {
1546                         bool matches = true;
1547                         int end;
1548
1549                         for (end = this.length; end > 0; end--) {
1550                                 if (trimChars != null) {
1551                                         matches = false;
1552                                         foreach (char c in trimChars) {
1553                                                 matches = this.c_str[end] == c;
1554                                                 if (matches)
1555                                                         break;
1556                                         }
1557                                 } else {
1558                                         matches = is_lwsp (this.c_str[end]);
1559                                 }
1560                         }
1561
1562                         if (end == 0)
1563                                 return String.Empty;
1564
1565                         return Substring (0, end);
1566                 }
1567
1568                 public string TrimStart (params char[] trimChars)
1569                 {
1570                         bool matches = true;
1571                         int begin;
1572
1573                         for (begin = 0; matches && begin < this.length; begin++) {
1574                                 if (trimChars != null) {
1575                                         matches = false;
1576                                         foreach (char c in trimChars) {
1577                                                 matches = this.c_str[begin] == c;
1578                                                 if (matches)
1579                                                         break;
1580                                         }
1581                                 } else {
1582                                         matches = is_lwsp (this.c_str[begin]);
1583                                 }
1584                         }
1585
1586                         if (begin == this.length)
1587                                 return String.Empty;
1588
1589                         return Substring (begin, this.length - begin);
1590                 }
1591
1592                 // Operators
1593                 public static bool operator ==(string a, string b)
1594                 {
1595                         if (a.length != b.length)
1596                                 return false;
1597
1598                         int l = a.length;
1599                         for (int i = 0; i < l; i++)
1600                                 if (a.c_str[i] != b.c_str[i])
1601                                         return false;
1602
1603                         return true;
1604                 }
1605
1606                 public static bool operator !=(string a, string b)
1607                 {
1608                         return !(a == b);
1609                 }
1610         }
1611 }