diff options
Diffstat (limited to 'src/mscorlib/src/System/Globalization/CompareInfo.cs')
-rw-r--r-- | src/mscorlib/src/System/Globalization/CompareInfo.cs | 1373 |
1 files changed, 1373 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Globalization/CompareInfo.cs b/src/mscorlib/src/System/Globalization/CompareInfo.cs new file mode 100644 index 0000000000..8de5c249b2 --- /dev/null +++ b/src/mscorlib/src/System/Globalization/CompareInfo.cs @@ -0,0 +1,1373 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +//////////////////////////////////////////////////////////////////////////// +// +// +// +// Purpose: This class implements a set of methods for comparing +// strings. +// +// +//////////////////////////////////////////////////////////////////////////// + +namespace System.Globalization { + + // + // We pass all of the sorting calls to the native side, preferrably to the OS to do + // the actual work. + // + + using System; + using System.Collections; + using System.Collections.Generic; + using System.Reflection; + using System.Runtime.Serialization; + using System.Runtime.CompilerServices; + using System.Runtime.ConstrainedExecution; + using System.Runtime.InteropServices; + using System.Runtime.Versioning; + using System.Threading; + using System.Security.Permissions; + using Microsoft.Win32; + using System.Security; + using System.Security.Principal; + using System.Diagnostics.Contracts; + + // + // Options can be used during string comparison. + // + // Native implementation (COMNlsInfo.cpp & SortingTable.cpp) relies on the values of these, + // If you change the values below, be sure to change the values in native part as well. + // + + +[Serializable] + [Flags] + [System.Runtime.InteropServices.ComVisible(true)] + public enum CompareOptions + { + None = 0x00000000, + IgnoreCase = 0x00000001, + IgnoreNonSpace = 0x00000002, + IgnoreSymbols = 0x00000004, + IgnoreKanaType = 0x00000008, // ignore kanatype + IgnoreWidth = 0x00000010, // ignore width + OrdinalIgnoreCase = 0x10000000, // This flag can not be used with other flags. + StringSort = 0x20000000, // use string sort method + Ordinal = 0x40000000, // This flag can not be used with other flags. + + // StopOnNull = 0x10000000, + + // StopOnNull is defined in SortingTable.h, but we didn't enable this option here. + // Do not use this value for other flags accidentally. + } + + + [Serializable] +[System.Runtime.InteropServices.ComVisible(true)] + + public class CompareInfo +#if FEATURE_SERIALIZATION + : IDeserializationCallback +#endif + { + // Mask used to check if IndexOf()/LastIndexOf()/IsPrefix()/IsPostfix() has the right flags. + private const CompareOptions ValidIndexMaskOffFlags = + ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | + CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType); + + // Mask used to check if Compare() has the right flags. + private const CompareOptions ValidCompareMaskOffFlags = + ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | + CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort); + + // Mask used to check if GetHashCodeOfString() has the right flags. + private const CompareOptions ValidHashCodeOfStringMaskOffFlags = + ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | + CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType); + + // + // CompareInfos have an interesting identity. They are attached to the locale that created them, + // ie: en-US would have an en-US sort. For haw-US (custom), then we serialize it as haw-US. + // The interesting part is that since haw-US doesn't have its own sort, it has to point at another + // locale, which is what SCOMPAREINFO does. + + [OptionalField(VersionAdded = 2)] + private String m_name; // The name used to construct this CompareInfo + + [NonSerialized] + private String m_sortName; // The name that defines our behavior + + [NonSerialized] + private IntPtr m_dataHandle; + + [NonSerialized] + private IntPtr m_handleOrigin; + + //////////////////////////////////////////////////////////////////////// + // + // CompareInfo Constructor + // + // + //////////////////////////////////////////////////////////////////////// + // Constructs an instance that most closely corresponds to the NLS locale + // identifier. + internal CompareInfo(CultureInfo culture) + { + this.m_name = culture.m_name; + this.m_sortName = culture.SortName; + + IntPtr handleOrigin; + this.m_dataHandle = InternalInitSortHandle(m_sortName, out handleOrigin); + this.m_handleOrigin = handleOrigin; + } + + /*=================================GetCompareInfo========================== + **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture. + ** Warning: The assembly versioning mechanism is dead! + **Returns: The CompareInfo for the specified culture. + **Arguments: + ** culture the ID of the culture + ** assembly the assembly which contains the sorting table. + **Exceptions: + ** ArugmentNullException when the assembly is null + ** ArgumentException if culture is invalid. + ============================================================================*/ +#if FEATURE_USE_LCID + // Assembly constructor should be deprecated, we don't act on the assembly information any more + public static CompareInfo GetCompareInfo(int culture, Assembly assembly){ + // Parameter checking. + if (assembly == null) { + throw new ArgumentNullException("assembly"); + } + if (assembly!=typeof(Object).Module.Assembly) { + throw new ArgumentException(Environment.GetResourceString("Argument_OnlyMscorlib")); + } + Contract.EndContractBlock(); + + return GetCompareInfo(culture); + } +#endif + + + /*=================================GetCompareInfo========================== + **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture. + ** The purpose of this method is to provide version for CompareInfo tables. + **Returns: The CompareInfo for the specified culture. + **Arguments: + ** name the name of the culture + ** assembly the assembly which contains the sorting table. + **Exceptions: + ** ArugmentNullException when the assembly is null + ** ArgumentException if name is invalid. + ============================================================================*/ + // Assembly constructor should be deprecated, we don't act on the assembly information any more + public static CompareInfo GetCompareInfo(String name, Assembly assembly){ + if (name == null || assembly == null) { + throw new ArgumentNullException(name == null ? "name" : "assembly"); + } + Contract.EndContractBlock(); + + if (assembly!=typeof(Object).Module.Assembly) { + throw new ArgumentException(Environment.GetResourceString("Argument_OnlyMscorlib")); + } + + return GetCompareInfo(name); + } + + /*=================================GetCompareInfo========================== + **Action: Get the CompareInfo for the specified culture. + ** This method is provided for ease of integration with NLS-based software. + **Returns: The CompareInfo for the specified culture. + **Arguments: + ** culture the ID of the culture. + **Exceptions: + ** ArgumentException if culture is invalid. + ============================================================================*/ + +#if FEATURE_USE_LCID + // People really shouldn't be calling LCID versions, no custom support + public static CompareInfo GetCompareInfo(int culture) + { + if (CultureData.IsCustomCultureId(culture)) + { + // Customized culture cannot be created by the LCID. + throw new ArgumentException(Environment.GetResourceString("Argument_CustomCultureCannotBePassedByNumber", "culture")); + } + + return CultureInfo.GetCultureInfo(culture).CompareInfo; + } +#endif + + /*=================================GetCompareInfo========================== + **Action: Get the CompareInfo for the specified culture. + **Returns: The CompareInfo for the specified culture. + **Arguments: + ** name the name of the culture. + **Exceptions: + ** ArgumentException if name is invalid. + ============================================================================*/ + + public static CompareInfo GetCompareInfo(String name) + { + if (name == null) + { + throw new ArgumentNullException("name"); + } + Contract.EndContractBlock(); + + return CultureInfo.GetCultureInfo(name).CompareInfo; + } + + [System.Runtime.InteropServices.ComVisible(false)] + public static bool IsSortable(char ch) { + return(IsSortable(ch.ToString())); + } + + [System.Security.SecuritySafeCritical] + [System.Runtime.InteropServices.ComVisible(false)] + public static bool IsSortable(String text) { + if (text == null) { + // A null param is invalid here. + throw new ArgumentNullException("text"); + } + + if (0 == text.Length) { + // A zero length string is not invalid, but it is also not sortable. + return(false); + } + + CompareInfo c = CultureInfo.InvariantCulture.CompareInfo; + + return (InternalIsSortable(c.m_dataHandle, c.m_handleOrigin, c.m_sortName, text, text.Length)); + } + + +#if FEATURE_SERIALIZATION // Only defined when FEATURE_USE_LCID is also defined +#region Serialization + // the following fields are defined to keep the compatibility with Whidbey. + // don't change/remove the names/types of these fields. +#if FEATURE_USE_LCID + [OptionalField(VersionAdded = 1)] + private int win32LCID; // mapped sort culture id of this instance + private int culture; // the culture ID used to create this instance. +#endif + [OnDeserializing] + private void OnDeserializing(StreamingContext ctx) + { + this.m_name = null; + } + + private void OnDeserialized() + { + CultureInfo ci; + // If we didn't have a name, use the LCID + if (this.m_name == null) + { + // From whidbey, didn't have a name + ci = CultureInfo.GetCultureInfo(this.culture); + this.m_name = ci.m_name; + } + else + { + ci = CultureInfo.GetCultureInfo(m_name); + } + this.m_sortName = ci.SortName; + + IntPtr handleOrigin; + this.m_dataHandle = InternalInitSortHandle(m_sortName, out handleOrigin); + this.m_handleOrigin = handleOrigin; + + } + + [OnDeserialized] + private void OnDeserialized(StreamingContext ctx) + { + OnDeserialized(); + } + + [OnSerializing] + private void OnSerializing(StreamingContext ctx) + { + // This is merely for serialization compatibility with Whidbey/Orcas, it can go away when we don't want that compat any more. + culture = CultureInfo.GetCultureInfo(this.Name).LCID; // This is the lcid of the constructing culture (still have to dereference to get target sort) + Contract.Assert(m_name != null, "CompareInfo.OnSerializing - expected m_name to be set already"); + } + + void IDeserializationCallback.OnDeserialization(Object sender) + { + OnDeserialized(); + } + +#endregion Serialization +#endif // FEATURE_SERIALIZATION + + + ///////////////////////////----- Name -----///////////////////////////////// + // + // Returns the name of the culture (well actually, of the sort). + // Very important for providing a non-LCID way of identifying + // what the sort is. + // + // Note that this name isn't dereferenced in case the CompareInfo is a different locale + // which is consistent with the behaviors of earlier versions. (so if you ask for a sort + // and the locale's changed behavior, then you'll get changed behavior, which is like + // what happens for a version update) + // + //////////////////////////////////////////////////////////////////////// + + [System.Runtime.InteropServices.ComVisible(false)] + public virtual String Name + { + get + { + Contract.Assert(m_name != null, "CompareInfo.Name Expected m_name to be set"); + if (m_name == "zh-CHT" || m_name == "zh-CHS") + { + return m_name; + } + + return (m_sortName); + } + } + + // These flags are used in the native Win32. so we need to map the managed options to those flags + private const int LINGUISTIC_IGNORECASE = 0x00000010; // linguistically appropriate 'ignore case' + private const int NORM_IGNORECASE = 0x00000001; // Ignores case. (use LINGUISTIC_IGNORECASE instead) + private const int NORM_IGNOREKANATYPE = 0x00010000; // Does not differentiate between Hiragana and Katakana characters. Corresponding Hiragana and Katakana will compare as equal. + private const int LINGUISTIC_IGNOREDIACRITIC = 0x00000020; // linguistically appropriate 'ignore nonspace' + private const int NORM_IGNORENONSPACE = 0x00000002; // Ignores nonspacing. This flag also removes Japanese accent characters. (use LINGUISTIC_IGNOREDIACRITIC instead) + private const int NORM_IGNORESYMBOLS = 0x00000004; // Ignores symbols. + private const int NORM_IGNOREWIDTH = 0x00020000; // Does not differentiate between a single-byte character and the same character as a double-byte character. + private const int SORT_STRINGSORT = 0x00001000; // Treats punctuation the same as symbols. + private const int COMPARE_OPTIONS_ORDINAL = 0x40000000; // Ordinal (handled by Comnlsinfo) + internal const int NORM_LINGUISTIC_CASING = 0x08000000; // use linguistic rules for casing + + + private const int RESERVED_FIND_ASCII_STRING = 0x20000000; // This flag used only to tell the sorting DLL can assume the string characters are in ASCII. + + [Pure] + internal static int GetNativeCompareFlags(CompareOptions options) + { + // some NLS VM functions can handle COMPARE_OPTIONS_ORDINAL + // in which case options should be simply cast to int instead of using this function + // Does not look like the best approach to me but for now I am going to leave it as it is + Contract.Assert(options != CompareOptions.OrdinalIgnoreCase, "[CompareInfo.GetNativeCompareFlags]CompareOptions.OrdinalIgnoreCase should be handled separately"); + + // Use "linguistic casing" by default (load the culture's casing exception tables) + int nativeCompareFlags = NORM_LINGUISTIC_CASING; + + if ((options & CompareOptions.IgnoreCase) != 0) { nativeCompareFlags |= NORM_IGNORECASE; } + if ((options & CompareOptions.IgnoreKanaType) != 0) { nativeCompareFlags |= NORM_IGNOREKANATYPE; } + if ((options & CompareOptions.IgnoreNonSpace) != 0) { nativeCompareFlags |= NORM_IGNORENONSPACE; } + if ((options & CompareOptions.IgnoreSymbols) != 0) { nativeCompareFlags |= NORM_IGNORESYMBOLS; } + if ((options & CompareOptions.IgnoreWidth) != 0) { nativeCompareFlags |= NORM_IGNOREWIDTH; } + if ((options & CompareOptions.StringSort) != 0) { nativeCompareFlags |= SORT_STRINGSORT; } + + // Suffix & Prefix shouldn't use this, make sure to turn off the NORM_LINGUISTIC_CASING flag + if (options == CompareOptions.Ordinal) { nativeCompareFlags = COMPARE_OPTIONS_ORDINAL; } + + Contract.Assert(((options & ~(CompareOptions.IgnoreCase | + CompareOptions.IgnoreKanaType | + CompareOptions.IgnoreNonSpace | + CompareOptions.IgnoreSymbols | + CompareOptions.IgnoreWidth | + CompareOptions.StringSort)) == 0) || + (options == CompareOptions.Ordinal), "[CompareInfo.GetNativeCompareFlags]Expected all flags to be handled"); + + Contract.Assert((nativeCompareFlags & RESERVED_FIND_ASCII_STRING) == 0, "[CompareInfo.GetNativeCompareFlags] RESERVED_FIND_ASCII_STRING shouldn't be set here"); + + return nativeCompareFlags; + } + + + //////////////////////////////////////////////////////////////////////// + // + // Compare + // + // Compares the two strings with the given options. Returns 0 if the + // two strings are equal, a number less than 0 if string1 is less + // than string2, and a number greater than 0 if string1 is greater + // than string2. + // + //////////////////////////////////////////////////////////////////////// + + + public virtual int Compare(String string1, String string2) + { + return (Compare(string1, string2, CompareOptions.None)); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe virtual int Compare(String string1, String string2, CompareOptions options){ + + if (options == CompareOptions.OrdinalIgnoreCase) + { + return String.Compare(string1, string2, StringComparison.OrdinalIgnoreCase); + } + + // Verify the options before we do any real comparison. + if ((options & CompareOptions.Ordinal) != 0) + { + if (options != CompareOptions.Ordinal) + { + throw new ArgumentException(Environment.GetResourceString("Argument_CompareOptionOrdinal"), "options"); + } + return String.CompareOrdinal(string1, string2); + } + + if ((options & ValidCompareMaskOffFlags) != 0) + { + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options"); + } + + //Our paradigm is that null sorts less than any other string and + //that two nulls sort as equal. + if (string1 == null) { + if (string2 == null) { + return (0); // Equal + } + return (-1); // null < non-null + } + if (string2 == null) { + return (1); // non-null > null + } + + return InternalCompareString(m_dataHandle, m_handleOrigin, m_sortName, string1, 0, string1.Length, string2, 0, string2.Length, GetNativeCompareFlags(options)); + } + + + //////////////////////////////////////////////////////////////////////// + // + // Compare + // + // Compares the specified regions of the two strings with the given + // options. + // Returns 0 if the two strings are equal, a number less than 0 if + // string1 is less than string2, and a number greater than 0 if + // string1 is greater than string2. + // + //////////////////////////////////////////////////////////////////////// + + + public unsafe virtual int Compare(String string1, int offset1, int length1, String string2, int offset2, int length2) + { + return Compare(string1, offset1, length1, string2, offset2, length2, 0); + } + + + public unsafe virtual int Compare(String string1, int offset1, String string2, int offset2, CompareOptions options) + { + return Compare(string1, offset1, string1 == null ? 0 : string1.Length-offset1, + string2, offset2, string2 == null ? 0 : string2.Length-offset2, options); + } + + + public unsafe virtual int Compare(String string1, int offset1, String string2, int offset2) + { + return Compare(string1, offset1, string2, offset2, 0); + } + + + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe virtual int Compare(String string1, int offset1, int length1, String string2, int offset2, int length2, CompareOptions options) + { + if (options == CompareOptions.OrdinalIgnoreCase) + { + int result = String.Compare(string1, offset1, string2, offset2, length1<length2 ? length1 : length2, StringComparison.OrdinalIgnoreCase); + if ((length1 != length2) && result == 0) + return (length1 > length2? 1: -1); + return (result); + } + + // Verify inputs + if (length1 < 0 || length2 < 0) + { + throw new ArgumentOutOfRangeException((length1 < 0) ? "length1" : "length2", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); + } + if (offset1 < 0 || offset2 < 0) + { + throw new ArgumentOutOfRangeException((offset1 < 0) ? "offset1" : "offset2", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); + } + if (offset1 > (string1 == null ? 0 : string1.Length) - length1) + { + throw new ArgumentOutOfRangeException("string1", Environment.GetResourceString("ArgumentOutOfRange_OffsetLength")); + } + if (offset2 > (string2 == null ? 0 : string2.Length) - length2) + { + throw new ArgumentOutOfRangeException("string2", Environment.GetResourceString("ArgumentOutOfRange_OffsetLength")); + } + if ((options & CompareOptions.Ordinal) != 0) + { + if (options != CompareOptions.Ordinal) + { + throw new ArgumentException(Environment.GetResourceString("Argument_CompareOptionOrdinal"), + "options"); + } + } + else if ((options & ValidCompareMaskOffFlags) != 0) + { + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options"); + } + + // + // Check for the null case. + // + if (string1 == null) + { + if (string2 == null) + { + return (0); + } + return (-1); + } + if (string2 == null) + { + return (1); + } + + if (options == CompareOptions.Ordinal) + { + return CompareOrdinal(string1, offset1, length1, + string2, offset2, length2); + } + return InternalCompareString(this.m_dataHandle, this.m_handleOrigin, this.m_sortName, + string1, offset1, length1, + string2, offset2, length2, + GetNativeCompareFlags(options)); + } + + [System.Security.SecurityCritical] + private static int CompareOrdinal(string string1, int offset1, int length1, string string2, int offset2, int length2) + { + int result = String.nativeCompareOrdinalEx(string1, offset1, string2, offset2, + (length1 < length2 ? length1 : length2)); + if ((length1 != length2) && result == 0) + { + return (length1 > length2 ? 1 : -1); + } + return (result); + } + + //////////////////////////////////////////////////////////////////////// + // + // IsPrefix + // + // Determines whether prefix is a prefix of string. If prefix equals + // String.Empty, true is returned. + // + //////////////////////////////////////////////////////////////////////// + + + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe virtual bool IsPrefix(String source, String prefix, CompareOptions options) + { + if (source == null || prefix == null) { + throw new ArgumentNullException((source == null ? "source" : "prefix"), + Environment.GetResourceString("ArgumentNull_String")); + } + Contract.EndContractBlock(); + int prefixLen = prefix.Length; + + if (prefixLen == 0) + { + return (true); + } + + if (options == CompareOptions.OrdinalIgnoreCase) + { + return source.StartsWith(prefix, StringComparison.OrdinalIgnoreCase); + } + + if (options == CompareOptions.Ordinal) + { + return source.StartsWith(prefix, StringComparison.Ordinal); + } + + if ((options & ValidIndexMaskOffFlags) != 0) { + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options"); + } + + + // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to + // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString. + + return (InternalFindNLSStringEx( + m_dataHandle, m_handleOrigin, m_sortName, + GetNativeCompareFlags(options) | Win32Native.FIND_STARTSWITH | ((source.IsAscii() && prefix.IsAscii()) ? RESERVED_FIND_ASCII_STRING : 0), + source, source.Length, 0, prefix, prefix.Length) > -1); + } + + public virtual bool IsPrefix(String source, String prefix) + { + return (IsPrefix(source, prefix, 0)); + } + + + //////////////////////////////////////////////////////////////////////// + // + // IsSuffix + // + // Determines whether suffix is a suffix of string. If suffix equals + // String.Empty, true is returned. + // + //////////////////////////////////////////////////////////////////////// + + + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe virtual bool IsSuffix(String source, String suffix, CompareOptions options) + { + if (source == null || suffix == null) { + throw new ArgumentNullException((source == null ? "source" : "suffix"), + Environment.GetResourceString("ArgumentNull_String")); + } + Contract.EndContractBlock(); + int suffixLen = suffix.Length; + + if (suffixLen == 0) + { + return (true); + } + + if (options == CompareOptions.OrdinalIgnoreCase) { + return source.EndsWith(suffix, StringComparison.OrdinalIgnoreCase); + } + + if (options == CompareOptions.Ordinal) { + return source.EndsWith(suffix, StringComparison.Ordinal); + } + + if ((options & ValidIndexMaskOffFlags) != 0) { + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options"); + } + + // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to + // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString. + return InternalFindNLSStringEx( + m_dataHandle, m_handleOrigin, m_sortName, + GetNativeCompareFlags(options) | Win32Native.FIND_ENDSWITH | ((source.IsAscii() && suffix.IsAscii()) ? RESERVED_FIND_ASCII_STRING : 0), + source, source.Length, source.Length - 1, suffix, suffix.Length) >= 0; + } + + + public virtual bool IsSuffix(String source, String suffix) + { + return (IsSuffix(source, suffix, 0)); + } + + //////////////////////////////////////////////////////////////////////// + // + // IndexOf + // + // Returns the first index where value is found in string. The + // search starts from startIndex and ends at endIndex. Returns -1 if + // the specified value is not found. If value equals String.Empty, + // startIndex is returned. Throws IndexOutOfRange if startIndex or + // endIndex is less than zero or greater than the length of string. + // Throws ArgumentException if value is null. + // + //////////////////////////////////////////////////////////////////////// + + + public unsafe virtual int IndexOf(String source, char value) + { + if (source==null) + throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + return IndexOf(source, value, 0, source.Length, CompareOptions.None); + } + + + public unsafe virtual int IndexOf(String source, String value) + { + if (source==null) + throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + return IndexOf(source, value, 0, source.Length, CompareOptions.None); + } + + + public unsafe virtual int IndexOf(String source, char value, CompareOptions options) + { + if (source==null) + throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + return IndexOf(source, value, 0, source.Length, options); + } + + + public unsafe virtual int IndexOf(String source, String value, CompareOptions options) + { + if (source==null) + throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + return IndexOf(source, value, 0, source.Length, options); + } + + + public unsafe virtual int IndexOf(String source, char value, int startIndex) + { + if (source == null) + throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None); + } + + + public unsafe virtual int IndexOf(String source, String value, int startIndex) + { + if (source == null) + throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None); + } + + + public unsafe virtual int IndexOf(String source, char value, int startIndex, CompareOptions options) + { + if (source == null) + throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + return IndexOf(source, value, startIndex, source.Length - startIndex, options); + } + + + public unsafe virtual int IndexOf(String source, String value, int startIndex, CompareOptions options) + { + if (source == null) + throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + return IndexOf(source, value, startIndex, source.Length - startIndex, options); + } + + + public unsafe virtual int IndexOf(String source, char value, int startIndex, int count) + { + return IndexOf(source, value, startIndex, count, CompareOptions.None); + } + + + public unsafe virtual int IndexOf(String source, String value, int startIndex, int count) + { + return IndexOf(source, value, startIndex, count, CompareOptions.None); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe virtual int IndexOf(String source, char value, int startIndex, int count, CompareOptions options) + { + // Validate inputs + if (source == null) + throw new ArgumentNullException("source"); + + if (startIndex < 0 || startIndex > source.Length) + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); + + if (count < 0 || startIndex > source.Length - count) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count")); + Contract.EndContractBlock(); + + if (options == CompareOptions.OrdinalIgnoreCase) + { + return source.IndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase); + } + + // Validate CompareOptions + // Ordinal can't be selected with other flags + if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal)) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options"); + + // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to + // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString. + return InternalFindNLSStringEx( + m_dataHandle, m_handleOrigin, m_sortName, + GetNativeCompareFlags(options) | Win32Native.FIND_FROMSTART | ((source.IsAscii() && (value <= '\x007f')) ? RESERVED_FIND_ASCII_STRING : 0), + source, count, startIndex, new String(value, 1), 1); + } + + + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe virtual int IndexOf(String source, String value, int startIndex, int count, CompareOptions options) + { + // Validate inputs + if (source == null) + throw new ArgumentNullException("source"); + if (value == null) + throw new ArgumentNullException("value"); + + if (startIndex > source.Length) + { + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); + } + Contract.EndContractBlock(); + + // In Everett we used to return -1 for empty string even if startIndex is negative number so we keeping same behavior here. + // We return 0 if both source and value are empty strings for Everett compatibility too. + if (source.Length == 0) + { + if (value.Length == 0) + { + return 0; + } + return -1; + } + + if (startIndex < 0) + { + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); + } + + if (count < 0 || startIndex > source.Length - count) + throw new ArgumentOutOfRangeException("count",Environment.GetResourceString("ArgumentOutOfRange_Count")); + + if (options == CompareOptions.OrdinalIgnoreCase) + { + return source.IndexOf(value, startIndex, count, StringComparison.OrdinalIgnoreCase); + } + + // Validate CompareOptions + // Ordinal can't be selected with other flags + if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal)) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options"); + + // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to + // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString. + return InternalFindNLSStringEx( + m_dataHandle, m_handleOrigin, m_sortName, + GetNativeCompareFlags(options) | Win32Native.FIND_FROMSTART | ((source.IsAscii() && value.IsAscii()) ? RESERVED_FIND_ASCII_STRING : 0), + source, count, startIndex, value, value.Length); + } + + //////////////////////////////////////////////////////////////////////// + // + // LastIndexOf + // + // Returns the last index where value is found in string. The + // search starts from startIndex and ends at endIndex. Returns -1 if + // the specified value is not found. If value equals String.Empty, + // endIndex is returned. Throws IndexOutOfRange if startIndex or + // endIndex is less than zero or greater than the length of string. + // Throws ArgumentException if value is null. + // + //////////////////////////////////////////////////////////////////////// + + + public unsafe virtual int LastIndexOf(String source, char value) + { + if (source==null) + throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + // Can't start at negative index, so make sure we check for the length == 0 case. + return LastIndexOf(source, value, source.Length - 1, + source.Length, CompareOptions.None); + } + + + public virtual int LastIndexOf(String source, String value) + { + if (source==null) + throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + // Can't start at negative index, so make sure we check for the length == 0 case. + return LastIndexOf(source, value, source.Length - 1, + source.Length, CompareOptions.None); + } + + + public virtual int LastIndexOf(String source, char value, CompareOptions options) + { + if (source==null) + throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + // Can't start at negative index, so make sure we check for the length == 0 case. + return LastIndexOf(source, value, source.Length - 1, + source.Length, options); + } + + public unsafe virtual int LastIndexOf(String source, String value, CompareOptions options) + { + if (source==null) + throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + // Can't start at negative index, so make sure we check for the length == 0 case. + return LastIndexOf(source, value, source.Length - 1, + source.Length, options); + } + + + public unsafe virtual int LastIndexOf(String source, char value, int startIndex) + { + return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None); + } + + + public unsafe virtual int LastIndexOf(String source, String value, int startIndex) + { + return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None); + } + + + public unsafe virtual int LastIndexOf(String source, char value, int startIndex, CompareOptions options) + { + return LastIndexOf(source, value, startIndex, startIndex + 1, options); + } + + + public unsafe virtual int LastIndexOf(String source, String value, int startIndex, CompareOptions options) + { + return LastIndexOf(source, value, startIndex, startIndex + 1, options); + } + + + public unsafe virtual int LastIndexOf(String source, char value, int startIndex, int count) + { + return LastIndexOf(source, value, startIndex, count, CompareOptions.None); + } + + + public unsafe virtual int LastIndexOf(String source, String value, int startIndex, int count) + { + return LastIndexOf(source, value, startIndex, count, CompareOptions.None); + } + + + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe virtual int LastIndexOf(String source, char value, int startIndex, int count, CompareOptions options) + { + // Verify Arguments + if (source==null) + throw new ArgumentNullException("source"); + Contract.EndContractBlock(); + + // Validate CompareOptions + // Ordinal can't be selected with other flags + if ((options & ValidIndexMaskOffFlags) != 0 && + (options != CompareOptions.Ordinal) && + (options != CompareOptions.OrdinalIgnoreCase)) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options"); + + // Special case for 0 length input strings + if (source.Length == 0 && (startIndex == -1 || startIndex == 0)) + return -1; + + // Make sure we're not out of range + if (startIndex < 0 || startIndex > source.Length) + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); + + // Make sure that we allow startIndex == source.Length + if (startIndex == source.Length) + { + startIndex--; + if (count > 0) + count--; + } + + // 2nd have of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0. + if (count < 0 || startIndex - count + 1 < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count")); + + if (options == CompareOptions.OrdinalIgnoreCase) + { + return source.LastIndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase); + } + + // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to + // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString. + return InternalFindNLSStringEx( + m_dataHandle, m_handleOrigin, m_sortName, + GetNativeCompareFlags(options) | Win32Native.FIND_FROMEND | ((source.IsAscii() && (value <= '\x007f')) ? RESERVED_FIND_ASCII_STRING : 0), + source, count, startIndex, new String(value, 1), 1); + } + + + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe virtual int LastIndexOf(String source, String value, int startIndex, int count, CompareOptions options) + { + // Verify Arguments + if (source == null) + throw new ArgumentNullException("source"); + if (value == null) + throw new ArgumentNullException("value"); + Contract.EndContractBlock(); + + // Validate CompareOptions + // Ordinal can't be selected with other flags + if ((options & ValidIndexMaskOffFlags) != 0 && + (options != CompareOptions.Ordinal) && + (options != CompareOptions.OrdinalIgnoreCase)) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options"); + + // Special case for 0 length input strings + if (source.Length == 0 && (startIndex == -1 || startIndex == 0)) + return (value.Length == 0) ? 0 : -1; + + // Make sure we're not out of range + if (startIndex < 0 || startIndex > source.Length) + throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); + + // Make sure that we allow startIndex == source.Length + if (startIndex == source.Length) + { + startIndex--; + if (count > 0) + count--; + + // If we are looking for nothing, just return 0 + if (value.Length == 0 && count >= 0 && startIndex - count + 1 >= 0) + return startIndex; + } + + // 2nd half of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0. + if (count < 0 || startIndex - count + 1 < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count")); + + if (options == CompareOptions.OrdinalIgnoreCase) + { + return source.LastIndexOf(value, startIndex, count, StringComparison.OrdinalIgnoreCase); + } + + // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to + // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString. + return InternalFindNLSStringEx( + m_dataHandle, m_handleOrigin, m_sortName, + GetNativeCompareFlags(options) | Win32Native.FIND_FROMEND | ((source.IsAscii() && value.IsAscii()) ? RESERVED_FIND_ASCII_STRING : 0), + source, count, startIndex, value, value.Length); + } + + + //////////////////////////////////////////////////////////////////////// + // + // GetSortKey + // + // Gets the SortKey for the given string with the given options. + // + //////////////////////////////////////////////////////////////////////// + public unsafe virtual SortKey GetSortKey(String source, CompareOptions options) + { + return CreateSortKey(source, options); + } + + + public unsafe virtual SortKey GetSortKey(String source) + { + return CreateSortKey(source, CompareOptions.None); + } + + [System.Security.SecuritySafeCritical] + private SortKey CreateSortKey(String source, CompareOptions options) + { + if (source==null) { throw new ArgumentNullException("source"); } + Contract.EndContractBlock(); + + // Mask used to check if we have the right flags. + const CompareOptions ValidSortkeyCtorMaskOffFlags = ~(CompareOptions.IgnoreCase | + CompareOptions.IgnoreSymbols | + CompareOptions.IgnoreNonSpace | + CompareOptions.IgnoreWidth | + CompareOptions.IgnoreKanaType | + CompareOptions.StringSort); + + if ((options & ValidSortkeyCtorMaskOffFlags) != 0) + { + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options"); + } + byte[] keyData = null; + // The OS doesn't have quite the same behavior so we have to test for empty inputs + if (String.IsNullOrEmpty(source)) + { + // Empty strings get an empty sort key + keyData = EmptyArray<Byte>.Value; + // Fake value to test though so we can verify our flags + source = "\x0000"; + } + + int flags = GetNativeCompareFlags(options); + + // Go ahead and call the OS + // First get the count + int length = InternalGetSortKey(m_dataHandle, m_handleOrigin, m_sortName, flags, source, source.Length, null, 0); + + // If there was an error, return an error + if (length == 0) + { + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "source"); + } + + // If input was empty, return the empty byte[] we made earlier and skip this + if (keyData == null) + { + // Make an appropriate byte array + keyData = new byte[length]; + + // Fill up the array + length = InternalGetSortKey(m_dataHandle, m_handleOrigin, m_sortName, flags, source, source.Length, keyData, keyData.Length); + } + else + { + source = String.Empty; // back to original + } + + return new SortKey(Name, source, options, keyData); + } + + + //////////////////////////////////////////////////////////////////////// + // + // Equals + // + // Implements Object.Equals(). Returns a boolean indicating whether + // or not object refers to the same CompareInfo as the current + // instance. + // + //////////////////////////////////////////////////////////////////////// + + + public override bool Equals(Object value) + { + CompareInfo that = value as CompareInfo; + + if (that != null) + { + return this.Name == that.Name; + } + + return (false); + } + + + //////////////////////////////////////////////////////////////////////// + // + // GetHashCode + // + // Implements Object.GetHashCode(). Returns the hash code for the + // CompareInfo. The hash code is guaranteed to be the same for + // CompareInfo A and B where A.Equals(B) is true. + // + //////////////////////////////////////////////////////////////////////// + + + public override int GetHashCode() + { + return (this.Name.GetHashCode()); + } + + // + // return hash value for the string according to the input CompareOptions + // + + public virtual int GetHashCode(string source, CompareOptions options) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + + if (options == CompareOptions.Ordinal) + { + return source.GetHashCode(); + } + + if (options == CompareOptions.OrdinalIgnoreCase) + { + return TextInfo.GetHashCodeOrdinalIgnoreCase(source); + } + + // + // GetHashCodeOfString does more parameters validation. basically will throw when + // having Ordinal, OrdinalIgnoreCase and StringSort + // + + return GetHashCodeOfString(source, options, false, 0); + } + + //////////////////////////////////////////////////////////////////////// + // + // GetHashCodeOfString + // + // This internal method allows a method that allows the equivalent of creating a Sortkey for a + // string from CompareInfo, and generate a hashcode value from it. It is not very convenient + // to use this method as is and it creates an unnecessary Sortkey object that will be GC'ed. + // + // The hash code is guaranteed to be the same for string A and B where A.Equals(B) is true and both + // the CompareInfo and the CompareOptions are the same. If two different CompareInfo objects + // treat the string the same way, this implementation will treat them differently (the same way that + // Sortkey does at the moment). + // + // This method will never be made public itself, but public consumers of it could be created, e.g.: + // + // string.GetHashCode(CultureInfo) + // string.GetHashCode(CompareInfo) + // string.GetHashCode(CultureInfo, CompareOptions) + // string.GetHashCode(CompareInfo, CompareOptions) + // etc. + // + // (the methods above that take a CultureInfo would use CultureInfo.CompareInfo) + // + //////////////////////////////////////////////////////////////////////// + internal int GetHashCodeOfString(string source, CompareOptions options) + { + return GetHashCodeOfString(source, options, false, 0); + } + + [System.Security.SecuritySafeCritical] // auto-generated + internal int GetHashCodeOfString(string source, CompareOptions options, bool forceRandomizedHashing, long additionalEntropy) + { + // + // Parameter validation + // + if(null == source) + { + throw new ArgumentNullException("source"); + } + + if ((options & ValidHashCodeOfStringMaskOffFlags) != 0) + { + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options"); + } + Contract.EndContractBlock(); + + if(0 == source.Length) + { + return(0); + } + + // + //////////////////////////////////////////////////////////////////////// + return (InternalGetGlobalizedHashCode(m_dataHandle, m_handleOrigin, this.m_sortName, source, source.Length, GetNativeCompareFlags(options), forceRandomizedHashing, additionalEntropy)); + } + + //////////////////////////////////////////////////////////////////////// + // + // ToString + // + // Implements Object.ToString(). Returns a string describing the + // CompareInfo. + // + //////////////////////////////////////////////////////////////////////// + + + public override String ToString() + { + return ("CompareInfo - " + this.Name); + } + +#if FEATURE_USE_LCID + public int LCID + { + get + { + return CultureInfo.GetCultureInfo(this.Name).LCID; + } + } +#endif + + [System.Security.SecuritySafeCritical] + internal static IntPtr InternalInitSortHandle(String localeName, out IntPtr handleOrigin) + { + return NativeInternalInitSortHandle(localeName, out handleOrigin); + } + +#if !FEATURE_CORECLR + private const int SORT_VERSION_WHIDBEY = 0x00001000; + private const int SORT_VERSION_V4 = 0x00060101; + + internal static bool IsLegacy20SortingBehaviorRequested + { + get + { + return InternalSortVersion == SORT_VERSION_WHIDBEY; + } + } + + private static uint InternalSortVersion + { + [System.Security.SecuritySafeCritical] + get + { + return InternalGetSortVersion(); + } + } + + [OptionalField(VersionAdded = 3)] + private SortVersion m_SortVersion; + + public SortVersion Version + { + [SecuritySafeCritical] + get + { + if(m_SortVersion == null) + { + Win32Native.NlsVersionInfoEx v = new Win32Native.NlsVersionInfoEx(); + v.dwNLSVersionInfoSize = Marshal.SizeOf(typeof(Win32Native.NlsVersionInfoEx)); + InternalGetNlsVersionEx(m_dataHandle, m_handleOrigin, m_sortName, ref v); + m_SortVersion = new SortVersion(v.dwNLSVersion, (v.dwEffectiveId != 0) ? v.dwEffectiveId : LCID, v.guidCustomVersion); + } + + return m_SortVersion; + } + } + + [System.Security.SecurityCritical] + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool InternalGetNlsVersionEx(IntPtr handle, IntPtr handleOrigin, String localeName, ref Win32Native.NlsVersionInfoEx lpNlsVersionInformation); + + [System.Security.SecurityCritical] + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + private static extern uint InternalGetSortVersion(); + +#endif + [System.Security.SecurityCritical] + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + private static extern IntPtr NativeInternalInitSortHandle(String localeName, out IntPtr handleOrigin); + + // Get a locale sensitive sort hash code from native code -- COMNlsInfo::InternalGetGlobalizedHashCode + [System.Security.SecurityCritical] // auto-generated + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + private static extern int InternalGetGlobalizedHashCode(IntPtr handle, IntPtr handleOrigin, string localeName, string source, int length, int dwFlags, bool forceRandomizedHashing, long additionalEntropy); + + // Use native API calls to see if this string is entirely defined -- COMNlsInfo::InternalIsSortable + [System.Security.SecurityCritical] // auto-generated + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool InternalIsSortable(IntPtr handle, IntPtr handleOrigin, String localeName, String source, int length); + + // Compare a string using the native API calls -- COMNlsInfo::InternalCompareString + [System.Security.SecurityCritical] // auto-generated + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + private static extern int InternalCompareString(IntPtr handle, IntPtr handleOrigin, String localeName, String string1, int offset1, int length1, + String string2, int offset2, int length2, int flags); + + // InternalFindNLSStringEx parameters is not exactly matching kernel32::FindNLSStringEx parameters. + // Call through to NewApis::FindNLSStringEx so we can get the right behavior + [System.Security.SecurityCritical] // auto-generated + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + private static extern int InternalFindNLSStringEx(IntPtr handle, IntPtr handleOrigin, String localeName, int flags, String source, int sourceCount, int startIndex, string target, int targetCount); + + // Call through to NewAPis::LCMapStringEx so we can get appropriate behavior for all platforms + [System.Security.SecurityCritical] // auto-generated + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + private static extern int InternalGetSortKey(IntPtr handle, IntPtr handleOrigin, String localeName, int flags, String source, int sourceCount, byte[] target, int targetCount); + } +} |