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