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