Use System.Globalization.Native for casing
authorMatt Ellis <matell@microsoft.com>
Thu, 18 Jun 2015 19:03:11 +0000 (12:03 -0700)
committerMatt Ellis <matell@microsoft.com>
Tue, 22 Sep 2015 18:48:36 +0000 (11:48 -0700)
This is a fairly straight forward change to start using
System.Globalization.Native (and hence ICU) for casing support.  There
are a few caveats due to how the work is being staged.

 - There are some fast paths in mscorlib that do simple ASCII casing if
   the input string is ASCII and we know the locale does not do special
   casing for ascii characters.  This is detected by doing a case
   insensitive string comparision between "ABC..XYZ" and "abc...xyz".
   However, since we don't have real collation support yet, this check
   would always return true and hence we would never actually go to
   TextInfo to do casing.  For now, this code is just disabled so we
   always go to TextInfo

 - Some of the dummy collation implementations used the ASCII casing
   routines on TextInfo.  Since they are no longer needed for TextInfo
   we move them to CompareInfo.  The alternative would be to do proper
   casing via TextInfo, but since all the collation code is throw away
   it doesn't seem prudent to spend time moving them to TextInfo.

 - Detection on if we should do turkish casing is based on the locale
   name, when we have collation support we should likely detect this by
   doing a case insenstive string comparision between i and LATIN
   CAPTIAL I WITH DOT ABOVE.

Commit migrated from https://github.com/dotnet/coreclr/commit/71fab326cb589002ed6fa37a60a4a8da6949fe24

src/coreclr/src/mscorlib/corefx/Interop/Unix/Interop.Libraries.cs [new file with mode: 0644]
src/coreclr/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.Casing.cs [new file with mode: 0644]
src/coreclr/src/mscorlib/corefx/System/Globalization/CompareInfo.Unix.cs
src/coreclr/src/mscorlib/corefx/System/Globalization/CultureData.Unix.cs
src/coreclr/src/mscorlib/corefx/System/Globalization/TextInfo.Unix.cs
src/coreclr/src/mscorlib/corefx/System/Globalization/TextInfo.cs
src/coreclr/src/mscorlib/mscorlib.shared.sources.props

diff --git a/src/coreclr/src/mscorlib/corefx/Interop/Unix/Interop.Libraries.cs b/src/coreclr/src/mscorlib/corefx/Interop/Unix/Interop.Libraries.cs
new file mode 100644 (file)
index 0000000..f48d47d
--- /dev/null
@@ -0,0 +1,10 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+internal static partial class Interop
+{
+    private static partial class Libraries
+    {
+        internal const string GlobalizationInterop = "System.Globalization.Native"; // CoreFX wrappers for ICU
+    }
+}
diff --git a/src/coreclr/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.Casing.cs b/src/coreclr/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.Casing.cs
new file mode 100644 (file)
index 0000000..dbc8abf
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+    internal static partial class GlobalizationInterop
+    {
+        [DllImport(Libraries.GlobalizationInterop, CharSet = CharSet.Unicode)]
+        internal unsafe static extern void ToUpperSimple(char* src, int srcLen, char* dstBuffer, int dstBufferCapacity);
+
+        [DllImport(Libraries.GlobalizationInterop, CharSet = CharSet.Unicode)]
+        internal unsafe static extern void ToLowerSimple(char* src, int srcLen, char* dstBuffer, int dstBufferCapacity);
+
+        [DllImport(Libraries.GlobalizationInterop, CharSet = CharSet.Unicode)]
+        internal unsafe static extern void ToUpperSimpleTurkishAzeri(char* src, int srcLen, char* dstBuffer, int dstBufferCapacity);
+
+        [DllImport(Libraries.GlobalizationInterop, CharSet = CharSet.Unicode)]
+        internal unsafe static extern void ToLowerSimpleTurkishAzeri(char* src, int srcLen, char* dstBuffer, int dstBufferCapacity);
+    }
+}
index 70dfe90..46fb25b 100644 (file)
@@ -93,7 +93,7 @@ namespace System.Globalization
             {
                 for (int i = 0; i < source.Length; i++)
                 {
-                    hash = ((hash << 5) + hash) + TextInfo.ChangeCaseAscii(source[i]);
+                    hash = ((hash << 5) + hash) + ChangeCaseAscii(source[i]);
                 }
             }
 
@@ -171,7 +171,23 @@ namespace System.Globalization
             return StringEqualsAscii(source.Substring(source.Length - suffix.Length), suffix, IgnoreCase(options));
         }
 
