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