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