-        // PAL ends here
+        // -----------------------------
+        // ---- PAL layer ends here ----
+        // -----------------------------
+
+        private static char ChangeCaseAscii(char c, bool toUpper = true)
+        {
+            if (toUpper && c >= 'a' && c <= 'z')
+            {
+                return (char)('A' + (c - 'a'));
+            }
+            else if (!toUpper && c >= 'A' && c <= 'Z')
+            {
+                return (char)('a' + (c - 'A'));
+            }
+
+            return c;
+        }
 
         private static bool StringEqualsAscii(string s1, string s2, bool ignoreCase = true)
         {
@@ -179,8 +195,8 @@ namespace System.Globalization
 
             for (int i = 0; i < s1.Length; i++)
             {
-                char c1 = ignoreCase ? TextInfo.ChangeCaseAscii(s1[i]) : s1[i];
-                char c2 = ignoreCase ? TextInfo.ChangeCaseAscii(s2[i]) : s2[i];
+                char c1 = ignoreCase ? ChangeCaseAscii(s1[i]) : s1[i];
+                char c2 = ignoreCase ? ChangeCaseAscii(s2[i]) : s2[i];
 
                 if (c1 != c2) return false;
             }
@@ -195,8 +211,8 @@ namespace System.Globalization
             {
                 for (int i = 0; i < countMin; i++)
                 {
-                    char c1 = ignoreCase ? TextInfo.ChangeCaseAscii(s1[i]) : s1[i];
-                    char c2 = ignoreCase ? TextInfo.ChangeCaseAscii(s2[i]) : s2[i];
+                    char c1 = ignoreCase ? ChangeCaseAscii(s1[i]) : s1[i];
+                    char c2 = ignoreCase ? ChangeCaseAscii(s2[i]) : s2[i];
 
                     if (c1 < c2)
                     {
index 69fa794..f8fab2f 100644 (file)
@@ -8,48 +8,21 @@ namespace System.Globalization
     internal partial class CultureData
     {       
         /// <summary>
-        /// Check with the OS to see if this is a valid culture.
-        /// If so we populate a limited number of fields.  If its not valid we return false.
-        ///
-        /// The fields we populate:
-        ///
-        /// sWindowsName -- The name that windows thinks this culture is, ie:
-        ///                            en-US if you pass in en-US
-        ///                            de-DE_phoneb if you pass in de-DE_phoneb
-        ///                            fj-FJ if you pass in fj (neutral, on a pre-Windows 7 machine)
-        ///                            fj if you pass in fj (neutral, post-Windows 7 machine)
-        ///
-        /// sRealName -- The name you used to construct the culture, in pretty form
-        ///                       en-US if you pass in EN-us
-        ///                       en if you pass in en
-        ///                       de-DE_phoneb if you pass in de-DE_phoneb
-        ///
-        /// sSpecificCulture -- The specific culture for this culture
-        ///                             en-US for en-US
-        ///                             en-US for en
-        ///                             de-DE_phoneb for alt sort
-        ///                             fj-FJ for fj (neutral)
-        ///
-        /// sName -- The IETF name of this culture (ie: no sort info, could be neutral)
-        ///                en-US if you pass in en-US
-        ///                en if you pass in en
-        ///                de-DE if you pass in de-DE_phoneb
-        ///
-        /// bNeutral -- TRUE if it is a neutral locale
-        ///
-        /// For a neutral we just populate the neutral name, but we leave the windows name pointing to the
-        /// windows locale that's going to provide data for us.
+        /// This method uses the sRealName field (which is initialized by the constructor before this is called) to
+        /// initialize the rest of the state of CultureData based on the underlying OS globalization library.
         /// </summary>
         private unsafe bool InitCultureData()
         {
-            // TODO: Implement this fully.
+            Contract.Assert(this.sRealName != null);
+
+            // This is a bit of misnomer, since it doesn't have anything to do with Windows.  Instead, this is the
+            // identifier the underlying OS uses for the culture.
+            this.sWindowsName = AnsiToLower(this.sRealName);
 
             // For now, just use all of the Invariant's data
+            // TODO: Implement this fully.
             CultureData invariant = CultureData.Invariant;
 
-            this.sRealName = invariant.sRealName;
-            this.sWindowsName = invariant.sWindowsName;
-
             // Identity
             this.sName = invariant.sName;
             this.sParent = invariant.sParent;
index 0300844..c27e6af 100644 (file)
@@ -1,5 +1,6 @@
 // Copyright (c) Microsoft. All rights reserved.
 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
 using System.Diagnostics.Contracts;
 using System.Text;
 
@@ -7,6 +8,8 @@ namespace System.Globalization
 {
     public partial class TextInfo
     {
+        private readonly bool m_needsTurkishCasing;
+
         //////////////////////////////////////////////////////////////////////////
         ////
         ////  TextInfo Constructors
@@ -17,46 +20,76 @@ namespace System.Globalization
         internal unsafe TextInfo(CultureData cultureData)
         {
             // TODO: Implement this fully.
-            this.m_cultureData = cultureData;
-            this.m_cultureName = this.m_cultureData.CultureName;
-            this.m_textInfoName = this.m_cultureData.STEXTINFO;
+            m_cultureData = cultureData;
+            m_cultureName = m_cultureData.CultureName;
+            m_textInfoName = m_cultureData.STEXTINFO;
+            m_needsTurkishCasing = NeedsTurkishCasing(this.m_textInfoName);
         }
 
+        [System.Security.SecuritySafeCritical]
         private unsafe string ChangeCase(string s, bool toUpper)
         {
-            Contract.Assert(s != null);              
-            // TODO: Implement this fully.
+            Contract.Assert(s != null);
 
-            StringBuilder sb = new StringBuilder(s.Length);
+            char[] dstBuf = new char[s.Length];
 
-            for (int i = 0; i < s.Length; i++)
+            fixed (char* pSrc = s, pDst = dstBuf)
             {
-                sb.Append(ChangeCaseAscii(s[i], toUpper));
+                ChangeCaseCore(pSrc, s.Length, pDst, dstBuf.Length, toUpper);
             }
 
-            return sb.ToString();
+            return new string(dstBuf);
         }
 
+        [System.Security.SecuritySafeCritical]
         private unsafe char ChangeCase(char c, bool toUpper)
         {
-            // TODO: Implement this fully.
-            return ChangeCaseAscii(c, toUpper);
+            char* pSrc = stackalloc char[1];
+            char* pDst = stackalloc char[1];
+
+            pSrc[0] = c;
+
+            ChangeCaseCore(pSrc, 1, pDst, 1, toUpper);
+
+            return pDst[0];
         }
 
-        // PAL Methods end here.
+        // -----------------------------
+        // ---- PAL layer ends here ----
+        // -----------------------------
 
-        internal static char ChangeCaseAscii(char c, bool toUpper = true)
+        private unsafe void ChangeCaseCore(char* pSrc, int cchSrc, char* pDst, int cchDst, bool toUpper)
         {
-            if (toUpper && c >= 'a' && c <= 'z')
+            if (toUpper)
             {
-                return (char)('A' + (c - 'a'));
+                if (!m_needsTurkishCasing)
+                {
+                    Interop.GlobalizationInterop.ToUpperSimple(pSrc, cchSrc, pDst, cchDst);
+                }
+                else
+                {
+                    Interop.GlobalizationInterop.ToUpperSimpleTurkishAzeri(pSrc, cchSrc, pDst, cchDst);
+                }
             }
-            else if (!toUpper && c >= 'A' && c <= 'Z')
+            else
             {
-                return (char)('a' + (c - 'A'));
+                if (!m_needsTurkishCasing)
+                {
+                    Interop.GlobalizationInterop.ToLowerSimple(pSrc, cchSrc, pDst, cchDst);
+                }
+                else
+                {
+                    Interop.GlobalizationInterop.ToLowerSimpleTurkishAzeri(pSrc, cchSrc, pDst, cchDst);
+                }
             }
+        }
+
+        private bool NeedsTurkishCasing(string localeName)
+        {
+            Contract.Assert(localeName != null);
 
-            return c;
+            string lcName = CultureData.AnsiToLower(localeName);
+            return lcName.Length >= 2 && ((lcName[0] == 't' && lcName[1] == 'r') || (lcName[0] == 'a' && lcName[1] == 'z'));
         }
     }
 }
\ No newline at end of file
index 7de2b67..e99151d 100644 (file)
@@ -288,6 +288,14 @@ namespace System.Globalization
         {
             get
             {
+#if PLATFORM_UNIX
+                // UNIXTODO: This hack can be removed once collation works and the code after this correctly returns "false".
+                if (m_needsTurkishCasing)
+                {
+                    return false;
+                }
+#endif
+
                 if (m_IsAsciiCasingSameAsInvariant == null)
                 {
                     m_IsAsciiCasingSameAsInvariant = CultureInfo.GetCultureInfo(m_textInfoName).CompareInfo.Compare("abcdefghijklmnopqrstuvwxyz",
index dbf0258..aeee7f1 100644 (file)
     <GlobalizationSources Include="$(BclSourcesRoot)\System\Globalization\EncodingTable.Unix.cs" />
     <GlobalizationSources Include="$(BclSourcesRoot)\System\Globalization\EncodingDataItem.Unix.cs" />
 
+    <GlobalizationSources Include="$(CoreFxSourcesRoot)\Interop\Unix\Interop.Libraries.cs" />
+    <GlobalizationSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Globalization.Native\Interop.Casing.cs" />
     <GlobalizationSources Include="$(CoreFxSourcesRoot)\System\Globalization\CalendarData.Unix.cs" />
     <GlobalizationSources Include="$(CoreFxSourcesRoot)\System\Globalization\CompareInfo.Unix.cs" />
     <GlobalizationSources Include="$(CoreFxSourcesRoot)\System\Globalization\CultureData.Unix.cs" />