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