X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2Fcorlib%2FSystem%2FString.cs;h=e46ed88a8cf8561f80e0f7508041ca93f5527f02;hb=40ac954554e6025948c91b56c3c867f7482d1f8a;hp=cf66fd4336c54bbf83609e49805bdd51e0a2b6bf;hpb=b585d00928892398dfbfc315ed78b8032fa14708;p=mono.git diff --git a/mcs/class/corlib/System/String.cs b/mcs/class/corlib/System/String.cs index cf66fd4336c..7533f40da25 100644 --- a/mcs/class/corlib/System/String.cs +++ b/mcs/class/corlib/System/String.cs @@ -6,6 +6,8 @@ // Jeffrey Stedfast (fejj@ximian.com) // Dan Lewis (dihlewis@yahoo.co.uk) // Sebastien Pouliot +// Marek Safar (marek.safar@seznam.cz) +// Andreas Nahr (Classdevelopment@A-SoftTech.com) // // (C) 2001 Ximian, Inc. http://www.ximian.com // Copyright (C) 2004-2005 Novell (http://www.novell.com) @@ -29,6 +31,14 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +// +// +// This class contains all implementation for culture-insensitive methods. +// Culture-sensitive methods are implemented in the System.Globalization or +// Mono.Globalization namespace. +// +// Ensure that argument checks on methods don't overflow +// using System.Text; using System.Collections; @@ -36,8 +46,10 @@ using System.Globalization; using System.Runtime.CompilerServices; #if NET_2_0 +using System.Collections.Generic; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; +using Mono.Globalization.Unicode; #endif namespace System @@ -45,7 +57,7 @@ namespace System [Serializable] #if NET_2_0 [ComVisible (true)] - public sealed class String : IConvertible, ICloneable, IEnumerable, IComparable, IComparable, IEquatable + public sealed class String : IConvertible, ICloneable, IEnumerable, IComparable, IComparable, IEquatable , IEnumerable #else public sealed class String : IConvertible, ICloneable, IEnumerable, IComparable #endif @@ -53,10 +65,6 @@ namespace System [NonSerialized] private int length; [NonSerialized] private char start_char; - private const int COMPARE_CASE = 0; - private const int COMPARE_INCASE = 1; - private const int COMPARE_ORDINAL = 2; - public static readonly String Empty = ""; public static unsafe bool Equals (string a, string b) @@ -72,28 +80,42 @@ namespace System if (len != b.length) return false; - if (len == 0) - return true; + fixed (char* s1 = &a.start_char, s2 = &b.start_char) { + char* s1_ptr = s1; + char* s2_ptr = s2; + + while (len >= 8) { + if (((int*)s1_ptr)[0] != ((int*)s2_ptr)[0] || + ((int*)s1_ptr)[1] != ((int*)s2_ptr)[1] || + ((int*)s1_ptr)[2] != ((int*)s2_ptr)[2] || + ((int*)s1_ptr)[3] != ((int*)s2_ptr)[3]) + return false; - fixed (char * s1 = &a.start_char, s2 = &b.start_char) { - // it must be one char, because 0 len is done above - if (len < 2) - return *s1 == *s2; + s1_ptr += 8; + s2_ptr += 8; + len -= 8; + } - // check by twos - int * sint1 = (int *) s1, sint2 = (int *) s2; - int n2 = len >> 1; - do { - if (*sint1++ != *sint2++) + if (len >= 4) { + if (((int*)s1_ptr)[0] != ((int*)s2_ptr)[0] || + ((int*)s1_ptr)[1] != ((int*)s2_ptr)[1]) + return false; + + s1_ptr += 4; + s2_ptr += 4; + len -= 4; + } + + if (len > 1) { + if (((int*)s1_ptr)[0] != ((int*)s2_ptr)[0]) return false; - } while (--n2 != 0); - // nothing left - if ((len & 1) == 0) - return true; + s1_ptr += 2; + s2_ptr += 2; + len -= 2; + } - // check the last one - return *(char *) sint1 == *(char *) sint2; + return len == 0 || *s1_ptr == *s2_ptr; } } @@ -124,9 +146,13 @@ namespace System } [IndexerName ("Chars")] - public extern char this [int index] { - [MethodImplAttribute (MethodImplOptions.InternalCall)] - get; + public unsafe char this [int index] { + get { + if (index < 0 || index >= length) + throw new IndexOutOfRangeException (); + fixed (char* c = &start_char) + return c[index]; + } } public Object Clone () @@ -139,44 +165,45 @@ namespace System return TypeCode.String; } - public void CopyTo (int sourceIndex, char[] destination, int destinationIndex, int count) + public unsafe void CopyTo (int sourceIndex, char[] destination, int destinationIndex, int count) { - // LAMESPEC: should I null-terminate? if (destination == null) throw new ArgumentNullException ("destination"); - - if (sourceIndex < 0 || destinationIndex < 0 || count < 0) - throw new ArgumentOutOfRangeException (); - - // re-ordered to avoid possible integer overflow + if (sourceIndex < 0) + throw new ArgumentOutOfRangeException ("sourceIndex", "Cannot be negative"); + if (destinationIndex < 0) + throw new ArgumentOutOfRangeException ("destinationIndex", "Cannot be negative."); + if (count < 0) + throw new ArgumentOutOfRangeException ("count", "Cannot be negative."); if (sourceIndex > Length - count) - throw new ArgumentOutOfRangeException ("sourceIndex + count > Length"); - // re-ordered to avoid possible integer overflow + throw new ArgumentOutOfRangeException ("sourceIndex", "sourceIndex + count > Length"); if (destinationIndex > destination.Length - count) - throw new ArgumentOutOfRangeException ("destinationIndex + count > destination.Length"); + throw new ArgumentOutOfRangeException ("destinationIndex", "destinationIndex + count > destination.Length"); - InternalCopyTo (sourceIndex, destination, destinationIndex, count); + fixed (char* dest = destination, src = this) + CharCopy (dest + destinationIndex, src + sourceIndex, count); } - public char[] ToCharArray () + public unsafe char[] ToCharArray () { - return ToCharArray (0, length); + char[] tmp = new char [length]; + fixed (char* dest = tmp, src = this) + CharCopy (dest, src, length); + return tmp; } - public char[] ToCharArray (int startIndex, int length) + public unsafe char[] ToCharArray (int startIndex, int length) { if (startIndex < 0) throw new ArgumentOutOfRangeException ("startIndex", "< 0"); if (length < 0) throw new ArgumentOutOfRangeException ("length", "< 0"); - // re-ordered to avoid possible integer overflow if (startIndex > this.length - length) - throw new ArgumentOutOfRangeException ("startIndex + length > this.length"); + throw new ArgumentOutOfRangeException ("startIndex", "Must be greater than the length of the string."); char[] tmp = new char [length]; - - InternalCopyTo (startIndex, tmp, 0, length); - + fixed (char* dest = tmp, src = this) + CharCopy (dest, src + startIndex, length); return tmp; } @@ -197,14 +224,14 @@ namespace System return new String[0]; if (count == 1) - return new String[1] { ToString() }; + return new String[1] { this }; - return InternalSplit (separator, count); + return InternalSplit (separator, count, 0); } #if NET_2_0 [ComVisible (false)] - [MonoTODO] + [MonoDocumentationNote ("code should be moved to managed")] public String[] Split (char[] separator, int count, StringSplitOptions options) { if (separator == null || separator.Length == 0) @@ -213,14 +240,12 @@ namespace System if (count < 0) throw new ArgumentOutOfRangeException ("count", "Count cannot be less than zero."); if ((options != StringSplitOptions.None) && (options != StringSplitOptions.RemoveEmptyEntries)) - throw new ArgumentException ("options must be one of the values in the StringSplitOptions enumeration", "options"); + throw new ArgumentException ("Illegal enum value: " + options + "."); - bool removeEmpty = (options & StringSplitOptions.RemoveEmptyEntries) == StringSplitOptions.RemoveEmptyEntries; + if (count == 0) + return new string [0]; - if (!removeEmpty) - return Split (separator, count); - else - throw new NotImplementedException (); + return InternalSplit (separator, count, (int)options); } [ComVisible (false)] @@ -232,7 +257,7 @@ namespace System if (count < 0) throw new ArgumentOutOfRangeException ("count", "Count cannot be less than zero."); if ((options != StringSplitOptions.None) && (options != StringSplitOptions.RemoveEmptyEntries)) - throw new ArgumentException ("Illegal enum value: " + options + ".", "options"); + throw new ArgumentException ("Illegal enum value: " + options + "."); bool removeEmpty = (options & StringSplitOptions.RemoveEmptyEntries) == StringSplitOptions.RemoveEmptyEntries; @@ -242,6 +267,7 @@ namespace System ArrayList arr = new ArrayList (); int pos = 0; + int matchCount = 0; while (pos < this.Length) { int matchIndex = -1; int matchPos = Int32.MaxValue; @@ -262,21 +288,18 @@ namespace System if (matchIndex == -1) break; - if (matchPos == pos && removeEmpty) { - pos = matchPos + separator [matchIndex].Length; - } - else { + if (!(matchPos == pos && removeEmpty)) arr.Add (this.Substring (pos, matchPos - pos)); - pos = matchPos + separator [matchIndex].Length; + pos = matchPos + separator [matchIndex].Length; - if (arr.Count == count - 1) { - break; - } - } + matchCount ++; + + if (matchCount == count - 1) + break; } - if (arr.Count == 0) + if (matchCount == 0) return new String [] { this }; else { if (removeEmpty && pos == this.Length) { @@ -308,41 +331,57 @@ namespace System } #endif - public unsafe String Substring (int startIndex) + public String Substring (int startIndex) { +#if NET_2_0 + if (startIndex == 0) + return this; if (startIndex < 0 || startIndex > this.length) throw new ArgumentOutOfRangeException ("startIndex"); +#else + if (startIndex < 0) + throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative."); - int newlen = this.length - startIndex; - string tmp = InternalAllocateStr (newlen); - if (newlen != 0) { - fixed (char *dest = tmp, src = this) { - memcpy ((byte*)dest, (byte*)(src + startIndex), newlen * 2); - } - } - return tmp; + if (startIndex > this.length) + throw new ArgumentOutOfRangeException ("length", "Cannot exceed length of string."); +#endif + + return SubstringUnchecked (startIndex, this.length - startIndex); } - public unsafe String Substring (int startIndex, int length) + public String Substring (int startIndex, int length) { if (length < 0) - throw new ArgumentOutOfRangeException ("length", "< 0"); + throw new ArgumentOutOfRangeException ("length", "Cannot be negative."); if (startIndex < 0) - throw new ArgumentOutOfRangeException ("startIndex", "< 0"); - // re-ordered to avoid possible integer overflow + throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative."); +#if NET_2_0 + if (startIndex > this.length) + throw new ArgumentOutOfRangeException ("startIndex", "Cannot exceed length of string."); +#endif if (startIndex > this.length - length) - throw new ArgumentOutOfRangeException ("startIndex + length > this.length"); + throw new ArgumentOutOfRangeException ("length", "startIndex + length > this.length"); +#if NET_2_0 + if (startIndex == 0 && length == this.length) + return this; +#endif + + return SubstringUnchecked (startIndex, length); + } + // This method is used by StringBuilder.ToString() and is expected to + // always create a new string object (or return String.Empty). + internal unsafe String SubstringUnchecked (int startIndex, int length) + { if (length == 0) return String.Empty; string tmp = InternalAllocateStr (length); - fixed (char *dest = tmp, src = this) { - memcpy ((byte*)dest, (byte*)(src + startIndex), length * 2); + fixed (char* dest = tmp, src = this) { + CharCopy (dest, src + startIndex, length); } - return tmp; - } + } private static readonly char[] WhiteChars = { (char) 0x9, (char) 0xA, (char) 0xB, (char) 0xC, (char) 0xD, #if NET_2_0 @@ -354,41 +393,127 @@ namespace System public String Trim () { - return InternalTrim (WhiteChars, 0); + if (length == 0) + return String.Empty; + int start = FindNotWhiteSpace (0, length, 1); + + if (start == length) + return String.Empty; + + int end = FindNotWhiteSpace (length - 1, start, -1); + + int newLength = end - start + 1; + if (newLength == length) + return this; + + return SubstringUnchecked (start, newLength); } public String Trim (params char[] trimChars) { if (trimChars == null || trimChars.Length == 0) - trimChars = WhiteChars; + return Trim (); + + if (length == 0) + return String.Empty; + int start = FindNotInTable (0, length, 1, trimChars); + + if (start == length) + return String.Empty; + + int end = FindNotInTable (length - 1, start, -1, trimChars); + + int newLength = end - start + 1; + if (newLength == length) + return this; - return InternalTrim (trimChars, 0); + return SubstringUnchecked (start, newLength); } public String TrimStart (params char[] trimChars) { + if (length == 0) + return String.Empty; + int start; if (trimChars == null || trimChars.Length == 0) - trimChars = WhiteChars; + start = FindNotWhiteSpace (0, length, 1); + else + start = FindNotInTable (0, length, 1, trimChars); + + if (start == 0) + return this; - return InternalTrim (trimChars, 1); + return SubstringUnchecked (start, length - start); } public String TrimEnd (params char[] trimChars) { + if (length == 0) + return String.Empty; + int end; if (trimChars == null || trimChars.Length == 0) - trimChars = WhiteChars; + end = FindNotWhiteSpace (length - 1, -1, -1); + else + end = FindNotInTable (length - 1, -1, -1, trimChars); + + end++; + if (end == length) + return this; - return InternalTrim (trimChars, 2); + return SubstringUnchecked (0, end); + } + + private int FindNotWhiteSpace (int pos, int target, int change) + { + while (pos != target) { + char c = this[pos]; + if (c < 0x85) { + if (c != 0x20) { + if (c < 0x9 || c > 0xD) + return pos; + } + } + else { + if (c != 0xA0 && c != 0xFEFF && c != 0x3000) { +#if NET_2_0 + if (c != 0x85 && c != 0x1680 && c != 0x2028 && c != 0x2029) +#endif + if (c < 0x2000 || c > 0x200B) + return pos; + } + } + pos += change; + } + return pos; + } + + private unsafe int FindNotInTable (int pos, int target, int change, char[] table) + { + fixed (char* tablePtr = table, thisPtr = this) { + while (pos != target) { + char c = thisPtr[pos]; + int x = 0; + while (x < table.Length) { + if (c == tablePtr[x]) + break; + x++; + } + if (x == table.Length) + return pos; + pos += change; + } + } + return pos; } public static int Compare (String strA, String strB) { - return Compare (strA, strB, false, CultureInfo.CurrentCulture); + return CultureInfo.CurrentCulture.CompareInfo.Compare (strA, strB, CompareOptions.None); } public static int Compare (String strA, String strB, bool ignoreCase) { - return Compare (strA, strB, ignoreCase, CultureInfo.CurrentCulture); + return CultureInfo.CurrentCulture.CompareInfo.Compare (strA, strB, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); } public static int Compare (String strA, String strB, bool ignoreCase, CultureInfo culture) @@ -396,25 +521,7 @@ namespace System if (culture == null) throw new ArgumentNullException ("culture"); - if (strA == null) { - if (strB == null) - return 0; - else - return -1; - - } - else if (strB == null) { - return 1; - } - - CompareOptions compopts; - - if (ignoreCase) - compopts = CompareOptions.IgnoreCase; - else - compopts = CompareOptions.None; - - return culture.CompareInfo.Compare (strA, strB, compopts); + return culture.CompareInfo.Compare (strA, strB, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); } public static int Compare (String strA, int indexA, String strB, int indexB, int length) @@ -456,11 +563,11 @@ namespace System else compopts = CompareOptions.None; - /* Need to cap the requested length to the - * length of the string, because - * CompareInfo.Compare will insist that length - * <= (string.Length - offset) - */ + // Need to cap the requested length to the + // length of the string, because + // CompareInfo.Compare will insist that length + // <= (string.Length - offset) + int len1 = length; int len2 = length; @@ -472,8 +579,87 @@ namespace System len2 = strB.Length - indexB; } + // ENHANCE: Might call internal_compare_switch directly instead of doing all checks twice return culture.CompareInfo.Compare (strA, indexA, len1, strB, indexB, len2, compopts); } +#if NET_2_0 + public static int Compare (string strA, string strB, StringComparison comparisonType) + { + switch (comparisonType) { + case StringComparison.CurrentCulture: + return Compare (strA, strB, false, CultureInfo.CurrentCulture); + case StringComparison.CurrentCultureIgnoreCase: + return Compare (strA, strB, true, CultureInfo.CurrentCulture); + case StringComparison.InvariantCulture: + return Compare (strA, strB, false, CultureInfo.InvariantCulture); + case StringComparison.InvariantCultureIgnoreCase: + return Compare (strA, strB, true, CultureInfo.InvariantCulture); + case StringComparison.Ordinal: + return CompareOrdinalUnchecked (strA, 0, Int32.MaxValue, strB, 0, Int32.MaxValue); + case StringComparison.OrdinalIgnoreCase: + return CompareOrdinalCaseInsensitiveUnchecked (strA, 0, Int32.MaxValue, strB, 0, Int32.MaxValue); + default: + string msg = Locale.GetText ("Invalid value '{0}' for StringComparison", comparisonType); + throw new ArgumentException (msg, "comparisonType"); + } + } + + public static int Compare (string strA, int indexA, string strB, int indexB, int length, StringComparison comparisonType) + { + switch (comparisonType) { + case StringComparison.CurrentCulture: + return Compare (strA, indexA, strB, indexB, length, false, CultureInfo.CurrentCulture); + case StringComparison.CurrentCultureIgnoreCase: + return Compare (strA, indexA, strB, indexB, length, true, CultureInfo.CurrentCulture); + case StringComparison.InvariantCulture: + return Compare (strA, indexA, strB, indexB, length, false, CultureInfo.InvariantCulture); + case StringComparison.InvariantCultureIgnoreCase: + return Compare (strA, indexA, strB, indexB, length, true, CultureInfo.InvariantCulture); + case StringComparison.Ordinal: + return CompareOrdinal (strA, indexA, strB, indexB, length); + case StringComparison.OrdinalIgnoreCase: + return CompareOrdinalCaseInsensitive (strA, indexA, strB, indexB, length); + default: + string msg = Locale.GetText ("Invalid value '{0}' for StringComparison", comparisonType); + throw new ArgumentException (msg, "comparisonType"); + } + } + + public static bool Equals (string a, string b, StringComparison comparisonType) + { + return String.Compare (a, b, comparisonType) == 0; + } + + public bool Equals (string value, StringComparison comparisonType) + { + return String.Compare (value, this, comparisonType) == 0; + } + + public static int Compare (string strA, string strB, CultureInfo culture, CompareOptions options) + { + if (culture == null) + throw new ArgumentNullException ("culture"); + + return culture.CompareInfo.Compare (strA, strB, options); + } + + public static int Compare (string strA, int indexA, string strB, int indexB, int length, CultureInfo culture, CompareOptions options) + { + if (culture == null) + throw new ArgumentNullException ("culture"); + + int len1 = length; + int len2 = length; + + if (length > (strA.Length - indexA)) + len1 = strA.Length - indexA; + + if (length > (strB.Length - indexB)) + len2 = strB.Length - indexB; + + return culture.CompareInfo.Compare (strA, indexA, len1, strB, indexB, len2, options); + } +#endif public int CompareTo (Object value) { @@ -483,7 +669,7 @@ namespace System if (!(value is String)) throw new ArgumentException (); - return String.Compare (this, (String) value, false); + return String.Compare (this, (String) value); } public int CompareTo (String strB) @@ -491,65 +677,101 @@ namespace System if (strB == null) return 1; - return Compare (this, strB, false); + return Compare (this, strB); } public static int CompareOrdinal (String strA, String strB) + { + return CompareOrdinalUnchecked (strA, 0, Int32.MaxValue, strB, 0, Int32.MaxValue); + } + + public static int CompareOrdinal (String strA, int indexA, String strB, int indexB, int length) + { + if ((indexA > strA.Length) || (indexB > strB.Length) || (indexA < 0) || (indexB < 0) || (length < 0)) + throw new ArgumentOutOfRangeException (); + + return CompareOrdinalUnchecked (strA, indexA, length, strB, indexB, length); + } + + internal static int CompareOrdinalCaseInsensitive (String strA, int indexA, String strB, int indexB, int length) + { + if ((indexA > strA.Length) || (indexB > strB.Length) || (indexA < 0) || (indexB < 0) || (length < 0)) + throw new ArgumentOutOfRangeException (); + + return CompareOrdinalCaseInsensitiveUnchecked (strA, indexA, length, strB, indexB, length); + } + + internal static unsafe int CompareOrdinalUnchecked (String strA, int indexA, int lenA, String strB, int indexB, int lenB) { if (strA == null) { if (strB == null) return 0; else return -1; - } - else if (strB == null) { + } else if (strB == null) { return 1; } + int lengthA = Math.Min (lenA, strA.Length - indexA); + int lengthB = Math.Min (lenB, strB.Length - indexB); + + if (lengthA == lengthB && Object.ReferenceEquals (strA, strB)) + return 0; - /* Invariant, because that is cheaper to - * instantiate (and chances are it already has - * been.) - */ - return CultureInfo.InvariantCulture.CompareInfo.Compare (strA, strB, CompareOptions.Ordinal); + fixed (char* aptr = strA, bptr = strB) { + char* ap = aptr + indexA; + char* end = ap + Math.Min (lengthA, lengthB); + char* bp = bptr + indexB; + while (ap < end) { + if (*ap != *bp) + return *ap - *bp; + ap++; + bp++; + } + return lengthA - lengthB; + } } - public static int CompareOrdinal (String strA, int indexA, String strB, int indexB, int length) + internal static unsafe int CompareOrdinalCaseInsensitiveUnchecked (String strA, int indexA, int lenA, String strB, int indexB, int lenB) { - if ((indexA > strA.Length) || (indexB > strB.Length) || (indexA < 0) || (indexB < 0) || (length < 0)) - throw new ArgumentOutOfRangeException (); - + // Same as above, but checks versus uppercase characters if (strA == null) { if (strB == null) return 0; else return -1; - } - else if (strB == null) { + } else if (strB == null) { return 1; } + int lengthA = Math.Min (lenA, strA.Length - indexA); + int lengthB = Math.Min (lenB, strB.Length - indexB); - /* Need to cap the requested length to the - * length of the string, because - * CompareInfo.Compare will insist that length - * <= (string.Length - offset) - */ - int len1 = length; - int len2 = length; - - if (length > (strA.Length - indexA)) { - len1 = strA.Length - indexA; - } + if (lengthA == lengthB && Object.ReferenceEquals (strA, strB)) + return 0; - if (length > (strB.Length - indexB)) { - len2 = strB.Length - indexB; + fixed (char* aptr = strA, bptr = strB) { + char* ap = aptr + indexA; + char* end = ap + Math.Min (lengthA, lengthB); + char* bp = bptr + indexB; + while (ap < end) { + if (*ap != *bp) { + char c1 = Char.ToUpperInvariant (*ap); + char c2 = Char.ToUpperInvariant (*bp); + if (c1 != c2) + return c1 - c2; + } + ap++; + bp++; + } + return lengthA - lengthB; } - - return CultureInfo.InvariantCulture.CompareInfo.Compare (strA, indexA, len1, strB, indexB, len2, CompareOptions.Ordinal); } public bool EndsWith (String value) { - return EndsWith (value, false, CultureInfo.CurrentCulture); + if (value == null) + throw new ArgumentNullException ("value"); + + return CultureInfo.CurrentCulture.CompareInfo.IsSuffix (this, value, CompareOptions.None); } #if NET_2_0 @@ -559,97 +781,428 @@ namespace System #endif bool EndsWith (String value, bool ignoreCase, CultureInfo culture) { - return (culture.CompareInfo.IsSuffix (this, value, - ignoreCase ? CompareOptions.IgnoreCase : - CompareOptions.None)); + if (value == null) + throw new ArgumentNullException ("value"); + if (culture == null) + culture = CultureInfo.CurrentCulture; + + return culture.CompareInfo.IsSuffix (this, value, + ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); } + // Following methods are culture-insensitive public int IndexOfAny (char [] anyOf) { if (anyOf == null) - throw new ArgumentNullException ("anyOf"); + throw new ArgumentNullException (); + if (this.length == 0) + return -1; - return InternalIndexOfAny (anyOf, 0, this.length); + return IndexOfAnyUnchecked (anyOf, 0, this.length); } public int IndexOfAny (char [] anyOf, int startIndex) { if (anyOf == null) - throw new ArgumentNullException ("anyOf"); + throw new ArgumentNullException (); if (startIndex < 0 || startIndex > this.length) - throw new ArgumentOutOfRangeException ("startIndex"); + throw new ArgumentOutOfRangeException (); - return InternalIndexOfAny (anyOf, startIndex, this.length - startIndex); + return IndexOfAnyUnchecked (anyOf, startIndex, this.length - startIndex); } public int IndexOfAny (char [] anyOf, int startIndex, int count) { if (anyOf == null) - throw new ArgumentNullException ("anyOf"); + throw new ArgumentNullException (); + if (startIndex < 0 || startIndex > this.length) + throw new ArgumentOutOfRangeException (); + if (count < 0 || startIndex > this.length - count) + throw new ArgumentOutOfRangeException ("count", "Count cannot be negative, and startIndex + count must be less than length of the string."); + + return IndexOfAnyUnchecked (anyOf, startIndex, count); + } + + private unsafe int IndexOfAnyUnchecked (char[] anyOf, int startIndex, int count) + { + if (anyOf.Length == 0) + return -1; + + if (anyOf.Length == 1) + return IndexOfUnchecked (anyOf[0], startIndex, count); + + fixed (char* any = anyOf) { + int highest = *any; + int lowest = *any; + + char* end_any_ptr = any + anyOf.Length; + char* any_ptr = any; + while (++any_ptr != end_any_ptr) { + if (*any_ptr > highest) { + highest = *any_ptr; + continue; + } + + if (*any_ptr < lowest) + lowest = *any_ptr; + } + + fixed (char* start = &start_char) { + char* ptr = start + startIndex; + char* end_ptr = ptr + count; + + while (ptr != end_ptr) { + if (*ptr > highest || *ptr < lowest) { + ptr++; + continue; + } + + if (*ptr == *any) + return (int)(ptr - start); + + any_ptr = any; + while (++any_ptr != end_any_ptr) { + if (*ptr == *any_ptr) + return (int)(ptr - start); + } + + ptr++; + } + } + } + return -1; + } + + +#if NET_2_0 + public int IndexOf (string value, StringComparison comparisonType) + { + return IndexOf (value, 0, this.Length, comparisonType); + } + + public int IndexOf (string value, int startIndex, StringComparison comparisonType) + { + return IndexOf (value, startIndex, this.Length - startIndex, comparisonType); + } + + public int IndexOf (string value, int startIndex, int count, StringComparison comparisonType) + { + switch (comparisonType) { + case StringComparison.CurrentCulture: + return CultureInfo.CurrentCulture.CompareInfo.IndexOf (this, value, startIndex, count, CompareOptions.None); + case StringComparison.CurrentCultureIgnoreCase: + return CultureInfo.CurrentCulture.CompareInfo.IndexOf (this, value, startIndex, count, CompareOptions.IgnoreCase); + case StringComparison.InvariantCulture: + return CultureInfo.InvariantCulture.CompareInfo.IndexOf (this, value, startIndex, count, CompareOptions.None); + case StringComparison.InvariantCultureIgnoreCase: + return CultureInfo.InvariantCulture.CompareInfo.IndexOf (this, value, startIndex, count, CompareOptions.IgnoreCase); + case StringComparison.Ordinal: + return IndexOfOrdinal (value, startIndex, count, CompareOptions.Ordinal); + case StringComparison.OrdinalIgnoreCase: + return IndexOfOrdinal (value, startIndex, count, CompareOptions.OrdinalIgnoreCase); + default: + string msg = Locale.GetText ("Invalid value '{0}' for StringComparison", comparisonType); + throw new ArgumentException (msg, "comparisonType"); + } + } +#endif + + internal int IndexOfOrdinal (string value, int startIndex, int count, CompareOptions options) + { + if (value == null) + throw new ArgumentNullException ("value"); if (startIndex < 0) - throw new ArgumentOutOfRangeException ("startIndex", "< 0"); - if (count < 0) - throw new ArgumentOutOfRangeException ("count", "< 0"); - // re-ordered to avoid possible integer overflow - if (startIndex > this.length - count) - throw new ArgumentOutOfRangeException ("startIndex + count > this.length"); + throw new ArgumentOutOfRangeException ("startIndex"); + if (count < 0 || (this.length - startIndex) < count) + throw new ArgumentOutOfRangeException ("count"); - return InternalIndexOfAny (anyOf, startIndex, count); + if (options == CompareOptions.Ordinal) + return IndexOfOrdinalUnchecked (value, startIndex, count); + return IndexOfOrdinalIgnoreCaseUnchecked (value, startIndex, count); } - public int IndexOf (char value) + internal unsafe int IndexOfOrdinalUnchecked (string value, int startIndex, int count) { - return IndexOf (value, 0, this.length); + int valueLen = value.Length; + if (count < valueLen) + return -1; + + if (valueLen <= 1) { + if (valueLen == 1) + return IndexOfUnchecked (value[0], startIndex, count); + return 0; + } + + fixed (char* thisptr = this, valueptr = value) { + char* ap = thisptr + startIndex; + char* thisEnd = ap + count - valueLen + 1; + while (ap != thisEnd) { + if (*ap == *valueptr) { + for (int i = 1; i < valueLen; i++) { + if (ap[i] != valueptr[i]) + goto NextVal; + } + return (int)(ap - thisptr); + } + NextVal: + ap++; + } + } + return -1; } - public int IndexOf (String value) + internal unsafe int IndexOfOrdinalIgnoreCaseUnchecked (string value, int startIndex, int count) { - return IndexOf (value, 0, this.length); + int valueLen = value.Length; + if (count < valueLen) + return -1; + + if (valueLen == 0) + return 0; + + fixed (char* thisptr = this, valueptr = value) { + char* ap = thisptr + startIndex; + char* thisEnd = ap + count - valueLen + 1; + while (ap != thisEnd) { + for (int i = 0; i < valueLen; i++) { + if (Char.ToUpperInvariant (ap[i]) != Char.ToUpperInvariant (valueptr[i])) + goto NextVal; + } + return (int)(ap - thisptr); + NextVal: + ap++; + } + } + return -1; } - public int IndexOf (char value, int startIndex) +#if NET_2_0 + + public int LastIndexOf (string value, StringComparison comparisonType) { - return IndexOf (value, startIndex, this.length - startIndex); + if (this.Length == 0) + return value == String.Empty ? 0 : -1; + else + return LastIndexOf (value, this.Length - 1, this.Length, comparisonType); } - public int IndexOf (String value, int startIndex) + public int LastIndexOf (string value, int startIndex, StringComparison comparisonType) { - return IndexOf (value, startIndex, this.length - startIndex); + return LastIndexOf (value, startIndex, startIndex + 1, comparisonType); } - /* This method is culture-insensitive */ - public int IndexOf (char value, int startIndex, int count) + public int LastIndexOf (string value, int startIndex, int count, StringComparison comparisonType) + { + switch (comparisonType) { + case StringComparison.CurrentCulture: + return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf (this, value, startIndex, count, CompareOptions.None); + case StringComparison.CurrentCultureIgnoreCase: + return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf (this, value, startIndex, count, CompareOptions.IgnoreCase); + case StringComparison.InvariantCulture: + return CultureInfo.InvariantCulture.CompareInfo.LastIndexOf (this, value, startIndex, count, CompareOptions.None); + case StringComparison.InvariantCultureIgnoreCase: + return CultureInfo.InvariantCulture.CompareInfo.LastIndexOf (this, value, startIndex, count, CompareOptions.IgnoreCase); + case StringComparison.Ordinal: + return LastIndexOfOrdinal (value, startIndex, count, CompareOptions.Ordinal); + case StringComparison.OrdinalIgnoreCase: + return LastIndexOfOrdinal (value, startIndex, count, CompareOptions.OrdinalIgnoreCase); + default: + string msg = Locale.GetText ("Invalid value '{0}' for StringComparison", comparisonType); + throw new ArgumentException (msg, "comparisonType"); + } + } +#endif + + internal int LastIndexOfOrdinal (string value, int startIndex, int count, CompareOptions options) + { + if (value == null) + throw new ArgumentNullException ("value"); + if (startIndex < 0 || startIndex > length) + throw new ArgumentOutOfRangeException ("startIndex"); + if (count < 0 || (startIndex < count - 1)) + throw new ArgumentOutOfRangeException ("count"); + + if (options == CompareOptions.Ordinal) + return LastIndexOfOrdinalUnchecked (value, startIndex, count); + return LastIndexOfOrdinalIgnoreCaseUnchecked (value, startIndex, count); + } + + internal unsafe int LastIndexOfOrdinalUnchecked (string value, int startIndex, int count) + { + int valueLen = value.Length; + if (count < valueLen) + return -1; + + if (valueLen <= 1) { + if (valueLen == 1) + return LastIndexOfUnchecked (value[0], startIndex, count); + return 0; + } + + fixed (char* thisptr = this, valueptr = value) { + char* ap = thisptr + startIndex - valueLen + 1; + char* thisEnd = ap - count + valueLen - 1; + while (ap != thisEnd) { + if (*ap == *valueptr) { + for (int i = 1; i < valueLen; i++) { + if (ap[i] != valueptr[i]) + goto NextVal; + } + return (int)(ap - thisptr); + } + NextVal: + ap--; + } + } + return -1; + } + + internal unsafe int LastIndexOfOrdinalIgnoreCaseUnchecked (string value, int startIndex, int count) + { + int valueLen = value.Length; + if (count < valueLen) + return -1; + + if (valueLen == 0) + return 0; + + fixed (char* thisptr = this, valueptr = value) { + char* ap = thisptr + startIndex - valueLen + 1; + char* thisEnd = ap - count + valueLen - 1; + while (ap != thisEnd) { + for (int i = 0; i < valueLen; i++) { + if (Char.ToUpperInvariant (ap[i]) != Char.ToUpperInvariant (valueptr[i])) + goto NextVal; + } + return (int)(ap - thisptr); + NextVal: + ap--; + } + } + return -1; + } + + // Following methods are culture-insensitive + public int IndexOf (char value) + { + if (this.length == 0) + return -1; + + return IndexOfUnchecked (value, 0, this.length); + } + + public int IndexOf (char value, int startIndex) { if (startIndex < 0) throw new ArgumentOutOfRangeException ("startIndex", "< 0"); + if (startIndex > this.length) + throw new ArgumentOutOfRangeException ("startIndex", "startIndex > this.length"); + + if ((startIndex == 0 && this.length == 0) || (startIndex == this.length)) + return -1; + + return IndexOfUnchecked (value, startIndex, this.length - startIndex); + } + + public int IndexOf (char value, int startIndex, int count) + { + if (startIndex < 0 || startIndex > this.length) + throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative and must be< 0"); if (count < 0) throw new ArgumentOutOfRangeException ("count", "< 0"); - // re-ordered to avoid possible integer overflow if (startIndex > this.length - count) - throw new ArgumentOutOfRangeException ("startIndex + count > this.length"); + throw new ArgumentOutOfRangeException ("count", "startIndex + count > this.length"); if ((startIndex == 0 && this.length == 0) || (startIndex == this.length) || (count == 0)) return -1; - for (int pos = startIndex; pos < startIndex + count; pos++) { - if (this[pos] == value) - return(pos); + return IndexOfUnchecked (value, startIndex, count); + } + + internal unsafe int IndexOfUnchecked (char value, int startIndex, int count) + { + // It helps JIT compiler to optimize comparison + int value_32 = (int)value; + + fixed (char* start = &start_char) { + char* ptr = start + startIndex; + char* end_ptr = ptr + (count >> 3 << 3); + + while (ptr != end_ptr) { + if (*ptr == value_32) + return (int)(ptr - start); + if (ptr[1] == value_32) + return (int)(ptr - start + 1); + if (ptr[2] == value_32) + return (int)(ptr - start + 2); + if (ptr[3] == value_32) + return (int)(ptr - start + 3); + if (ptr[4] == value_32) + return (int)(ptr - start + 4); + if (ptr[5] == value_32) + return (int)(ptr - start + 5); + if (ptr[6] == value_32) + return (int)(ptr - start + 6); + if (ptr[7] == value_32) + return (int)(ptr - start + 7); + + ptr += 8; + } + + end_ptr += count & 0x07; + while (ptr != end_ptr) { + if (*ptr == value_32) + return (int)(ptr - start); + + ptr++; + } + return -1; + } + } + + internal unsafe int IndexOfOrdinalIgnoreCase (char value, int startIndex, int count) + { + if (length == 0) + return -1; + int end = startIndex + count; + char c = Char.ToUpperInvariant (value); + fixed (char* s = &start_char) { + for (int i = startIndex; i < end; i++) + if (Char.ToUpperInvariant (s [i]) == c) + return i; } return -1; } - /* But this one is culture-sensitive */ + // Following methods are culture-sensitive + public int IndexOf (String value) + { + if (value == null) + throw new ArgumentNullException ("value"); + if (value.length == 0) + return 0; + if (this.length == 0) + return -1; + return CultureInfo.CurrentCulture.CompareInfo.IndexOf (this, value, 0, length); + } + + public int IndexOf (String value, int startIndex) + { + return IndexOf (value, startIndex, this.length - startIndex); + } + public int IndexOf (String value, int startIndex, int count) { if (value == null) +#if NET_2_0 throw new ArgumentNullException ("value"); - if (startIndex < 0) - throw new ArgumentOutOfRangeException ("startIndex", "< 0"); - if (count < 0) - throw new ArgumentOutOfRangeException ("count", "< 0"); - // re-ordered to avoid possible integer overflow - if (startIndex > this.length - count) - throw new ArgumentOutOfRangeException ("startIndex + count > this.length"); +#else + throw new ArgumentNullException ("string2"); +#endif + if (startIndex < 0 || startIndex > this.length) + throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative, and should not exceed length of string."); + if (count < 0 || startIndex > this.length - count) + throw new ArgumentOutOfRangeException ("count", "Cannot be negative, and should point to location in string."); if (value.length == 0) return startIndex; @@ -663,32 +1216,33 @@ namespace System return CultureInfo.CurrentCulture.CompareInfo.IndexOf (this, value, startIndex, count); } + // Following methods are culture-insensitive public int LastIndexOfAny (char [] anyOf) { if (anyOf == null) - throw new ArgumentNullException ("anyOf"); + throw new ArgumentNullException (); - return InternalLastIndexOfAny (anyOf, this.length - 1, this.length); + return LastIndexOfAnyUnchecked (anyOf, this.length - 1, this.length); } public int LastIndexOfAny (char [] anyOf, int startIndex) { - if (anyOf == null) - throw new ArgumentNullException ("anyOf"); + if (anyOf == null) + throw new ArgumentNullException (); if (startIndex < 0 || startIndex >= this.length) - throw new ArgumentOutOfRangeException (); + throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative, and should be less than length of string."); if (this.length == 0) return -1; - return InternalLastIndexOfAny (anyOf, startIndex, startIndex + 1); + return LastIndexOfAnyUnchecked (anyOf, startIndex, startIndex + 1); } public int LastIndexOfAny (char [] anyOf, int startIndex, int count) { if (anyOf == null) - throw new ArgumentNullException ("anyOf"); + throw new ArgumentNullException (); if ((startIndex < 0) || (startIndex >= this.Length)) throw new ArgumentOutOfRangeException ("startIndex", "< 0 || > this.Length"); @@ -700,24 +1254,40 @@ namespace System if (this.length == 0) return -1; - return InternalLastIndexOfAny (anyOf, startIndex, count); + return LastIndexOfAnyUnchecked (anyOf, startIndex, count); } - public int LastIndexOf (char value) + private unsafe int LastIndexOfAnyUnchecked (char [] anyOf, int startIndex, int count) { - if (this.length == 0) + if (anyOf.Length == 1) + return LastIndexOfUnchecked (anyOf[0], startIndex, count); + + fixed (char* start = this, testStart = anyOf) { + char* ptr = start + startIndex; + char* ptrEnd = ptr - count; + char* test; + char* testEnd = testStart + anyOf.Length; + + while (ptr != ptrEnd) { + test = testStart; + while (test != testEnd) { + if (*test == *ptr) + return (int)(ptr - start); + test++; + } + ptr--; + } return -1; - else - return LastIndexOf (value, this.length - 1, this.length); + } } - public int LastIndexOf (String value) + // Following methods are culture-insensitive + public int LastIndexOf (char value) { if (this.length == 0) - /* This overload does additional checking */ - return LastIndexOf (value, 0, 0); - else - return LastIndexOf (value, this.length - 1, this.length); + return -1; + + return LastIndexOfUnchecked (value, this.length - 1, this.length); } public int LastIndexOf (char value, int startIndex) @@ -725,17 +1295,6 @@ namespace System return LastIndexOf (value, startIndex, startIndex + 1); } - public int LastIndexOf (String value, int startIndex) - { - if (value == null) - throw new ArgumentNullException ("value"); - int max = startIndex; - if (max < this.Length) - max++; - return LastIndexOf (value, startIndex, max); - } - - /* This method is culture-insensitive */ public int LastIndexOf (char value, int startIndex, int count) { if (startIndex == 0 && this.length == 0) @@ -749,18 +1308,91 @@ namespace System if (startIndex - count + 1 < 0) throw new ArgumentOutOfRangeException ("startIndex - count + 1 < 0"); - for(int pos = startIndex; pos > startIndex - count; pos--) { - if (this [pos] == value) - return pos; + return LastIndexOfUnchecked (value, startIndex, count); + } + + internal unsafe int LastIndexOfUnchecked (char value, int startIndex, int count) + { + // It helps JIT compiler to optimize comparison + int value_32 = (int)value; + + fixed (char* start = &start_char) { + char* ptr = start + startIndex; + char* end_ptr = ptr - (count >> 3 << 3); + + while (ptr != end_ptr) { + if (*ptr == value_32) + return (int)(ptr - start); + if (ptr[-1] == value_32) + return (int)(ptr - start) - 1; + if (ptr[-2] == value_32) + return (int)(ptr - start) - 2; + if (ptr[-3] == value_32) + return (int)(ptr - start) - 3; + if (ptr[-4] == value_32) + return (int)(ptr - start) - 4; + if (ptr[-5] == value_32) + return (int)(ptr - start) - 5; + if (ptr[-6] == value_32) + return (int)(ptr - start) - 6; + if (ptr[-7] == value_32) + return (int)(ptr - start) - 7; + + ptr -= 8; + } + + end_ptr -= count & 0x07; + while (ptr != end_ptr) { + if (*ptr == value_32) + return (int)(ptr - start); + + ptr--; + } + return -1; + } + } + + internal unsafe int LastIndexOfOrdinalIgnoreCase (char value, int startIndex, int count) + { + if (length == 0) + return -1; + int end = startIndex - count; + char c = Char.ToUpperInvariant (value); + fixed (char* s = &start_char) { + for (int i = startIndex; i > end; i--) + if (Char.ToUpperInvariant (s [i]) == c) + return i; } return -1; } - /* But this one is culture-sensitive */ + // Following methods are culture-sensitive + public int LastIndexOf (String value) + { + if (this.length == 0) + // This overload does additional checking + return LastIndexOf (value, 0, 0); + else + return LastIndexOf (value, this.length - 1, this.length); + } + + public int LastIndexOf (String value, int startIndex) + { + int max = startIndex; + if (max < this.Length) + max++; + return LastIndexOf (value, startIndex, max); + } + public int LastIndexOf (String value, int startIndex, int count) { if (value == null) +#if NET_2_0 throw new ArgumentNullException ("value"); +#else + throw new ArgumentNullException ("string2"); +#endif + // -1 > startIndex > for string (0 > startIndex >= for char) if ((startIndex < -1) || (startIndex > this.Length)) throw new ArgumentOutOfRangeException ("startIndex", "< 0 || > this.Length"); @@ -770,7 +1402,7 @@ namespace System throw new ArgumentOutOfRangeException ("startIndex - count + 1 < 0"); if (value.Length == 0) - return 0; + return startIndex; if (startIndex == 0 && this.length == 0) return -1; @@ -779,9 +1411,6 @@ namespace System if (this.length == 0 && value.length > 0) return -1; - if (value.length > startIndex) - return -1; - if (count == 0) return -1; @@ -801,6 +1430,44 @@ namespace System return (value == null) || (value.Length == 0); } + public string Normalize () + { + return Normalization.Normalize (this, 0); + } + + public string Normalize (NormalizationForm normalizationForm) + { + switch (normalizationForm) { + default: + return Normalization.Normalize (this, 0); + case NormalizationForm.FormD: + return Normalization.Normalize (this, 1); + case NormalizationForm.FormKC: + return Normalization.Normalize (this, 2); + case NormalizationForm.FormKD: + return Normalization.Normalize (this, 3); + } + } + + public bool IsNormalized () + { + return Normalization.IsNormalized (this, 0); + } + + public bool IsNormalized (NormalizationForm normalizationForm) + { + switch (normalizationForm) { + default: + return Normalization.IsNormalized (this, 0); + case NormalizationForm.FormD: + return Normalization.IsNormalized (this, 1); + case NormalizationForm.FormKC: + return Normalization.IsNormalized (this, 2); + case NormalizationForm.FormKD: + return Normalization.IsNormalized (this, 3); + } + } + public string Remove (int startIndex) { if (startIndex < 0) @@ -817,15 +1484,27 @@ namespace System return PadLeft (totalWidth, ' '); } - public String PadLeft (int totalWidth, char paddingChar) + public unsafe String PadLeft (int totalWidth, char paddingChar) { + //LAMESPEC: MSDN Doc says this is reversed for RtL languages, but this seems to be untrue + if (totalWidth < 0) throw new ArgumentOutOfRangeException ("totalWidth", "< 0"); if (totalWidth < this.length) - return String.Copy (this); + return this; + + String tmp = InternalAllocateStr (totalWidth); - return InternalPad (totalWidth, paddingChar, false); + fixed (char* dest = tmp, src = this) { + char* padPos = dest; + char* padTo = dest + (totalWidth - length); + while (padPos != padTo) + *padPos++ = paddingChar; + + CharCopy (padTo, src, length); + } + return tmp; } public String PadRight (int totalWidth) @@ -833,21 +1512,88 @@ namespace System return PadRight (totalWidth, ' '); } - public String PadRight (int totalWidth, char paddingChar) + public unsafe String PadRight (int totalWidth, char paddingChar) { + //LAMESPEC: MSDN Doc says this is reversed for RtL languages, but this seems to be untrue + if (totalWidth < 0) throw new ArgumentOutOfRangeException ("totalWidth", "< 0"); if (totalWidth < this.length) - return String.Copy (this); + return this; + + String tmp = InternalAllocateStr (totalWidth); - return InternalPad (totalWidth, paddingChar, true); + fixed (char* dest = tmp, src = this) { + CharCopy (dest, src, length); + + char* padPos = dest + length; + char* padTo = dest + totalWidth; + while (padPos != padTo) + *padPos++ = paddingChar; + } + return tmp; } public bool StartsWith (String value) { - return StartsWith (value, false, CultureInfo.CurrentCulture); + if (value == null) + throw new ArgumentNullException ("value"); + + return CultureInfo.CurrentCulture.CompareInfo.IsPrefix (this, value, CompareOptions.None); + } + +#if NET_2_0 + [ComVisible (false)] + public bool StartsWith (string value, StringComparison comparisonType) + { + if (value == null) + throw new ArgumentNullException ("value"); + + switch (comparisonType) { + case StringComparison.CurrentCulture: + return CultureInfo.CurrentCulture.CompareInfo.IsPrefix (this, value, CompareOptions.None); + case StringComparison.CurrentCultureIgnoreCase: + return CultureInfo.CurrentCulture.CompareInfo.IsPrefix (this, value, CompareOptions.IgnoreCase); + case StringComparison.InvariantCulture: + return CultureInfo.InvariantCulture.CompareInfo.IsPrefix (this, value, CompareOptions.None); + case StringComparison.InvariantCultureIgnoreCase: + return CultureInfo.InvariantCulture.CompareInfo.IsPrefix (this, value, CompareOptions.IgnoreCase); + case StringComparison.Ordinal: + return CultureInfo.CurrentCulture.CompareInfo.IsPrefix (this, value, CompareOptions.Ordinal); + case StringComparison.OrdinalIgnoreCase: + return CultureInfo.CurrentCulture.CompareInfo.IsPrefix (this, value, CompareOptions.OrdinalIgnoreCase); + default: + string msg = Locale.GetText ("Invalid value '{0}' for StringComparison", comparisonType); + throw new ArgumentException (msg, "comparisonType"); + } + } + + [ComVisible (false)] + public bool EndsWith (string value, StringComparison comparisonType) + { + if (value == null) + throw new ArgumentNullException ("value"); + + switch (comparisonType) { + case StringComparison.CurrentCulture: + return CultureInfo.CurrentCulture.CompareInfo.IsSuffix (this, value, CompareOptions.None); + case StringComparison.CurrentCultureIgnoreCase: + return CultureInfo.CurrentCulture.CompareInfo.IsSuffix (this, value, CompareOptions.IgnoreCase); + case StringComparison.InvariantCulture: + return CultureInfo.InvariantCulture.CompareInfo.IsSuffix (this, value, CompareOptions.None); + case StringComparison.InvariantCultureIgnoreCase: + return CultureInfo.InvariantCulture.CompareInfo.IsSuffix (this, value, CompareOptions.IgnoreCase); + case StringComparison.Ordinal: + return CultureInfo.CurrentCulture.CompareInfo.IsSuffix (this, value, CompareOptions.Ordinal); + case StringComparison.OrdinalIgnoreCase: + return CultureInfo.CurrentCulture.CompareInfo.IsSuffix (this, value, CompareOptions.OrdinalIgnoreCase); + default: + string msg = Locale.GetText ("Invalid value '{0}' for StringComparison", comparisonType); + throw new ArgumentException (msg, "comparisonType"); + } } +#endif #if NET_2_0 public @@ -856,20 +1602,53 @@ namespace System #endif bool StartsWith (String value, bool ignoreCase, CultureInfo culture) { - return (culture.CompareInfo.IsPrefix (this, value, - ignoreCase ? CompareOptions.IgnoreCase : - CompareOptions.None)); + if (culture == null) + culture = CultureInfo.CurrentCulture; + + return culture.CompareInfo.IsPrefix (this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); } - /* This method is culture insensitive */ - public String Replace (char oldChar, char newChar) - { - return InternalReplace (oldChar, newChar); + // Following method is culture-insensitive + public unsafe String Replace (char oldChar, char newChar) + { + if (this.length == 0 || oldChar == newChar) + return this; + + int start_pos = IndexOfUnchecked (oldChar, 0, this.length); + if (start_pos == -1) + return this; + + if (start_pos < 4) + start_pos = 0; + + string tmp = InternalAllocateStr (length); + fixed (char* dest = tmp, src = &start_char) { + if (start_pos != 0) + CharCopy (dest, src, start_pos); + + char* end_ptr = dest + length; + char* dest_ptr = dest + start_pos; + char* src_ptr = src + start_pos; + + while (dest_ptr != end_ptr) { + if (*src_ptr == oldChar) + *dest_ptr = newChar; + else + *dest_ptr = *src_ptr; + + ++src_ptr; + ++dest_ptr; + } + } + return tmp; } - /* This method is culture sensitive */ + // culture-insensitive using ordinal search (See testcase StringTest.ReplaceStringCultureTests) public String Replace (String oldValue, String newValue) { + // LAMESPEC: According to MSDN the following method is culture-sensitive but this seems to be incorrect + // LAMESPEC: Result is undefined if result length is longer than maximum string length + if (oldValue == null) throw new ArgumentNullException ("oldValue"); @@ -882,27 +1661,91 @@ namespace System if (newValue == null) newValue = String.Empty; - return InternalReplace (oldValue, newValue, CultureInfo.CurrentCulture.CompareInfo); + return ReplaceUnchecked (oldValue, newValue); + } + + private unsafe String ReplaceUnchecked (String oldValue, String newValue) + { + if (oldValue.length > length) + return this; + if (oldValue.length == 1 && newValue.length == 1) { + return Replace (oldValue[0], newValue[0]); + // ENHANCE: It would be possible to special case oldValue.length == newValue.length + // because the length of the result would be this.length and length calculation unneccesary + } + + const int maxValue = 200; // Allocate 800 byte maximum + int* dat = stackalloc int[maxValue]; + fixed (char* source = this, replace = newValue) { + int i = 0, count = 0; + while (i < length) { + int found = IndexOfOrdinalUnchecked (oldValue, i, length - i); + if (found < 0) + break; + else { + if (count < maxValue) + dat[count++] = found; + else + return ReplaceFallback (oldValue, newValue, maxValue); + } + i = found + oldValue.length; + } + if (count == 0) + return this; + int nlen = this.length + ((newValue.length - oldValue.length) * count); + String tmp = InternalAllocateStr (nlen); + + int curPos = 0, lastReadPos = 0; + fixed (char* dest = tmp) { + for (int j = 0; j < count; j++) { + int precopy = dat[j] - lastReadPos; + CharCopy (dest + curPos, source + lastReadPos, precopy); + curPos += precopy; + lastReadPos = dat[j] + oldValue.length; + CharCopy (dest + curPos, replace, newValue.length); + curPos += newValue.length; + } + CharCopy (dest + curPos, source + lastReadPos, length - lastReadPos); + } + return tmp; + } + } + + private String ReplaceFallback (String oldValue, String newValue, int testedCount) + { + int lengthEstimate = this.length + ((newValue.length - oldValue.length) * testedCount); + StringBuilder sb = new StringBuilder (lengthEstimate); + for (int i = 0; i < length;) { + int found = IndexOfOrdinalUnchecked (oldValue, i, length - i); + if (found < 0) { + sb.Append (SubstringUnchecked (i, length - i)); + break; + } + sb.Append (SubstringUnchecked (i, found - i)); + sb.Append (newValue); + i = found + oldValue.Length; + } + return sb.ToString (); + } public unsafe String Remove (int startIndex, int count) { if (startIndex < 0) - throw new ArgumentOutOfRangeException ("startIndex", "< 0"); + throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative."); if (count < 0) - throw new ArgumentOutOfRangeException ("count", "< 0"); - // re-ordered to avoid possible integer overflow + throw new ArgumentOutOfRangeException ("count", "Cannot be negative."); if (startIndex > this.length - count) - throw new ArgumentOutOfRangeException ("startIndex + count > this.length"); + throw new ArgumentOutOfRangeException ("count", "startIndex + count > this.length"); String tmp = InternalAllocateStr (this.length - count); fixed (char *dest = tmp, src = this) { char *dst = dest; - memcpy ((byte*)dst, (byte*)src, startIndex * 2); + CharCopy (dst, src, startIndex); int skip = startIndex + count; dst += startIndex; - memcpy ((byte*)dst, (byte*)(src + skip), (length - skip) * 2); + CharCopy (dst, src + skip, length - skip); } return tmp; } @@ -917,9 +1760,9 @@ namespace System if (culture == null) throw new ArgumentNullException ("culture"); - if (culture.LCID == 0x007F) { // Invariant + if (culture.LCID == 0x007F) // Invariant return ToLowerInvariant (); - } + return culture.TextInfo.ToLower (this); } @@ -954,9 +1797,9 @@ namespace System if (culture == null) throw new ArgumentNullException ("culture"); - if (culture.LCID == 0x007F) { // Invariant + if (culture.LCID == 0x007F) // Invariant return ToUpperInvariant (); - } + return culture.TextInfo.ToUpper (this); } @@ -1013,15 +1856,34 @@ namespace System public static string Format (IFormatProvider provider, string format, params object[] args) { - StringBuilder b = new StringBuilder (); - FormatHelper (b, provider, format, args); + StringBuilder b = FormatHelper (null, provider, format, args); return b.ToString (); } - internal static void FormatHelper (StringBuilder result, IFormatProvider provider, string format, params object[] args) + internal static StringBuilder FormatHelper (StringBuilder result, IFormatProvider provider, string format, params object[] args) { - if (format == null || args == null) - throw new ArgumentNullException (); + if (format == null) + throw new ArgumentNullException ("format"); + if (args == null) + throw new ArgumentNullException ("args"); + + if (result == null) { + /* Try to approximate the size of result to avoid reallocations */ + int i, len; + + len = 0; + for (i = 0; i < args.Length; ++i) { + string s = args [i] as string; + if (s != null) + len += s.length; + else + break; + } + if (i == args.Length) + result = new StringBuilder (len + format.length); + else + result = new StringBuilder (); + } int ptr = 0; int start = ptr; @@ -1053,8 +1915,14 @@ namespace System object arg = args[n]; string str; + ICustomFormatter formatter = null; + if (provider != null) + formatter = provider.GetFormat (typeof (ICustomFormatter)) + as ICustomFormatter; if (arg == null) - str = ""; + str = String.Empty; + else if (formatter != null) + str = formatter.Format (arg_format, arg, provider); else if (arg is IFormattable) str = ((IFormattable)arg).ToString (arg_format, provider); else @@ -1091,6 +1959,8 @@ namespace System if (start < format.length) result.Append (format, start, format.Length - start); + + return result; } public unsafe static String Copy (String str) @@ -1103,26 +1973,26 @@ namespace System String tmp = InternalAllocateStr (length); if (length != 0) { fixed (char *dest = tmp, src = str) { - memcpy ((byte*)dest, (byte*)src, length * 2); + CharCopy (dest, src, length); } } return tmp; } - public static String Concat (Object obj) + public static String Concat (Object arg0) { - if (obj == null) + if (arg0 == null) return String.Empty; - return obj.ToString (); + return arg0.ToString (); } - public unsafe static String Concat (Object obj1, Object obj2) + public unsafe static String Concat (Object arg0, Object arg1) { string s1, s2; - s1 = (obj1 != null) ? obj1.ToString () : null; - s2 = (obj2 != null) ? obj2.ToString () : null; + s1 = (arg0 != null) ? arg0.ToString () : null; + s2 = (arg1 != null) ? arg1.ToString () : null; if (s1 == null) { if (s2 == null) @@ -1135,67 +2005,67 @@ namespace System String tmp = InternalAllocateStr (s1.Length + s2.Length); if (s1.Length != 0) { fixed (char *dest = tmp, src = s1) { - memcpy ((byte*)dest, (byte*)src, s1.length * 2); + CharCopy (dest, src, s1.length); } } if (s2.Length != 0) { fixed (char *dest = tmp, src = s2) { - memcpy ((byte*)(dest + s1.Length), (byte*)src, s2.length * 2); + CharCopy (dest + s1.Length, src, s2.length); } } return tmp; } - public static String Concat (Object obj1, Object obj2, Object obj3) + public static String Concat (Object arg0, Object arg1, Object arg2) { string s1, s2, s3; - if (obj1 == null) + if (arg0 == null) s1 = String.Empty; else - s1 = obj1.ToString (); + s1 = arg0.ToString (); - if (obj2 == null) + if (arg1 == null) s2 = String.Empty; else - s2 = obj2.ToString (); + s2 = arg1.ToString (); - if (obj3 == null) + if (arg2 == null) s3 = String.Empty; else - s3 = obj3.ToString (); + s3 = arg2.ToString (); return Concat (s1, s2, s3); } #if ! BOOTSTRAP_WITH_OLDLIB [CLSCompliant(false)] - public static String Concat (Object obj1, Object obj2, Object obj3, - Object obj4, __arglist) + public static String Concat (Object arg0, Object arg1, Object arg2, + Object arg3, __arglist) { string s1, s2, s3, s4; - if (obj1 == null) + if (arg0 == null) s1 = String.Empty; else - s1 = obj1.ToString (); + s1 = arg0.ToString (); - if (obj2 == null) + if (arg1 == null) s2 = String.Empty; else - s2 = obj2.ToString (); + s2 = arg1.ToString (); - if (obj3 == null) + if (arg2 == null) s3 = String.Empty; else - s3 = obj3.ToString (); + s3 = arg2.ToString (); ArgIterator iter = new ArgIterator (__arglist); int argCount = iter.GetRemainingCount(); StringBuilder sb = new StringBuilder (); - if (obj4 != null) - sb.Append (obj4.ToString ()); + if (arg3 != null) + sb.Append (arg3.ToString ()); for (int i = 0; i < argCount; i++) { TypedReference typedRef = iter.GetNextArg (); @@ -1208,113 +2078,106 @@ namespace System } #endif - public unsafe static String Concat (String s1, String s2) + public unsafe static String Concat (String str0, String str1) { - if (s1 == null) { - if (s2 == null) + if (str0 == null || str0.Length == 0) { + if (str1 == null || str1.Length == 0) return String.Empty; - return s2; + return str1; } - if (s2 == null) - return s1; + if (str1 == null || str1.Length == 0) + return str0; - String tmp = InternalAllocateStr (s1.length + s2.length); + String tmp = InternalAllocateStr (str0.length + str1.length); - if (s1.Length != 0) { - fixed (char *dest = tmp, src = s1) { - memcpy ((byte*)dest, (byte*)src, s1.length * 2); - } - } - if (s2.Length != 0) { - fixed (char *dest = tmp, src = s2) { - memcpy ((byte*)(dest + s1.Length), (byte*)src, s2.length * 2); - } - } + fixed (char *dest = tmp, src = str0) + CharCopy (dest, src, str0.length); + fixed (char *dest = tmp, src = str1) + CharCopy (dest + str0.Length, src, str1.length); return tmp; } - public unsafe static String Concat (String s1, String s2, String s3) + public unsafe static String Concat (String str0, String str1, String str2) { - if (s1 == null){ - if (s2 == null){ - if (s3 == null) + if (str0 == null || str0.Length == 0){ + if (str1 == null || str1.Length == 0){ + if (str2 == null || str2.Length == 0) return String.Empty; - return s3; + return str2; } else { - if (s3 == null) - return s2; + if (str2 == null || str2.Length == 0) + return str1; } - s1 = String.Empty; + str0 = String.Empty; } else { - if (s2 == null){ - if (s3 == null) - return s1; + if (str1 == null || str1.Length == 0){ + if (str2 == null || str2.Length == 0) + return str0; else - s2 = String.Empty; + str1 = String.Empty; } else { - if (s3 == null) - s3 = String.Empty; + if (str2 == null || str2.Length == 0) + str2 = String.Empty; } } - //return InternalConcat (s1, s2, s3); - String tmp = InternalAllocateStr (s1.length + s2.length + s3.length); + String tmp = InternalAllocateStr (str0.length + str1.length + str2.length); - if (s1.Length != 0) { - fixed (char *dest = tmp, src = s1) { - memcpy ((byte*)dest, (byte*)src, s1.length * 2); + if (str0.Length != 0) { + fixed (char *dest = tmp, src = str0) { + CharCopy (dest, src, str0.length); } } - if (s2.Length != 0) { - fixed (char *dest = tmp, src = s2) { - memcpy ((byte*)(dest + s1.Length), (byte*)src, s2.length * 2); + if (str1.Length != 0) { + fixed (char *dest = tmp, src = str1) { + CharCopy (dest + str0.Length, src, str1.length); } } - if (s3.Length != 0) { - fixed (char *dest = tmp, src = s3) { - memcpy ((byte*)(dest + s1.Length + s2.Length), (byte*)src, s3.length * 2); + if (str2.Length != 0) { + fixed (char *dest = tmp, src = str2) { + CharCopy (dest + str0.Length + str1.Length, src, str2.length); } } return tmp; } - public unsafe static String Concat (String s1, String s2, String s3, String s4) + public unsafe static String Concat (String str0, String str1, String str2, String str3) { - if (s1 == null && s2 == null && s3 == null && s4 == null) + if (str0 == null && str1 == null && str2 == null && str3 == null) return String.Empty; - if (s1 == null) - s1 = String.Empty; - if (s2 == null) - s2 = String.Empty; - if (s3 == null) - s3 = String.Empty; - if (s4 == null) - s4 = String.Empty; + if (str0 == null) + str0 = String.Empty; + if (str1 == null) + str1 = String.Empty; + if (str2 == null) + str2 = String.Empty; + if (str3 == null) + str3 = String.Empty; - String tmp = InternalAllocateStr (s1.length + s2.length + s3.length + s4.length); + String tmp = InternalAllocateStr (str0.length + str1.length + str2.length + str3.length); - if (s1.Length != 0) { - fixed (char *dest = tmp, src = s1) { - memcpy ((byte*)dest, (byte*)src, s1.length * 2); + if (str0.Length != 0) { + fixed (char *dest = tmp, src = str0) { + CharCopy (dest, src, str0.length); } } - if (s2.Length != 0) { - fixed (char *dest = tmp, src = s2) { - memcpy ((byte*)(dest + s1.Length), (byte*)src, s2.length * 2); + if (str1.Length != 0) { + fixed (char *dest = tmp, src = str1) { + CharCopy (dest + str0.Length, src, str1.length); } } - if (s3.Length != 0) { - fixed (char *dest = tmp, src = s3) { - memcpy ((byte*)(dest + s1.Length + s2.Length), (byte*)src, s3.length * 2); + if (str2.Length != 0) { + fixed (char *dest = tmp, src = str2) { + CharCopy (dest + str0.Length + str1.Length, src, str2.length); } } - if (s4.Length != 0) { - fixed (char *dest = tmp, src = s4) { - memcpy ((byte*)(dest + s1.Length + s2.Length + s3.Length), (byte*)src, s4.length * 2); + if (str3.Length != 0) { + fixed (char *dest = tmp, src = str3) { + CharCopy (dest + str0.Length + str1.Length + str2.Length, src, str3.length); } } @@ -1326,27 +2189,22 @@ namespace System if (args == null) throw new ArgumentNullException ("args"); - int i = args.Length; - if (i == 0) + int argLen = args.Length; + if (argLen == 0) return String.Empty; - string [] strings = new string [i]; - i = 0; + string [] strings = new string [argLen]; int len = 0; - foreach (object arg in args) { - if (arg == null) { - strings[i] = String.Empty; - } else { - strings[i] = arg.ToString (); + for (int i = 0; i < argLen; i++) { + if (args[i] != null) { + strings[i] = args[i].ToString (); len += strings[i].length; } - i++; } - if (len == 0) return String.Empty; - return InternalJoin (String.Empty, strings, 0, strings.Length); + return ConcatInternal (strings, len); } public static String Concat (params String[] values) @@ -1354,7 +2212,35 @@ namespace System if (values == null) throw new ArgumentNullException ("values"); - return InternalJoin (String.Empty, values, 0, values.Length); + int len = 0; + for (int i = 0; i < values.Length; i++) { + String s = values[i]; + if (s != null) + len += s.length; + } + if (len == 0) + return String.Empty; + + return ConcatInternal (values, len); + } + + private static unsafe String ConcatInternal (String[] values, int length) + { + String tmp = InternalAllocateStr (length); + + fixed (char* dest = tmp) { + int pos = 0; + for (int i = 0; i < values.Length; i++) { + String source = values[i]; + if (source != null) { + fixed (char* src = source) { + CharCopy (dest + pos, src, source.length); + } + pos += source.Length; + } + } + } + return tmp; } public unsafe String Insert (int startIndex, String value) @@ -1363,7 +2249,7 @@ namespace System throw new ArgumentNullException ("value"); if (startIndex < 0 || startIndex > this.length) - throw new ArgumentOutOfRangeException (); + throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative and must be less than or equal to length of string."); if (value.Length == 0) return this; @@ -1373,16 +2259,15 @@ namespace System fixed (char *dest = tmp, src = this, val = value) { char *dst = dest; - memcpy ((byte*)dst, (byte*)src, startIndex * 2); + CharCopy (dst, src, startIndex); dst += startIndex; - memcpy ((byte*)dst, (byte*)val, value.length * 2); + CharCopy (dst, val, value.length); dst += value.length; - memcpy ((byte*)dst, (byte*)(src + startIndex), (length - startIndex) * 2); + CharCopy (dst, src + startIndex, length - startIndex); } return tmp; } - public static string Intern (string str) { if (str == null) @@ -1403,8 +2288,10 @@ namespace System { if (value == null) throw new ArgumentNullException ("value"); + if (separator == null) + separator = String.Empty; - return Join (separator, value, 0, value.Length); + return JoinUnchecked (separator, value, 0, value.Length); } public static string Join (string separator, string[] value, int startIndex, int count) @@ -1415,16 +2302,65 @@ namespace System throw new ArgumentOutOfRangeException ("startIndex", "< 0"); if (count < 0) throw new ArgumentOutOfRangeException ("count", "< 0"); - // re-ordered to avoid possible integer overflow if (startIndex > value.Length - count) - throw new ArgumentOutOfRangeException ("startIndex + count > value.length"); + throw new ArgumentOutOfRangeException ("startIndex", "startIndex + count > value.length"); if (startIndex == value.Length) return String.Empty; if (separator == null) separator = String.Empty; - return InternalJoin (separator, value, startIndex, count); + return JoinUnchecked (separator, value, startIndex, count); + } + + private static unsafe string JoinUnchecked (string separator, string[] value, int startIndex, int count) + { + // Unchecked parameters + // startIndex, count must be >= 0; startIndex + count must be <= value.length + // separator and value must not be null + + int length = 0; + int maxIndex = startIndex + count; + // Precount the number of characters that the resulting string will have + for (int i = startIndex; i < maxIndex; i++) { + String s = value[i]; + if (s != null) + length += s.length; + } + length += separator.length * (count - 1); + if (length <= 0) + return String.Empty; + + String tmp = InternalAllocateStr (length); + + maxIndex--; + fixed (char* dest = tmp, sepsrc = separator) { + // Copy each string from value except the last one and add a separator for each + int pos = 0; + for (int i = startIndex; i < maxIndex; i++) { + String source = value[i]; + if (source != null) { + if (source.Length > 0) { + fixed (char* src = source) + CharCopy (dest + pos, src, source.Length); + pos += source.Length; + } + } + if (separator.Length > 0) { + CharCopy (dest + pos, sepsrc, separator.Length); + pos += separator.Length; + } + } + // Append last string that does not get an additional separator + String sourceLast = value[maxIndex]; + if (sourceLast != null) { + if (sourceLast.Length > 0) { + fixed (char* src = sourceLast) + CharCopy (dest + pos, src, sourceLast.Length); + } + } + } + return tmp; } bool IConvertible.ToBoolean (IFormatProvider provider) @@ -1471,46 +2407,64 @@ namespace System { return Convert.ToInt64 (this, provider); } - + +#if ONLY_1_1 +#pragma warning disable 3019 + [CLSCompliant (false)] +#endif sbyte IConvertible.ToSByte (IFormatProvider provider) { return Convert.ToSByte (this, provider); } +#if ONLY_1_1 +#pragma warning restore 3019 +#endif float IConvertible.ToSingle (IFormatProvider provider) { return Convert.ToSingle (this, provider); } - string IConvertible.ToString (IFormatProvider format) - { - return this; - } - - object IConvertible.ToType (Type conversionType, IFormatProvider provider) + object IConvertible.ToType (Type type, IFormatProvider provider) { - return Convert.ToType (this, conversionType, provider); + return Convert.ToType (this, type, provider, false); } +#if ONLY_1_1 +#pragma warning disable 3019 + [CLSCompliant (false)] +#endif ushort IConvertible.ToUInt16 (IFormatProvider provider) { return Convert.ToUInt16 (this, provider); } +#if ONLY_1_1 +#pragma warning restore 3019 +#endif +#if ONLY_1_1 +#pragma warning disable 3019 + [CLSCompliant (false)] +#endif uint IConvertible.ToUInt32 (IFormatProvider provider) { return Convert.ToUInt32 (this, provider); } +#if ONLY_1_1 +#pragma warning restore 3019 +#endif +#if ONLY_1_1 +#pragma warning disable 3019 + [CLSCompliant (false)] +#endif ulong IConvertible.ToUInt64 (IFormatProvider provider) { return Convert.ToUInt64 (this, provider); } - - TypeCode IConvertible.GetTypeCode () - { - return TypeCode.String; - } +#if ONLY_1_1 +#pragma warning restore 3019 +#endif public int Length { get { @@ -1523,6 +2477,13 @@ namespace System return new CharEnumerator (this); } +#if NET_2_0 + IEnumerator IEnumerable.GetEnumerator () + { + return new CharEnumerator (this); + } +#endif + IEnumerator IEnumerable.GetEnumerator () { return new CharEnumerator (this); @@ -1565,7 +2526,7 @@ namespace System else { width = 0; left_align = false; - format = ""; + format = String.Empty; } // F = argument format (string) @@ -1624,17 +2585,24 @@ namespace System if (newLength > length) throw new ArgumentOutOfRangeException ("newLength", "newLength as to be <= length"); - length = newLength; - // zero terminate, we can pass string objects directly via pinvoke + // we also zero the rest of the string, since the new GC needs to be + // able to handle the changing size (it will skip the 0 bytes). fixed (char * pStr = &start_char) { - pStr [length] = '\0'; + char *p = pStr + newLength; + char *end = pStr + length; + while (p < end) { + p [0] = '\0'; + p++; + } } + length = newLength; } #if NET_2_0 [ReliabilityContractAttribute (Consistency.WillNotCorruptState, Cer.MayFail)] #endif + // When modifying it, GetCaseInsensitiveHashCode() should be modified as well. public unsafe override int GetHashCode () { fixed (char * c = this) { @@ -1652,6 +2620,196 @@ namespace System } } + internal unsafe int GetCaseInsensitiveHashCode () + { + fixed (char * c = this) { + char * cc = c; + char * end = cc + length - 1; + int h = 0; + for (;cc < end; cc += 2) { + h = (h << 5) - h + Char.ToUpperInvariant (*cc); + h = (h << 5) - h + Char.ToUpperInvariant (cc [1]); + } + ++end; + if (cc < end) + h = (h << 5) - h + Char.ToUpperInvariant (*cc); + return h; + } + } + + // Certain constructors are redirected to CreateString methods with + // matching argument list. The this pointer should not be used. +#pragma warning disable 169 + private unsafe String CreateString (sbyte* value) + { + if (value == null) + return String.Empty; + + byte* bytes = (byte*) value; + int length = 0; + + try { + while (bytes++ [0] != 0) + length++; + } catch (NullReferenceException) { + throw new ArgumentOutOfRangeException ("ptr", "Value does not refer to a valid string."); +#if NET_2_0 + } catch (AccessViolationException) { + throw new ArgumentOutOfRangeException ("ptr", "Value does not refer to a valid string."); +#endif + } + + return CreateString (value, 0, length, null); + } + + private unsafe String CreateString (sbyte* value, int startIndex, int length) + { + return CreateString (value, startIndex, length, null); + } + + private unsafe String CreateString (sbyte* value, int startIndex, int length, Encoding enc) + { + if (length < 0) + throw new ArgumentOutOfRangeException ("length", "Non-negative number required."); + if (startIndex < 0) + throw new ArgumentOutOfRangeException ("startIndex", "Non-negative number required."); + if (value + startIndex < value) + throw new ArgumentOutOfRangeException ("startIndex", "Value, startIndex and length do not refer to a valid string."); + + bool isDefaultEncoding; + + if (isDefaultEncoding = (enc == null)) { +#if NET_2_0 + if (value == null) + throw new ArgumentNullException ("value"); + if (length == 0) +#else + if (value == null || length == 0) +#endif + return String.Empty; + + enc = Encoding.Default; + } + + byte [] bytes = new byte [length]; + + if (length != 0) + fixed (byte* bytePtr = bytes) + try { + memcpy (bytePtr, (byte*) (value + startIndex), length); + } catch (NullReferenceException) { +#if !NET_2_0 + if (!isDefaultEncoding) + throw; +#endif + + throw new ArgumentOutOfRangeException ("ptr", "Value, startIndex and length do not refer to a valid string."); +#if NET_2_0 + } catch (AccessViolationException) { + if (!isDefaultEncoding) + throw; + + throw new ArgumentOutOfRangeException ("value", "Value, startIndex and length do not refer to a valid string."); +#endif + } + + // GetString () is called even when length == 0 + return enc.GetString (bytes); + } + + unsafe string CreateString (char *value) + { + if (value == null) + return string.Empty; + char *p = value; + int i = 0; + while (*p != 0) { + ++i; + ++p; + } + string result = InternalAllocateStr (i); + + if (i != 0) { + fixed (char *dest = result) { + CharCopy (dest, value, i); + } + } + return result; + } + + unsafe string CreateString (char *value, int startIndex, int length) + { + if (length == 0) + return string.Empty; + if (value == null) + throw new ArgumentNullException ("value"); + if (startIndex < 0) + throw new ArgumentOutOfRangeException ("startIndex"); + if (length < 0) + throw new ArgumentOutOfRangeException ("length"); + + string result = InternalAllocateStr (length); + + fixed (char *dest = result) { + CharCopy (dest, value + startIndex, length); + } + return result; + } + + unsafe string CreateString (char [] val, int startIndex, int length) + { + if (val == null) + throw new ArgumentNullException ("value"); + if (startIndex < 0) + throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative."); + if (length < 0) + throw new ArgumentOutOfRangeException ("length", "Cannot be negative."); + if (startIndex > val.Length - length) + throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative, and should be less than length of string."); + if (length == 0) + return string.Empty; + + string result = InternalAllocateStr (length); + + fixed (char *dest = result, src = val) { + CharCopy (dest, src + startIndex, length); + } + return result; + } + + unsafe string CreateString (char [] val) + { + if (val == null) + return string.Empty; + if (val.Length == 0) + return string.Empty; + string result = InternalAllocateStr (val.Length); + + fixed (char *dest = result, src = val) { + CharCopy (dest, src, val.Length); + } + return result; + } + + unsafe string CreateString (char c, int count) + { + if (count < 0) + throw new ArgumentOutOfRangeException ("count"); + if (count == 0) + return string.Empty; + string result = InternalAllocateStr (count); + fixed (char *dest = result) { + char *p = dest; + char *end = p + count; + while (p < end) { + *p = c; + p++; + } + } + return result; + } +#pragma warning restore 169 + /* helpers used by the runtime as well as above or eslewhere in corlib */ internal static unsafe void memset (byte *dest, int val, int len) { @@ -1699,7 +2857,7 @@ namespace System } } - internal static unsafe void memcpy4 (byte *dest, byte *src, int size) { + static unsafe void memcpy4 (byte *dest, byte *src, int size) { /*while (size >= 32) { // using long is better than int and slower than double // FIXME: enable this only on correct alignment or on platforms @@ -1777,7 +2935,8 @@ namespace System if (size > 0) ((byte*)dest) [0] = ((byte*)src) [0]; } - static unsafe void memcpy (byte *dest, byte *src, int size) { + + internal static unsafe void memcpy (byte *dest, byte *src, int size) { // FIXME: if pointers are not aligned, try to align them // so a faster routine can be used. Handle the case where // the pointers can't be reduced to have the same alignment @@ -1807,6 +2966,55 @@ namespace System memcpy4 (dest, src, size); } + internal static unsafe void CharCopy (char *dest, char *src, int count) { + // Same rules as for memcpy, but with the premise that + // chars can only be aligned to even addresses if their + // enclosing types are correctly aligned + if ((((int)(byte*)dest | (int)(byte*)src) & 3) != 0) { + if (((int)(byte*)dest & 2) != 0 && ((int)(byte*)src & 2) != 0 && count > 0) { + ((short*)dest) [0] = ((short*)src) [0]; + dest++; + src++; + count--; + } + if ((((int)(byte*)dest | (int)(byte*)src) & 2) != 0) { + memcpy2 ((byte*)dest, (byte*)src, count * 2); + return; + } + } + memcpy4 ((byte*)dest, (byte*)src, count * 2); + } + + internal static unsafe void CharCopyReverse (char *dest, char *src, int count) + { + dest += count; + src += count; + for (int i = count; i > 0; i--) { + dest--; + src--; + *dest = *src; + } + } + + internal static unsafe void CharCopy (String target, int targetIndex, String source, int sourceIndex, int count) + { + fixed (char* dest = target, src = source) + CharCopy (dest + targetIndex, src + sourceIndex, count); + } + + internal static unsafe void CharCopy (String target, int targetIndex, Char[] source, int sourceIndex, int count) + { + fixed (char* dest = target, src = source) + CharCopy (dest + targetIndex, src + sourceIndex, count); + } + + // Use this method if you cannot block copy from left to right (e.g. because you are coping within the same string) + internal static unsafe void CharCopyReverse (String target, int targetIndex, String source, int sourceIndex, int count) + { + fixed (char* dest = target, src = source) + CharCopyReverse (dest + targetIndex, src + sourceIndex, count); + } + [CLSCompliant (false), MethodImplAttribute (MethodImplOptions.InternalCall)] unsafe public extern String (char *value); @@ -1823,40 +3031,34 @@ namespace System unsafe public extern String (sbyte *value, int startIndex, int length, Encoding enc); [MethodImplAttribute (MethodImplOptions.InternalCall)] - public extern String (char [] val, int startIndex, int length); + public extern String (char [] value, int startIndex, int length); [MethodImplAttribute (MethodImplOptions.InternalCall)] - public extern String (char [] val); + public extern String (char [] value); [MethodImplAttribute (MethodImplOptions.InternalCall)] public extern String (char c, int count); - [MethodImplAttribute (MethodImplOptions.InternalCall)] - private extern static string InternalJoin (string separator, string[] value, int sIndex, int count); - - [MethodImplAttribute (MethodImplOptions.InternalCall)] - private extern String InternalReplace (char oldChar, char newChar); - - [MethodImplAttribute (MethodImplOptions.InternalCall)] - private extern String InternalReplace (String oldValue, string newValue, CompareInfo comp); +// [MethodImplAttribute (MethodImplOptions.InternalCall)] +// private extern static string InternalJoin (string separator, string[] value, int sIndex, int count); - [MethodImplAttribute (MethodImplOptions.InternalCall)] - private extern void InternalCopyTo (int sIndex, char[] dest, int destIndex, int count); +// [MethodImplAttribute (MethodImplOptions.InternalCall)] +// private extern String InternalReplace (String oldValue, string newValue, CompareInfo comp); - [MethodImplAttribute (MethodImplOptions.InternalCall)] - private extern String[] InternalSplit (char[] separator, int count); +// [MethodImplAttribute (MethodImplOptions.InternalCall)] +// private extern void InternalCopyTo (int sIndex, char[] dest, int destIndex, int count); [MethodImplAttribute (MethodImplOptions.InternalCall)] - private extern String InternalTrim (char[] chars, int typ); + private extern String[] InternalSplit (char[] separator, int count, int options); - [MethodImplAttribute (MethodImplOptions.InternalCall)] - private extern int InternalIndexOfAny (char [] arr, int sIndex, int count); +// [MethodImplAttribute (MethodImplOptions.InternalCall)] +// private extern String InternalTrim (char[] chars, int typ); - [MethodImplAttribute (MethodImplOptions.InternalCall)] - private extern int InternalLastIndexOfAny (char [] anyOf, int sIndex, int count); +// [MethodImplAttribute (MethodImplOptions.InternalCall)] +// private extern int InternalLastIndexOfAny (char [] anyOf, int sIndex, int count); - [MethodImplAttribute (MethodImplOptions.InternalCall)] - private extern String InternalPad (int width, char chr, bool right); +// [MethodImplAttribute (MethodImplOptions.InternalCall)] +// private extern String InternalPad (int width, char chr, bool right); [MethodImplAttribute (MethodImplOptions.InternalCall)] internal extern static String InternalAllocateStr (int length);