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