e088a82ded08a54c3f11584eac27e6ea3c8e3f57
[platform/upstream/coreclr.git] / src / mscorlib / shared / System / Globalization / CompareInfo.cs
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 ////////////////////////////////////////////////////////////////////////////
6 //
7 //
8 //
9 //  Purpose:  This class implements a set of methods for comparing
10 //            strings.
11 //
12 //
13 ////////////////////////////////////////////////////////////////////////////
14
15 using System.Reflection;
16 using System.Diagnostics;
17 using System.Runtime.InteropServices;
18 using System.Runtime.Serialization;
19
20 namespace System.Globalization
21 {
22     [Flags]
23     public enum CompareOptions
24     {
25         None = 0x00000000,
26         IgnoreCase = 0x00000001,
27         IgnoreNonSpace = 0x00000002,
28         IgnoreSymbols = 0x00000004,
29         IgnoreKanaType = 0x00000008,   // ignore kanatype
30         IgnoreWidth = 0x00000010,   // ignore width
31         OrdinalIgnoreCase = 0x10000000,   // This flag can not be used with other flags.
32         StringSort = 0x20000000,   // use string sort method
33         Ordinal = 0x40000000,   // This flag can not be used with other flags.
34     }
35
36     [Serializable]
37     [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
38     public partial class CompareInfo : IDeserializationCallback
39     {
40         // Mask used to check if IndexOf()/LastIndexOf()/IsPrefix()/IsPostfix() has the right flags.
41         private const CompareOptions ValidIndexMaskOffFlags =
42             ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
43               CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType);
44
45         // Mask used to check if Compare() has the right flags.
46         private const CompareOptions ValidCompareMaskOffFlags =
47             ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
48               CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort);
49
50         // Mask used to check if GetHashCodeOfString() has the right flags.
51         private const CompareOptions ValidHashCodeOfStringMaskOffFlags =
52             ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
53               CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType);
54
55         // Mask used to check if we have the right flags.
56         private const CompareOptions ValidSortkeyCtorMaskOffFlags =
57             ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
58               CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort);
59
60         //
61         // CompareInfos have an interesting identity.  They are attached to the locale that created them,
62         // ie: en-US would have an en-US sort.  For haw-US (custom), then we serialize it as haw-US.
63         // The interesting part is that since haw-US doesn't have its own sort, it has to point at another
64         // locale, which is what SCOMPAREINFO does.
65         [OptionalField(VersionAdded = 2)]
66         private string m_name;  // The name used to construct this CompareInfo. Do not rename (binary serialization)
67
68         [NonSerialized]
69         private string _sortName; // The name that defines our behavior
70
71         [OptionalField(VersionAdded = 3)]
72         private SortVersion m_SortVersion; // Do not rename (binary serialization)
73
74         // _invariantMode is defined for the perf reason as accessing the instance field is faster than access the static property GlobalizationMode.Invariant
75         [NonSerialized]
76         private readonly bool _invariantMode = GlobalizationMode.Invariant;
77
78         private int culture; // Do not rename (binary serialization). The fields sole purpose is to support Desktop serialization.
79
80         internal CompareInfo(CultureInfo culture)
81         {
82             m_name = culture._name;
83             InitSort(culture);
84         }
85
86         /*=================================GetCompareInfo==========================
87         **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture.
88         **       Warning: The assembly versioning mechanism is dead!
89         **Returns: The CompareInfo for the specified culture.
90         **Arguments:
91         **   culture    the ID of the culture
92         **   assembly   the assembly which contains the sorting table.
93         **Exceptions:
94         **  ArugmentNullException when the assembly is null
95         **  ArgumentException if culture is invalid.
96         ============================================================================*/
97         // Assembly constructor should be deprecated, we don't act on the assembly information any more
98         public static CompareInfo GetCompareInfo(int culture, Assembly assembly)
99         {
100             // Parameter checking.
101             if (assembly == null)
102             {
103                 throw new ArgumentNullException(nameof(assembly));
104             }
105             if (assembly != typeof(Object).Module.Assembly)
106             {
107                 throw new ArgumentException(SR.Argument_OnlyMscorlib);
108             }
109
110             return GetCompareInfo(culture);
111         }
112
113         /*=================================GetCompareInfo==========================
114         **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture.
115         **       The purpose of this method is to provide version for CompareInfo tables.
116         **Returns: The CompareInfo for the specified culture.
117         **Arguments:
118         **   name      the name of the culture
119         **   assembly  the assembly which contains the sorting table.
120         **Exceptions:
121         **  ArugmentNullException when the assembly is null
122         **  ArgumentException if name is invalid.
123         ============================================================================*/
124         // Assembly constructor should be deprecated, we don't act on the assembly information any more
125         public static CompareInfo GetCompareInfo(string name, Assembly assembly)
126         {
127             if (name == null || assembly == null)
128             {
129                 throw new ArgumentNullException(name == null ? nameof(name) : nameof(assembly));
130             }
131
132             if (assembly != typeof(Object).Module.Assembly)
133             {
134                 throw new ArgumentException(SR.Argument_OnlyMscorlib);
135             }
136
137             return GetCompareInfo(name);
138         }
139
140         /*=================================GetCompareInfo==========================
141         **Action: Get the CompareInfo for the specified culture.
142         ** This method is provided for ease of integration with NLS-based software.
143         **Returns: The CompareInfo for the specified culture.
144         **Arguments:
145         **   culture    the ID of the culture.
146         **Exceptions:
147         **  ArgumentException if culture is invalid.
148         ============================================================================*/
149         // People really shouldn't be calling LCID versions, no custom support
150         public static CompareInfo GetCompareInfo(int culture)
151         {
152             if (CultureData.IsCustomCultureId(culture))
153             {
154                 // Customized culture cannot be created by the LCID.
155                 throw new ArgumentException(SR.Argument_CustomCultureCannotBePassedByNumber, nameof(culture));
156             }
157
158             return CultureInfo.GetCultureInfo(culture).CompareInfo;
159         }
160
161         /*=================================GetCompareInfo==========================
162         **Action: Get the CompareInfo for the specified culture.
163         **Returns: The CompareInfo for the specified culture.
164         **Arguments:
165         **   name    the name of the culture.
166         **Exceptions:
167         **  ArgumentException if name is invalid.
168         ============================================================================*/
169
170         public static CompareInfo GetCompareInfo(string name)
171         {
172             if (name == null)
173             {
174                 throw new ArgumentNullException(nameof(name));
175             }
176
177             return CultureInfo.GetCultureInfo(name).CompareInfo;
178         }
179
180         public static unsafe bool IsSortable(char ch)
181         {
182             if (GlobalizationMode.Invariant)
183             {
184                 return true;
185             }
186             char *pChar = &ch;
187             return IsSortable(pChar, 1);
188         }
189
190         public static unsafe bool IsSortable(string text)
191         {
192             if (text == null)
193             {
194                 // A null param is invalid here.
195                 throw new ArgumentNullException(nameof(text));
196             }
197
198             if (text.Length == 0)
199             {
200                 // A zero length string is not invalid, but it is also not sortable.
201                 return (false);
202             }
203
204             if (GlobalizationMode.Invariant)
205             {
206                 return true;
207             }
208
209             fixed (char *pChar = text)
210             {
211                 return IsSortable(pChar, text.Length);
212             }
213         }
214
215         [OnDeserializing]
216         private void OnDeserializing(StreamingContext ctx)
217         {
218             m_name = null;
219         }
220
221         void IDeserializationCallback.OnDeserialization(object sender)
222         {
223             OnDeserialized();
224         }
225
226         [OnDeserialized]
227         private void OnDeserialized(StreamingContext ctx)
228         {
229             OnDeserialized();
230         }
231
232         private void OnDeserialized()
233         {
234             // If we didn't have a name, use the LCID
235             if (m_name == null)
236             {
237                 // From whidbey, didn't have a name
238                 CultureInfo ci = CultureInfo.GetCultureInfo(this.culture);
239                 m_name = ci._name;
240             }
241             else
242             {
243                 InitSort(CultureInfo.GetCultureInfo(m_name));
244             }
245         }
246
247         [OnSerializing]
248         private void OnSerializing(StreamingContext ctx)
249         {
250             // This is merely for serialization compatibility with Whidbey/Orcas, it can go away when we don't want that compat any more.
251             culture = CultureInfo.GetCultureInfo(this.Name).LCID; // This is the lcid of the constructing culture (still have to dereference to get target sort)
252             Debug.Assert(m_name != null, "CompareInfo.OnSerializing - expected m_name to be set already");
253         }
254
255         ///////////////////////////----- Name -----/////////////////////////////////
256         //
257         //  Returns the name of the culture (well actually, of the sort).
258         //  Very important for providing a non-LCID way of identifying
259         //  what the sort is.
260         //
261         //  Note that this name isn't dereferenced in case the CompareInfo is a different locale
262         //  which is consistent with the behaviors of earlier versions.  (so if you ask for a sort
263         //  and the locale's changed behavior, then you'll get changed behavior, which is like
264         //  what happens for a version update)
265         //
266         ////////////////////////////////////////////////////////////////////////
267
268         public virtual string Name
269         {
270             get
271             {
272                 Debug.Assert(m_name != null, "CompareInfo.Name Expected _name to be set");
273                 if (m_name == "zh-CHT" || m_name == "zh-CHS")
274                 {
275                     return m_name;
276                 }
277
278                 return _sortName;
279             }
280         }
281
282         ////////////////////////////////////////////////////////////////////////
283         //
284         //  Compare
285         //
286         //  Compares the two strings with the given options.  Returns 0 if the
287         //  two strings are equal, a number less than 0 if string1 is less
288         //  than string2, and a number greater than 0 if string1 is greater
289         //  than string2.
290         //
291         ////////////////////////////////////////////////////////////////////////
292
293         public virtual int Compare(string string1, string string2)
294         {
295             return (Compare(string1, string2, CompareOptions.None));
296         }
297
298         public unsafe virtual int Compare(string string1, string string2, CompareOptions options)
299         {
300             if (options == CompareOptions.OrdinalIgnoreCase)
301             {
302                 return String.Compare(string1, string2, StringComparison.OrdinalIgnoreCase);
303             }
304
305             // Verify the options before we do any real comparison.
306             if ((options & CompareOptions.Ordinal) != 0)
307             {
308                 if (options != CompareOptions.Ordinal)
309                 {
310                     throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options));
311                 }
312
313                 return String.CompareOrdinal(string1, string2);
314             }
315
316             if ((options & ValidCompareMaskOffFlags) != 0)
317             {
318                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
319             }
320
321             //Our paradigm is that null sorts less than any other string and
322             //that two nulls sort as equal.
323             if (string1 == null)
324             {
325                 if (string2 == null)
326                 {
327                     return (0);     // Equal
328                 }
329                 return (-1);    // null < non-null
330             }
331             if (string2 == null)
332             {
333                 return (1);     // non-null > null
334             }
335
336             if (_invariantMode)
337             {
338                 if ((options & CompareOptions.IgnoreCase) != 0)
339                     return CompareOrdinalIgnoreCase(string1, 0, string1.Length, string2, 0, string2.Length);
340
341                 return String.CompareOrdinal(string1, string2);
342             }
343
344             return CompareString(string1.AsReadOnlySpan(), string2.AsReadOnlySpan(), options);
345         }
346
347         // TODO https://github.com/dotnet/coreclr/issues/13827:
348         // This method shouldn't be necessary, as we should be able to just use the overload
349         // that takes two spans.  But due to this issue, that's adding significant overhead.
350         internal unsafe int Compare(ReadOnlySpan<char> string1, string string2, CompareOptions options)
351         {
352             if (options == CompareOptions.OrdinalIgnoreCase)
353             {
354                 return CompareOrdinalIgnoreCase(string1, string2.AsReadOnlySpan());
355             }
356
357             // Verify the options before we do any real comparison.
358             if ((options & CompareOptions.Ordinal) != 0)
359             {
360                 if (options != CompareOptions.Ordinal)
361                 {
362                     throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options));
363                 }
364
365                 return string.CompareOrdinal(string1, string2.AsReadOnlySpan());
366             }
367
368             if ((options & ValidCompareMaskOffFlags) != 0)
369             {
370                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
371             }
372
373             // null sorts less than any other string.
374             if (string2 == null)
375             {
376                 return 1;
377             }
378
379             if (_invariantMode)
380             {
381                 return (options & CompareOptions.IgnoreCase) != 0 ?
382                     CompareOrdinalIgnoreCase(string1, string2.AsReadOnlySpan()) :
383                     string.CompareOrdinal(string1, string2.AsReadOnlySpan());
384             }
385
386             return CompareString(string1, string2, options);
387         }
388
389         // TODO https://github.com/dotnet/corefx/issues/21395: Expose this publicly?
390         internal unsafe virtual int Compare(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions options)
391         {
392             if (options == CompareOptions.OrdinalIgnoreCase)
393             {
394                 return CompareOrdinalIgnoreCase(string1, string2);
395             }
396
397             // Verify the options before we do any real comparison.
398             if ((options & CompareOptions.Ordinal) != 0)
399             {
400                 if (options != CompareOptions.Ordinal)
401                 {
402                     throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options));
403                 }
404
405                 return string.CompareOrdinal(string1, string2);
406             }
407
408             if ((options & ValidCompareMaskOffFlags) != 0)
409             {
410                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
411             }
412
413             if (_invariantMode)
414             {
415                 return (options & CompareOptions.IgnoreCase) != 0 ?
416                     CompareOrdinalIgnoreCase(string1, string2) :
417                     string.CompareOrdinal(string1, string2);
418             }
419
420             return CompareString(string1, string2, options);
421         }
422
423         ////////////////////////////////////////////////////////////////////////
424         //
425         //  Compare
426         //
427         //  Compares the specified regions of the two strings with the given
428         //  options.
429         //  Returns 0 if the two strings are equal, a number less than 0 if
430         //  string1 is less than string2, and a number greater than 0 if
431         //  string1 is greater than string2.
432         //
433         ////////////////////////////////////////////////////////////////////////
434
435
436         public unsafe virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2)
437         {
438             return Compare(string1, offset1, length1, string2, offset2, length2, 0);
439         }
440
441
442         public virtual int Compare(string string1, int offset1, string string2, int offset2, CompareOptions options)
443         {
444             return Compare(string1, offset1, string1 == null ? 0 : string1.Length - offset1,
445                            string2, offset2, string2 == null ? 0 : string2.Length - offset2, options);
446         }
447
448
449         public virtual int Compare(string string1, int offset1, string string2, int offset2)
450         {
451             return Compare(string1, offset1, string2, offset2, 0);
452         }
453
454
455         public virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options)
456         {
457             if (options == CompareOptions.OrdinalIgnoreCase)
458             {
459                 int result = String.Compare(string1, offset1, string2, offset2, length1 < length2 ? length1 : length2, StringComparison.OrdinalIgnoreCase);
460                 if ((length1 != length2) && result == 0)
461                     return (length1 > length2 ? 1 : -1);
462                 return (result);
463             }
464
465             // Verify inputs
466             if (length1 < 0 || length2 < 0)
467             {
468                 throw new ArgumentOutOfRangeException((length1 < 0) ? nameof(length1) : nameof(length2), SR.ArgumentOutOfRange_NeedPosNum);
469             }
470             if (offset1 < 0 || offset2 < 0)
471             {
472                 throw new ArgumentOutOfRangeException((offset1 < 0) ? nameof(offset1) : nameof(offset2), SR.ArgumentOutOfRange_NeedPosNum);
473             }
474             if (offset1 > (string1 == null ? 0 : string1.Length) - length1)
475             {
476                 throw new ArgumentOutOfRangeException(nameof(string1), SR.ArgumentOutOfRange_OffsetLength);
477             }
478             if (offset2 > (string2 == null ? 0 : string2.Length) - length2)
479             {
480                 throw new ArgumentOutOfRangeException(nameof(string2), SR.ArgumentOutOfRange_OffsetLength);
481             }
482             if ((options & CompareOptions.Ordinal) != 0)
483             {
484                 if (options != CompareOptions.Ordinal)
485                 {
486                     throw new ArgumentException(SR.Argument_CompareOptionOrdinal,
487                                                 nameof(options));
488                 }
489             }
490             else if ((options & ValidCompareMaskOffFlags) != 0)
491             {
492                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
493             }
494
495             //
496             // Check for the null case.
497             //
498             if (string1 == null)
499             {
500                 if (string2 == null)
501                 {
502                     return (0);
503                 }
504                 return (-1);
505             }
506             if (string2 == null)
507             {
508                 return (1);
509             }
510
511             if (options == CompareOptions.Ordinal)
512             {
513                 return CompareOrdinal(string1, offset1, length1,
514                                       string2, offset2, length2);
515             }
516
517             if (_invariantMode)
518             {
519                 if ((options & CompareOptions.IgnoreCase) != 0)
520                     return CompareOrdinalIgnoreCase(string1, offset1, length1, string2, offset2, length2);
521
522                 return CompareOrdinal(string1, offset1, length1, string2, offset2, length2);
523             }
524
525             return CompareString(
526                 string1.AsReadOnlySpan().Slice(offset1, length1),
527                 string2.AsReadOnlySpan().Slice(offset2, length2),
528                 options);
529         }
530
531         private static int CompareOrdinal(string string1, int offset1, int length1, string string2, int offset2, int length2)
532         {
533             int result = String.CompareOrdinal(string1, offset1, string2, offset2,
534                                                        (length1 < length2 ? length1 : length2));
535             if ((length1 != length2) && result == 0)
536             {
537                 return (length1 > length2 ? 1 : -1);
538             }
539             return (result);
540         }
541
542         //
543         // CompareOrdinalIgnoreCase compare two string ordinally with ignoring the case.
544         // it assumes the strings are Ascii string till we hit non Ascii character in strA or strB and then we continue the comparison by
545         // calling the OS.
546         //
547         internal static unsafe int CompareOrdinalIgnoreCase(string strA, int indexA, int lengthA, string strB, int indexB, int lengthB)
548         {
549             Debug.Assert(indexA + lengthA <= strA.Length);
550             Debug.Assert(indexB + lengthB <= strB.Length);
551             return CompareOrdinalIgnoreCase(strA.AsReadOnlySpan().Slice(indexA, lengthA), strB.AsReadOnlySpan().Slice(indexB, lengthB));
552         }
553
554         internal static unsafe int CompareOrdinalIgnoreCase(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
555         {
556             int length = Math.Min(strA.Length, strB.Length);
557             int range = length;
558
559             fixed (char* ap = &MemoryMarshal.GetReference(strA))
560             fixed (char* bp = &MemoryMarshal.GetReference(strB))
561             {
562                 char* a = ap;
563                 char* b = bp;
564
565                 // in InvariantMode we support all range and not only the ascii characters.
566                 char maxChar = (char) (GlobalizationMode.Invariant ? 0xFFFF : 0x80);
567
568                 while (length != 0 && (*a <= maxChar) && (*b <= maxChar))
569                 {
570                     int charA = *a;
571                     int charB = *b;
572
573                     if (charA == charB)
574                     {
575                         a++; b++;
576                         length--;
577                         continue;
578                     }
579
580                     // uppercase both chars - notice that we need just one compare per char
581                     if ((uint)(charA - 'a') <= 'z' - 'a') charA -= 0x20;
582                     if ((uint)(charB - 'a') <= 'z' - 'a') charB -= 0x20;
583
584                     // Return the (case-insensitive) difference between them.
585                     if (charA != charB)
586                         return charA - charB;
587
588                     // Next char
589                     a++; b++;
590                     length--;
591                 }
592
593                 if (length == 0)
594                     return strA.Length - strB.Length;
595
596                 Debug.Assert(!GlobalizationMode.Invariant);
597
598                 range -= length;
599
600                 return CompareStringOrdinalIgnoreCase(a, strA.Length - range, b, strB.Length - range);
601             }
602         }
603
604         ////////////////////////////////////////////////////////////////////////
605         //
606         //  IsPrefix
607         //
608         //  Determines whether prefix is a prefix of string.  If prefix equals
609         //  String.Empty, true is returned.
610         //
611         ////////////////////////////////////////////////////////////////////////
612         public virtual bool IsPrefix(string source, string prefix, CompareOptions options)
613         {
614             if (source == null || prefix == null)
615             {
616                 throw new ArgumentNullException((source == null ? nameof(source) : nameof(prefix)),
617                     SR.ArgumentNull_String);
618             }
619
620             if (prefix.Length == 0)
621             {
622                 return (true);
623             }
624
625             if (source.Length == 0)
626             {
627                 return false;
628             }
629
630             if (options == CompareOptions.OrdinalIgnoreCase)
631             {
632                 return source.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
633             }
634
635             if (options == CompareOptions.Ordinal)
636             {
637                 return source.StartsWith(prefix, StringComparison.Ordinal);
638             }
639
640             if ((options & ValidIndexMaskOffFlags) != 0)
641             {
642                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
643             }
644
645             if (_invariantMode)
646             {
647                 return source.StartsWith(prefix, (options & CompareOptions.IgnoreCase) != 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
648             }
649
650             return StartsWith(source, prefix, options);
651         }
652
653         public virtual bool IsPrefix(string source, string prefix)
654         {
655             return (IsPrefix(source, prefix, 0));
656         }
657
658         ////////////////////////////////////////////////////////////////////////
659         //
660         //  IsSuffix
661         //
662         //  Determines whether suffix is a suffix of string.  If suffix equals
663         //  String.Empty, true is returned.
664         //
665         ////////////////////////////////////////////////////////////////////////
666         public virtual bool IsSuffix(string source, string suffix, CompareOptions options)
667         {
668             if (source == null || suffix == null)
669             {
670                 throw new ArgumentNullException((source == null ? nameof(source) : nameof(suffix)),
671                     SR.ArgumentNull_String);
672             }
673
674             if (suffix.Length == 0)
675             {
676                 return (true);
677             }
678
679             if (source.Length == 0)
680             {
681                 return false;
682             }
683
684             if (options == CompareOptions.OrdinalIgnoreCase)
685             {
686                 return source.EndsWith(suffix, StringComparison.OrdinalIgnoreCase);
687             }
688
689             if (options == CompareOptions.Ordinal)
690             {
691                 return source.EndsWith(suffix, StringComparison.Ordinal);
692             }
693
694             if ((options & ValidIndexMaskOffFlags) != 0)
695             {
696                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
697             }
698
699             if (_invariantMode)
700             {
701                 return source.EndsWith(suffix, (options & CompareOptions.IgnoreCase) != 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
702             }
703
704             return EndsWith(source, suffix, options);
705         }
706
707
708         public virtual bool IsSuffix(string source, string suffix)
709         {
710             return (IsSuffix(source, suffix, 0));
711         }
712
713         ////////////////////////////////////////////////////////////////////////
714         //
715         //  IndexOf
716         //
717         //  Returns the first index where value is found in string.  The
718         //  search starts from startIndex and ends at endIndex.  Returns -1 if
719         //  the specified value is not found.  If value equals String.Empty,
720         //  startIndex is returned.  Throws IndexOutOfRange if startIndex or
721         //  endIndex is less than zero or greater than the length of string.
722         //  Throws ArgumentException if value is null.
723         //
724         ////////////////////////////////////////////////////////////////////////
725
726
727         public virtual int IndexOf(string source, char value)
728         {
729             if (source == null)
730                 throw new ArgumentNullException(nameof(source));
731
732             return IndexOf(source, value, 0, source.Length, CompareOptions.None);
733         }
734
735
736         public virtual int IndexOf(string source, string value)
737         {
738             if (source == null)
739                 throw new ArgumentNullException(nameof(source));
740
741             return IndexOf(source, value, 0, source.Length, CompareOptions.None);
742         }
743
744
745         public virtual int IndexOf(string source, char value, CompareOptions options)
746         {
747             if (source == null)
748                 throw new ArgumentNullException(nameof(source));
749
750             return IndexOf(source, value, 0, source.Length, options);
751         }
752
753
754         public virtual int IndexOf(string source, string value, CompareOptions options)
755         {
756             if (source == null)
757                 throw new ArgumentNullException(nameof(source));
758
759             return IndexOf(source, value, 0, source.Length, options);
760         }
761
762         public virtual int IndexOf(string source, char value, int startIndex)
763         {
764             if (source == null)
765                 throw new ArgumentNullException(nameof(source));
766
767             return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
768         }
769
770         public virtual int IndexOf(string source, string value, int startIndex)
771         {
772             if (source == null)
773                 throw new ArgumentNullException(nameof(source));
774
775             return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
776         }
777
778         public virtual int IndexOf(string source, char value, int startIndex, CompareOptions options)
779         {
780             if (source == null)
781                 throw new ArgumentNullException(nameof(source));
782
783             return IndexOf(source, value, startIndex, source.Length - startIndex, options);
784         }
785
786
787         public virtual int IndexOf(string source, string value, int startIndex, CompareOptions options)
788         {
789             if (source == null)
790                 throw new ArgumentNullException(nameof(source));
791
792             return IndexOf(source, value, startIndex, source.Length - startIndex, options);
793         }
794
795
796         public virtual int IndexOf(string source, char value, int startIndex, int count)
797         {
798             return IndexOf(source, value, startIndex, count, CompareOptions.None);
799         }
800
801
802         public virtual int IndexOf(string source, string value, int startIndex, int count)
803         {
804             return IndexOf(source, value, startIndex, count, CompareOptions.None);
805         }
806
807         public unsafe virtual int IndexOf(string source, char value, int startIndex, int count, CompareOptions options)
808         {
809             // Validate inputs
810             if (source == null)
811                 throw new ArgumentNullException(nameof(source));
812
813             if (startIndex < 0 || startIndex > source.Length)
814                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
815
816             if (count < 0 || startIndex > source.Length - count)
817                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
818
819             if (options == CompareOptions.OrdinalIgnoreCase)
820             {
821                 return source.IndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase);
822             }
823
824             // Validate CompareOptions
825             // Ordinal can't be selected with other flags
826             if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal))
827                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
828             
829             if (_invariantMode)
830                 return IndexOfOrdinal(source, new string(value, 1), startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
831
832             return IndexOfCore(source, new string(value, 1), startIndex, count, options, null);
833         }
834
835         public unsafe virtual int IndexOf(string source, string value, int startIndex, int count, CompareOptions options)
836         {
837             // Validate inputs
838             if (source == null)
839                 throw new ArgumentNullException(nameof(source));
840             if (value == null)
841                 throw new ArgumentNullException(nameof(value));
842
843             if (startIndex > source.Length)
844             {
845                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
846             }
847
848             // In Everett we used to return -1 for empty string even if startIndex is negative number so we keeping same behavior here.
849             // We return 0 if both source and value are empty strings for Everett compatibility too.
850             if (source.Length == 0)
851             {
852                 if (value.Length == 0)
853                 {
854                     return 0;
855                 }
856                 return -1;
857             }
858
859             if (startIndex < 0)
860             {
861                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
862             }
863
864             if (count < 0 || startIndex > source.Length - count)
865                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
866
867             if (options == CompareOptions.OrdinalIgnoreCase)
868             {
869                 return IndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
870             }
871
872             // Validate CompareOptions
873             // Ordinal can't be selected with other flags
874             if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal))
875                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
876
877             if (_invariantMode)
878                 return IndexOfOrdinal(source, value, startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
879
880             return IndexOfCore(source, value, startIndex, count, options, null);
881         }
882
883         // The following IndexOf overload is mainly used by String.Replace. This overload assumes the parameters are already validated
884         // and the caller is passing a valid matchLengthPtr pointer.
885         internal unsafe int IndexOf(string source, string value, int startIndex, int count, CompareOptions options, int* matchLengthPtr)
886         {
887             Debug.Assert(source != null);
888             Debug.Assert(value != null);
889             Debug.Assert(startIndex >= 0);
890             Debug.Assert(matchLengthPtr != null);
891             *matchLengthPtr = 0;
892
893             if (source.Length == 0)
894             {
895                 if (value.Length == 0)
896                 {
897                     return 0;
898                 }
899                 return -1;
900             }
901
902             if (startIndex >= source.Length)
903             {
904                 return -1;
905             }
906
907             if (options == CompareOptions.OrdinalIgnoreCase)
908             {
909                 int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
910                 if (res >= 0)
911                 {
912                     *matchLengthPtr = value.Length;
913                 }
914                 return res;
915             }
916
917             if (_invariantMode)
918             {
919                 int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
920                 if (res >= 0)
921                 {
922                     *matchLengthPtr = value.Length;
923                 }
924                 return res;
925             }
926
927             return IndexOfCore(source, value, startIndex, count, options, matchLengthPtr);
928         }
929
930         internal int IndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase)
931         {
932             if (_invariantMode)
933             {
934                 return InvariantIndexOf(source, value, startIndex, count, ignoreCase);
935             }
936
937             return IndexOfOrdinalCore(source, value, startIndex, count, ignoreCase);
938         }
939
940         ////////////////////////////////////////////////////////////////////////
941         //
942         //  LastIndexOf
943         //
944         //  Returns the last index where value is found in string.  The
945         //  search starts from startIndex and ends at endIndex.  Returns -1 if
946         //  the specified value is not found.  If value equals String.Empty,
947         //  endIndex is returned.  Throws IndexOutOfRange if startIndex or
948         //  endIndex is less than zero or greater than the length of string.
949         //  Throws ArgumentException if value is null.
950         //
951         ////////////////////////////////////////////////////////////////////////
952
953
954         public virtual int LastIndexOf(String source, char value)
955         {
956             if (source == null)
957                 throw new ArgumentNullException(nameof(source));
958
959             // Can't start at negative index, so make sure we check for the length == 0 case.
960             return LastIndexOf(source, value, source.Length - 1, source.Length, CompareOptions.None);
961         }
962
963
964         public virtual int LastIndexOf(string source, string value)
965         {
966             if (source == null)
967                 throw new ArgumentNullException(nameof(source));
968
969             // Can't start at negative index, so make sure we check for the length == 0 case.
970             return LastIndexOf(source, value, source.Length - 1,
971                 source.Length, CompareOptions.None);
972         }
973
974
975         public virtual int LastIndexOf(string source, char value, CompareOptions options)
976         {
977             if (source == null)
978                 throw new ArgumentNullException(nameof(source));
979
980             // Can't start at negative index, so make sure we check for the length == 0 case.
981             return LastIndexOf(source, value, source.Length - 1,
982                 source.Length, options);
983         }
984
985         public virtual int LastIndexOf(string source, string value, CompareOptions options)
986         {
987             if (source == null)
988                 throw new ArgumentNullException(nameof(source));
989
990             // Can't start at negative index, so make sure we check for the length == 0 case.
991             return LastIndexOf(source, value, source.Length - 1, source.Length, options);
992         }
993
994         public virtual int LastIndexOf(string source, char value, int startIndex)
995         {
996             return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
997         }
998
999
1000         public virtual int LastIndexOf(string source, string value, int startIndex)
1001         {
1002             return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
1003         }
1004
1005         public virtual int LastIndexOf(string source, char value, int startIndex, CompareOptions options)
1006         {
1007             return LastIndexOf(source, value, startIndex, startIndex + 1, options);
1008         }
1009
1010
1011         public virtual int LastIndexOf(string source, string value, int startIndex, CompareOptions options)
1012         {
1013             return LastIndexOf(source, value, startIndex, startIndex + 1, options);
1014         }
1015
1016
1017         public virtual int LastIndexOf(string source, char value, int startIndex, int count)
1018         {
1019             return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
1020         }
1021
1022
1023         public virtual int LastIndexOf(string source, string value, int startIndex, int count)
1024         {
1025             return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
1026         }
1027
1028
1029         public virtual int LastIndexOf(string source, char value, int startIndex, int count, CompareOptions options)
1030         {
1031             // Verify Arguments
1032             if (source == null)
1033                 throw new ArgumentNullException(nameof(source));
1034
1035             // Validate CompareOptions
1036             // Ordinal can't be selected with other flags
1037             if ((options & ValidIndexMaskOffFlags) != 0 &&
1038                 (options != CompareOptions.Ordinal) &&
1039                 (options != CompareOptions.OrdinalIgnoreCase))
1040                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
1041
1042             // Special case for 0 length input strings
1043             if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
1044                 return -1;
1045
1046             // Make sure we're not out of range
1047             if (startIndex < 0 || startIndex > source.Length)
1048                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
1049
1050             // Make sure that we allow startIndex == source.Length
1051             if (startIndex == source.Length)
1052             {
1053                 startIndex--;
1054                 if (count > 0)
1055                     count--;
1056             }
1057
1058             // 2nd have of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
1059             if (count < 0 || startIndex - count + 1 < 0)
1060                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
1061
1062             if (options == CompareOptions.OrdinalIgnoreCase)
1063             {
1064                 return source.LastIndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase);
1065             }
1066
1067             if (_invariantMode)
1068                 return InvariantLastIndexOf(source, new string(value, 1), startIndex, count, (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
1069
1070             return LastIndexOfCore(source, value.ToString(), startIndex, count, options);
1071         }
1072
1073
1074         public virtual int LastIndexOf(string source, string value, int startIndex, int count, CompareOptions options)
1075         {
1076             // Verify Arguments
1077             if (source == null)
1078                 throw new ArgumentNullException(nameof(source));
1079             if (value == null)
1080                 throw new ArgumentNullException(nameof(value));
1081
1082             // Validate CompareOptions
1083             // Ordinal can't be selected with other flags
1084             if ((options & ValidIndexMaskOffFlags) != 0 &&
1085                 (options != CompareOptions.Ordinal) &&
1086                 (options != CompareOptions.OrdinalIgnoreCase))
1087                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
1088
1089             // Special case for 0 length input strings
1090             if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
1091                 return (value.Length == 0) ? 0 : -1;
1092
1093             // Make sure we're not out of range
1094             if (startIndex < 0 || startIndex > source.Length)
1095                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
1096
1097             // Make sure that we allow startIndex == source.Length
1098             if (startIndex == source.Length)
1099             {
1100                 startIndex--;
1101                 if (count > 0)
1102                     count--;
1103
1104                 // If we are looking for nothing, just return 0
1105                 if (value.Length == 0 && count >= 0 && startIndex - count + 1 >= 0)
1106                     return startIndex;
1107             }
1108
1109             // 2nd half of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
1110             if (count < 0 || startIndex - count + 1 < 0)
1111                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
1112
1113             if (options == CompareOptions.OrdinalIgnoreCase)
1114             {
1115                 return LastIndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
1116             }
1117
1118             if (_invariantMode)
1119                 return InvariantLastIndexOf(source, value, startIndex, count, (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
1120
1121             return LastIndexOfCore(source, value, startIndex, count, options);
1122         }
1123
1124         internal int LastIndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase)
1125         {
1126             if (_invariantMode)
1127             {
1128                 return InvariantLastIndexOf(source, value, startIndex, count, ignoreCase);
1129             }
1130
1131             return LastIndexOfOrdinalCore(source, value, startIndex, count, ignoreCase);
1132         }
1133
1134         ////////////////////////////////////////////////////////////////////////
1135         //
1136         //  GetSortKey
1137         //
1138         //  Gets the SortKey for the given string with the given options.
1139         //
1140         ////////////////////////////////////////////////////////////////////////
1141         public virtual SortKey GetSortKey(string source, CompareOptions options)
1142         {
1143             if (_invariantMode)
1144                 return InvariantCreateSortKey(source, options);
1145
1146             return CreateSortKey(source, options);
1147         }
1148
1149
1150         public virtual SortKey GetSortKey(string source)
1151         {
1152             if (_invariantMode)
1153                 return InvariantCreateSortKey(source, CompareOptions.None);
1154
1155             return CreateSortKey(source, CompareOptions.None);
1156         }
1157
1158         ////////////////////////////////////////////////////////////////////////
1159         //
1160         //  Equals
1161         //
1162         //  Implements Object.Equals().  Returns a boolean indicating whether
1163         //  or not object refers to the same CompareInfo as the current
1164         //  instance.
1165         //
1166         ////////////////////////////////////////////////////////////////////////
1167
1168
1169         public override bool Equals(Object value)
1170         {
1171             CompareInfo that = value as CompareInfo;
1172
1173             if (that != null)
1174             {
1175                 return this.Name == that.Name;
1176             }
1177
1178             return (false);
1179         }
1180
1181
1182         ////////////////////////////////////////////////////////////////////////
1183         //
1184         //  GetHashCode
1185         //
1186         //  Implements Object.GetHashCode().  Returns the hash code for the
1187         //  CompareInfo.  The hash code is guaranteed to be the same for
1188         //  CompareInfo A and B where A.Equals(B) is true.
1189         //
1190         ////////////////////////////////////////////////////////////////////////
1191
1192
1193         public override int GetHashCode()
1194         {
1195             return (this.Name.GetHashCode());
1196         }
1197
1198
1199         ////////////////////////////////////////////////////////////////////////
1200         //
1201         //  GetHashCodeOfString
1202         //
1203         //  This internal method allows a method that allows the equivalent of creating a Sortkey for a
1204         //  string from CompareInfo, and generate a hashcode value from it.  It is not very convenient
1205         //  to use this method as is and it creates an unnecessary Sortkey object that will be GC'ed.
1206         //
1207         //  The hash code is guaranteed to be the same for string A and B where A.Equals(B) is true and both
1208         //  the CompareInfo and the CompareOptions are the same. If two different CompareInfo objects
1209         //  treat the string the same way, this implementation will treat them differently (the same way that
1210         //  Sortkey does at the moment).
1211         //
1212         //  This method will never be made public itself, but public consumers of it could be created, e.g.:
1213         //
1214         //      string.GetHashCode(CultureInfo)
1215         //      string.GetHashCode(CompareInfo)
1216         //      string.GetHashCode(CultureInfo, CompareOptions)
1217         //      string.GetHashCode(CompareInfo, CompareOptions)
1218         //      etc.
1219         //
1220         //  (the methods above that take a CultureInfo would use CultureInfo.CompareInfo)
1221         //
1222         ////////////////////////////////////////////////////////////////////////
1223         internal int GetHashCodeOfString(string source, CompareOptions options)
1224         {
1225             //
1226             //  Parameter validation
1227             //
1228             if (null == source)
1229             {
1230                 throw new ArgumentNullException(nameof(source));
1231             }
1232
1233             if ((options & ValidHashCodeOfStringMaskOffFlags) != 0)
1234             {
1235                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
1236             }
1237
1238             return GetHashCodeOfStringCore(source, options);
1239         }
1240
1241         public virtual int GetHashCode(string source, CompareOptions options)
1242         {
1243             if (source == null)
1244             {
1245                 throw new ArgumentNullException(nameof(source));
1246             }
1247
1248             if (options == CompareOptions.Ordinal)
1249             {
1250                 return source.GetHashCode();
1251             }
1252
1253             if (options == CompareOptions.OrdinalIgnoreCase)
1254             {
1255                 return TextInfo.GetHashCodeOrdinalIgnoreCase(source);
1256             }
1257
1258             //
1259             // GetHashCodeOfString does more parameters validation. basically will throw when  
1260             // having Ordinal, OrdinalIgnoreCase and StringSort
1261             //
1262
1263             return GetHashCodeOfString(source, options);
1264         }
1265
1266         ////////////////////////////////////////////////////////////////////////
1267         //
1268         //  ToString
1269         //
1270         //  Implements Object.ToString().  Returns a string describing the
1271         //  CompareInfo.
1272         //
1273         ////////////////////////////////////////////////////////////////////////
1274         public override string ToString()
1275         {
1276             return ("CompareInfo - " + this.Name);
1277         }
1278
1279         public SortVersion Version
1280         {
1281             get
1282             {
1283                 if (m_SortVersion == null)
1284                 {
1285                     if (_invariantMode)
1286                     {
1287                         m_SortVersion = new SortVersion(0, CultureInfo.LOCALE_INVARIANT, new Guid(0, 0, 0, 0, 0, 0, 0,
1288                                                                         (byte) (CultureInfo.LOCALE_INVARIANT >> 24),
1289                                                                         (byte) ((CultureInfo.LOCALE_INVARIANT  & 0x00FF0000) >> 16),
1290                                                                         (byte) ((CultureInfo.LOCALE_INVARIANT  & 0x0000FF00) >> 8),
1291                                                                         (byte) (CultureInfo.LOCALE_INVARIANT  & 0xFF)));
1292                     }
1293                     else
1294                     {
1295                         m_SortVersion = GetSortVersion();
1296                     }
1297                 }
1298
1299                 return m_SortVersion;
1300             }
1301         }
1302
1303         public int LCID
1304         {
1305             get
1306             {
1307                 return CultureInfo.GetCultureInfo(Name).LCID;
1308             }
1309         }
1310     }
1311 }