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