*** empty log message ***
[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
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
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 Concat (this, arg ? arg.ToString () : String.Empty);
472                 }
473
474                 public static string Concat (params object[] args)
475                 {
476                         string[] strings;
477                         string 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                         foreach (object arg in args) {
486                                 /* use Empty for each null argument */
487                                 if (arg == null)
488                                         strings[i] = String.Empty;
489                                 else
490                                         strings[i] = arg.ToString ();
491                                 len += strings[i].Length;
492                         }
493
494                         if (len == 0)
495                                 return String.Empty;
496
497                         str = new string [len + 1];
498                         i = 0;
499                         for (int j = 0; j < strings.Length; j++)
500                                 for (int k = 0; k < strings[j].Length; k++)
501                                         str[i++] = strings[j][k];
502                         str[i] = '\0';
503
504                         return str;
505                 }
506
507                 public static string Concat (params string[] values)
508                 {
509                         int len, i;
510                         string str;
511
512                         if (values == null)
513                                 throw new ArgumentNullException ();
514
515                         len = 0;
516                         foreach (string value in values)
517                                 len += value ? value.Length : 0;
518
519                         if (len == 0)
520                                 return String.Empty;
521
522                         str = new string [len + 1];
523                         i = 0;
524                         foreach (string value in values) {
525                                 if (value == null)
526                                         continue;
527
528                                 for (int j = 0; j < value.Length; j++)
529                                         str[i++] = value[j];
530                         }
531                         str[i] = '\0';
532
533                         return str;
534                 }
535
536                 public static string Concat (object arg0, object arg1)
537                 {
538                         string str0 = arg0 ? arg0.ToString () : String.Empty;
539                         string str1 = arg1 ? arg1.ToString () : String.Empty;
540
541                         return Concat (str0, str1);
542                 }
543
544                 public static string Concat (string str0, string str1)
545                 {
546                         string concat;
547                         int i, j, len;
548
549                         if (str0 == null)
550                                 str0 = String.Empty;
551                         if (str1 == null)
552                                 str1 == String.Empty;
553
554                         len = str0.Length + str1.Length;
555                         if (len == 0)
556                                 return String.Empty;
557
558                         concat = new string [len + 1];
559                         for (i = 0; i < str0.Length; i++)
560                                 concat[i] = str0[i];
561                         for (j = 0 ; j < str1.Length; j++)
562                                 concat[i + j] = str1[j];
563                         concat[len] = '\0';
564
565                         return concat;
566                 }
567
568                 public static string Concat (object arg0, object arg1, object arg2)
569                 {
570                         string str0 = arg0 ? arg0.ToString () : String.Empty;
571                         string str1 = arg1 ? arg1.ToString () : String.Empty;
572                         string str2 = arg2 ? arg2.ToString () : String.Empty;
573
574                         return Concat (str0, str1, str2);
575                 }
576
577                 public static string Concat (string str0, string str1, string str2)
578                 {
579                         string concat;
580                         int i, j, k, len;
581
582                         if (str0 == null)
583                                 str0 = String.Empty;
584                         if (str1 == null)
585                                 str1 = String.Empty;
586                         if (str2 == null)
587                                 str2 = String.Empty;
588
589                         len = str0.Length + str1.Length + str2.Length;
590                         if (len == 0)
591                                 return String.Empty;
592
593                         concat = new string [len + 1];
594                         for (i = 0; i < str0.Length; i++)
595                                 concat[i] = str0[i];
596                         for (j = 0; j < str1.Length; j++)
597                                 concat[i + j] = str1[j];
598                         for (k = 0; k < str2.Length; k++)
599                                 concat[i + j + k] = str2[k];
600                         concat[len] = '\0';
601
602                         return concat;
603                 }
604
605                 public static string Concat (string str0, string str1, string str2, string str3)
606                 {
607                         string concat;
608                         int i, j, k, l, len;
609
610                         if (str0 == null)
611                                 str0 = String.Empty;
612                         if (str1 == null)
613                                 str1 = String.Empty;
614                         if (str2 == null)
615                                 str2 = String.Empty;
616                         if (str3 == null)
617                                 str3 = String.Empty;
618
619                         len = str0.Length + str1.Length + str2.Length + str3.Length;
620                         if (len == 0)
621                                 return String.Empty;
622
623                         concat = new string [len + 1];
624                         for (i = 0; i < str0.Length; i++)
625                                 concat[i] = str0[i];
626                         for (j = 0; j < str1.Length; j++)
627                                 concat[i + j] = str1[j];
628                         for (k = 0; k < str2.Length; k++)
629                                 concat[i + j + k] = str2[k];
630                         for (l = 0; l < str3.Length; l++)
631                                 concat[i + j + k + l] = str3[l];
632                         concat[len] = '\0';
633
634                         return concat;
635                 }
636
637                 public static string Copy (string str)
638                 {
639                         if (str == null)
640                                 throw new ArgumentNullException ();
641
642                         return new String (str);
643                 }
644
645                 public void CopyTo (int sourceIndex, char[] destination, int destinationIndex, int count)
646                 {
647                         // LAMESPEC: should I null-terminate?
648                         int i;
649
650                         if (destination == null)
651                                 throw new ArgumentNullException ();
652
653                         if (sourceIndex < 0 || destinationIndex < 0 || count < 0)
654                                 throw new ArgumentOutOfRangeException ();
655
656                         if (sourceIndex + count > this.length)
657                                 throw new ArgumentOutOfRangeException ();
658
659                         if (destinationIndex + count > destination.Length)
660                                 throw new ArgumentOutOfRangeException ();
661
662                         for (i = 0; i < count; i++)
663                                 destination[destinationIndex + i] = this.c_str[sourceIndex + i];
664                 }
665
666                 public bool EndsWith (string value)
667                 {
668                         bool endswith = true;
669                         int start, i;
670
671                         if (value == null)
672                                 throw new ArgumentNullException ();
673
674                         start = this.length - value.Length;
675                         if (start < 0)
676                                 return false;
677
678                         for (i = start; i < this.length && endswith; i++)
679                                 endswith = this.c_str[i] == value[i - start];
680
681                         return endswith;
682                 }
683
684                 public override bool Equals (object obj)
685                 {
686                         if (!(obj is String))
687                                 return false;
688
689                         return this == (String) obj;
690                 }
691
692                 public bool Equals (string value)
693                 {
694                         return this == value;
695                 }
696
697                 public static bool Equals (string a, string b)
698                 {
699                         return a == b;
700                 }
701
702                 public static string Format (string format, object arg0)
703                 {
704                         // FIXME: implement me
705                         return null;
706                 }
707
708                 public static string Format (string format, params object[] args)
709                 {
710                         // FIXME: implement me
711                         return null;
712                 }
713
714                 public static string Format (IFormatProvider provider, string format, params object[] args)
715                 {
716                         // FIXME: implement me
717                         return null;
718                 }
719
720                 public static string Format (string format, object arg0, object arg1)
721                 {
722                         // FIXME: implement me
723                         return null;
724                 }
725
726                 public static string Format (string format, object arg0, object arg1, object arg2)
727                 {
728                         // FIXME: implement me
729                         return null;
730                 }
731
732                 //public CharEnumerator GetEnumerator ()
733                 public IEnumerator GetEnumerator ()
734                 {
735                         // FIXME: implement me
736                         return null;
737                 }
738
739                 public override int GetHashCode ()
740                 {
741                         // FIXME: implement me
742                         return 0;
743                 }
744
745                 public new Type GetType ()
746                 {
747                         // FIXME: implement me
748                         return null;
749                 }
750
751                 public TypeCode GetTypeCode ()
752                 {
753                         // FIXME: implement me
754                         return null;
755                 }
756
757                 public int IndexOf (char value)
758                 {
759                         return IndexOf (value, 0, this.length);
760                 }
761
762                 public int IndexOf (string value)
763                 {
764                         return IndexOf (value, 0, this.length);
765                 }
766
767                 public int IndexOf (char value, int startIndex)
768                 {
769                         return IndexOf (value, startIndex, this.length - startIndex);
770                 }
771
772                 public int IndexOf (string value, int startIndex)
773                 {
774                         return IndexOf (value, startIndex, this.length - startIndex);
775                 }
776
777                 public int IndexOf (char value, int startIndex, int count)
778                 {
779                         int i;
780
781                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
782                                 throw new ArgumentOutOfRangeException ();
783
784                         for (i = startIndex; i - startIndex < count; i++)
785                                 if (this.c_str[i] == value)
786                                         return i;
787
788                         return -1;
789                 }
790
791                 public int IndexOf (string value, int startIndex, int count)
792                 {
793                         // FIXME: Use a modified Boyer-Moore algorithm to work with unicode?
794                         int i;
795
796                         if (value == null)
797                                 throw new ArgumentNullException ();
798
799                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
800                                 throw new ArgumentOutOfRangeException ();
801
802                         for (i = startIndex; i - startIndex + value.Length <= count; ) {
803                                 if (this.c_str[i] == value[0]) {
804                                         bool equal = true;
805                                         int j, nexti = 0;
806
807                                         for (j = 1; equal && value[j] != '\0'; j++) {
808                                                 equal = this.c_str[i + j] == value[j];
809                                                 if (this.c_str[i + j] == value[0] && nexti == 0)
810                                                         nexti = i + j;
811                                         }
812
813                                         if (equal)
814                                                 return i;
815
816                                         if (nexti != 0)
817                                                 i = nexti;
818                                         else
819                                                 i += j;
820                                 } else
821                                         i++;
822                         }
823
824                         return -1;
825                 }
826
827                 public int IndexOfAny (char[] values)
828                 {
829                         return IndexOfAny (values, 0, this.length);
830                 }
831
832                 public int IndexOfAny (char[] values, int startIndex)
833                 {
834                         return IndexOfAny (values, startIndex, this.length - startIndex);
835                 }
836
837                 public int IndexOfAny (char[] values, int startIndex, int count)
838                 {
839                         if (values == null)
840                                 throw new ArgumentNullException ();
841
842                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
843                                 throw new ArgumentOutOfRangeException ();
844
845                         for (int i = startIndex; i < startIndex + count; i++) {
846                                 for (int j = 0; j < strlen (values); j++) {
847                                         if (this.c_str[i] == values[j])
848                                                 return i;
849                                 }
850                         }
851
852                         return -1;
853                 }
854
855                 public string Insert (int startIndex, string value)
856                 {
857                         string str;
858                         int i, j;
859
860                         if (value == null)
861                                 throw new ArgumentNullException ();
862
863                         if (startIndex < 0 || startIndex > this.length)
864                                 throw new ArgumentOutOfRangeException ();
865
866                         str = new string [value.Length + this.length + 1];
867                         for (i = 0; i < startIndex; i++)
868                                 str[i] = this.c_str[i];
869                         for (j = 0; j < value.Length; j++)
870                                 str[i + j] = value[j];
871                         for ( ; i < this.length; i++)
872                                 str[i + j] = this.c_str[i];
873                         str[i + j] = '\0';
874
875                         return str;
876                 }
877
878                 public static string Intern (string str)
879                 {
880                         if (str == null)
881                                 throw new ArgumentNullException ();
882                         // FIXME: implement me
883                         return null;
884                 }
885
886                 public static string IsInterned (string str)
887                 {
888                         if (str == null)
889                                 throw new ArgumentNullException ();
890                         // FIXME: implement me
891                         return null;
892                 }
893
894                 public static string Join (string separator, string[] value)
895                 {
896                         return Join (separator, value, 0, value.Length);
897                 }
898
899                 public static string Join (string separator, string[] value, int startIndex, int count)
900                 {
901                         // LAMESPEC: msdn doesn't specify what happens when separator is null
902                         int len, i, j, used;
903                         string str;
904
905                         if (separator == null || value == null)
906                                 throw new ArgumentNullException ();
907
908                         if (startIndex + count > value.Length)
909                                 throw new ArgumentOutOfRangeException ();
910
911                         len = 0;
912                         for (i = startIndex, used = 0; used < count; i++, used++) {
913                                 if (i != startIndex)
914                                         len += separator.Length;
915
916                                 len += value[i].Length;
917                         }
918
919                         // We have no elements to join?
920                         if (i == startIndex)
921                                 return String.Empty;
922
923                         str = new string [len + 1];
924                         for (i = 0; i < value[startIndex].Length; i++)
925                                 str[i] = value[startIndex][i];
926
927                         used = 1;
928                         for (j = startIndex + 1; used < count; j++, used++) {
929                                 int k;
930
931                                 for (k = 0; k < separator.Length; k++)
932                                         str[i++] = separator[k];
933                                 for (k = 0; k < value[j].Length; k++)
934                                         str[i++] = value[j][k];
935                         }
936                         str[i] = '\0';
937
938                         return str;
939                 }
940
941                 public int LastIndexOf (char value)
942                 {
943                         for (int i = this.length; i >= 0; i--) {
944                                 if (this.c_str[i] == value)
945                                         return i;
946                         }
947
948                         return -1;
949                 }
950
951                 public int LastIndexOf (string value)
952                 {
953                         return LastIndexOf (value, this.length, this.length);
954                 }
955
956                 public int LastIndexOf (char value, int startIndex)
957                 {
958                         if (startIndex < 0 || startIndex > this.length)
959                                 throw new ArgumentOutOfRangeException ();
960
961                         for (int i = startIndex; i >= 0; i--) {
962                                 if (this.c_str[i] == value)
963                                         return i;
964                         }
965
966                         return -1;
967                 }
968
969                 public int LastIndexOf (string value, int startIndex)
970                 {
971                         return LastIndexOf (value, startIndex, this.length);
972                 }
973
974                 public int LastIndexOf (char value, int startIndex, int count)
975                 {
976                         if (startIndex < 0 || count < 0)
977                                 throw new ArgumentOutOfRangeException ();
978
979                         if (startIndex > this.length || startIndex - count < 0)
980                                 throw new ArgumentOutOfRangeException ();
981
982                         for (int i = startIndex; i >= startIndex - count; i--) {
983                                 if (this.c_str[i] == value)
984                                         return i;
985                         }
986
987                         return -1;
988                 }
989
990                 public int LastIndexOf (string value, int startIndex, int count)
991                 {
992                         // LAMESPEC: currently I'm using startIndex as the 0-position in the comparison,
993                         //           but maybe it's the end-position in MS's implementation?
994                         //           msdn is unclear on this point. I think this is correct though.
995                         int i, len;
996
997                         if (value == null)
998                                 throw new ArgumentNullException ();
999
1000                         if (startIndex < 0 || startIndex > this.length)
1001                                 throw new ArgumentOutOfRangeException ();
1002
1003                         if (count < 0 || startIndex - count < 0)
1004                                 throw new ArgumentOutOfRangeException ();
1005
1006                         if (value == String.Empty)
1007                                 return startIndex;
1008
1009                         if (startIndex + value.Length > this.length) {
1010                                 /* just a little optimization */
1011                                 int start;
1012
1013                                 start = this.length - value.Length;
1014                                 count -= startIndex - start;
1015                                 startIndex = start;
1016                         }
1017
1018                         // FIXME: use a reversed-unicode-safe-Boyer-Moore?
1019                         len = value.Length - 1;
1020                         for (i = startIndex; i >= startIndex - count; i--) {
1021                                 if (this.c_str[i + len] == value[len]) {
1022                                         bool equal = true;
1023                                         int j;
1024
1025                                         for (j = len - 1; equal && j >= 0; j--)
1026                                                 equal = this.c_str[i + j] == value[j];
1027
1028                                         if (equal)
1029                                                 return i;
1030                                 }
1031                         }
1032
1033                         return -1;
1034                 }
1035
1036                 public int LastIndexOfAny (char[] values)
1037                 {
1038                         return LastIndexOfAny (values, this.length, this.length);
1039                 }
1040
1041                 public int LastIndexOfAny (char[] values, int startIndex)
1042                 {
1043                         return LastIndexOfAny (values, startIndex, startIndex);
1044                 }
1045
1046                 public int LastIndexOfAny (char[] values, int startIndex, int count)
1047                 {
1048                         int i;
1049
1050                         if (values == null)
1051                                 throw new ArgumentNullException ();
1052
1053                         if (startIndex < 0 || count < 0 || startIndex - count < 0)
1054                                 throw new ArgumentOutOfRangeException ();
1055
1056                         for (i = startIndex; i >= startIndex - count; i--) {
1057                                 for (int j = 0; j < strlen (values); j++) {
1058                                         if (this.c_str[i] == values[j])
1059                                                 return i;
1060                                 }
1061                         }
1062
1063                         return -1;
1064                 }
1065
1066                 public string PadLeft (int totalWidth)
1067                 {
1068                         return PadLeft (totalWidth, ' ');
1069                 }
1070
1071                 public string PadLeft (int totalWidth, char padChar)
1072                 {
1073                         string str;
1074                         int i, j;
1075
1076                         if (totalWidth < 0)
1077                                 throw new ArgumentException ();
1078
1079                         str = new string [totalWidth > this.length ? totalWidth : this.length + 1];
1080                         for (i = 0; i < totalWidth - this.length; i++)
1081                                 str[i] = padChar;
1082
1083                         for (j = 0; j < this.length; i++, j++)
1084                                 str[i] = this.c_str[j];
1085
1086                         str[i] = '\0';
1087
1088                         return str;
1089                 }
1090
1091                 public string PadRight (int totalWidth)
1092                 {
1093                         return PadRight (totalWidth, ' ');
1094                 }
1095
1096                 public string PadRight (int totalWidth, char padChar)
1097                 {
1098                         string str;
1099                         int i;
1100
1101                         if (totalWidth < 0)
1102                                 throw new ArgumentException ();
1103
1104                         str = new string [totalWidth > this.length ? totalWidth : this.length + 1];
1105                         for (i = 0; i < this.length; i++)
1106                                 str[i] = this.c_str[i];
1107
1108                         for ( ; j < str.Length; i++)
1109                                 str[i] = padChar;
1110
1111                         str[i] = '\0';
1112
1113                         return str;
1114                 }
1115
1116                 public string Remove (int startIndex, int count)
1117                 {
1118                         string str;
1119                         int i, j, len;
1120
1121                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
1122                                 throw new ArgumentOutOfRangeException ();
1123
1124                         len = this.length - count;
1125                         if (len == 0)
1126                                 return String.Empty;
1127
1128                         str = new string [len + 1];
1129                         for (i = 0; i < startIndex; i++)
1130                                 str[i] = this.c_str[i];
1131                         for (j = i + count; j < this.length; j++)
1132                                 str[i++] = this.c_str[j];
1133                         str[i] = '\0';
1134
1135                         return str;
1136                 }
1137
1138                 public string Replace (char oldChar, char newChar)
1139                 {
1140                         string str;
1141                         int i;
1142
1143                         str = new string [this.length + 1];
1144                         for (i = 0; i < this.length; i++) {
1145                                 if (this.c_str[i] == oldChar)
1146                                         str[i] = newChar;
1147                                 else
1148                                         str[i] = this.c_str[i];
1149                         }
1150                         str[i] = '\0';
1151
1152                         return str;
1153                 }
1154
1155                 public string Replace (string oldValue, string newValue)
1156                 {
1157                         // LAMESPEC: msdn doesn't specify what to do if either args is null
1158                         int index, len, i, j;
1159                         string str;
1160
1161                         if (oldValue == null || newValue == null)
1162                                 throw new ArgumentNullException ();
1163
1164                         // Use IndexOf in case I later rewrite it to use Boyer-Moore
1165                         index = IndexOf (oldValue, 0);
1166                         if (index == -1) {
1167                                 // This is the easy one ;-)
1168                                 return Substring (0, this.length);
1169                         }
1170
1171                         len = this.length - oldValue.Length + newValue.Length;
1172                         if (len == 0)
1173                                 return String.Empty;
1174
1175                         str = new string [len + 1];
1176                         for (i = 0; i < index; i++)
1177                                 str[i] = this.c_str[i];
1178                         for (j = 0; j < newValue.Length; j++)
1179                                 str[i++] = newValue[j];
1180                         for (j = index + oldValue.Length; j < this.length; j++)
1181                                 str[i++] = this.c_str[j];
1182                         str[i] = '\0';
1183
1184                         return str;
1185                 }
1186
1187                 private int splitme (char[] separators, int startIndex)
1188                 {
1189                         /* this is basically a customized IndexOfAny() for the Split() methods */
1190                         for (int i = startIndex; i < this.length; i++) {
1191                                 if (separators != null) {
1192                                         foreach (char sep in separators) {
1193                                                 if (this.c_str[i] == sep)
1194                                                         return i - startIndex;
1195                                         }
1196                                 } else if (is_lwsp (this.c_str[i])) {
1197                                         return i - startIndex;
1198                                 }
1199                         }
1200
1201                         return -1;
1202                 }
1203
1204                 public string[] Split (params char[] separator)
1205                 {
1206                         /**
1207                          * split:
1208                          * @separator: delimiting chars or null to split on whtspc
1209                          *
1210                          * Returns: 1. An array consisting of a single
1211                          * element (@this) if none of the delimiting
1212                          * chars appear in @this. 2. An array of
1213                          * substrings which are delimited by one of
1214                          * the separator chars. 3. An array of
1215                          * substrings separated by whitespace if
1216                          * @separator is null. The Empty string should
1217                          * be returned wherever 2 delimiting chars are
1218                          * adjacent.
1219                          **/
1220                         // FIXME: would using a Queue be better?
1221                         string[] strings;
1222                         ArrayList list;
1223                         int index, len;
1224
1225                         list = new ArrayList ();
1226                         for (index = 0, len = 0; index < this.length; index += len + 1) {
1227                                 len = splitme (separator, index);
1228                                 len = len > -1 ? len : this.length - index;
1229                                 if (len == 0) {
1230                                         list.Add (String.Empty);
1231                                 } else {
1232                                         string str;
1233                                         int i;
1234
1235                                         str = new string [len + 1];
1236                                         for (i = 0; i < len; i++)
1237                                                 str[i] = this.c_str[index + i];
1238                                         str[i] = '\0';
1239
1240                                         list.Add (str);
1241                                 }
1242                         }
1243
1244                         strings = new string [list.Count];
1245                         if (list.Count == 1) {
1246                                 /* special case for an array holding @this */
1247                                 strings[0] = this;
1248                         } else {
1249                                 for (index = 0; index < list.Count; index++)
1250                                         strings[index] = (string) list[index];
1251                         }
1252
1253                         return strings;
1254                 }
1255
1256                 public string[] Split (char[] separator, int maxCount)
1257                 {
1258                         // FIXME: what to do if maxCount <= 0?
1259                         // FIXME: would using Queue be better than ArrayList?
1260                         string[] strings;
1261                         ArrayList list;
1262                         int index, len, used;
1263
1264                         used = 0;
1265                         list = new ArrayList ();
1266                         for (index = 0, len = 0; index < this.length && used < maxCount; index += len + 1) {
1267                                 len = splitme (separator, index);
1268                                 len = len > -1 ? len : this.length - index;
1269                                 if (len == 0) {
1270                                         list.Add (String.Empty);
1271                                 } else {
1272                                         string str;
1273                                         int i;
1274
1275                                         str = new string [len + 1];
1276                                         for (i = 0; i < len; i++)
1277                                                 str[i] = this.c_str[index + i];
1278                                         str[i] = '\0';
1279
1280                                         list.Add (str);
1281                                 }
1282                                 used++;
1283                         }
1284
1285                         /* fit the remaining chunk of the @this into it's own element */
1286                         if (index != this.length) {
1287                                 string str;
1288                                 int i;
1289
1290                                 str = new string [this.length - index + 1];
1291                                 for (i = index; i < this.length; i++)
1292                                         str[i - index] = this.c_str[i];
1293                                 str[i - index] = '\0';
1294
1295                                 list.Add (str);
1296                         }
1297
1298                         strings = new string [list.Count];
1299                         if (list.Count == 1) {
1300                                 /* special case for an array holding @this */
1301                                 strings[0] = this;
1302                         } else {
1303                                 for (index = 0; index < list.Count; index++)
1304                                         strings[index] = (string) list[index];
1305                         }
1306
1307                         return strings;
1308                 }
1309
1310                 public bool StartsWith (string value)
1311                 {
1312                         bool startswith = true;
1313                         int i;
1314
1315                         if (value == null)
1316                                 throw new ArgumentNullException ();
1317
1318                         if (value.Length > this.length)
1319                                 return false;
1320
1321                         for (i = 0; i < value.Length && startswith; i++)
1322                                 startswith = startswith && value[i] == this.c_str[i];
1323
1324                         return startswith;
1325                 }
1326
1327                 public string Substring (int startIndex)
1328                 {
1329                         string str;
1330                         int i, len;
1331
1332                         if (startIndex < 0 || startIndex > this.length)
1333                                 throw new ArgumentOutOfRangeException ();
1334
1335                         len = this.length - startIndex;
1336                         if (len == 0)
1337                                 return String.Empty;
1338
1339                         str = new string [len + 1];
1340                         for (i = startIndex; i < this.length; i++)
1341                                 str[i - startIndex] = this.c_str[i];
1342                         str[i] = '\0';
1343
1344                         return str;
1345                 }
1346
1347                 public string Substring (int startIndex, int length)
1348                 {
1349                         string str;
1350                         int i;
1351
1352                         if (startIndex < 0 || length < 0 || startIndex + length > this.length)
1353                                 throw new ArgumentOutOfRangeException ();
1354
1355                         if (length == 0)
1356                                 return String.Empty;
1357
1358                         str = new string [length + 1];
1359                         for (i = startIndex; i < startIndex + length; i++)
1360                                 str[i - startIndex] = this.c_str[i];
1361                         str[i] = '\0';
1362
1363                         return str;
1364                 }
1365
1366                 public bool ToBoolean (IFormatProvider provider)
1367                 {
1368                         // FIXME: implement me
1369                         return false;
1370                 }
1371
1372                 public byte ToByte (IFormatProvider provider)
1373                 {
1374                         // FIXME: implement me
1375                         return '\0';
1376                 }
1377
1378                 public char ToChar (IFormatProvider provider)
1379                 {
1380                         // FIXME: implement me
1381                         return '\0';
1382                 }
1383
1384                 public char[] ToCharArray ()
1385                 {
1386                         return ToCharArray (0, this.length);
1387                 }
1388
1389                 public char[] ToCharArray (int startIndex, int length)
1390                 {
1391                         char[] chars;
1392                         int i, j;
1393
1394                         if (startIndex < 0 || length < 0 || startIndex + length > this.length)
1395                                 throw new ArgumentOutOfRangeException ();
1396
1397                         chars = new char [length + 1];
1398                         for (i = startIndex, i < length; i++)
1399                                 chars[i - startIndex] = this.c_str[i];
1400
1401                         chars[length] = '\0';
1402
1403                         return chars;
1404                 }
1405
1406                 public DateTime ToDateTime (IFormatProvider provider)
1407                 {
1408                         // FIXME: implement me
1409                         return null;
1410                 }
1411
1412                 public decimal ToDecimal (IFormatProvider provider)
1413                 {
1414                         // FIXME: implement me
1415                         return 0.0D;
1416                 }
1417
1418                 public double ToDouble (IFormatProvider provider)
1419                 {
1420                         // FIXME: implement me
1421                         return 0.0;
1422                 }
1423
1424                 public short ToInt16 (IFormatProvider provider)
1425                 {
1426                         // FIXME: implement me
1427                         return 0;
1428                 }
1429
1430                 public int ToInt32 (IFormatProvider provider)
1431                 {
1432                         // FIXME: implement me
1433                         return 0;
1434                 }
1435
1436                 public long ToInt64 (IFormatProvider provider)
1437                 {
1438                         // FIXME: implement me
1439                         return 0;
1440                 }
1441
1442                 public string ToLower ()
1443                 {
1444                         string str;
1445                         int i;
1446
1447                         str = new string [this.length + 1];
1448                         for (i = 0; i < this.length; i++)
1449                                 str[i] = tolower (this.c_str[i]);
1450                         str[i] = '\0';
1451
1452                         return str;
1453                 }
1454
1455                 public string ToLower (CultureInfo culture)
1456                 {
1457                         // FIXME: implement me
1458                         return null;
1459                 }
1460
1461                 public sbyte ToSByte (IFormatProvider provider)
1462                 {
1463                         // FIXME: implement me
1464                         return 0;
1465                 }
1466
1467                 public float ToSingle (IFormatProvider provider)
1468                 {
1469                         // FIXME: implement me
1470                         return 0.0F;
1471                 }
1472
1473                 public override string ToString ()
1474                 {
1475                         return Substring (0, this.length);
1476                 }
1477
1478                 public string ToString (IFormatProvider format)
1479                 {
1480                         // FIXME: implement me
1481                         return null;
1482                 }
1483
1484                 public object ToType (Type conversionType, IFormatProvider provider)
1485                 {
1486                         // FIXME: implement me
1487                         return null;
1488                 }
1489
1490                 public ushort ToUInt16 (IFormatProvider provider)
1491                 {
1492                         // FIXME: implement me
1493                         return 0;
1494                 }
1495
1496                 public uint ToUInt32 (IFormatProvider provider)
1497                 {
1498                         // FIXME: implement me
1499                         return 0;
1500                 }
1501
1502                 public ulong ToUInt64 (IFormatProvider provider)
1503                 {
1504                         // FIXME: implement me
1505                         return 0;
1506                 }
1507
1508                 public string ToUpper ()
1509                 {
1510                         string str;
1511                         int i;
1512
1513                         str = new string [this.length + 1];
1514                         for (i = 0; i < this.length; i++)
1515                                 str[i] = toupper (this.c_str[i]);
1516                         str[i] = '\0';
1517
1518                         return str;
1519                 }
1520
1521                 public string ToUpper (CultureInfo culture)
1522                 {
1523                         // FIXME: implement me
1524                         return null;
1525                 }
1526
1527                 public string Trim ()
1528                 {
1529                         return Trim (null);
1530                 }
1531
1532                 public string Trim (params char[] trimChars)
1533                 {
1534                         int begin, end;
1535                         bool matches;
1536
1537                         matches = true;
1538                         for (begin = 0; matches && begin < this.length; begin++) {
1539                                 if (trimChars != null) {
1540                                         matches = false;
1541                                         foreach (char c in trimChars)
1542                                                 matches = this.c_str[begin] == c;
1543                                 } else {
1544                                         matches = is_lwsp (this.c_str[begin]);
1545                                 }
1546                         }
1547
1548                         matches = true;
1549                         for (end = this.length; end > begin; end--) {
1550                                 if (trimChars != null) {
1551                                         matches = false;
1552                                         foreach (char c in trimChars)
1553                                                 matches = this.c_str[end] == c;
1554                                 } else {
1555                                         matches = is_lwsp (this.c_str[end]);
1556                                 }
1557                         }
1558
1559                         if (begin == end)
1560                                 return String.Empty;
1561
1562                         return Substring (begin, end - begin);
1563                 }
1564
1565                 public string TrimEnd (params char[] trimChars)
1566                 {
1567                         bool matches = true;
1568                         int end;
1569
1570                         for (end = this.length; end > 0; end--) {
1571                                 if (trimChars != null) {
1572                                         matches = false;
1573                                         foreach (char c in trimChars)
1574                                                 matches = this.c_str[end] == c;
1575                                 } else {
1576                                         matches = is_lwsp (this.c_str[end]);
1577                                 }
1578                         }
1579
1580                         if (end == 0)
1581                                 return String.Empty;
1582
1583                         return Substring (0, end);
1584                 }
1585
1586                 public string TrimStart (params char[] trimChars)
1587                 {
1588                         bool matches = true;
1589                         int begin;
1590
1591                         for (begin = 0; matches && begin < this.length; begin++) {
1592                                 if (trimChars != null) {
1593                                         matches = false;
1594                                         foreach (char c in trimChars)
1595                                                 matches = this.c_str[begin] == c;
1596                                 } else {
1597                                         matches = is_lwsp (this.c_str[begin]);
1598                                 }
1599                         }
1600
1601                         if (begin == this.length)
1602                                 return String.Empty;
1603
1604                         return Substring (begin, this.length - begin);
1605                 }
1606
1607                 // Operators
1608                 public static bool operator ==(string a, string b)
1609                 {
1610                         return a == b;
1611                 }
1612
1613                 public static bool operator !=(string a, string b)
1614                 {
1615                         return a != b;
1616                 }
1617         }
1618
1619 }