Collapse AsSpan().Slice(...) into .AsSpan(...) (dotnet/corefx#27867)
[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         internal virtual int CompareOptionNone(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2)
393         {
394             return _invariantMode ?
395                 string.CompareOrdinal(string1, string2) :
396                 CompareString(string1, string2, CompareOptions.None);
397         }
398
399         internal virtual int CompareOptionIgnoreCase(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2)
400         {
401             return _invariantMode ?
402                 CompareOrdinalIgnoreCase(string1, string2) :
403                 CompareString(string1, string2, CompareOptions.IgnoreCase);
404         }
405
406         ////////////////////////////////////////////////////////////////////////
407         //
408         //  Compare
409         //
410         //  Compares the specified regions of the two strings with the given
411         //  options.
412         //  Returns 0 if the two strings are equal, a number less than 0 if
413         //  string1 is less than string2, and a number greater than 0 if
414         //  string1 is greater than string2.
415         //
416         ////////////////////////////////////////////////////////////////////////
417
418
419         public virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2)
420         {
421             return Compare(string1, offset1, length1, string2, offset2, length2, 0);
422         }
423
424
425         public virtual int Compare(string string1, int offset1, string string2, int offset2, CompareOptions options)
426         {
427             return Compare(string1, offset1, string1 == null ? 0 : string1.Length - offset1,
428                            string2, offset2, string2 == null ? 0 : string2.Length - offset2, options);
429         }
430
431
432         public virtual int Compare(string string1, int offset1, string string2, int offset2)
433         {
434             return Compare(string1, offset1, string2, offset2, 0);
435         }
436
437
438         public virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options)
439         {
440             if (options == CompareOptions.OrdinalIgnoreCase)
441             {
442                 int result = String.Compare(string1, offset1, string2, offset2, length1 < length2 ? length1 : length2, StringComparison.OrdinalIgnoreCase);
443                 if ((length1 != length2) && result == 0)
444                     return (length1 > length2 ? 1 : -1);
445                 return (result);
446             }
447
448             // Verify inputs
449             if (length1 < 0 || length2 < 0)
450             {
451                 throw new ArgumentOutOfRangeException((length1 < 0) ? nameof(length1) : nameof(length2), SR.ArgumentOutOfRange_NeedPosNum);
452             }
453             if (offset1 < 0 || offset2 < 0)
454             {
455                 throw new ArgumentOutOfRangeException((offset1 < 0) ? nameof(offset1) : nameof(offset2), SR.ArgumentOutOfRange_NeedPosNum);
456             }
457             if (offset1 > (string1 == null ? 0 : string1.Length) - length1)
458             {
459                 throw new ArgumentOutOfRangeException(nameof(string1), SR.ArgumentOutOfRange_OffsetLength);
460             }
461             if (offset2 > (string2 == null ? 0 : string2.Length) - length2)
462             {
463                 throw new ArgumentOutOfRangeException(nameof(string2), SR.ArgumentOutOfRange_OffsetLength);
464             }
465             if ((options & CompareOptions.Ordinal) != 0)
466             {
467                 if (options != CompareOptions.Ordinal)
468                 {
469                     throw new ArgumentException(SR.Argument_CompareOptionOrdinal,
470                                                 nameof(options));
471                 }
472             }
473             else if ((options & ValidCompareMaskOffFlags) != 0)
474             {
475                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
476             }
477
478             //
479             // Check for the null case.
480             //
481             if (string1 == null)
482             {
483                 if (string2 == null)
484                 {
485                     return (0);
486                 }
487                 return (-1);
488             }
489             if (string2 == null)
490             {
491                 return (1);
492             }
493
494             if (options == CompareOptions.Ordinal)
495             {
496                 return CompareOrdinal(string1, offset1, length1,
497                                       string2, offset2, length2);
498             }
499
500             if (_invariantMode)
501             {
502                 if ((options & CompareOptions.IgnoreCase) != 0)
503                     return CompareOrdinalIgnoreCase(string1, offset1, length1, string2, offset2, length2);
504
505                 return CompareOrdinal(string1, offset1, length1, string2, offset2, length2);
506             }
507
508             return CompareString(
509                 string1.AsSpan(offset1, length1),
510                 string2.AsSpan(offset2, length2),
511                 options);
512         }
513
514         private static int CompareOrdinal(string string1, int offset1, int length1, string string2, int offset2, int length2)
515         {
516             int result = String.CompareOrdinal(string1, offset1, string2, offset2,
517                                                        (length1 < length2 ? length1 : length2));
518             if ((length1 != length2) && result == 0)
519             {
520                 return (length1 > length2 ? 1 : -1);
521             }
522             return (result);
523         }
524
525         //
526         // CompareOrdinalIgnoreCase compare two string ordinally with ignoring the case.
527         // it assumes the strings are Ascii string till we hit non Ascii character in strA or strB and then we continue the comparison by
528         // calling the OS.
529         //
530         internal static int CompareOrdinalIgnoreCase(string strA, int indexA, int lengthA, string strB, int indexB, int lengthB)
531         {
532             Debug.Assert(indexA + lengthA <= strA.Length);
533             Debug.Assert(indexB + lengthB <= strB.Length);
534             return CompareOrdinalIgnoreCase(strA.AsSpan(indexA, lengthA), strB.AsSpan(indexB, lengthB));
535         }
536
537         internal static unsafe int CompareOrdinalIgnoreCase(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
538         {
539             int length = Math.Min(strA.Length, strB.Length);
540             int range = length;
541
542             fixed (char* ap = &MemoryMarshal.GetReference(strA))
543             fixed (char* bp = &MemoryMarshal.GetReference(strB))
544             {
545                 char* a = ap;
546                 char* b = bp;
547
548                 // in InvariantMode we support all range and not only the ascii characters.
549                 char maxChar = (char) (GlobalizationMode.Invariant ? 0xFFFF : 0x7F);
550
551                 while (length != 0 && (*a <= maxChar) && (*b <= maxChar))
552                 {
553                     int charA = *a;
554                     int charB = *b;
555
556                     if (charA == charB)
557                     {
558                         a++; b++;
559                         length--;
560                         continue;
561                     }
562
563                     // uppercase both chars - notice that we need just one compare per char
564                     if ((uint)(charA - 'a') <= 'z' - 'a') charA -= 0x20;
565                     if ((uint)(charB - 'a') <= 'z' - 'a') charB -= 0x20;
566
567                     // Return the (case-insensitive) difference between them.
568                     if (charA != charB)
569                         return charA - charB;
570
571                     // Next char
572                     a++; b++;
573                     length--;
574                 }
575
576                 if (length == 0)
577                     return strA.Length - strB.Length;
578
579                 Debug.Assert(!GlobalizationMode.Invariant);
580
581                 range -= length;
582
583                 return CompareStringOrdinalIgnoreCase(a, strA.Length - range, b, strB.Length - range);
584             }
585         }
586
587         ////////////////////////////////////////////////////////////////////////
588         //
589         //  IsPrefix
590         //
591         //  Determines whether prefix is a prefix of string.  If prefix equals
592         //  String.Empty, true is returned.
593         //
594         ////////////////////////////////////////////////////////////////////////
595         public virtual bool IsPrefix(string source, string prefix, CompareOptions options)
596         {
597             if (source == null || prefix == null)
598             {
599                 throw new ArgumentNullException((source == null ? nameof(source) : nameof(prefix)),
600                     SR.ArgumentNull_String);
601             }
602
603             if (prefix.Length == 0)
604             {
605                 return (true);
606             }
607
608             if (source.Length == 0)
609             {
610                 return false;
611             }
612
613             if (options == CompareOptions.OrdinalIgnoreCase)
614             {
615                 return source.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
616             }
617
618             if (options == CompareOptions.Ordinal)
619             {
620                 return source.StartsWith(prefix, StringComparison.Ordinal);
621             }
622
623             if ((options & ValidIndexMaskOffFlags) != 0)
624             {
625                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
626             }
627
628             if (_invariantMode)
629             {
630                 return source.StartsWith(prefix, (options & CompareOptions.IgnoreCase) != 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
631             }
632
633             return StartsWith(source, prefix, options);
634         }
635
636         internal bool IsPrefix(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
637         {
638             Debug.Assert(prefix.Length != 0);
639             Debug.Assert(source.Length != 0);
640             Debug.Assert((options & ValidIndexMaskOffFlags) == 0);
641             Debug.Assert(!_invariantMode);
642             Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
643
644             return StartsWith(source, prefix, options);
645         }
646
647         public virtual bool IsPrefix(string source, string prefix)
648         {
649             return (IsPrefix(source, prefix, 0));
650         }
651
652         ////////////////////////////////////////////////////////////////////////
653         //
654         //  IsSuffix
655         //
656         //  Determines whether suffix is a suffix of string.  If suffix equals
657         //  String.Empty, true is returned.
658         //
659         ////////////////////////////////////////////////////////////////////////
660         public virtual bool IsSuffix(string source, string suffix, CompareOptions options)
661         {
662             if (source == null || suffix == null)
663             {
664                 throw new ArgumentNullException((source == null ? nameof(source) : nameof(suffix)),
665                     SR.ArgumentNull_String);
666             }
667
668             if (suffix.Length == 0)
669             {
670                 return (true);
671             }
672
673             if (source.Length == 0)
674             {
675                 return false;
676             }
677
678             if (options == CompareOptions.OrdinalIgnoreCase)
679             {
680                 return source.EndsWith(suffix, StringComparison.OrdinalIgnoreCase);
681             }
682
683             if (options == CompareOptions.Ordinal)
684             {
685                 return source.EndsWith(suffix, StringComparison.Ordinal);
686             }
687
688             if ((options & ValidIndexMaskOffFlags) != 0)
689             {
690                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
691             }
692
693             if (_invariantMode)
694             {
695                 return source.EndsWith(suffix, (options & CompareOptions.IgnoreCase) != 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
696             }
697
698             return EndsWith(source, suffix, options);
699         }
700
701         internal bool IsSuffix(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
702         {
703             Debug.Assert(suffix.Length != 0);
704             Debug.Assert(source.Length != 0);
705             Debug.Assert((options & ValidIndexMaskOffFlags) == 0);
706             Debug.Assert(!_invariantMode);
707             Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
708
709             return EndsWith(source, suffix, options);
710         }
711
712
713         public virtual bool IsSuffix(string source, string suffix)
714         {
715             return (IsSuffix(source, suffix, 0));
716         }
717
718         ////////////////////////////////////////////////////////////////////////
719         //
720         //  IndexOf
721         //
722         //  Returns the first index where value is found in string.  The
723         //  search starts from startIndex and ends at endIndex.  Returns -1 if
724         //  the specified value is not found.  If value equals String.Empty,
725         //  startIndex is returned.  Throws IndexOutOfRange if startIndex or
726         //  endIndex is less than zero or greater than the length of string.
727         //  Throws ArgumentException if value is null.
728         //
729         ////////////////////////////////////////////////////////////////////////
730
731
732         public virtual int IndexOf(string source, char value)
733         {
734             if (source == null)
735                 throw new ArgumentNullException(nameof(source));
736
737             return IndexOf(source, value, 0, source.Length, CompareOptions.None);
738         }
739
740
741         public virtual int IndexOf(string source, string value)
742         {
743             if (source == null)
744                 throw new ArgumentNullException(nameof(source));
745
746             return IndexOf(source, value, 0, source.Length, CompareOptions.None);
747         }
748
749
750         public virtual int IndexOf(string source, char value, CompareOptions options)
751         {
752             if (source == null)
753                 throw new ArgumentNullException(nameof(source));
754
755             return IndexOf(source, value, 0, source.Length, options);
756         }
757
758
759         public virtual int IndexOf(string source, string value, CompareOptions options)
760         {
761             if (source == null)
762                 throw new ArgumentNullException(nameof(source));
763
764             return IndexOf(source, value, 0, source.Length, options);
765         }
766
767         public virtual int IndexOf(string source, char value, int startIndex)
768         {
769             if (source == null)
770                 throw new ArgumentNullException(nameof(source));
771
772             return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
773         }
774
775         public virtual int IndexOf(string source, string value, int startIndex)
776         {
777             if (source == null)
778                 throw new ArgumentNullException(nameof(source));
779
780             return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
781         }
782
783         public virtual int IndexOf(string source, char value, int startIndex, CompareOptions options)
784         {
785             if (source == null)
786                 throw new ArgumentNullException(nameof(source));
787
788             return IndexOf(source, value, startIndex, source.Length - startIndex, options);
789         }
790
791
792         public virtual int IndexOf(string source, string value, int startIndex, CompareOptions options)
793         {
794             if (source == null)
795                 throw new ArgumentNullException(nameof(source));
796
797             return IndexOf(source, value, startIndex, source.Length - startIndex, options);
798         }
799
800
801         public virtual int IndexOf(string source, char value, int startIndex, int count)
802         {
803             return IndexOf(source, value, startIndex, count, CompareOptions.None);
804         }
805
806
807         public virtual int IndexOf(string source, string value, int startIndex, int count)
808         {
809             return IndexOf(source, value, startIndex, count, CompareOptions.None);
810         }
811
812         public unsafe virtual int IndexOf(string source, char value, int startIndex, int count, CompareOptions options)
813         {
814             // Validate inputs
815             if (source == null)
816                 throw new ArgumentNullException(nameof(source));
817
818             if (startIndex < 0 || startIndex > source.Length)
819                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
820
821             if (count < 0 || startIndex > source.Length - count)
822                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
823
824             if (source.Length == 0)
825             {
826                 return -1;
827             }
828
829             if (options == CompareOptions.OrdinalIgnoreCase)
830             {
831                 return source.IndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase);
832             }
833
834             // Validate CompareOptions
835             // Ordinal can't be selected with other flags
836             if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal))
837                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
838
839             if (_invariantMode)
840                 return IndexOfOrdinal(source, new string(value, 1), startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
841
842             return IndexOfCore(source, new string(value, 1), startIndex, count, options, null);
843         }
844
845         public unsafe virtual int IndexOf(string source, string value, int startIndex, int count, CompareOptions options)
846         {
847             // Validate inputs
848             if (source == null)
849                 throw new ArgumentNullException(nameof(source));
850             if (value == null)
851                 throw new ArgumentNullException(nameof(value));
852
853             if (startIndex > source.Length)
854             {
855                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
856             }
857
858             // In Everett we used to return -1 for empty string even if startIndex is negative number so we keeping same behavior here.
859             // We return 0 if both source and value are empty strings for Everett compatibility too.
860             if (source.Length == 0)
861             {
862                 if (value.Length == 0)
863                 {
864                     return 0;
865                 }
866                 return -1;
867             }
868
869             if (startIndex < 0)
870             {
871                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
872             }
873
874             if (count < 0 || startIndex > source.Length - count)
875                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
876
877             if (options == CompareOptions.OrdinalIgnoreCase)
878             {
879                 return IndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
880             }
881
882             // Validate CompareOptions
883             // Ordinal can't be selected with other flags
884             if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal))
885                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
886
887             if (_invariantMode)
888                 return IndexOfOrdinal(source, value, startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
889
890             return IndexOfCore(source, value, startIndex, count, options, null);
891         }
892
893         internal virtual int IndexOfOrdinal(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
894         {
895             Debug.Assert(!_invariantMode);
896             return IndexOfOrdinalCore(source, value, ignoreCase);
897         }
898
899         internal unsafe virtual int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options)
900         {
901             Debug.Assert(!_invariantMode);
902             return IndexOfCore(source, value, options, null);
903         }
904
905         // The following IndexOf overload is mainly used by String.Replace. This overload assumes the parameters are already validated
906         // and the caller is passing a valid matchLengthPtr pointer.
907         internal unsafe int IndexOf(string source, string value, int startIndex, int count, CompareOptions options, int* matchLengthPtr)
908         {
909             Debug.Assert(source != null);
910             Debug.Assert(value != null);
911             Debug.Assert(startIndex >= 0);
912             Debug.Assert(matchLengthPtr != null);
913             *matchLengthPtr = 0;
914
915             if (source.Length == 0)
916             {
917                 if (value.Length == 0)
918                 {
919                     return 0;
920                 }
921                 return -1;
922             }
923
924             if (startIndex >= source.Length)
925             {
926                 return -1;
927             }
928
929             if (options == CompareOptions.OrdinalIgnoreCase)
930             {
931                 int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
932                 if (res >= 0)
933                 {
934                     *matchLengthPtr = value.Length;
935                 }
936                 return res;
937             }
938
939             if (_invariantMode)
940             {
941                 int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
942                 if (res >= 0)
943                 {
944                     *matchLengthPtr = value.Length;
945                 }
946                 return res;
947             }
948
949             return IndexOfCore(source, value, startIndex, count, options, matchLengthPtr);
950         }
951
952         internal int IndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase)
953         {
954             if (_invariantMode)
955             {
956                 return InvariantIndexOf(source, value, startIndex, count, ignoreCase);
957             }
958
959             return IndexOfOrdinalCore(source, value, startIndex, count, ignoreCase);
960         }
961
962         ////////////////////////////////////////////////////////////////////////
963         //
964         //  LastIndexOf
965         //
966         //  Returns the last index where value is found in string.  The
967         //  search starts from startIndex and ends at endIndex.  Returns -1 if
968         //  the specified value is not found.  If value equals String.Empty,
969         //  endIndex is returned.  Throws IndexOutOfRange if startIndex or
970         //  endIndex is less than zero or greater than the length of string.
971         //  Throws ArgumentException if value is null.
972         //
973         ////////////////////////////////////////////////////////////////////////
974
975
976         public virtual int LastIndexOf(String source, char value)
977         {
978             if (source == null)
979                 throw new ArgumentNullException(nameof(source));
980
981             // Can't start at negative index, so make sure we check for the length == 0 case.
982             return LastIndexOf(source, value, source.Length - 1, source.Length, CompareOptions.None);
983         }
984
985
986         public virtual int LastIndexOf(string source, string value)
987         {
988             if (source == null)
989                 throw new ArgumentNullException(nameof(source));
990
991             // Can't start at negative index, so make sure we check for the length == 0 case.
992             return LastIndexOf(source, value, source.Length - 1,
993                 source.Length, CompareOptions.None);
994         }
995
996
997         public virtual int LastIndexOf(string source, char value, CompareOptions options)
998         {
999             if (source == null)
1000                 throw new ArgumentNullException(nameof(source));
1001
1002             // Can't start at negative index, so make sure we check for the length == 0 case.
1003             return LastIndexOf(source, value, source.Length - 1,
1004                 source.Length, options);
1005         }
1006
1007         public virtual int LastIndexOf(string source, string value, CompareOptions options)
1008         {
1009             if (source == null)
1010                 throw new ArgumentNullException(nameof(source));
1011
1012             // Can't start at negative index, so make sure we check for the length == 0 case.
1013             return LastIndexOf(source, value, source.Length - 1, source.Length, options);
1014         }
1015
1016         public virtual int LastIndexOf(string source, char value, int startIndex)
1017         {
1018             return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
1019         }
1020
1021
1022         public virtual int LastIndexOf(string source, string value, int startIndex)
1023         {
1024             return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
1025         }
1026
1027         public virtual int LastIndexOf(string source, char value, int startIndex, CompareOptions options)
1028         {
1029             return LastIndexOf(source, value, startIndex, startIndex + 1, options);
1030         }
1031
1032
1033         public virtual int LastIndexOf(string source, string value, int startIndex, CompareOptions options)
1034         {
1035             return LastIndexOf(source, value, startIndex, startIndex + 1, options);
1036         }
1037
1038
1039         public virtual int LastIndexOf(string source, char value, int startIndex, int count)
1040         {
1041             return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
1042         }
1043
1044
1045         public virtual int LastIndexOf(string source, string value, int startIndex, int count)
1046         {
1047             return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
1048         }
1049
1050
1051         public virtual int LastIndexOf(string source, char value, int startIndex, int count, CompareOptions options)
1052         {
1053             // Verify Arguments
1054             if (source == null)
1055                 throw new ArgumentNullException(nameof(source));
1056
1057             // Validate CompareOptions
1058             // Ordinal can't be selected with other flags
1059             if ((options & ValidIndexMaskOffFlags) != 0 &&
1060                 (options != CompareOptions.Ordinal) &&
1061                 (options != CompareOptions.OrdinalIgnoreCase))
1062                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
1063
1064             // Special case for 0 length input strings
1065             if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
1066                 return -1;
1067
1068             // Make sure we're not out of range
1069             if (startIndex < 0 || startIndex > source.Length)
1070                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
1071
1072             // Make sure that we allow startIndex == source.Length
1073             if (startIndex == source.Length)
1074             {
1075                 startIndex--;
1076                 if (count > 0)
1077                     count--;
1078             }
1079
1080             // 2nd have of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
1081             if (count < 0 || startIndex - count + 1 < 0)
1082                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
1083
1084             if (options == CompareOptions.OrdinalIgnoreCase)
1085             {
1086                 return source.LastIndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase);
1087             }
1088
1089             if (_invariantMode)
1090                 return InvariantLastIndexOf(source, new string(value, 1), startIndex, count, (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
1091
1092             return LastIndexOfCore(source, value.ToString(), startIndex, count, options);
1093         }
1094
1095
1096         public virtual int LastIndexOf(string source, string value, int startIndex, int count, CompareOptions options)
1097         {
1098             // Verify Arguments
1099             if (source == null)
1100                 throw new ArgumentNullException(nameof(source));
1101             if (value == null)
1102                 throw new ArgumentNullException(nameof(value));
1103
1104             // Validate CompareOptions
1105             // Ordinal can't be selected with other flags
1106             if ((options & ValidIndexMaskOffFlags) != 0 &&
1107                 (options != CompareOptions.Ordinal) &&
1108                 (options != CompareOptions.OrdinalIgnoreCase))
1109                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
1110
1111             // Special case for 0 length input strings
1112             if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
1113                 return (value.Length == 0) ? 0 : -1;
1114
1115             // Make sure we're not out of range
1116             if (startIndex < 0 || startIndex > source.Length)
1117                 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
1118
1119             // Make sure that we allow startIndex == source.Length
1120             if (startIndex == source.Length)
1121             {
1122                 startIndex--;
1123                 if (count > 0)
1124                     count--;
1125
1126                 // If we are looking for nothing, just return 0
1127                 if (value.Length == 0 && count >= 0 && startIndex - count + 1 >= 0)
1128                     return startIndex;
1129             }
1130
1131             // 2nd half of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
1132             if (count < 0 || startIndex - count + 1 < 0)
1133                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
1134
1135             if (options == CompareOptions.OrdinalIgnoreCase)
1136             {
1137                 return LastIndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
1138             }
1139
1140             if (_invariantMode)
1141                 return InvariantLastIndexOf(source, value, startIndex, count, (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
1142
1143             return LastIndexOfCore(source, value, startIndex, count, options);
1144         }
1145
1146         internal int LastIndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase)
1147         {
1148             if (_invariantMode)
1149             {
1150                 return InvariantLastIndexOf(source, value, startIndex, count, ignoreCase);
1151             }
1152
1153             return LastIndexOfOrdinalCore(source, value, startIndex, count, ignoreCase);
1154         }
1155
1156         ////////////////////////////////////////////////////////////////////////
1157         //
1158         //  GetSortKey
1159         //
1160         //  Gets the SortKey for the given string with the given options.
1161         //
1162         ////////////////////////////////////////////////////////////////////////
1163         public virtual SortKey GetSortKey(string source, CompareOptions options)
1164         {
1165             if (_invariantMode)
1166                 return InvariantCreateSortKey(source, options);
1167
1168             return CreateSortKey(source, options);
1169         }
1170
1171
1172         public virtual SortKey GetSortKey(string source)
1173         {
1174             if (_invariantMode)
1175                 return InvariantCreateSortKey(source, CompareOptions.None);
1176
1177             return CreateSortKey(source, CompareOptions.None);
1178         }
1179
1180         ////////////////////////////////////////////////////////////////////////
1181         //
1182         //  Equals
1183         //
1184         //  Implements Object.Equals().  Returns a boolean indicating whether
1185         //  or not object refers to the same CompareInfo as the current
1186         //  instance.
1187         //
1188         ////////////////////////////////////////////////////////////////////////
1189
1190
1191         public override bool Equals(Object value)
1192         {
1193             CompareInfo that = value as CompareInfo;
1194
1195             if (that != null)
1196             {
1197                 return this.Name == that.Name;
1198             }
1199
1200             return (false);
1201         }
1202
1203
1204         ////////////////////////////////////////////////////////////////////////
1205         //
1206         //  GetHashCode
1207         //
1208         //  Implements Object.GetHashCode().  Returns the hash code for the
1209         //  CompareInfo.  The hash code is guaranteed to be the same for
1210         //  CompareInfo A and B where A.Equals(B) is true.
1211         //
1212         ////////////////////////////////////////////////////////////////////////
1213
1214
1215         public override int GetHashCode()
1216         {
1217             return (this.Name.GetHashCode());
1218         }
1219
1220
1221         ////////////////////////////////////////////////////////////////////////
1222         //
1223         //  GetHashCodeOfString
1224         //
1225         //  This internal method allows a method that allows the equivalent of creating a Sortkey for a
1226         //  string from CompareInfo, and generate a hashcode value from it.  It is not very convenient
1227         //  to use this method as is and it creates an unnecessary Sortkey object that will be GC'ed.
1228         //
1229         //  The hash code is guaranteed to be the same for string A and B where A.Equals(B) is true and both
1230         //  the CompareInfo and the CompareOptions are the same. If two different CompareInfo objects
1231         //  treat the string the same way, this implementation will treat them differently (the same way that
1232         //  Sortkey does at the moment).
1233         //
1234         //  This method will never be made public itself, but public consumers of it could be created, e.g.:
1235         //
1236         //      string.GetHashCode(CultureInfo)
1237         //      string.GetHashCode(CompareInfo)
1238         //      string.GetHashCode(CultureInfo, CompareOptions)
1239         //      string.GetHashCode(CompareInfo, CompareOptions)
1240         //      etc.
1241         //
1242         //  (the methods above that take a CultureInfo would use CultureInfo.CompareInfo)
1243         //
1244         ////////////////////////////////////////////////////////////////////////
1245         internal int GetHashCodeOfString(string source, CompareOptions options)
1246         {
1247             //
1248             //  Parameter validation
1249             //
1250             if (null == source)
1251             {
1252                 throw new ArgumentNullException(nameof(source));
1253             }
1254
1255             if ((options & ValidHashCodeOfStringMaskOffFlags) != 0)
1256             {
1257                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
1258             }
1259
1260             return GetHashCodeOfStringCore(source, options);
1261         }
1262
1263         public virtual int GetHashCode(string source, CompareOptions options)
1264         {
1265             if (source == null)
1266             {
1267                 throw new ArgumentNullException(nameof(source));
1268             }
1269
1270             if (options == CompareOptions.Ordinal)
1271             {
1272                 return source.GetHashCode();
1273             }
1274
1275             if (options == CompareOptions.OrdinalIgnoreCase)
1276             {
1277                 return TextInfo.GetHashCodeOrdinalIgnoreCase(source);
1278             }
1279
1280             //
1281             // GetHashCodeOfString does more parameters validation. basically will throw when
1282             // having Ordinal, OrdinalIgnoreCase and StringSort
1283             //
1284
1285             return GetHashCodeOfString(source, options);
1286         }
1287
1288         ////////////////////////////////////////////////////////////////////////
1289         //
1290         //  ToString
1291         //
1292         //  Implements Object.ToString().  Returns a string describing the
1293         //  CompareInfo.
1294         //
1295         ////////////////////////////////////////////////////////////////////////
1296         public override string ToString()
1297         {
1298             return ("CompareInfo - " + this.Name);
1299         }
1300
1301         public SortVersion Version
1302         {
1303             get
1304             {
1305                 if (m_SortVersion == null)
1306                 {
1307                     if (_invariantMode)
1308                     {
1309                         m_SortVersion = new SortVersion(0, CultureInfo.LOCALE_INVARIANT, new Guid(0, 0, 0, 0, 0, 0, 0,
1310                                                                         (byte) (CultureInfo.LOCALE_INVARIANT >> 24),
1311                                                                         (byte) ((CultureInfo.LOCALE_INVARIANT  & 0x00FF0000) >> 16),
1312                                                                         (byte) ((CultureInfo.LOCALE_INVARIANT  & 0x0000FF00) >> 8),
1313                                                                         (byte) (CultureInfo.LOCALE_INVARIANT  & 0xFF)));
1314                     }
1315                     else
1316                     {
1317                         m_SortVersion = GetSortVersion();
1318                     }
1319                 }
1320
1321                 return m_SortVersion;
1322             }
1323         }
1324
1325         public int LCID
1326         {
1327             get
1328             {
1329                 return CultureInfo.GetCultureInfo(Name).LCID;
1330             }
1331         }
1332     }
1333 }