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