From db20f3f1bb8595633a7e16c8900fd401a453a6b5 Mon Sep 17 00:00:00 2001 From: Jiyoung Yun Date: Tue, 27 Dec 2016 16:46:08 +0900 Subject: Imported Upstream version 1.0.0.9127 --- src/mscorlib/corefx/System/Buffers/ArrayPool.cs | 113 + .../corefx/System/Buffers/ArrayPoolEventSource.cs | 78 + .../corefx/System/Buffers/ConfigurableArrayPool.cs | 265 ++ .../TlsOverPerCoreLockedStacksArrayPool.Unix.cs | 28 + .../TlsOverPerCoreLockedStacksArrayPool.Windows.cs | 20 + .../Buffers/TlsOverPerCoreLockedStacksArrayPool.cs | 328 ++ src/mscorlib/corefx/System/Buffers/Utilities.cs | 35 + .../corefx/System/Globalization/Calendar.cs | 42 +- .../System/Globalization/CalendarAlgorithmType.cs | 20 + .../System/Globalization/CalendarData.Unix.cs | 3 +- .../corefx/System/Globalization/CalendarData.cs | 8 +- .../Globalization/CalendricalCalculationsHelper.cs | 31 +- .../corefx/System/Globalization/CharUnicodeInfo.cs | 110 +- .../System/Globalization/CharUnicodeInfoData.cs | 25 + .../Globalization/ChineseLunisolarCalendar.cs | 4 +- .../System/Globalization/CompareInfo.Unix.cs | 140 +- .../System/Globalization/CompareInfo.Windows.cs | 78 +- .../corefx/System/Globalization/CompareInfo.cs | 283 +- .../System/Globalization/CultureData.Unix.cs | 164 +- .../System/Globalization/CultureData.Windows.cs | 132 +- .../corefx/System/Globalization/CultureData.cs | 392 +- .../System/Globalization/CultureInfo.Windows.cs | 6 +- .../corefx/System/Globalization/CultureInfo.cs | 528 ++- .../Globalization/CultureNotFoundException.cs | 35 +- .../corefx/System/Globalization/CultureTypes.cs | 28 + .../System/Globalization/DateTimeFormatInfo.cs | 343 +- .../corefx/System/Globalization/DayLightTime.cs | 49 +- .../corefx/System/Globalization/DigitShapes.cs | 17 + .../Globalization/EastAsianLunisolarCalendar.cs | 41 +- .../System/Globalization/GregorianCalendar.cs | 57 +- .../Globalization/GregorianCalendarHelper.cs | 24 +- .../corefx/System/Globalization/HebrewCalendar.cs | 43 +- .../corefx/System/Globalization/HebrewNumber.cs | 11 +- .../System/Globalization/HijriCalendar.Win32.cs | 86 + .../System/Globalization/HijriCalendar.WinRT.cs | 16 + .../System/Globalization/HijriCalendar.Windows.cs | 16 - .../corefx/System/Globalization/HijriCalendar.cs | 31 +- .../corefx/System/Globalization/IdnMapping.Unix.cs | 134 + .../System/Globalization/IdnMapping.Windows.cs | 113 + .../corefx/System/Globalization/IdnMapping.cs | 152 + .../System/Globalization/JapaneseCalendar.Unix.cs | 3 - .../System/Globalization/JapaneseCalendar.Win32.cs | 209 + .../System/Globalization/JapaneseCalendar.WinRT.cs | 62 + .../Globalization/JapaneseCalendar.Windows.cs | 62 - .../System/Globalization/JapaneseCalendar.cs | 18 +- .../corefx/System/Globalization/JulianCalendar.cs | 37 +- .../corefx/System/Globalization/KoreanCalendar.cs | 11 +- .../Globalization/KoreanLunisolarCalendar.cs | 4 +- .../corefx/System/Globalization/LocaleData.Unix.cs | 4572 ++++++++++++++++++++ .../System/Globalization/NumberFormatInfo.cs | 91 +- .../corefx/System/Globalization/PersianCalendar.cs | 54 +- .../corefx/System/Globalization/RegionInfo.cs | 117 +- src/mscorlib/corefx/System/Globalization/STUBS.cs | 180 - .../corefx/System/Globalization/SortKey.cs | 209 + .../corefx/System/Globalization/SortVersion.cs | 101 + .../corefx/System/Globalization/StringInfo.cs | 89 +- .../corefx/System/Globalization/TaiwanCalendar.cs | 13 +- .../System/Globalization/TextElementEnumerator.cs | 10 +- .../corefx/System/Globalization/TextInfo.Unix.cs | 5 +- .../System/Globalization/TextInfo.Windows.cs | 6 +- .../corefx/System/Globalization/TextInfo.cs | 324 +- .../System/Globalization/ThaiBuddhistCalendar.cs | 11 +- .../System/Globalization/UmAlQuraCalendar.cs | 40 +- src/mscorlib/corefx/System/HResults.cs | 236 + src/mscorlib/corefx/System/IO/Error.cs | 44 + .../corefx/System/IO/FileStream.NetStandard17.cs | 76 + src/mscorlib/corefx/System/IO/FileStream.Unix.cs | 934 ++++ src/mscorlib/corefx/System/IO/FileStream.Win32.cs | 1770 ++++++++ src/mscorlib/corefx/System/IO/FileStream.cs | 654 +++ .../System/IO/FileStreamCompletionSource.Win32.cs | 221 + src/mscorlib/corefx/System/IO/Path.Unix.cs | 256 ++ src/mscorlib/corefx/System/IO/Path.Win32.cs | 36 + src/mscorlib/corefx/System/IO/Path.Windows.cs | 153 + src/mscorlib/corefx/System/IO/Path.cs | 578 +++ .../corefx/System/IO/PathHelper.Windows.cs | 389 ++ .../System/IO/PathInternal.CaseSensitivity.cs | 75 + src/mscorlib/corefx/System/IO/PathInternal.Unix.cs | 122 + .../System/IO/PathInternal.Windows.StringBuffer.cs | 89 + .../corefx/System/IO/PathInternal.Windows.cs | 482 +++ src/mscorlib/corefx/System/IO/PathInternal.cs | 230 + src/mscorlib/corefx/System/IO/Win32Marshal.cs | 134 + .../System/Runtime/InteropServices/NativeBuffer.cs | 157 + .../Runtime/InteropServices/SafeHeapHandle.cs | 109 + .../Runtime/InteropServices/SafeHeapHandleCache.cs | 97 + .../System/Runtime/InteropServices/StringBuffer.cs | 354 ++ .../System/Security/CryptographicException.cs | 44 + .../corefx/System/Security/SafeBSTRHandle.cs | 81 + .../corefx/System/Security/SecureString.Unix.cs | 295 ++ .../corefx/System/Security/SecureString.Windows.cs | 310 ++ .../corefx/System/Security/SecureString.cs | 189 + .../System/Threading/ClrThreadPoolBoundHandle.cs | 319 ++ .../ClrThreadPoolBoundHandleOverlapped.cs | 52 + .../ClrThreadPoolPreAllocatedOverlapped.cs | 105 + .../System/Threading/DeferredDisposableLifetime.cs | 116 + 94 files changed, 18017 insertions(+), 947 deletions(-) create mode 100644 src/mscorlib/corefx/System/Buffers/ArrayPool.cs create mode 100644 src/mscorlib/corefx/System/Buffers/ArrayPoolEventSource.cs create mode 100644 src/mscorlib/corefx/System/Buffers/ConfigurableArrayPool.cs create mode 100644 src/mscorlib/corefx/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.Unix.cs create mode 100644 src/mscorlib/corefx/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.Windows.cs create mode 100644 src/mscorlib/corefx/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs create mode 100644 src/mscorlib/corefx/System/Buffers/Utilities.cs create mode 100644 src/mscorlib/corefx/System/Globalization/CalendarAlgorithmType.cs create mode 100644 src/mscorlib/corefx/System/Globalization/CultureTypes.cs create mode 100644 src/mscorlib/corefx/System/Globalization/DigitShapes.cs create mode 100644 src/mscorlib/corefx/System/Globalization/HijriCalendar.Win32.cs create mode 100644 src/mscorlib/corefx/System/Globalization/HijriCalendar.WinRT.cs delete mode 100644 src/mscorlib/corefx/System/Globalization/HijriCalendar.Windows.cs create mode 100644 src/mscorlib/corefx/System/Globalization/IdnMapping.Unix.cs create mode 100644 src/mscorlib/corefx/System/Globalization/IdnMapping.Windows.cs create mode 100644 src/mscorlib/corefx/System/Globalization/IdnMapping.cs create mode 100644 src/mscorlib/corefx/System/Globalization/JapaneseCalendar.Win32.cs create mode 100644 src/mscorlib/corefx/System/Globalization/JapaneseCalendar.WinRT.cs delete mode 100644 src/mscorlib/corefx/System/Globalization/JapaneseCalendar.Windows.cs create mode 100644 src/mscorlib/corefx/System/Globalization/LocaleData.Unix.cs delete mode 100644 src/mscorlib/corefx/System/Globalization/STUBS.cs create mode 100644 src/mscorlib/corefx/System/Globalization/SortKey.cs create mode 100644 src/mscorlib/corefx/System/Globalization/SortVersion.cs create mode 100644 src/mscorlib/corefx/System/HResults.cs create mode 100644 src/mscorlib/corefx/System/IO/Error.cs create mode 100644 src/mscorlib/corefx/System/IO/FileStream.NetStandard17.cs create mode 100644 src/mscorlib/corefx/System/IO/FileStream.Unix.cs create mode 100644 src/mscorlib/corefx/System/IO/FileStream.Win32.cs create mode 100644 src/mscorlib/corefx/System/IO/FileStream.cs create mode 100644 src/mscorlib/corefx/System/IO/FileStreamCompletionSource.Win32.cs create mode 100644 src/mscorlib/corefx/System/IO/Path.Unix.cs create mode 100644 src/mscorlib/corefx/System/IO/Path.Win32.cs create mode 100644 src/mscorlib/corefx/System/IO/Path.Windows.cs create mode 100644 src/mscorlib/corefx/System/IO/Path.cs create mode 100644 src/mscorlib/corefx/System/IO/PathHelper.Windows.cs create mode 100644 src/mscorlib/corefx/System/IO/PathInternal.CaseSensitivity.cs create mode 100644 src/mscorlib/corefx/System/IO/PathInternal.Unix.cs create mode 100644 src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs create mode 100644 src/mscorlib/corefx/System/IO/PathInternal.Windows.cs create mode 100644 src/mscorlib/corefx/System/IO/PathInternal.cs create mode 100644 src/mscorlib/corefx/System/IO/Win32Marshal.cs create mode 100644 src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs create mode 100644 src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs create mode 100644 src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs create mode 100644 src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs create mode 100644 src/mscorlib/corefx/System/Security/CryptographicException.cs create mode 100644 src/mscorlib/corefx/System/Security/SafeBSTRHandle.cs create mode 100644 src/mscorlib/corefx/System/Security/SecureString.Unix.cs create mode 100644 src/mscorlib/corefx/System/Security/SecureString.Windows.cs create mode 100644 src/mscorlib/corefx/System/Security/SecureString.cs create mode 100644 src/mscorlib/corefx/System/Threading/ClrThreadPoolBoundHandle.cs create mode 100644 src/mscorlib/corefx/System/Threading/ClrThreadPoolBoundHandleOverlapped.cs create mode 100644 src/mscorlib/corefx/System/Threading/ClrThreadPoolPreAllocatedOverlapped.cs create mode 100644 src/mscorlib/corefx/System/Threading/DeferredDisposableLifetime.cs (limited to 'src/mscorlib/corefx/System') diff --git a/src/mscorlib/corefx/System/Buffers/ArrayPool.cs b/src/mscorlib/corefx/System/Buffers/ArrayPool.cs new file mode 100644 index 0000000000..441e48dab4 --- /dev/null +++ b/src/mscorlib/corefx/System/Buffers/ArrayPool.cs @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Buffers +{ + /// + /// Provides a resource pool that enables reusing instances of type . + /// + /// + /// + /// Renting and returning buffers with an can increase performance + /// in situations where arrays are created and destroyed frequently, resulting in significant + /// memory pressure on the garbage collector. + /// + /// + /// This class is thread-safe. All members may be used by multiple threads concurrently. + /// + /// + public abstract class ArrayPool + { + /// + /// Retrieves a shared instance. + /// + /// + /// The shared pool provides a default implementation of + /// that's intended for general applicability. It maintains arrays of multiple sizes, and + /// may hand back a larger array than was actually requested, but will never hand back a smaller + /// array than was requested. Renting a buffer from it with will result in an + /// existing buffer being taken from the pool if an appropriate buffer is available or in a new + /// buffer being allocated if one is not available. + /// + public static ArrayPool Shared => SharedPool.Value; + + /// Stores a cached pool instance for T[]. + /// + /// Separated out into a nested class to enable lazy-initialization of the pool provided by + /// the runtime, only forced when Shared is used (and not when Create is called or when + /// other non-Shared accesses happen). + /// + private static class SharedPool + { + /// Per-type cached pool. + /// + /// byte[] and char[] are the most commonly pooled array types. For these we use a special pool type + /// optimized for very fast access speeds, at the expense of more memory consumption. + /// + internal readonly static ArrayPool Value = + typeof(T) == typeof(byte) || typeof(T) == typeof(char) ? new TlsOverPerCoreLockedStacksArrayPool() : + Create(); + } + + /// + /// Creates a new instance using default configuration options. + /// + /// A new instance. + public static ArrayPool Create() => new ConfigurableArrayPool(); + + /// + /// Creates a new instance using custom configuration options. + /// + /// The maximum length of array instances that may be stored in the pool. + /// + /// The maximum number of array instances that may be stored in each bucket in the pool. The pool + /// groups arrays of similar lengths into buckets for faster access. + /// + /// A new instance with the specified configuration options. + /// + /// The created pool will group arrays into buckets, with no more than + /// in each bucket and with those arrays not exceeding in length. + /// + public static ArrayPool Create(int maxArrayLength, int maxArraysPerBucket) => + new ConfigurableArrayPool(maxArrayLength, maxArraysPerBucket); + + /// + /// Retrieves a buffer that is at least the requested length. + /// + /// The minimum length of the array needed. + /// + /// An that is at least in length. + /// + /// + /// This buffer is loaned to the caller and should be returned to the same pool via + /// so that it may be reused in subsequent usage of . + /// It is not a fatal error to not return a rented buffer, but failure to do so may lead to + /// decreased application performance, as the pool may need to create a new buffer to replace + /// the one lost. + /// + public abstract T[] Rent(int minimumLength); + + /// + /// Returns to the pool an array that was previously obtained via on the same + /// instance. + /// + /// + /// The buffer previously obtained from to return to the pool. + /// + /// + /// If true and if the pool will store the buffer to enable subsequent reuse, + /// will clear of its contents so that a subsequent consumer via + /// will not see the previous consumer's content. If false or if the pool will release the buffer, + /// the array's contents are left unchanged. + /// + /// + /// Once a buffer has been returned to the pool, the caller gives up all ownership of the buffer + /// and must not use it. The reference returned from a given call to must only be + /// returned via once. The default + /// may hold onto the returned buffer in order to rent it again, or it may release the returned buffer + /// if it's determined that the pool already has enough buffers stored. + /// + public abstract void Return(T[] array, bool clearArray = false); + } +} diff --git a/src/mscorlib/corefx/System/Buffers/ArrayPoolEventSource.cs b/src/mscorlib/corefx/System/Buffers/ArrayPoolEventSource.cs new file mode 100644 index 0000000000..9482744144 --- /dev/null +++ b/src/mscorlib/corefx/System/Buffers/ArrayPoolEventSource.cs @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.Tracing; + +namespace System.Buffers +{ + [EventSource(Name = "System.Buffers.ArrayPoolEventSource")] + internal sealed class ArrayPoolEventSource : EventSource + { + internal readonly static ArrayPoolEventSource Log = new ArrayPoolEventSource(); + + /// The reason for a BufferAllocated event. + internal enum BufferAllocatedReason : int + { + /// The pool is allocating a buffer to be pooled in a bucket. + Pooled, + /// The requested buffer size was too large to be pooled. + OverMaximumSize, + /// The pool has already allocated for pooling as many buffers of a particular size as it's allowed. + PoolExhausted + } + + /// + /// Event for when a buffer is rented. This is invoked once for every successful call to Rent, + /// regardless of whether a buffer is allocated or a buffer is taken from the pool. In a + /// perfect situation where all rented buffers are returned, we expect to see the number + /// of BufferRented events exactly match the number of BuferReturned events, with the number + /// of BufferAllocated events being less than or equal to those numbers (ideally significantly + /// less than). + /// + [Event(1, Level = EventLevel.Verbose)] + internal unsafe void BufferRented(int bufferId, int bufferSize, int poolId, int bucketId) + { + EventData* payload = stackalloc EventData[4]; + payload[0].Size = sizeof(int); + payload[0].DataPointer = ((IntPtr)(&bufferId)); + payload[1].Size = sizeof(int); + payload[1].DataPointer = ((IntPtr)(&bufferSize)); + payload[2].Size = sizeof(int); + payload[2].DataPointer = ((IntPtr)(&poolId)); + payload[3].Size = sizeof(int); + payload[3].DataPointer = ((IntPtr)(&bucketId)); + WriteEventCore(1, 4, payload); + } + + /// + /// Event for when a buffer is allocated by the pool. In an ideal situation, the number + /// of BufferAllocated events is significantly smaller than the number of BufferRented and + /// BufferReturned events. + /// + [Event(2, Level = EventLevel.Informational)] + internal unsafe void BufferAllocated(int bufferId, int bufferSize, int poolId, int bucketId, BufferAllocatedReason reason) + { + EventData* payload = stackalloc EventData[5]; + payload[0].Size = sizeof(int); + payload[0].DataPointer = ((IntPtr)(&bufferId)); + payload[1].Size = sizeof(int); + payload[1].DataPointer = ((IntPtr)(&bufferSize)); + payload[2].Size = sizeof(int); + payload[2].DataPointer = ((IntPtr)(&poolId)); + payload[3].Size = sizeof(int); + payload[3].DataPointer = ((IntPtr)(&bucketId)); + payload[4].Size = sizeof(BufferAllocatedReason); + payload[4].DataPointer = ((IntPtr)(&reason)); + WriteEventCore(2, 5, payload); + } + + /// + /// Event raised when a buffer is returned to the pool. This event is raised regardless of whether + /// the returned buffer is stored or dropped. In an ideal situation, the number of BufferReturned + /// events exactly matches the number of BufferRented events. + /// + [Event(3, Level = EventLevel.Verbose)] + internal void BufferReturned(int bufferId, int bufferSize, int poolId) => WriteEvent(3, bufferId, bufferSize, poolId); + } +} diff --git a/src/mscorlib/corefx/System/Buffers/ConfigurableArrayPool.cs b/src/mscorlib/corefx/System/Buffers/ConfigurableArrayPool.cs new file mode 100644 index 0000000000..1e0e769530 --- /dev/null +++ b/src/mscorlib/corefx/System/Buffers/ConfigurableArrayPool.cs @@ -0,0 +1,265 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Threading; + +namespace System.Buffers +{ + internal sealed partial class ConfigurableArrayPool : ArrayPool + { + /// The default maximum length of each array in the pool (2^20). + private const int DefaultMaxArrayLength = 1024 * 1024; + /// The default maximum number of arrays per bucket that are available for rent. + private const int DefaultMaxNumberOfArraysPerBucket = 50; + + private readonly Bucket[] _buckets; + + internal ConfigurableArrayPool() : this(DefaultMaxArrayLength, DefaultMaxNumberOfArraysPerBucket) + { + } + + internal ConfigurableArrayPool(int maxArrayLength, int maxArraysPerBucket) + { + if (maxArrayLength <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maxArrayLength)); + } + if (maxArraysPerBucket <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maxArraysPerBucket)); + } + + // Our bucketing algorithm has a min length of 2^4 and a max length of 2^30. + // Constrain the actual max used to those values. + const int MinimumArrayLength = 0x10, MaximumArrayLength = 0x40000000; + if (maxArrayLength > MaximumArrayLength) + { + maxArrayLength = MaximumArrayLength; + } + else if (maxArrayLength < MinimumArrayLength) + { + maxArrayLength = MinimumArrayLength; + } + + // Create the buckets. + int poolId = Id; + int maxBuckets = Utilities.SelectBucketIndex(maxArrayLength); + var buckets = new Bucket[maxBuckets + 1]; + for (int i = 0; i < buckets.Length; i++) + { + buckets[i] = new Bucket(Utilities.GetMaxSizeForBucket(i), maxArraysPerBucket, poolId); + } + _buckets = buckets; + } + + /// Gets an ID for the pool to use with events. + private int Id => GetHashCode(); + + public override T[] Rent(int minimumLength) + { + // Arrays can't be smaller than zero. We allow requesting zero-length arrays (even though + // pooling such an array isn't valuable) as it's a valid length array, and we want the pool + // to be usable in general instead of using `new`, even for computed lengths. + if (minimumLength < 0) + { + throw new ArgumentOutOfRangeException(nameof(minimumLength)); + } + else if (minimumLength == 0) + { + // No need for events with the empty array. Our pool is effectively infinite + // and we'll never allocate for rents and never store for returns. + return EmptyArray.Value; + } + + var log = ArrayPoolEventSource.Log; + T[] buffer = null; + + int index = Utilities.SelectBucketIndex(minimumLength); + if (index < _buckets.Length) + { + // Search for an array starting at the 'index' bucket. If the bucket is empty, bump up to the + // next higher bucket and try that one, but only try at most a few buckets. + const int MaxBucketsToTry = 2; + int i = index; + do + { + // Attempt to rent from the bucket. If we get a buffer from it, return it. + buffer = _buckets[i].Rent(); + if (buffer != null) + { + if (log.IsEnabled()) + { + log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, _buckets[i].Id); + } + return buffer; + } + } + while (++i < _buckets.Length && i != index + MaxBucketsToTry); + + // The pool was exhausted for this buffer size. Allocate a new buffer with a size corresponding + // to the appropriate bucket. + buffer = new T[_buckets[index]._bufferLength]; + } + else + { + // The request was for a size too large for the pool. Allocate an array of exactly the requested length. + // When it's returned to the pool, we'll simply throw it away. + buffer = new T[minimumLength]; + } + + if (log.IsEnabled()) + { + int bufferId = buffer.GetHashCode(), bucketId = -1; // no bucket for an on-demand allocated buffer + log.BufferRented(bufferId, buffer.Length, Id, bucketId); + log.BufferAllocated(bufferId, buffer.Length, Id, bucketId, index >= _buckets.Length ? + ArrayPoolEventSource.BufferAllocatedReason.OverMaximumSize : ArrayPoolEventSource.BufferAllocatedReason.PoolExhausted); + } + + return buffer; + } + + public override void Return(T[] array, bool clearArray = false) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + else if (array.Length == 0) + { + // Ignore empty arrays. When a zero-length array is rented, we return a singleton + // rather than actually taking a buffer out of the lowest bucket. + return; + } + + // Determine with what bucket this array length is associated + int bucket = Utilities.SelectBucketIndex(array.Length); + + // If we can tell that the buffer was allocated, drop it. Otherwise, check if we have space in the pool + if (bucket < _buckets.Length) + { + // Clear the array if the user requests + if (clearArray) + { + Array.Clear(array, 0, array.Length); + } + + // Return the buffer to its bucket. In the future, we might consider having Return return false + // instead of dropping a bucket, in which case we could try to return to a lower-sized bucket, + // just as how in Rent we allow renting from a higher-sized bucket. + _buckets[bucket].Return(array); + } + + // Log that the buffer was returned + var log = ArrayPoolEventSource.Log; + if (log.IsEnabled()) + { + log.BufferReturned(array.GetHashCode(), array.Length, Id); + } + } + + /// Provides a thread-safe bucket containing buffers that can be Rent'd and Return'd. + private sealed class Bucket + { + internal readonly int _bufferLength; + private readonly T[][] _buffers; + private readonly int _poolId; + + private SpinLock _lock; // do not make this readonly; it's a mutable struct + private int _index; + + /// + /// Creates the pool with numberOfBuffers arrays where each buffer is of bufferLength length. + /// + internal Bucket(int bufferLength, int numberOfBuffers, int poolId) + { + _lock = new SpinLock(Debugger.IsAttached); // only enable thread tracking if debugger is attached; it adds non-trivial overheads to Enter/Exit + _buffers = new T[numberOfBuffers][]; + _bufferLength = bufferLength; + _poolId = poolId; + } + + /// Gets an ID for the bucket to use with events. + internal int Id => GetHashCode(); + + /// Takes an array from the bucket. If the bucket is empty, returns null. + internal T[] Rent() + { + T[][] buffers = _buffers; + T[] buffer = null; + + // While holding the lock, grab whatever is at the next available index and + // update the index. We do as little work as possible while holding the spin + // lock to minimize contention with other threads. The try/finally is + // necessary to properly handle thread aborts on platforms which have them. + bool lockTaken = false, allocateBuffer = false; + try + { + _lock.Enter(ref lockTaken); + + if (_index < buffers.Length) + { + buffer = buffers[_index]; + buffers[_index++] = null; + allocateBuffer = buffer == null; + } + } + finally + { + if (lockTaken) _lock.Exit(false); + } + + // While we were holding the lock, we grabbed whatever was at the next available index, if + // there was one. If we tried and if we got back null, that means we hadn't yet allocated + // for that slot, in which case we should do so now. + if (allocateBuffer) + { + buffer = new T[_bufferLength]; + + var log = ArrayPoolEventSource.Log; + if (log.IsEnabled()) + { + log.BufferAllocated(buffer.GetHashCode(), _bufferLength, _poolId, Id, + ArrayPoolEventSource.BufferAllocatedReason.Pooled); + } + } + + return buffer; + } + + /// + /// Attempts to return the buffer to the bucket. If successful, the buffer will be stored + /// in the bucket and true will be returned; otherwise, the buffer won't be stored, and false + /// will be returned. + /// + internal void Return(T[] array) + { + // Check to see if the buffer is the correct size for this bucket + if (array.Length != _bufferLength) + { + throw new ArgumentException(SR.ArgumentException_BufferNotFromPool, nameof(array)); + } + + // While holding the spin lock, if there's room available in the bucket, + // put the buffer into the next available slot. Otherwise, we just drop it. + // The try/finally is necessary to properly handle thread aborts on platforms + // which have them. + bool lockTaken = false; + try + { + _lock.Enter(ref lockTaken); + + if (_index != 0) + { + _buffers[--_index] = array; + } + } + finally + { + if (lockTaken) _lock.Exit(false); + } + } + } + } +} diff --git a/src/mscorlib/corefx/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.Unix.cs b/src/mscorlib/corefx/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.Unix.cs new file mode 100644 index 0000000000..8a1d006b12 --- /dev/null +++ b/src/mscorlib/corefx/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.Unix.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32; +using System.Runtime.CompilerServices; + +namespace System.Buffers +{ + internal sealed partial class TlsOverPerCoreLockedStacksArrayPool + { + /// Get an identifier for the current thread to use to index into the stacks. + private static int ExecutionId + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + // On Unix, CurrentProcessorNumber is implemented in terms of sched_getcpu, which + // doesn't exist on all platforms. On those it doesn't exist on, GetCurrentProcessorNumber + // returns -1. As a fallback in that case and to spread the threads across the buckets + // by default, we use the current managed thread ID as a proxy. + int id = CurrentProcessorNumber; + if (id < 0) id = Environment.CurrentManagedThreadId; + return id; + } + } + } +} diff --git a/src/mscorlib/corefx/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.Windows.cs b/src/mscorlib/corefx/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.Windows.cs new file mode 100644 index 0000000000..d42242c910 --- /dev/null +++ b/src/mscorlib/corefx/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.Windows.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace System.Buffers +{ + internal sealed partial class TlsOverPerCoreLockedStacksArrayPool : ArrayPool + { + /// Get an identifier for the current thread to use to index into the stacks. + private static int ExecutionId + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return CurrentProcessorNumber; } + } + } +} diff --git a/src/mscorlib/corefx/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs b/src/mscorlib/corefx/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs new file mode 100644 index 0000000000..debc33615f --- /dev/null +++ b/src/mscorlib/corefx/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs @@ -0,0 +1,328 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace System.Buffers +{ + /// + /// Provides an ArrayPool implementation meant to be used as the singleton returned from ArrayPool.Shared. + /// + /// + /// The implementation uses a tiered caching scheme, with a small per-thread cache for each array size, followed + /// by a cache per array size shared by all threads, split into per-core stacks meant to be used by threads + /// running on that core. Locks are used to protect each per-core stack, because a thread can migrate after + /// checking its processor number, because multiple threads could interleave on the same core, and because + /// a thread is allowed to check other core's buckets if its core's bucket is empty/full. + /// + internal sealed partial class TlsOverPerCoreLockedStacksArrayPool : ArrayPool + { + // TODO: #7747: "Investigate optimizing ArrayPool heuristics" + // - Explore caching in TLS more than one array per size per thread, and moving stale buffers to the global queue. + // - Explore dumping stale buffers from the global queue, similar to PinnableBufferCache (maybe merging them). + // - Explore changing the size of each per-core bucket, potentially dynamically or based on other factors like array size. + // - Explore changing number of buckets and what sizes of arrays are cached. + // - Investigate whether false sharing is causing any issues, in particular on LockedStack's count and the contents of its array. + // ... + + /// The number of buckets (array sizes) in the pool, one for each array length, starting from length 16. + private const int NumBuckets = 17; // Utilities.SelectBucketIndex(2*1024*1024) + /// Maximum number of per-core stacks to use per array size. + private const int MaxPerCorePerArraySizeStacks = 64; // selected to avoid needing to worry about processor groups + /// The maximum number of buffers to store in a bucket's global queue. + private const int MaxBuffersPerArraySizePerCore = 8; + + /// The length of arrays stored in the corresponding indices in and . + private readonly int[] _bucketArraySizes; + /// + /// An array of per-core array stacks. The slots are lazily initialized to avoid creating + /// lots of overhead for unused array sizes. + /// + private readonly PerCoreLockedStacks[] _buckets = new PerCoreLockedStacks[NumBuckets]; + /// A per-thread array of arrays, to cache one array per array size per thread. + [ThreadStatic] + private static T[][] t_tlsBuckets; + /// + /// Cached processor number used as a hint for which per-core stack to access. + /// + [ThreadStatic] + private static int? t_cachedProcessorNumber; + + /// Initialize the pool. + public TlsOverPerCoreLockedStacksArrayPool() + { + var sizes = new int[NumBuckets]; + for (int i = 0; i < sizes.Length; i++) + { + sizes[i] = Utilities.GetMaxSizeForBucket(i); + } + _bucketArraySizes = sizes; + } + + /// Allocate a new PerCoreLockedStacks and try to store it into the array. + private PerCoreLockedStacks CreatePerCoreLockedStacks(int bucketIndex) + { + var inst = new PerCoreLockedStacks(); + return Interlocked.CompareExchange(ref _buckets[bucketIndex], inst, null) ?? inst; + } + + /// Gets an ID for the pool to use with events. + private int Id => GetHashCode(); + + /// Gets the processor number associated with the current thread. + /// Uses a cached value if one exists on the current thread. + private static int CurrentProcessorNumber + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + int? num = t_cachedProcessorNumber; + if (!num.HasValue) + { + t_cachedProcessorNumber = num = Environment.CurrentProcessorNumber; + } + return num.GetValueOrDefault(); + } + } + + public override T[] Rent(int minimumLength) + { + // Arrays can't be smaller than zero. We allow requesting zero-length arrays (even though + // pooling such an array isn't valuable) as it's a valid length array, and we want the pool + // to be usable in general instead of using `new`, even for computed lengths. + if (minimumLength < 0) + { + throw new ArgumentOutOfRangeException(nameof(minimumLength)); + } + else if (minimumLength == 0) + { + // No need to log the empty array. Our pool is effectively infinite + // and we'll never allocate for rents and never store for returns. + return EmptyArray.Value; + } + + ArrayPoolEventSource log = ArrayPoolEventSource.Log; + T[] buffer; + + // Get the bucket number for the array length + int bucketIndex = Utilities.SelectBucketIndex(minimumLength); + + // If the array could come from a bucket... + if (bucketIndex < _buckets.Length) + { + // First try to get it from TLS if possible. + T[][] tlsBuckets = t_tlsBuckets; + if (tlsBuckets != null) + { + buffer = tlsBuckets[bucketIndex]; + if (buffer != null) + { + tlsBuckets[bucketIndex] = null; + if (log.IsEnabled()) + { + log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, bucketIndex); + } + return buffer; + } + } + + // We couldn't get a buffer from TLS, so try the global stack. + PerCoreLockedStacks b = _buckets[bucketIndex]; + if (b != null) + { + buffer = b.TryPop(); + if (buffer != null) + { + if (log.IsEnabled()) + { + log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, bucketIndex); + } + return buffer; + } + } + + // No buffer available. Allocate a new buffer with a size corresponding to the appropriate bucket. + buffer = new T[_bucketArraySizes[bucketIndex]]; + } + else + { + // The request was for a size too large for the pool. Allocate an array of exactly the requested length. + // When it's returned to the pool, we'll simply throw it away. + buffer = new T[minimumLength]; + } + + if (log.IsEnabled()) + { + int bufferId = buffer.GetHashCode(), bucketId = -1; // no bucket for an on-demand allocated buffer + log.BufferRented(bufferId, buffer.Length, Id, bucketId); + log.BufferAllocated(bufferId, buffer.Length, Id, bucketId, bucketIndex >= _buckets.Length ? + ArrayPoolEventSource.BufferAllocatedReason.OverMaximumSize : + ArrayPoolEventSource.BufferAllocatedReason.PoolExhausted); + } + + return buffer; + } + + public override void Return(T[] array, bool clearArray = false) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + // Determine with what bucket this array length is associated + int bucketIndex = Utilities.SelectBucketIndex(array.Length); + + // If we can tell that the buffer was allocated (or empty), drop it. Otherwise, check if we have space in the pool. + if (bucketIndex < _buckets.Length) + { + // Clear the array if the user requests. + if (clearArray) + { + Array.Clear(array, 0, array.Length); + } + + // Check to see if the buffer is the correct size for this bucket + if (array.Length != _bucketArraySizes[bucketIndex]) + { + throw new ArgumentException(SR.ArgumentException_BufferNotFromPool, nameof(array)); + } + + // Write through the TLS bucket. If there weren't any buckets, create them + // and store this array into it. If there were, store this into it, and + // if there was a previous one there, push that to the global stack. This + // helps to keep LIFO access such that the most recently pushed stack will + // be in TLS and the first to be popped next. + T[][] tlsBuckets = t_tlsBuckets; + if (tlsBuckets == null) + { + t_tlsBuckets = tlsBuckets = new T[NumBuckets][]; + tlsBuckets[bucketIndex] = array; + } + else + { + T[] prev = tlsBuckets[bucketIndex]; + tlsBuckets[bucketIndex] = array; + if (prev != null) + { + PerCoreLockedStacks bucket = _buckets[bucketIndex] ?? CreatePerCoreLockedStacks(bucketIndex); + bucket.TryPush(prev); + } + } + } + + // Log that the buffer was returned + ArrayPoolEventSource log = ArrayPoolEventSource.Log; + if (log.IsEnabled()) + { + log.BufferReturned(array.GetHashCode(), array.Length, Id); + } + } + + /// + /// Stores a set of stacks of arrays, with one stack per core. + /// + private sealed class PerCoreLockedStacks + { + /// The stacks. + private readonly LockedStack[] _perCoreStacks; + + /// Initializes the stacks. + public PerCoreLockedStacks() + { + // Create the stacks. We create as many as there are processors, limited by our max. + var stacks = new LockedStack[Math.Min(Environment.ProcessorCount, MaxPerCorePerArraySizeStacks)]; + for (int i = 0; i < stacks.Length; i++) + { + stacks[i] = new LockedStack(); + } + _perCoreStacks = stacks; + } + + /// Try to push the array into the stacks. If each is full when it's tested, the array will be dropped. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void TryPush(T[] array) + { + // Try to push on to the associated stack first. If that fails, + // round-robin through the other stacks. + LockedStack[] stacks = _perCoreStacks; + int index = ExecutionId % stacks.Length; + for (int i = 0; i < stacks.Length; i++) + { + if (stacks[index].TryPush(array)) return; + if (++index == stacks.Length) index = 0; + } + } + + /// Try to get an array from the stacks. If each is empty when it's tested, null will be returned. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T[] TryPop() + { + // Try to pop from the associated stack first. If that fails, + // round-robin through the other stacks. + T[] arr; + LockedStack[] stacks = _perCoreStacks; + int index = ExecutionId % stacks.Length; + for (int i = 0; i < stacks.Length; i++) + { + if ((arr = stacks[index].TryPop()) != null) return arr; + if (++index == stacks.Length) index = 0; + } + return null; + } + } + + /// Provides a simple stack of arrays, protected by a lock. + private sealed class LockedStack + { + private readonly T[][] _arrays = new T[MaxBuffersPerArraySizePerCore][]; + private int _count; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPush(T[] array) + { + bool enqueued = false; + MonitorEnterWithProcNumberFlush(this); + if (_count < MaxBuffersPerArraySizePerCore) + { + _arrays[_count++] = array; + enqueued = true; + } + Monitor.Exit(this); + return enqueued; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T[] TryPop() + { + T[] arr = null; + MonitorEnterWithProcNumberFlush(this); + if (_count > 0) + { + arr = _arrays[--_count]; + _arrays[_count] = null; + } + Monitor.Exit(this); + return arr; + } + + /// + /// Enters the monitor on the object. If there is any contention while trying + /// to acquire the monitor, it flushes the cached processor number so that subsequent + /// attempts to access the per-core stacks will use an updated processor number. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void MonitorEnterWithProcNumberFlush(object obj) + { + if (!Monitor.TryEnter(obj)) + { + t_cachedProcessorNumber = null; + Monitor.Enter(obj); + } + } + } + } +} diff --git a/src/mscorlib/corefx/System/Buffers/Utilities.cs b/src/mscorlib/corefx/System/Buffers/Utilities.cs new file mode 100644 index 0000000000..823299f5fc --- /dev/null +++ b/src/mscorlib/corefx/System/Buffers/Utilities.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Buffers +{ + internal static class Utilities + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int SelectBucketIndex(int bufferSize) + { + uint bitsRemaining = ((uint)bufferSize - 1) >> 4; + + int poolIndex = 0; + if (bitsRemaining > 0xFFFF) { bitsRemaining >>= 16; poolIndex = 16; } + if (bitsRemaining > 0xFF) { bitsRemaining >>= 8; poolIndex += 8; } + if (bitsRemaining > 0xF) { bitsRemaining >>= 4; poolIndex += 4; } + if (bitsRemaining > 0x3) { bitsRemaining >>= 2; poolIndex += 2; } + if (bitsRemaining > 0x1) { bitsRemaining >>= 1; poolIndex += 1; } + + return poolIndex + (int)bitsRemaining; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int GetMaxSizeForBucket(int binIndex) + { + int maxSize = 16 << binIndex; + Debug.Assert(maxSize >= 0); + return maxSize; + } + } +} diff --git a/src/mscorlib/corefx/System/Globalization/Calendar.cs b/src/mscorlib/corefx/System/Globalization/Calendar.cs index 343682d156..78e9f00d08 100644 --- a/src/mscorlib/corefx/System/Globalization/Calendar.cs +++ b/src/mscorlib/corefx/System/Globalization/Calendar.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Runtime.CompilerServices; -using System.Globalization; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.Serialization; @@ -99,8 +97,14 @@ namespace System.Globalization } } - - + [System.Runtime.InteropServices.ComVisible(false)] + public virtual CalendarAlgorithmType AlgorithmType + { + get + { + return CalendarAlgorithmType.Unknown; + } + } protected Calendar() { @@ -164,9 +168,9 @@ namespace System.Globalization // //////////////////////////////////////////////////////////////////////// [System.Runtime.InteropServices.ComVisible(false)] - internal static Calendar ReadOnly(Calendar calendar) + public static Calendar ReadOnly(Calendar calendar) { - if (calendar == null) { throw new ArgumentNullException("calendar"); } + if (calendar == null) { throw new ArgumentNullException(nameof(calendar)); } Contract.EndContractBlock(); if (calendar.IsReadOnly) { return (calendar); } @@ -206,7 +210,7 @@ namespace System.Globalization // The following code assumes that the current era value can not be -1. if (_currentEraValue == -1) { - Contract.Assert(BaseCalendarID != CalendarId.UNINITIALIZED_VALUE, "[Calendar.CurrentEraValue] Expected a real calendar ID"); + Debug.Assert(BaseCalendarID != CalendarId.UNINITIALIZED_VALUE, "[Calendar.CurrentEraValue] Expected a real calendar ID"); _currentEraValue = CalendarData.GetCalendarData(BaseCalendarID).iCurrentEra; } return (_currentEraValue); @@ -242,7 +246,7 @@ namespace System.Globalization double tempMillis = (value * scale + (value >= 0 ? 0.5 : -0.5)); if (!((tempMillis > -(double)MaxMillis) && (tempMillis < (double)MaxMillis))) { - throw new ArgumentOutOfRangeException("value", SR.ArgumentOutOfRange_AddValue); + throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_AddValue); } long millis = (long)tempMillis; @@ -521,7 +525,7 @@ namespace System.Globalization // this value can be less than 0. It's fine since we are making it positive again in calculating offset. int dayForJan1 = (int)GetDayOfWeek(time) - (dayOfYear % 7); int offset = (dayForJan1 - firstDayOfWeek + 14) % 7; - Contract.Assert(offset >= 0, "Calendar.GetFirstDayWeekOfYear(): offset >= 0"); + Debug.Assert(offset >= 0, "Calendar.GetFirstDayWeekOfYear(): offset >= 0"); return ((dayOfYear + offset) / 7 + 1); } @@ -646,7 +650,7 @@ namespace System.Globalization if ((int)firstDayOfWeek < 0 || (int)firstDayOfWeek > 6) { throw new ArgumentOutOfRangeException( - "firstDayOfWeek", SR.Format(SR.ArgumentOutOfRange_Range, + nameof(firstDayOfWeek), SR.Format(SR.ArgumentOutOfRange_Range, DayOfWeek.Sunday, DayOfWeek.Saturday)); } Contract.EndContractBlock(); @@ -660,7 +664,7 @@ namespace System.Globalization return (GetWeekOfYearFullDays(time, (int)firstDayOfWeek, 4)); } throw new ArgumentOutOfRangeException( - "rule", SR.Format(SR.ArgumentOutOfRange_Range, + nameof(rule), SR.Format(SR.ArgumentOutOfRange_Range, CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek)); } @@ -700,6 +704,16 @@ namespace System.Globalization public abstract bool IsLeapMonth(int year, int month, int era); + // Returns the leap month in a calendar year of the current era. This method returns 0 + // if this calendar does not have leap month, or this year is not a leap year. + // + + [System.Runtime.InteropServices.ComVisible(false)] + public virtual int GetLeapMonth(int year) + { + return (GetLeapMonth(year, CurrentEra)); + } + // Returns the leap month in a calendar year of the specified era. This method returns 0 // if this calendar does not have leap month, or this year is not a leap year. // @@ -808,7 +822,7 @@ namespace System.Globalization { if (year < 0) { - throw new ArgumentOutOfRangeException("year", + throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedNonNegNum); } Contract.EndContractBlock(); @@ -830,7 +844,7 @@ namespace System.Globalization if (millisecond < 0 || millisecond >= MillisPerSecond) { throw new ArgumentOutOfRangeException( - "millisecond", + nameof(millisecond), String.Format( CultureInfo.InvariantCulture, SR.Format(SR.ArgumentOutOfRange_Range, 0, MillisPerSecond - 1))); diff --git a/src/mscorlib/corefx/System/Globalization/CalendarAlgorithmType.cs b/src/mscorlib/corefx/System/Globalization/CalendarAlgorithmType.cs new file mode 100644 index 0000000000..159b0e6f77 --- /dev/null +++ b/src/mscorlib/corefx/System/Globalization/CalendarAlgorithmType.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace System.Globalization +{ + public enum CalendarAlgorithmType + { + Unknown = 0, // This is the default value to return in the Calendar base class. + SolarCalendar = 1, // Solar-base calendar, such as GregorianCalendar, jaoaneseCalendar, JulianCalendar, etc. + // Solar calendars are based on the solar year and seasons. + LunarCalendar = 2, // Lunar-based calendar, such as Hijri and UmAlQuraCalendar. + // Lunar calendars are based on the path of the moon. The seasons are not accurately represented. + LunisolarCalendar = 3 // Lunisolar-based calendar which use leap month rule, such as HebrewCalendar and Asian Lunisolar calendars. + // Lunisolar calendars are based on the cycle of the moon, but consider the seasons as a secondary consideration, + // so they align with the seasons as well as lunar events. + } +} diff --git a/src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs b/src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs index 6c6a18ec37..270d62f143 100644 --- a/src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs +++ b/src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.InteropServices; using System.Security; @@ -200,7 +201,7 @@ namespace System.Globalization break; default: const string unsupportedDateFieldSymbols = "YuUrQqwWDFg"; - Contract.Assert(unsupportedDateFieldSymbols.IndexOf(input[index]) == -1, + Debug.Assert(unsupportedDateFieldSymbols.IndexOf(input[index]) == -1, string.Format(CultureInfo.InvariantCulture, "Encountered an unexpected date field symbol '{0}' from ICU which has no known corresponding .NET equivalent.", input[index])); diff --git a/src/mscorlib/corefx/System/Globalization/CalendarData.cs b/src/mscorlib/corefx/System/Globalization/CalendarData.cs index 2dbd1b8069..d22bd67ac1 100644 --- a/src/mscorlib/corefx/System/Globalization/CalendarData.cs +++ b/src/mscorlib/corefx/System/Globalization/CalendarData.cs @@ -2,11 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; -using System.Diagnostics.Contracts; -using System.Collections.Generic; +using System.Diagnostics; namespace System.Globalization { @@ -108,7 +104,7 @@ namespace System.Globalization if (!LoadCalendarDataFromSystem(localeName, calendarId)) { - Contract.Assert(false, "[CalendarData] LoadCalendarDataFromSystem call isn't expected to fail for calendar " + calendarId + " locale " + localeName); + Debug.Assert(false, "[CalendarData] LoadCalendarDataFromSystem call isn't expected to fail for calendar " + calendarId + " locale " + localeName); // Something failed, try invariant for missing parts // This is really not good, but we don't want the callers to crash. diff --git a/src/mscorlib/corefx/System/Globalization/CalendricalCalculationsHelper.cs b/src/mscorlib/corefx/System/Globalization/CalendricalCalculationsHelper.cs index ba7601b420..149e63c689 100644 --- a/src/mscorlib/corefx/System/Globalization/CalendricalCalculationsHelper.cs +++ b/src/mscorlib/corefx/System/Globalization/CalendricalCalculationsHelper.cs @@ -2,8 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Diagnostics.Contracts; +using System.Diagnostics; namespace System.Globalization { @@ -126,7 +125,7 @@ namespace System.Globalization return longitude; } - static public double AsDayFraction(double longitude) + public static double AsDayFraction(double longitude) { return longitude / FullCircleOfArc; } @@ -153,7 +152,7 @@ namespace System.Globalization // the following formulas defines a polynomial function which gives us the amount that the earth is slowing down for specific year ranges private static double DefaultEphemerisCorrection(int gregorianYear) { - Contract.Assert(gregorianYear < 1620 || 2020 <= gregorianYear); + Debug.Assert(gregorianYear < 1620 || 2020 <= gregorianYear); long january1stOfYear = GetNumberOfDays(new DateTime(gregorianYear, 1, 1)); double daysSinceStartOf1810 = january1stOfYear - StartOf1810; double x = TwelveHours + daysSinceStartOf1810; @@ -162,34 +161,34 @@ namespace System.Globalization private static double EphemerisCorrection1988to2019(int gregorianYear) { - Contract.Assert(1988 <= gregorianYear && gregorianYear <= 2019); + Debug.Assert(1988 <= gregorianYear && gregorianYear <= 2019); return (double)(gregorianYear - 1933) / SecondsPerDay; } private static double EphemerisCorrection1900to1987(int gregorianYear) { - Contract.Assert(1900 <= gregorianYear && gregorianYear <= 1987); + Debug.Assert(1900 <= gregorianYear && gregorianYear <= 1987); double centuriesFrom1900 = CenturiesFrom1900(gregorianYear); return PolynomialSum(s_coefficients1900to1987, centuriesFrom1900); } private static double EphemerisCorrection1800to1899(int gregorianYear) { - Contract.Assert(1800 <= gregorianYear && gregorianYear <= 1899); + Debug.Assert(1800 <= gregorianYear && gregorianYear <= 1899); double centuriesFrom1900 = CenturiesFrom1900(gregorianYear); return PolynomialSum(s_coefficients1800to1899, centuriesFrom1900); } private static double EphemerisCorrection1700to1799(int gregorianYear) { - Contract.Assert(1700 <= gregorianYear && gregorianYear <= 1799); + Debug.Assert(1700 <= gregorianYear && gregorianYear <= 1799); double yearsSince1700 = gregorianYear - 1700; return PolynomialSum(s_coefficients1700to1799, yearsSince1700) / SecondsPerDay; } private static double EphemerisCorrection1620to1699(int gregorianYear) { - Contract.Assert(1620 <= gregorianYear && gregorianYear <= 1699); + Debug.Assert(1620 <= gregorianYear && gregorianYear <= 1699); double yearsSince1600 = gregorianYear - 1600; return PolynomialSum(s_coefficients1620to1699, yearsSince1600) / SecondsPerDay; } @@ -216,11 +215,11 @@ namespace System.Globalization } } - Contract.Assert(false, "Not expected to come here"); + Debug.Assert(false, "Not expected to come here"); return DefaultEphemerisCorrection(year); } - static public double JulianCenturies(double moment) + public static double JulianCenturies(double moment) { double dynamicalMoment = moment + EphemerisCorrection(moment); return (dynamicalMoment - Noon2000Jan01) / DaysInUniformLengthCentury; @@ -274,7 +273,7 @@ namespace System.Globalization } // midday - static public double Midday(double date, double longitude) + public static double Midday(double date, double longitude) { return AsLocalTime(date + TwelveHours, longitude) - AsDayFraction(longitude); } @@ -285,7 +284,7 @@ namespace System.Globalization } // midday-in-tehran - static public double MiddayAtPersianObservationSite(double date) + public static double MiddayAtPersianObservationSite(double date) { return Midday(date, InitLongitude(52.5)); // 52.5 degrees east - longitude of UTC+3:30 which defines Iranian Standard Time } @@ -362,7 +361,7 @@ namespace System.Globalization return (-0.004778 * SinOfDegree(a)) - (0.0003667 * SinOfDegree(b)); } - static public double Compute(double time) + public static double Compute(double time) { double julianCenturies = JulianCenturies(time); double lambda = 282.7771834 @@ -373,7 +372,7 @@ namespace System.Globalization return InitLongitude(longitude); } - static public double AsSeason(double longitude) + public static double AsSeason(double longitude) { return (longitude < 0) ? (longitude + FullCircleOfArc) : longitude; } @@ -405,7 +404,7 @@ namespace System.Globalization break; } } - Contract.Assert(day != upperBoundNewYearDay); + Debug.Assert(day != upperBoundNewYearDay); return day - 1; } diff --git a/src/mscorlib/corefx/System/Globalization/CharUnicodeInfo.cs b/src/mscorlib/corefx/System/Globalization/CharUnicodeInfo.cs index 4cb95fb8f1..dc38ca405b 100644 --- a/src/mscorlib/corefx/System/Globalization/CharUnicodeInfo.cs +++ b/src/mscorlib/corefx/System/Globalization/CharUnicodeInfo.cs @@ -12,12 +12,7 @@ // //////////////////////////////////////////////////////////////////////////// -using System; -using System.Threading; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; -using System.Reflection; -using System.Security; +using System.Diagnostics; using System.Diagnostics.Contracts; namespace System.Globalization @@ -59,8 +54,8 @@ namespace System.Globalization internal static int InternalConvertToUtf32(String s, int index) { - Contract.Assert(s != null, "s != null"); - Contract.Assert(index >= 0 && index < s.Length, "index < s.Length"); + Debug.Assert(s != null, "s != null"); + Debug.Assert(index >= 0 && index < s.Length, "index < s.Length"); if (index < s.Length - 1) { int temp1 = (int)s[index] - HIGH_SURROGATE_START; @@ -100,9 +95,9 @@ namespace System.Globalization internal static int InternalConvertToUtf32(String s, int index, out int charLength) { - Contract.Assert(s != null, "s != null"); - Contract.Assert(s.Length > 0, "s.Length > 0"); - Contract.Assert(index >= 0 && index < s.Length, "index >= 0 && index < s.Length"); + Debug.Assert(s != null, "s != null"); + Debug.Assert(s.Length > 0, "s.Length > 0"); + Debug.Assert(index >= 0 && index < s.Length, "index >= 0 && index < s.Length"); charLength = 1; if (index < s.Length - 1) { @@ -131,8 +126,8 @@ namespace System.Globalization internal static bool IsWhiteSpace(String s, int index) { - Contract.Assert(s != null, "s!=null"); - Contract.Assert(index >= 0 && index < s.Length, "index >= 0 && index < s.Length"); + Debug.Assert(s != null, "s!=null"); + Debug.Assert(index >= 0 && index < s.Length, "index >= 0 && index < s.Length"); UnicodeCategory uc = GetUnicodeCategory(s, index); // In Unicode 3.0, U+2028 is the only character which is under the category "LineSeparator". @@ -170,9 +165,9 @@ namespace System.Globalization // // Note that for ch in the range D800-DFFF we just treat it as any other non-numeric character // - internal unsafe static double InternalGetNumericValue(int ch) + internal static unsafe double InternalGetNumericValue(int ch) { - Contract.Assert(ch >= 0 && ch <= 0x10ffff, "ch is not in valid Unicode range."); + Debug.Assert(ch >= 0 && ch <= 0x10ffff, "ch is not in valid Unicode range."); // Get the level 2 item from the highest 12 bit (8 - 19) of ch. ushort index = s_pNumericLevel1Index[ch >> 8]; // Get the level 2 WORD offset from the 4 - 7 bit of ch. This provides the base offset of the level 3 table. @@ -191,6 +186,21 @@ namespace System.Globalization } } + internal static unsafe ushort InternalGetDigitValues(int ch) + { + Debug.Assert(ch >= 0 && ch <= 0x10ffff, "ch is not in valid Unicode range."); + // Get the level 2 item from the highest 12 bit (8 - 19) of ch. + ushort index = s_pNumericLevel1Index[ch >> 8]; + // Get the level 2 WORD offset from the 4 - 7 bit of ch. This provides the base offset of the level 3 table. + // Note that & has the lower precedence than addition, so don't forget the parathesis. + index = s_pNumericLevel1Index[index + ((ch >> 4) & 0x000f)]; + + fixed (ushort* pUshortPtr = &(s_pNumericLevel1Index[index])) + { + byte* pBytePtr = (byte*)pUshortPtr; + return s_pDigitValues[pBytePtr[(ch & 0x000f)]]; + } + } //////////////////////////////////////////////////////////////////////// // @@ -218,16 +228,58 @@ namespace System.Globalization { if (s == null) { - throw new ArgumentNullException("s"); + throw new ArgumentNullException(nameof(s)); } if (index < 0 || index >= s.Length) { - throw new ArgumentOutOfRangeException("index", SR.ArgumentOutOfRange_Index); + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index); } Contract.EndContractBlock(); return (InternalGetNumericValue(InternalConvertToUtf32(s, index))); } + public static int GetDecimalDigitValue(char ch) + { + return (sbyte) (InternalGetDigitValues(ch) >> 8); + } + + public static int GetDecimalDigitValue(String s, int index) + { + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (index < 0 || index >= s.Length) + { + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index); + } + Contract.EndContractBlock(); + + return (sbyte) (InternalGetDigitValues(InternalConvertToUtf32(s, index)) >> 8); + } + + public static int GetDigitValue(char ch) + { + return (sbyte) (InternalGetDigitValues(ch) & 0x00FF); + } + + public static int GetDigitValue(String s, int index) + { + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (index < 0 || index >= s.Length) + { + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index); + } + + Contract.EndContractBlock(); + return (sbyte) (InternalGetDigitValues(InternalConvertToUtf32(s, index)) & 0x00FF); + } + public static UnicodeCategory GetUnicodeCategory(char ch) { return (InternalGetUnicodeCategory(ch)); @@ -236,16 +288,16 @@ namespace System.Globalization public static UnicodeCategory GetUnicodeCategory(String s, int index) { if (s == null) - throw new ArgumentNullException("s"); + throw new ArgumentNullException(nameof(s)); if (((uint)index) >= ((uint)s.Length)) { - throw new ArgumentOutOfRangeException("index"); + throw new ArgumentOutOfRangeException(nameof(index)); } Contract.EndContractBlock(); return InternalGetUnicodeCategory(s, index); } - internal unsafe static UnicodeCategory InternalGetUnicodeCategory(int ch) + internal static unsafe UnicodeCategory InternalGetUnicodeCategory(int ch) { return ((UnicodeCategory)InternalGetCategoryValue(ch, UNICODE_CATEGORY_OFFSET)); } @@ -265,9 +317,9 @@ namespace System.Globalization // //////////////////////////////////////////////////////////////////////// - internal unsafe static byte InternalGetCategoryValue(int ch, int offset) + internal static unsafe byte InternalGetCategoryValue(int ch, int offset) { - Contract.Assert(ch >= 0 && ch <= 0x10ffff, "ch is not in valid Unicode range."); + Debug.Assert(ch >= 0 && ch <= 0x10ffff, "ch is not in valid Unicode range."); // Get the level 2 item from the highest 12 bit (8 - 19) of ch. ushort index = s_pCategoryLevel1Index[ch >> 8]; // Get the level 2 WORD offset from the 4 - 7 bit of ch. This provides the base offset of the level 3 table. @@ -284,7 +336,7 @@ namespace System.Globalization // Make sure that OtherNotAssigned is the last category in UnicodeCategory. // If that changes, change the following assertion as well. // - //Contract.Assert(uc >= 0 && uc <= UnicodeCategory.OtherNotAssigned, "Table returns incorrect Unicode category"); + //Debug.Assert(uc >= 0 && uc <= UnicodeCategory.OtherNotAssigned, "Table returns incorrect Unicode category"); return (uc); } } @@ -304,8 +356,8 @@ namespace System.Globalization internal static UnicodeCategory InternalGetUnicodeCategory(String value, int index) { - Contract.Assert(value != null, "value can not be null"); - Contract.Assert(index < value.Length, "index < value.Length"); + Debug.Assert(value != null, "value can not be null"); + Debug.Assert(index < value.Length, "index < value.Length"); return (InternalGetUnicodeCategory(InternalConvertToUtf32(value, index))); } @@ -319,16 +371,16 @@ namespace System.Globalization internal static UnicodeCategory InternalGetUnicodeCategory(String str, int index, out int charLength) { - Contract.Assert(str != null, "str can not be null"); - Contract.Assert(str.Length > 0, "str.Length > 0"); ; - Contract.Assert(index >= 0 && index < str.Length, "index >= 0 && index < str.Length"); + Debug.Assert(str != null, "str can not be null"); + Debug.Assert(str.Length > 0, "str.Length > 0"); ; + Debug.Assert(index >= 0 && index < str.Length, "index >= 0 && index < str.Length"); return (InternalGetUnicodeCategory(InternalConvertToUtf32(str, index, out charLength))); } internal static bool IsCombiningCategory(UnicodeCategory uc) { - Contract.Assert(uc >= 0, "uc >= 0"); + Debug.Assert(uc >= 0, "uc >= 0"); return ( uc == UnicodeCategory.NonSpacingMark || uc == UnicodeCategory.SpacingCombiningMark || diff --git a/src/mscorlib/corefx/System/Globalization/CharUnicodeInfoData.cs b/src/mscorlib/corefx/System/Globalization/CharUnicodeInfoData.cs index 7284cfd3bc..b1bef8146e 100644 --- a/src/mscorlib/corefx/System/Globalization/CharUnicodeInfoData.cs +++ b/src/mscorlib/corefx/System/Globalization/CharUnicodeInfoData.cs @@ -1218,5 +1218,30 @@ namespace System.Globalization 0x00, 0x00, 0x00, 0x00, 0x80, 0x84, 0x2e, 0x41, 0x00, 0x00, 0x00, 0x00, 0x84, 0xd7, 0x97, 0x41, 0x00, 0x00, 0x00, 0x20, 0x5f, 0xa0, 0x02, 0x42, 0x00, 0x00, 0x00, 0xa2, 0x94, 0x1a, 0x6d, 0x42 }; + + private static ushort[] s_pDigitValues = new ushort [] + { + 0xffff, 0x0000, 0x0101, 0x0202, 0x0303, 0x0404, 0x0505, 0x0606, + 0x0707, 0x0808, 0x0909, 0xff02, 0xff03, 0xff01, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xff04, 0xff05, 0xff06, + 0xff07, 0xff08, 0xff09, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xff00, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000 + }; + } } diff --git a/src/mscorlib/corefx/System/Globalization/ChineseLunisolarCalendar.cs b/src/mscorlib/corefx/System/Globalization/ChineseLunisolarCalendar.cs index 48f62019d7..271d9802ce 100644 --- a/src/mscorlib/corefx/System/Globalization/ChineseLunisolarCalendar.cs +++ b/src/mscorlib/corefx/System/Globalization/ChineseLunisolarCalendar.cs @@ -342,13 +342,13 @@ namespace System.Globalization { if (era != CurrentEra && era != ChineseEra) { - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } if (year < MIN_LUNISOLAR_YEAR || year > MAX_LUNISOLAR_YEAR) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, MIN_LUNISOLAR_YEAR, MAX_LUNISOLAR_YEAR)); diff --git a/src/mscorlib/corefx/System/Globalization/CompareInfo.Unix.cs b/src/mscorlib/corefx/System/Globalization/CompareInfo.Unix.cs index 2aaf5a22fa..21c3c9f7e4 100644 --- a/src/mscorlib/corefx/System/Globalization/CompareInfo.Unix.cs +++ b/src/mscorlib/corefx/System/Globalization/CompareInfo.Unix.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -17,7 +18,6 @@ namespace System.Globalization [NonSerialized] private bool _isAsciiEqualityOrdinal; - [SecuritySafeCritical] internal CompareInfo(CultureInfo culture) { _name = culture.m_name; @@ -27,14 +27,23 @@ namespace System.Globalization private void InitSort(CultureInfo culture) { _sortName = culture.SortName; - _sortHandle = Interop.GlobalizationInterop.GetSortHandle(GetNullTerminatedUtf8String(_sortName)); + Interop.GlobalizationInterop.ResultCode resultCode = Interop.GlobalizationInterop.GetSortHandle(GetNullTerminatedUtf8String(_sortName), out _sortHandle); + if (resultCode != Interop.GlobalizationInterop.ResultCode.Success) + { + _sortHandle.Dispose(); + + if (resultCode == Interop.GlobalizationInterop.ResultCode.OutOfMemory) + throw new OutOfMemoryException(); + + throw new ExternalException(SR.Arg_ExternalException); + } _isAsciiEqualityOrdinal = (_sortName == "en-US" || _sortName == ""); } internal static unsafe int IndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase) { - Contract.Assert(source != null); - Contract.Assert(value != null); + Debug.Assert(source != null); + Debug.Assert(value != null); if (value.Length == 0) { @@ -77,8 +86,8 @@ namespace System.Globalization internal static unsafe int LastIndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase) { - Contract.Assert(source != null); - Contract.Assert(value != null); + Debug.Assert(source != null); + Debug.Assert(value != null); if (value.Length == 0) { @@ -124,8 +133,8 @@ namespace System.Globalization private int GetHashCodeOfStringCore(string source, CompareOptions options) { - Contract.Assert(source != null); - Contract.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); + Debug.Assert(source != null); + Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); return GetHashCodeOfStringCore(source, options, forceRandomizedHashing: false, additionalEntropy: 0); } @@ -137,9 +146,9 @@ namespace System.Globalization private unsafe int CompareString(string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options) { - Contract.Assert(string1 != null); - Contract.Assert(string2 != null); - Contract.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); + Debug.Assert(string1 != null); + Debug.Assert(string2 != null); + Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); fixed (char* pString1 = string1) { @@ -152,9 +161,9 @@ namespace System.Globalization private unsafe int IndexOfCore(string source, string target, int startIndex, int count, CompareOptions options) { - Contract.Assert(!string.IsNullOrEmpty(source)); - Contract.Assert(target != null); - Contract.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0); + Debug.Assert(!string.IsNullOrEmpty(source)); + Debug.Assert(target != null); + Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0); if (target.Length == 0) { @@ -181,9 +190,9 @@ namespace System.Globalization private unsafe int LastIndexOfCore(string source, string target, int startIndex, int count, CompareOptions options) { - Contract.Assert(!string.IsNullOrEmpty(source)); - Contract.Assert(target != null); - Contract.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0); + Debug.Assert(!string.IsNullOrEmpty(source)); + Debug.Assert(target != null); + Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0); if (target.Length == 0) { @@ -212,12 +221,11 @@ namespace System.Globalization } } - [SecuritySafeCritical] private bool StartsWith(string source, string prefix, CompareOptions options) { - Contract.Assert(!string.IsNullOrEmpty(source)); - Contract.Assert(!string.IsNullOrEmpty(prefix)); - Contract.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); + Debug.Assert(!string.IsNullOrEmpty(source)); + Debug.Assert(!string.IsNullOrEmpty(prefix)); + Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && prefix.IsFastSort()) { @@ -227,12 +235,11 @@ namespace System.Globalization return Interop.GlobalizationInterop.StartsWith(_sortHandle, prefix, prefix.Length, source, source.Length, options); } - [SecuritySafeCritical] private bool EndsWith(string source, string suffix, CompareOptions options) { - Contract.Assert(!string.IsNullOrEmpty(source)); - Contract.Assert(!string.IsNullOrEmpty(suffix)); - Contract.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); + Debug.Assert(!string.IsNullOrEmpty(source)); + Debug.Assert(!string.IsNullOrEmpty(suffix)); + Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && suffix.IsFastSort()) { @@ -241,16 +248,81 @@ namespace System.Globalization return Interop.GlobalizationInterop.EndsWith(_sortHandle, suffix, suffix.Length, source, source.Length, options); } + + private unsafe SortKey CreateSortKey(String source, CompareOptions options) + { + if (source==null) { throw new ArgumentNullException(nameof(source)); } + Contract.EndContractBlock(); + + if ((options & ValidSortkeyCtorMaskOffFlags) != 0) + { + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), nameof(options)); + } + + byte [] keyData; + if (source.Length == 0) + { + keyData = EmptyArray.Value; + } + else + { + int sortKeyLength = Interop.GlobalizationInterop.GetSortKey(_sortHandle, source, source.Length, null, 0, options); + keyData = new byte[sortKeyLength]; + + fixed (byte* pSortKey = keyData) + { + Interop.GlobalizationInterop.GetSortKey(_sortHandle, source, source.Length, pSortKey, sortKeyLength, options); + } + } + + return new SortKey(Name, source, options, keyData); + } + + private unsafe static bool IsSortable(char *text, int length) + { + int index = 0; + UnicodeCategory uc; + + while (index < length) + { + if (Char.IsHighSurrogate(text[index])) + { + if (index == length - 1 || !Char.IsLowSurrogate(text[index+1])) + return false; // unpaired surrogate + + uc = CharUnicodeInfo.InternalGetUnicodeCategory(Char.ConvertToUtf32(text[index], text[index+1])); + if (uc == UnicodeCategory.PrivateUse || uc == UnicodeCategory.OtherNotAssigned) + return false; + + index += 2; + continue; + } + + if (Char.IsLowSurrogate(text[index])) + { + return false; // unpaired surrogate + } + + uc = CharUnicodeInfo.GetUnicodeCategory(text[index]); + if (uc == UnicodeCategory.PrivateUse || uc == UnicodeCategory.OtherNotAssigned) + { + return false; + } + + index++; + } + + return true; + } // ----------------------------- // ---- PAL layer ends here ---- // ----------------------------- - [SecuritySafeCritical] internal unsafe int GetHashCodeOfStringCore(string source, CompareOptions options, bool forceRandomizedHashing, long additionalEntropy) { - Contract.Assert(source != null); - Contract.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); + Debug.Assert(source != null); + Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); if (source.Length == 0) { @@ -307,9 +379,19 @@ namespace System.Globalization int bytesWritten = System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, buffer, 0); - Contract.Assert(bytesWritten == byteLen); + Debug.Assert(bytesWritten == byteLen); return buffer; } + + private SortVersion GetSortVersion() + { + int sortVersion = Interop.GlobalizationInterop.GetSortVersion(); + return new SortVersion(sortVersion, LCID, new Guid(sortVersion, 0, 0, 0, 0, 0, 0, + (byte) (LCID >> 24), + (byte) ((LCID & 0x00FF0000) >> 16), + (byte) ((LCID & 0x0000FF00) >> 8), + (byte) (LCID & 0xFF))); + } } } diff --git a/src/mscorlib/corefx/System/Globalization/CompareInfo.Windows.cs b/src/mscorlib/corefx/System/Globalization/CompareInfo.Windows.cs index 744a48b107..4ebaf9cb10 100644 --- a/src/mscorlib/corefx/System/Globalization/CompareInfo.Windows.cs +++ b/src/mscorlib/corefx/System/Globalization/CompareInfo.Windows.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; using System.Diagnostics.Contracts; namespace System.Globalization @@ -53,24 +54,24 @@ namespace System.Globalization internal static int IndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase) { - Contract.Assert(source != null); - Contract.Assert(value != null); + Debug.Assert(source != null); + Debug.Assert(value != null); return FindStringOrdinal(FIND_FROMSTART, source, startIndex, count, value, value.Length, ignoreCase); } internal static int LastIndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase) { - Contract.Assert(source != null); - Contract.Assert(value != null); + Debug.Assert(source != null); + Debug.Assert(value != null); return FindStringOrdinal(FIND_FROMEND, source, startIndex - count + 1, count, value, value.Length, ignoreCase); } private unsafe int GetHashCodeOfStringCore(string source, CompareOptions options) { - Contract.Assert(source != null); - Contract.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); + Debug.Assert(source != null); + Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); if (source.Length == 0) { @@ -102,9 +103,9 @@ namespace System.Globalization private unsafe int CompareString(string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options) { - Contract.Assert(string1 != null); - Contract.Assert(string2 != null); - Contract.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); + Debug.Assert(string1 != null); + Debug.Assert(string2 != null); + Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); string localeName = _sortHandle != IntPtr.Zero ? null : _sortName; @@ -167,9 +168,9 @@ namespace System.Globalization private int IndexOfCore(string source, string target, int startIndex, int count, CompareOptions options) { - Contract.Assert(!string.IsNullOrEmpty(source)); - Contract.Assert(target != null); - Contract.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0); + Debug.Assert(!string.IsNullOrEmpty(source)); + Debug.Assert(target != null); + Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0); // TODO: Consider moving this up to the relevent APIs we need to ensure this behavior for // and add a precondition that target is not empty. @@ -200,9 +201,9 @@ namespace System.Globalization private int LastIndexOfCore(string source, string target, int startIndex, int count, CompareOptions options) { - Contract.Assert(!string.IsNullOrEmpty(source)); - Contract.Assert(target != null); - Contract.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0); + Debug.Assert(!string.IsNullOrEmpty(source)); + Debug.Assert(target != null); + Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0); // TODO: Consider moving this up to the relevent APIs we need to ensure this behavior for // and add a precondition that target is not empty. @@ -234,9 +235,9 @@ namespace System.Globalization private bool StartsWith(string source, string prefix, CompareOptions options) { - Contract.Assert(!string.IsNullOrEmpty(source)); - Contract.Assert(!string.IsNullOrEmpty(prefix)); - Contract.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); + Debug.Assert(!string.IsNullOrEmpty(source)); + Debug.Assert(!string.IsNullOrEmpty(prefix)); + Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); return FindString(FIND_STARTSWITH | (uint)GetNativeCompareFlags(options), source, @@ -249,9 +250,9 @@ namespace System.Globalization private bool EndsWith(string source, string suffix, CompareOptions options) { - Contract.Assert(!string.IsNullOrEmpty(source)); - Contract.Assert(!string.IsNullOrEmpty(suffix)); - Contract.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); + Debug.Assert(!string.IsNullOrEmpty(source)); + Debug.Assert(!string.IsNullOrEmpty(suffix)); + Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0); return FindString(FIND_ENDSWITH | (uint)GetNativeCompareFlags(options), source, @@ -280,14 +281,10 @@ namespace System.Globalization int sourceStartIndex = findLastIndex ? startIndex - sourceCount + 1 : startIndex; -#if !TEST_CODEGEN_OPTIMIZATION fixed (char* pSource = source, spTarget = target) { char* spSubSource = pSource + sourceStartIndex; -#else - String.StringPointer spSubSource = source.GetStringPointer(sourceStartIndex); - String.StringPointer spTarget = target.GetStringPointer(); -#endif + if (findLastIndex) { int startPattern = (sourceCount - 1) - targetCount + 1; @@ -347,11 +344,29 @@ namespace System.Globalization retValue += startIndex; } } -#if !TEST_CODEGEN_OPTIMIZATION } return retValue; -#endif // TEST_CODEGEN_OPTIMIZATION + } + + private unsafe SortKey CreateSortKey(String source, CompareOptions options) + { + if (source==null) { throw new ArgumentNullException(nameof(source)); } + Contract.EndContractBlock(); + + if ((options & ValidSortkeyCtorMaskOffFlags) != 0) + { + throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); + } + + throw new NotImplementedException(); + } + + private static unsafe bool IsSortable(char *text, int length) + { + // CompareInfo c = CultureInfo.InvariantCulture.CompareInfo; + // return (InternalIsSortable(c.m_dataHandle, c.m_handleOrigin, c.m_sortName, text, text.Length)); + throw new NotImplementedException(); } private const int COMPARE_OPTIONS_ORDINAL = 0x40000000; // Ordinal @@ -381,7 +396,7 @@ namespace System.Globalization // Suffix & Prefix shouldn't use this, make sure to turn off the NORM_LINGUISTIC_CASING flag if (options == CompareOptions.Ordinal) { nativeCompareFlags = COMPARE_OPTIONS_ORDINAL; } - Contract.Assert(((options & ~(CompareOptions.IgnoreCase | + Debug.Assert(((options & ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreSymbols | @@ -391,5 +406,10 @@ namespace System.Globalization return nativeCompareFlags; } + + private SortVersion GetSortVersion() + { + throw new NotImplementedException(); + } } } diff --git a/src/mscorlib/corefx/System/Globalization/CompareInfo.cs b/src/mscorlib/corefx/System/Globalization/CompareInfo.cs index 77778af23c..64dbfe84f7 100644 --- a/src/mscorlib/corefx/System/Globalization/CompareInfo.cs +++ b/src/mscorlib/corefx/System/Globalization/CompareInfo.cs @@ -12,20 +12,15 @@ // //////////////////////////////////////////////////////////////////////////// -using System; -using System.Collections; -using System.Collections.Generic; +using System.Reflection; +using System.Diagnostics; using System.Diagnostics.Contracts; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Serialization; -using System.Threading; namespace System.Globalization { [Flags] [Serializable] - [System.Runtime.InteropServices.ComVisible(true)] public enum CompareOptions { None = 0x00000000, @@ -40,7 +35,6 @@ namespace System.Globalization } [Serializable] - [System.Runtime.InteropServices.ComVisible(true)] public partial class CompareInfo : IDeserializationCallback { // Mask used to check if IndexOf()/LastIndexOf()/IsPrefix()/IsPostfix() has the right flags. @@ -58,6 +52,11 @@ namespace System.Globalization ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType); + // Mask used to check if we have the right flags. + private const CompareOptions ValidSortkeyCtorMaskOffFlags = + ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | + CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort); + // // CompareInfos have an interesting identity. They are attached to the locale that created them, // ie: en-US would have an en-US sort. For haw-US (custom), then we serialize it as haw-US. @@ -69,6 +68,86 @@ namespace System.Globalization [NonSerialized] private String _sortName; // The name that defines our behavior + [OptionalField(VersionAdded = 3)] + private SortVersion _sortVersion; + + /*=================================GetCompareInfo========================== + **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture. + ** Warning: The assembly versioning mechanism is dead! + **Returns: The CompareInfo for the specified culture. + **Arguments: + ** culture the ID of the culture + ** assembly the assembly which contains the sorting table. + **Exceptions: + ** ArugmentNullException when the assembly is null + ** ArgumentException if culture is invalid. + ============================================================================*/ + // Assembly constructor should be deprecated, we don't act on the assembly information any more + public static CompareInfo GetCompareInfo(int culture, Assembly assembly) + { + // Parameter checking. + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + if (assembly != typeof(Object).Module.Assembly) + { + throw new ArgumentException(SR.Argument_OnlyMscorlib); + } + Contract.EndContractBlock(); + + return GetCompareInfo(culture); + } + + /*=================================GetCompareInfo========================== + **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture. + ** The purpose of this method is to provide version for CompareInfo tables. + **Returns: The CompareInfo for the specified culture. + **Arguments: + ** name the name of the culture + ** assembly the assembly which contains the sorting table. + **Exceptions: + ** ArugmentNullException when the assembly is null + ** ArgumentException if name is invalid. + ============================================================================*/ + // Assembly constructor should be deprecated, we don't act on the assembly information any more + public static CompareInfo GetCompareInfo(String name, Assembly assembly) + { + if (name == null || assembly == null) + { + throw new ArgumentNullException(name == null ? nameof(name) : nameof(assembly)); + } + Contract.EndContractBlock(); + + if (assembly != typeof(Object).Module.Assembly) + { + throw new ArgumentException(SR.Argument_OnlyMscorlib); + } + + return GetCompareInfo(name); + } + + /*=================================GetCompareInfo========================== + **Action: Get the CompareInfo for the specified culture. + ** This method is provided for ease of integration with NLS-based software. + **Returns: The CompareInfo for the specified culture. + **Arguments: + ** culture the ID of the culture. + **Exceptions: + ** ArgumentException if culture is invalid. + ============================================================================*/ + // People really shouldn't be calling LCID versions, no custom support + public static CompareInfo GetCompareInfo(int culture) + { + if (CultureData.IsCustomCultureId(culture)) + { + // Customized culture cannot be created by the LCID. + throw new ArgumentException(SR.Argument_CustomCultureCannotBePassedByNumber, nameof(culture)); + } + + return CultureInfo.GetCultureInfo(culture).CompareInfo; + } + /*=================================GetCompareInfo========================== **Action: Get the CompareInfo for the specified culture. **Returns: The CompareInfo for the specified culture. @@ -82,13 +161,40 @@ namespace System.Globalization { if (name == null) { - throw new ArgumentNullException("name"); + throw new ArgumentNullException(nameof(name)); } Contract.EndContractBlock(); return CultureInfo.GetCultureInfo(name).CompareInfo; } + public static unsafe bool IsSortable(char ch) + { + char *pChar = &ch; + return IsSortable(pChar, 1); + } + + public static unsafe bool IsSortable(string text) + { + if (text == null) + { + // A null param is invalid here. + throw new ArgumentNullException(nameof(text)); + } + + if (0 == text.Length) + { + // A zero length string is not invalid, but it is also not sortable. + return (false); + } + + fixed (char *pChar = text) + { + return IsSortable(pChar, text.Length); + } + } + + [OnDeserializing] private void OnDeserializing(StreamingContext ctx) { @@ -130,12 +236,11 @@ namespace System.Globalization // //////////////////////////////////////////////////////////////////////// - [System.Runtime.InteropServices.ComVisible(false)] public virtual String Name { get { - Contract.Assert(_name != null, "CompareInfo.Name Expected _name to be set"); + Debug.Assert(_name != null, "CompareInfo.Name Expected _name to be set"); if (_name == "zh-CHT" || _name == "zh-CHS") { return _name; @@ -173,14 +278,14 @@ namespace System.Globalization { if (options != CompareOptions.Ordinal) { - throw new ArgumentException(SR.Argument_CompareOptionOrdinal, "options"); + throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options)); } return String.CompareOrdinal(string1, string2); } if ((options & ValidCompareMaskOffFlags) != 0) { - throw new ArgumentException(SR.Argument_InvalidFlag, "options"); + throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); } //Our paradigm is that null sorts less than any other string and @@ -247,31 +352,31 @@ namespace System.Globalization // Verify inputs if (length1 < 0 || length2 < 0) { - throw new ArgumentOutOfRangeException((length1 < 0) ? "length1" : "length2", SR.ArgumentOutOfRange_NeedPosNum); + throw new ArgumentOutOfRangeException((length1 < 0) ? nameof(length1) : nameof(length2), SR.ArgumentOutOfRange_NeedPosNum); } if (offset1 < 0 || offset2 < 0) { - throw new ArgumentOutOfRangeException((offset1 < 0) ? "offset1" : "offset2", SR.ArgumentOutOfRange_NeedPosNum); + throw new ArgumentOutOfRangeException((offset1 < 0) ? nameof(offset1) : nameof(offset2), SR.ArgumentOutOfRange_NeedPosNum); } if (offset1 > (string1 == null ? 0 : string1.Length) - length1) { - throw new ArgumentOutOfRangeException("string1", SR.ArgumentOutOfRange_OffsetLength); + throw new ArgumentOutOfRangeException(nameof(string1), SR.ArgumentOutOfRange_OffsetLength); } if (offset2 > (string2 == null ? 0 : string2.Length) - length2) { - throw new ArgumentOutOfRangeException("string2", SR.ArgumentOutOfRange_OffsetLength); + throw new ArgumentOutOfRangeException(nameof(string2), SR.ArgumentOutOfRange_OffsetLength); } if ((options & CompareOptions.Ordinal) != 0) { if (options != CompareOptions.Ordinal) { throw new ArgumentException(SR.Argument_CompareOptionOrdinal, - "options"); + nameof(options)); } } else if ((options & ValidCompareMaskOffFlags) != 0) { - throw new ArgumentException(SR.Argument_InvalidFlag, "options"); + throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); } // @@ -318,8 +423,8 @@ namespace System.Globalization // internal static unsafe int CompareOrdinalIgnoreCase(string strA, int indexA, int lengthA, string strB, int indexB, int lengthB) { - Contract.Assert(indexA + lengthA <= strA.Length); - Contract.Assert(indexB + lengthB <= strB.Length); + Debug.Assert(indexA + lengthA <= strA.Length); + Debug.Assert(indexB + lengthB <= strB.Length); int length = Math.Min(lengthA, lengthB); int range = length; @@ -375,7 +480,7 @@ namespace System.Globalization { if (source == null || prefix == null) { - throw new ArgumentNullException((source == null ? "source" : "prefix"), + throw new ArgumentNullException((source == null ? nameof(source) : nameof(prefix)), SR.ArgumentNull_String); } Contract.EndContractBlock(); @@ -402,7 +507,7 @@ namespace System.Globalization if ((options & ValidIndexMaskOffFlags) != 0) { - throw new ArgumentException(SR.Argument_InvalidFlag, "options"); + throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); } return StartsWith(source, prefix, options); @@ -425,7 +530,7 @@ namespace System.Globalization { if (source == null || suffix == null) { - throw new ArgumentNullException((source == null ? "source" : "suffix"), + throw new ArgumentNullException((source == null ? nameof(source) : nameof(suffix)), SR.ArgumentNull_String); } Contract.EndContractBlock(); @@ -452,7 +557,7 @@ namespace System.Globalization if ((options & ValidIndexMaskOffFlags) != 0) { - throw new ArgumentException(SR.Argument_InvalidFlag, "options"); + throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); } return EndsWith(source, suffix, options); @@ -481,7 +586,7 @@ namespace System.Globalization public unsafe virtual int IndexOf(String source, char value) { if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); return IndexOf(source, value, 0, source.Length, CompareOptions.None); @@ -491,7 +596,7 @@ namespace System.Globalization public unsafe virtual int IndexOf(String source, String value) { if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); return IndexOf(source, value, 0, source.Length, CompareOptions.None); @@ -501,7 +606,7 @@ namespace System.Globalization public unsafe virtual int IndexOf(String source, char value, CompareOptions options) { if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); return IndexOf(source, value, 0, source.Length, options); @@ -511,17 +616,34 @@ namespace System.Globalization public unsafe virtual int IndexOf(String source, String value, CompareOptions options) { if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); return IndexOf(source, value, 0, source.Length, options); } + public unsafe virtual int IndexOf(String source, char value, int startIndex) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + Contract.EndContractBlock(); + + return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None); + } + + public unsafe virtual int IndexOf(String source, String value, int startIndex) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + Contract.EndContractBlock(); + + return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None); + } public unsafe virtual int IndexOf(String source, char value, int startIndex, CompareOptions options) { if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); return IndexOf(source, value, startIndex, source.Length - startIndex, options); @@ -531,7 +653,7 @@ namespace System.Globalization public unsafe virtual int IndexOf(String source, String value, int startIndex, CompareOptions options) { if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); return IndexOf(source, value, startIndex, source.Length - startIndex, options); @@ -553,13 +675,13 @@ namespace System.Globalization { // Validate inputs if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); if (startIndex < 0 || startIndex > source.Length) - throw new ArgumentOutOfRangeException("startIndex", SR.ArgumentOutOfRange_Index); + throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index); if (count < 0 || startIndex > source.Length - count) - throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_Count); + throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count); Contract.EndContractBlock(); if (options == CompareOptions.OrdinalIgnoreCase) @@ -570,7 +692,7 @@ namespace System.Globalization // Validate CompareOptions // Ordinal can't be selected with other flags if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal)) - throw new ArgumentException(SR.Argument_InvalidFlag, "options"); + throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); return IndexOfCore(source, new string(value, 1), startIndex, count, options); } @@ -580,13 +702,13 @@ namespace System.Globalization { // Validate inputs if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); if (value == null) - throw new ArgumentNullException("value"); + throw new ArgumentNullException(nameof(value)); if (startIndex > source.Length) { - throw new ArgumentOutOfRangeException("startIndex", SR.ArgumentOutOfRange_Index); + throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index); } Contract.EndContractBlock(); @@ -603,11 +725,11 @@ namespace System.Globalization if (startIndex < 0) { - throw new ArgumentOutOfRangeException("startIndex", SR.ArgumentOutOfRange_Index); + throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index); } if (count < 0 || startIndex > source.Length - count) - throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_Count); + throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count); if (options == CompareOptions.OrdinalIgnoreCase) { @@ -617,7 +739,7 @@ namespace System.Globalization // Validate CompareOptions // Ordinal can't be selected with other flags if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal)) - throw new ArgumentException(SR.Argument_InvalidFlag, "options"); + throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); return IndexOfCore(source, value, startIndex, count, options); } @@ -639,7 +761,7 @@ namespace System.Globalization public unsafe virtual int LastIndexOf(String source, char value) { if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); // Can't start at negative index, so make sure we check for the length == 0 case. @@ -651,7 +773,7 @@ namespace System.Globalization public virtual int LastIndexOf(String source, String value) { if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); // Can't start at negative index, so make sure we check for the length == 0 case. @@ -663,7 +785,7 @@ namespace System.Globalization public virtual int LastIndexOf(String source, char value, CompareOptions options) { if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); // Can't start at negative index, so make sure we check for the length == 0 case. @@ -674,7 +796,7 @@ namespace System.Globalization public unsafe virtual int LastIndexOf(String source, String value, CompareOptions options) { if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); // Can't start at negative index, so make sure we check for the length == 0 case. @@ -682,6 +804,16 @@ namespace System.Globalization source.Length, options); } + public unsafe virtual int LastIndexOf(String source, char value, int startIndex) + { + return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None); + } + + + public unsafe virtual int LastIndexOf(String source, String value, int startIndex) + { + return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None); + } public unsafe virtual int LastIndexOf(String source, char value, int startIndex, CompareOptions options) { @@ -711,7 +843,7 @@ namespace System.Globalization { // Verify Arguments if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); Contract.EndContractBlock(); // Validate CompareOptions @@ -719,7 +851,7 @@ namespace System.Globalization if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal) && (options != CompareOptions.OrdinalIgnoreCase)) - throw new ArgumentException(SR.Argument_InvalidFlag, "options"); + throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); // Special case for 0 length input strings if (source.Length == 0 && (startIndex == -1 || startIndex == 0)) @@ -727,7 +859,7 @@ namespace System.Globalization // Make sure we're not out of range if (startIndex < 0 || startIndex > source.Length) - throw new ArgumentOutOfRangeException("startIndex", SR.ArgumentOutOfRange_Index); + throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index); // Make sure that we allow startIndex == source.Length if (startIndex == source.Length) @@ -739,7 +871,7 @@ namespace System.Globalization // 2nd have of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0. if (count < 0 || startIndex - count + 1 < 0) - throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_Count); + throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count); if (options == CompareOptions.OrdinalIgnoreCase) { @@ -754,9 +886,9 @@ namespace System.Globalization { // Verify Arguments if (source == null) - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); if (value == null) - throw new ArgumentNullException("value"); + throw new ArgumentNullException(nameof(value)); Contract.EndContractBlock(); // Validate CompareOptions @@ -764,7 +896,7 @@ namespace System.Globalization if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal) && (options != CompareOptions.OrdinalIgnoreCase)) - throw new ArgumentException(SR.Argument_InvalidFlag, "options"); + throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); // Special case for 0 length input strings if (source.Length == 0 && (startIndex == -1 || startIndex == 0)) @@ -772,7 +904,7 @@ namespace System.Globalization // Make sure we're not out of range if (startIndex < 0 || startIndex > source.Length) - throw new ArgumentOutOfRangeException("startIndex", SR.ArgumentOutOfRange_Index); + throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index); // Make sure that we allow startIndex == source.Length if (startIndex == source.Length) @@ -788,7 +920,7 @@ namespace System.Globalization // 2nd half of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0. if (count < 0 || startIndex - count + 1 < 0) - throw new ArgumentOutOfRangeException("count", SR.ArgumentOutOfRange_Count); + throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count); if (options == CompareOptions.OrdinalIgnoreCase) { @@ -798,8 +930,24 @@ namespace System.Globalization return LastIndexOfCore(source, value, startIndex, count, options); } + //////////////////////////////////////////////////////////////////////// + // + // GetSortKey + // + // Gets the SortKey for the given string with the given options. + // + //////////////////////////////////////////////////////////////////////// + public unsafe virtual SortKey GetSortKey(String source, CompareOptions options) + { + return CreateSortKey(source, options); + } + public unsafe virtual SortKey GetSortKey(String source) + { + return CreateSortKey(source, CompareOptions.None); + } + //////////////////////////////////////////////////////////////////////// // // Equals @@ -872,12 +1020,12 @@ namespace System.Globalization // if (null == source) { - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); } if ((options & ValidHashCodeOfStringMaskOffFlags) != 0) { - throw new ArgumentException(SR.Argument_InvalidFlag, "options"); + throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); } Contract.EndContractBlock(); @@ -888,7 +1036,7 @@ namespace System.Globalization { if (source == null) { - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); } if (options == CompareOptions.Ordinal) @@ -921,5 +1069,26 @@ namespace System.Globalization { return ("CompareInfo - " + this.Name); } + + public SortVersion Version + { + get + { + if (_sortVersion == null) + { + _sortVersion = GetSortVersion(); + } + + return _sortVersion; + } + } + + public int LCID + { + get + { + return CultureInfo.GetCultureInfo(Name).LCID; + } + } } } diff --git a/src/mscorlib/corefx/System/Globalization/CultureData.Unix.cs b/src/mscorlib/corefx/System/Globalization/CultureData.Unix.cs index 58aae2f40b..7f2f17d9f5 100644 --- a/src/mscorlib/corefx/System/Globalization/CultureData.Unix.cs +++ b/src/mscorlib/corefx/System/Globalization/CultureData.Unix.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.InteropServices; using System.Security; @@ -17,7 +18,8 @@ namespace System.Globalization const int ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY = 100; // max size of keyword or value const int ICU_ULOC_FULLNAME_CAPACITY = 157; // max size of locale name const string ICU_COLLATION_KEYWORD = "@collation="; - + + /// /// 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. @@ -25,8 +27,8 @@ namespace System.Globalization [SecuritySafeCritical] private unsafe bool InitCultureData() { - Contract.Assert(_sRealName != null); - + Debug.Assert(_sRealName != null); + string alternateSortName = string.Empty; string realNameBuffer = _sRealName; @@ -66,23 +68,21 @@ namespace System.Globalization _sName = _sWindowsName; } _sRealName = _sName; - _sSpecificCulture = _sRealName; // we don't attempt to find a non-neutral locale if a neutral is passed in (unlike win32) _iLanguage = this.ILANGUAGE; if (_iLanguage == 0) { - _iLanguage = LOCALE_CUSTOM_UNSPECIFIED; + _iLanguage = CultureInfo.LOCALE_CUSTOM_UNSPECIFIED; } _bNeutral = (this.SISO3166CTRYNAME.Length == 0); - + + _sSpecificCulture = _bNeutral ? LocaleData.GetSpecificCultureName(_sRealName) : _sRealName; + // Remove the sort from sName unless custom culture - if (!_bNeutral) + if (index>0 && !_bNeutral && !IsCustomCultureId(_iLanguage)) { - if (!IsCustomCultureId(_iLanguage)) - { - _sName = _sWindowsName.Substring(0, index); - } + _sName = _sWindowsName.Substring(0, index); } return true; } @@ -120,10 +120,10 @@ namespace System.Globalization windowsName = StringBuilderCache.GetStringAndRelease(sb); // the name passed to subsequent ICU calls return true; } - + private string GetLocaleInfo(LocaleStringData type) { - Contract.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo] Expected _sWindowsName to be populated already"); + Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo] Expected _sWindowsName to be populated already"); return GetLocaleInfo(_sWindowsName, type); } @@ -132,7 +132,7 @@ namespace System.Globalization [SecuritySafeCritical] private string GetLocaleInfo(string localeName, LocaleStringData type) { - Contract.Assert(localeName != null, "[CultureData.GetLocaleInfo] Expected localeName to be not be null"); + Debug.Assert(localeName != null, "[CultureData.GetLocaleInfo] Expected localeName to be not be null"); switch (type) { @@ -149,7 +149,7 @@ namespace System.Globalization { // Failed, just use empty string StringBuilderCache.Release(sb); - Contract.Assert(false, "[CultureData.GetLocaleInfo(LocaleStringData)] Failed"); + Debug.Assert(false, "[CultureData.GetLocaleInfo(LocaleStringData)] Failed"); return String.Empty; } return StringBuilderCache.GetStringAndRelease(sb); @@ -158,7 +158,7 @@ namespace System.Globalization [SecuritySafeCritical] private int GetLocaleInfo(LocaleNumberData type) { - Contract.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo(LocaleNumberData)] Expected _sWindowsName to be populated already"); + Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo(LocaleNumberData)] Expected _sWindowsName to be populated already"); switch (type) { @@ -173,7 +173,7 @@ namespace System.Globalization if (!result) { // Failed, just use 0 - Contract.Assert(false, "[CultureData.GetLocaleInfo(LocaleNumberData)] failed"); + Debug.Assert(false, "[CultureData.GetLocaleInfo(LocaleNumberData)] failed"); } return value; @@ -182,14 +182,14 @@ namespace System.Globalization [SecuritySafeCritical] private int[] GetLocaleInfo(LocaleGroupingData type) { - Contract.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo(LocaleGroupingData)] Expected _sWindowsName to be populated already"); + Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo(LocaleGroupingData)] Expected _sWindowsName to be populated already"); int primaryGroupingSize = 0; int secondaryGroupingSize = 0; bool result = Interop.GlobalizationInterop.GetLocaleInfoGroupingSizes(_sWindowsName, (uint)type, ref primaryGroupingSize, ref secondaryGroupingSize); if (!result) { - Contract.Assert(false, "[CultureData.GetLocaleInfo(LocaleGroupingData type)] failed"); + Debug.Assert(false, "[CultureData.GetLocaleInfo(LocaleGroupingData type)] failed"); } if (secondaryGroupingSize == 0) @@ -208,7 +208,7 @@ namespace System.Globalization [SecuritySafeCritical] private string GetTimeFormatString(bool shortFormat) { - Contract.Assert(_sWindowsName != null, "[CultureData.GetTimeFormatString(bool shortFormat)] Expected _sWindowsName to be populated already"); + Debug.Assert(_sWindowsName != null, "[CultureData.GetTimeFormatString(bool shortFormat)] Expected _sWindowsName to be populated already"); StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY); @@ -217,7 +217,7 @@ namespace System.Globalization { // Failed, just use empty string StringBuilderCache.Release(sb); - Contract.Assert(false, "[CultureData.GetTimeFormatString(bool shortFormat)] Failed"); + Debug.Assert(false, "[CultureData.GetTimeFormatString(bool shortFormat)] Failed"); return String.Empty; } @@ -300,5 +300,127 @@ namespace System.Globalization return StringBuilderCache.GetStringAndRelease(sb); } + + private static string LCIDToLocaleName(int culture) + { + return LocaleData.LCIDToLocaleName(culture); + } + + private static int LocaleNameToLCID(string cultureName) + { + int lcid = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.Lcid); + return lcid == -1 ? CultureInfo.LOCALE_CUSTOM_UNSPECIFIED : lcid; + } + + private static int GetAnsiCodePage(string cultureName) + { + int ansiCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.AnsiCodePage); + return ansiCodePage == -1 ? CultureData.Invariant.IDEFAULTANSICODEPAGE : ansiCodePage; + } + + private static int GetOemCodePage(string cultureName) + { + int oemCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.OemCodePage); + return oemCodePage == -1 ? CultureData.Invariant.IDEFAULTOEMCODEPAGE : oemCodePage; + } + + private static int GetMacCodePage(string cultureName) + { + int macCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.MacCodePage); + return macCodePage == -1 ? CultureData.Invariant.IDEFAULTMACCODEPAGE : macCodePage; + } + + private static int GetEbcdicCodePage(string cultureName) + { + int ebcdicCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.EbcdicCodePage); + return ebcdicCodePage == -1 ? CultureData.Invariant.IDEFAULTEBCDICCODEPAGE : ebcdicCodePage; + } + + private static int GetGeoId(string cultureName) + { + int geoId = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.GeoId); + return geoId == -1 ? CultureData.Invariant.IGEOID : geoId; + } + + private static int GetDigitSubstitution(string cultureName) + { + int digitSubstitution = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.DigitSubstitution); + return digitSubstitution == -1 ? (int) DigitShapes.None : digitSubstitution; + } + + private static string GetThreeLetterWindowsLanguageName(string cultureName) + { + string langName = LocaleData.GetThreeLetterWindowsLangageName(cultureName); + return langName == null ? "ZZZ" /* default lang name */ : langName; + } + + private static CultureInfo[] EnumCultures(CultureTypes types) + { + if ((types & (CultureTypes.NeutralCultures | CultureTypes.SpecificCultures)) == 0) + { + return Array.Empty(); + } + + int bufferLength = Interop.GlobalizationInterop.GetLocales(null, 0); + if (bufferLength <= 0) + { + return Array.Empty(); + } + + Char [] chars = new Char[bufferLength]; + + bufferLength = Interop.GlobalizationInterop.GetLocales(chars, bufferLength); + if (bufferLength <= 0) + { + return Array.Empty(); + } + + bool enumNeutrals = (types & CultureTypes.NeutralCultures) != 0; + bool enumSpecificss = (types & CultureTypes.SpecificCultures) != 0; + + List list = new List(); + if (enumNeutrals) + { + list.Add(CultureInfo.InvariantCulture); + } + + int index = 0; + while (index < bufferLength) + { + int length = (int) chars[index++]; + if (index + length <= bufferLength) + { + CultureInfo ci = CultureInfo.GetCultureInfo(new String(chars, index, length)); + if ((enumNeutrals && ci.IsNeutralCulture) || (enumSpecificss && !ci.IsNeutralCulture)) + { + list.Add(ci); + } + } + + index += length; + } + + return list.ToArray(); + } + + private static string GetConsoleFallbackName(string cultureName) + { + return LocaleData.GetConsoleUICulture(cultureName); + } + + internal bool IsFramework // not applicable on Linux based systems + { + get { return false; } + } + + internal bool IsWin32Installed // not applicable on Linux based systems + { + get { return false; } + } + + internal bool IsReplacementCulture // not applicable on Linux based systems + { + get { return false; } + } } } diff --git a/src/mscorlib/corefx/System/Globalization/CultureData.Windows.cs b/src/mscorlib/corefx/System/Globalization/CultureData.Windows.cs index 9969ecbd81..d1c99da607 100644 --- a/src/mscorlib/corefx/System/Globalization/CultureData.Windows.cs +++ b/src/mscorlib/corefx/System/Globalization/CultureData.Windows.cs @@ -3,10 +3,13 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Diagnostics.Contracts; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; + +#if ENABLE_WINRT using Internal.Runtime.Augments; +#endif namespace System.Globalization { @@ -158,7 +161,7 @@ namespace System.Globalization private string GetLocaleInfo(LocaleStringData type) { - Contract.Assert(_sWindowsName != null, "[CultureData.DoGetLocaleInfo] Expected _sWindowsName to be populated by already"); + Debug.Assert(_sWindowsName != null, "[CultureData.DoGetLocaleInfo] Expected _sWindowsName to be populated by already"); return GetLocaleInfo(_sWindowsName, type); } @@ -183,7 +186,7 @@ namespace System.Globalization // Ask OS for data, note that we presume it returns success, so we have to know that // sWindowsName is valid before calling. - Contract.Assert(_sWindowsName != null, "[CultureData.DoGetLocaleInfoInt] Expected _sWindowsName to be populated by already"); + Debug.Assert(_sWindowsName != null, "[CultureData.DoGetLocaleInfoInt] Expected _sWindowsName to be populated by already"); int result = Interop.mincore.GetLocaleInfoExInt(_sWindowsName, lctype); return result; @@ -203,7 +206,7 @@ namespace System.Globalization private int GetFirstDayOfWeek() { - Contract.Assert(_sWindowsName != null, "[CultureData.DoGetLocaleInfoInt] Expected _sWindowsName to be populated by already"); + Debug.Assert(_sWindowsName != null, "[CultureData.DoGetLocaleInfoInt] Expected _sWindowsName to be populated by already"); const uint LOCALE_IFIRSTDAYOFWEEK = 0x0000100C; @@ -216,7 +219,7 @@ namespace System.Globalization private String[] GetTimeFormats() { // Note that this gets overrides for us all the time - Contract.Assert(_sWindowsName != null, "[CultureData.DoEnumTimeFormats] Expected _sWindowsName to be populated by already"); + Debug.Assert(_sWindowsName != null, "[CultureData.DoEnumTimeFormats] Expected _sWindowsName to be populated by already"); String[] result = ReescapeWin32Strings(nativeEnumTimeFormats(_sWindowsName, 0, UseUserOverride)); return result; @@ -225,7 +228,7 @@ namespace System.Globalization private String[] GetShortTimeFormats() { // Note that this gets overrides for us all the time - Contract.Assert(_sWindowsName != null, "[CultureData.DoEnumShortTimeFormats] Expected _sWindowsName to be populated by already"); + Debug.Assert(_sWindowsName != null, "[CultureData.DoEnumShortTimeFormats] Expected _sWindowsName to be populated by already"); String[] result = ReescapeWin32Strings(nativeEnumTimeFormats(_sWindowsName, TIME_NOSECONDS, UseUserOverride)); return result; @@ -235,7 +238,7 @@ namespace System.Globalization // region name match the requested region name private static CultureData GetCultureDataFromRegionName(String regionName) { - Contract.Assert(regionName != null); + Debug.Assert(regionName != null); const uint LOCALE_SUPPLEMENTAL = 0x00000002; const uint LOCALE_SPECIFICDATA = 0x00000020; @@ -264,26 +267,64 @@ namespace System.Globalization return null; } - private static string GetLanguageDisplayName(string cultureName) + private string GetLanguageDisplayName(string cultureName) { +#if ENABLE_WINRT return WinRTInterop.Callbacks.GetLanguageDisplayName(cultureName); +#else + // Usually the UI culture shouldn't be different than what we got from WinRT except + // if DefaultThreadCurrentUICulture was set + CultureInfo ci; + + if (CultureInfo.DefaultThreadCurrentUICulture != null && + ((ci = GetUserDefaultCulture()) != null) && + !CultureInfo.DefaultThreadCurrentUICulture.Name.Equals(ci.Name)) + { + return SNATIVEDISPLAYNAME; + } + else + { + return GetLocaleInfo(cultureName, LocaleStringData.LocalizedDisplayName); + } +#endif // ENABLE_WINRT } - private static string GetRegionDisplayName(string isoCountryCode) + private string GetRegionDisplayName(string isoCountryCode) { +#if ENABLE_WINRT return WinRTInterop.Callbacks.GetRegionDisplayName(isoCountryCode); +#else + // Usually the UI culture shouldn't be different than what we got from WinRT except + // if DefaultThreadCurrentUICulture was set + CultureInfo ci; + + if (CultureInfo.DefaultThreadCurrentUICulture != null && + ((ci = GetUserDefaultCulture()) != null) && + !CultureInfo.DefaultThreadCurrentUICulture.Name.Equals(ci.Name)) + { + return SNATIVECOUNTRY; + } + else + { + return GetLocaleInfo(LocaleStringData.LocalizedCountryName); + } +#endif // ENABLE_WINRT } private static CultureInfo GetUserDefaultCulture() { +#if ENABLE_WINRT return (CultureInfo)WinRTInterop.Callbacks.GetUserDefaultCulture(); +#else + return CultureInfo.GetUserDefaultCulture(); +#endif // ENABLE_WINRT } // PAL methods end here. private static string GetLocaleInfoFromLCType(string localeName, uint lctype, bool useUserOveride) { - Contract.Assert(localeName != null, "[CultureData.GetLocaleInfoFromLCType] Expected localeName to be not be null"); + Debug.Assert(localeName != null, "[CultureData.GetLocaleInfoFromLCType] Expected localeName to be not be null"); // Fix lctype if we don't want overrides if (!useUserOveride) @@ -557,5 +598,76 @@ namespace System.Globalization return null; } + + private int LocaleNameToLCID(string cultureName) + { + return GetLocaleInfo(LocaleNumberData.LanguageId); + } + + private static string LCIDToLocaleName(int culture) + { + throw new NotImplementedException(); + } + + private int GetAnsiCodePage(string cultureName) + { + return GetLocaleInfo(LocaleNumberData.AnsiCodePage); + } + + private int GetOemCodePage(string cultureName) + { + return GetLocaleInfo(LocaleNumberData.OemCodePage); + } + + private int GetMacCodePage(string cultureName) + { + return GetLocaleInfo(LocaleNumberData.MacCodePage); + } + + private int GetEbcdicCodePage(string cultureName) + { + return GetLocaleInfo(LocaleNumberData.EbcdicCodePage); + } + + private int GetGeoId(string cultureName) + { + return GetLocaleInfo(LocaleNumberData.GeoId); + } + + private int GetDigitSubstitution(string cultureName) + { + return GetLocaleInfo(LocaleNumberData.DigitSubstitution); + } + + private string GetThreeLetterWindowsLanguageName(string cultureName) + { + return GetLocaleInfo(cultureName, LocaleStringData.AbbreviatedWindowsLanguageName); + } + + private static CultureInfo[] EnumCultures(CultureTypes types) + { + throw new NotImplementedException(); + } + + private string GetConsoleFallbackName(string cultureName) + { + return GetLocaleInfo(cultureName, LocaleStringData.ConsoleFallbackName); + } + + internal bool IsFramework + { + get { throw new NotImplementedException(); } + } + + internal bool IsWin32Installed + { + get { throw new NotImplementedException(); } + } + + internal bool IsReplacementCulture + { + get { throw new NotImplementedException(); } + } + } } diff --git a/src/mscorlib/corefx/System/Globalization/CultureData.cs b/src/mscorlib/corefx/System/Globalization/CultureData.cs index eb71318fdb..c15a77cf45 100644 --- a/src/mscorlib/corefx/System/Globalization/CultureData.cs +++ b/src/mscorlib/corefx/System/Globalization/CultureData.cs @@ -2,25 +2,23 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Text; using System.Threading; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Diagnostics.Contracts; namespace System.Globalization { #if INSIDE_CLR using StringStringDictionary = Dictionary; - using StringCultureDataDictionary = Dictionary; + using StringCultureDataDictionary = Dictionary; + using LcidToCultureNameDictionary = Dictionary; using Lock = Object; #else using StringStringDictionary = LowLevelDictionary; using StringCultureDataDictionary = LowLevelDictionary; + using LcidToCultureNameDictionary = LowLevelDictionary; #endif // @@ -57,8 +55,6 @@ namespace System.Globalization internal partial class CultureData { private const int undef = -1; - private const int LOCALE_CUSTOM_UNSPECIFIED = 0x1000; - private const int LOCALE_CUSTOM_DEFAULT = 0x0c00; // Override flag private String _sRealName; // Name you passed in (ie: en-US, en, or de-DE_phoneb) @@ -74,9 +70,13 @@ namespace System.Globalization // Language private String _sISO639Language; // ISO 639 Language Name + private String _sISO639Language2; // ISO 639 Language Name private String _sLocalizedLanguage; // Localized name for this language private String _sEnglishLanguage; // English name for this language private String _sNativeLanguage; // Native name of this language + private String _sAbbrevLang; // abbreviated language name (Windows Language Name) ex: ENU + private string _sConsoleFallbackName; // The culture name for the console fallback UI culture + private int _iInputLanguageHandle=undef;// input language handle // Region private String _sRegionName; // (RegionInfo) @@ -84,11 +84,12 @@ namespace System.Globalization private String _sEnglishCountry; // english country name (RegionInfo) private String _sNativeCountry; // native country name private String _sISO3166CountryName; // ISO 3166 (RegionInfo), ie: US + private String _sISO3166CountryName2; // 3 char ISO 3166 country name 2 2(RegionInfo) ex: USA (ISO) + private int _iGeoId = undef; // GeoId // Numbers private String _sPositiveSign; // (user can override) positive sign private String _sNegativeSign; // (user can override) negative sign - private String[] _saNativeDigits; // (user can override) native characters for digits 0-9 // (nfi populates these 5, don't have to be = undef) private int _iDigits; // (user can override) number of fractional digits private int _iNegativeNumber; // (user can override) negative number format @@ -108,6 +109,8 @@ namespace System.Globalization // Currency private String _sCurrency; // (user can override) local monetary symbol private String _sIntlMonetarySymbol; // international monetary symbol (RegionInfo) + private String _sEnglishCurrency; // English name for this currency + private String _sNativeCurrency; // Native name for this currency // (nfi populates these 4, don't have to be = undef) private int _iCurrencyDigits; // (user can override) # local monetary fractional digits private int _iCurrency; // (user can override) positive currency format @@ -145,6 +148,11 @@ namespace System.Globalization // CoreCLR depends on this even though its not exposed publicly. + private int _iDefaultAnsiCodePage = undef; // default ansi code page ID (ACP) + private int _iDefaultOemCodePage = undef; // default oem code page ID (OCP or OEM) + private int _iDefaultMacCodePage = undef; // default macintosh code page + private int _iDefaultEbcdicCodePage = undef; // default EBCDIC code page + private int _iLanguage; // locale ID (0409) - NO sort information private bool _bUseOverrides; // use user overrides? private bool _bNeutral; // Flags for the culture (ie: neutral or not right now) @@ -391,6 +399,42 @@ namespace System.Globalization return retVal; } + // Clear our internal caches + internal static void ClearCachedData() + { + s_cachedCultures = null; + s_cachedRegions = null; + } + + internal static CultureInfo[] GetCultures(CultureTypes types) + { + // Disable warning 618: System.Globalization.CultureTypes.FrameworkCultures' is obsolete +#pragma warning disable 618 + // Validate flags + if ((int)types <= 0 || ((int)types & (int)~(CultureTypes.NeutralCultures | CultureTypes.SpecificCultures | + CultureTypes.InstalledWin32Cultures | CultureTypes.UserCustomCulture | + CultureTypes.ReplacementCultures | CultureTypes.WindowsOnlyCultures | + CultureTypes.FrameworkCultures)) != 0) + { + throw new ArgumentOutOfRangeException(nameof(types), + SR.Format(SR.ArgumentOutOfRange_Range, CultureTypes.NeutralCultures, CultureTypes.FrameworkCultures)); + } + + // We have deprecated CultureTypes.FrameworkCultures. + // When this enum is used, we will enumerate Whidbey framework cultures (for compatibility). + // + + // We have deprecated CultureTypes.WindowsOnlyCultures. + // When this enum is used, we will return an empty array for this enum. + if ((types & CultureTypes.WindowsOnlyCultures) != 0) + { + // Remove the enum as it is an no-op. + types &= (~CultureTypes.WindowsOnlyCultures); + } + +#pragma warning restore 618 + return EnumCultures(types); + } ///////////////////////////////////////////////////////////////////////// // Build our invariant information @@ -422,20 +466,25 @@ namespace System.Globalization // Language invariant._sISO639Language = "iv"; // ISO 639 Language Name + invariant._sISO639Language2 = "ivl"; // 3 char ISO 639 lang name 2 invariant._sLocalizedLanguage = "Invariant Language"; // Display name for this Language invariant._sEnglishLanguage = "Invariant Language"; // English name for this language invariant._sNativeLanguage = "Invariant Language"; // Native name of this language + invariant._sAbbrevLang = "IVL"; // abbreviated language name (Windows Language Name) + invariant._sConsoleFallbackName = ""; // The culture name for the console fallback UI culture + invariant._iInputLanguageHandle = 0x07F; // input language handle // Region - invariant._sRegionName = "IV"; // (RegionInfo) - invariant._sEnglishCountry = "Invariant Country"; // english country name (RegionInfo) - invariant._sNativeCountry = "Invariant Country"; // native country name (Windows Only) - invariant._sISO3166CountryName = "IV"; // (RegionInfo), ie: US + invariant._sRegionName = "IV"; // (RegionInfo) + invariant._sEnglishCountry = "Invariant Country"; // english country name (RegionInfo) + invariant._sNativeCountry = "Invariant Country"; // native country name (Windows Only) + invariant._sISO3166CountryName = "IV"; // (RegionInfo), ie: US + invariant._sISO3166CountryName2 = "ivc"; // 3 char ISO 3166 country name 2 2(RegionInfo) + invariant._iGeoId = 244; // GeoId (Windows Only) // Numbers invariant._sPositiveSign = "+"; // positive sign invariant._sNegativeSign = "-"; // negative sign - invariant._saNativeDigits = new String[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }; // native characters for digits 0-9 invariant._iDigits = 2; // number of fractional digits invariant._iNegativeNumber = 1; // negative number format invariant._waGrouping = new int[] { 3 }; // grouping of digits @@ -454,6 +503,8 @@ namespace System.Globalization // Currency invariant._sCurrency = "\x00a4"; // local monetary symbol: for international monetary symbol invariant._sIntlMonetarySymbol = "XDR"; // international monetary symbol (RegionInfo) + invariant._sEnglishCurrency = "International Monetary Fund"; // English name for this currency (Windows Only) + invariant._sNativeCurrency = "International Monetary Fund"; // Native name for this currency (Windows Only) invariant._iCurrencyDigits = 2; // # local monetary fractional digits invariant._iCurrency = 0; // positive currency format invariant._iNegativeCurrency = 0; // negative currency format @@ -487,7 +538,11 @@ namespace System.Globalization // These are desktop only, not coreclr - invariant._iLanguage = 0x007f; // locale ID (0409) - NO sort information + invariant._iLanguage = CultureInfo.LOCALE_INVARIANT; // locale ID (0409) - NO sort information + invariant._iDefaultAnsiCodePage = 1252; // default ansi code page ID (ACP) + invariant._iDefaultOemCodePage = 437; // default oem code page ID (OCP or OEM) + invariant._iDefaultMacCodePage = 10000; // default macintosh code page + invariant._iDefaultEbcdicCodePage = 037; // default EBCDIC code page // Remember it s_Invariant = invariant; } @@ -605,6 +660,33 @@ namespace System.Globalization return true; } + // We'd rather people use the named version since this doesn't allow custom locales + internal static CultureData GetCultureData(int culture, bool bUseUserOverride) + { + string localeName = null; + CultureData retVal = null; + + if (culture == CultureInfo.LOCALE_INVARIANT) + return Invariant; + + // Convert the lcid to a name, then use that + // Note that this'll return neutral names (unlike Vista native API) + localeName = LCIDToLocaleName(culture); + + if (!String.IsNullOrEmpty(localeName)) + { + // Valid name, use it + retVal = GetCultureData(localeName, bUseUserOverride); + } + + // If not successful, throw + if (retVal == null) + throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported); + + // Return the one we found + return retVal; + } + //////////////////////////////////////////////////////////////////////// // // All the accessors @@ -622,7 +704,7 @@ namespace System.Globalization { get { - Contract.Assert(_sRealName != null, "[CultureData.CultureName] Expected _sRealName to be populated by already"); + Debug.Assert(_sRealName != null, "[CultureData.CultureName] Expected _sRealName to be populated by already"); // since windows doesn't know about zh-CHS and zh-CHT, // we leave sRealName == zh-Hanx but we still need to // pretend that it was zh-CHX. @@ -834,6 +916,17 @@ namespace System.Globalization } } + // The culture name to be used in CultureInfo.CreateSpecificCulture() + internal string SSPECIFICCULTURE + { + get + { + // This got populated during the culture initialization + Debug.Assert(_sSpecificCulture != null, "[CultureData.SSPECIFICCULTURE] Expected this.sSpecificCulture to be populated by culture data initialization already"); + return _sSpecificCulture; + } + } + ///////////// // Language // ///////////// @@ -845,12 +938,38 @@ namespace System.Globalization { if (_sISO639Language == null) { - _sISO639Language = GetLocaleInfo(LocaleStringData.Iso639LanguageName); + _sISO639Language = GetLocaleInfo(LocaleStringData.Iso639LanguageTwoLetterName); } return _sISO639Language; } } + // iso 639 language name, ie: eng + internal string SISO639LANGNAME2 + { + get + { + if (_sISO639Language2 == null) + { + _sISO639Language2 = GetLocaleInfo(LocaleStringData.Iso639LanguageThreeLetterName); + } + return _sISO639Language2; + } + } + + // abbreviated windows language name (ie: enu) (non-standard, avoid this) + internal string SABBREVLANGNAME + { + get + { + if (_sAbbrevLang == null) + { + _sAbbrevLang = GetThreeLetterWindowsLanguageName(_sRealName); + } + return _sAbbrevLang; + } + } + // Localized name for this language (Windows Only) ie: Inglis // This is only valid for Windows 8 and higher neutrals: internal String SLOCALIZEDLANGUAGE @@ -922,6 +1041,17 @@ namespace System.Globalization } } + internal int IGEOID + { + get + { + if (_iGeoId == undef) + { + _iGeoId = GetGeoId(_sRealName); + } + return _iGeoId; + } + } // localized name for the country internal string SLOCALIZEDCOUNTRY @@ -987,17 +1117,51 @@ namespace System.Globalization } } - ///////////// - // Numbers // - //////////// - - // internal String sPositiveSign ; // (user can override) positive sign - // internal String sNegativeSign ; // (user can override) negative sign - // internal String[] saNativeDigits ; // (user can override) native characters for digits 0-9 - // internal int iDigits ; // (user can override) number of fractional digits - // internal int iNegativeNumber ; // (user can override) negative number format + // 3 letter ISO 3166 country code + internal String SISO3166CTRYNAME2 + { + get + { + if (_sISO3166CountryName2 == null) + { + _sISO3166CountryName2 = GetLocaleInfo(LocaleStringData.Iso3166CountryName2); + } + return _sISO3166CountryName2; + } + } + internal int IINPUTLANGUAGEHANDLE + { + get + { + if (_iInputLanguageHandle == undef) + { + if (IsSupplementalCustomCulture) + { + _iInputLanguageHandle = 0x0409; + } + else + { + // Input Language is same as LCID for built-in cultures + _iInputLanguageHandle = this.ILANGUAGE; + } + } + return _iInputLanguageHandle; + } + } + // Console fallback name (ie: locale to use for console apps for unicode-only locales) + internal string SCONSOLEFALLBACKNAME + { + get + { + if (_sConsoleFallbackName == null) + { + _sConsoleFallbackName = GetConsoleFallbackName(_sRealName); + } + return _sConsoleFallbackName; + } + } // (user can override) grouping of digits internal int[] WAGROUPING @@ -1144,6 +1308,32 @@ namespace System.Globalization } } + // English name for this currency (RegionInfo), eg: US Dollar + internal String SENGLISHCURRENCY + { + get + { + if (_sEnglishCurrency == null) + { + _sEnglishCurrency = GetLocaleInfo(LocaleStringData.CurrencyEnglishName); + } + return _sEnglishCurrency; + } + } + + // Native name for this currency (RegionInfo), eg: Schweiz Frank + internal String SNATIVECURRENCY + { + get + { + if (_sNativeCurrency == null) + { + _sNativeCurrency = GetLocaleInfo(LocaleStringData.CurrencyNativeName); + } + return _sNativeCurrency; + } + } + // internal int iCurrencyDigits ; // (user can override) # local monetary fractional digits // internal int iCurrency ; // (user can override) positive currency format // internal int iNegativeCurrency ; // (user can override) negative currency format @@ -1522,7 +1712,7 @@ namespace System.Globalization // We then have to copy that list to a new array of the right size. // Default calendar should be first CalendarId[] calendars = new CalendarId[23]; - Contract.Assert(_sWindowsName != null, "[CultureData.CalendarIds] Expected _sWindowsName to be populated by already"); + Debug.Assert(_sWindowsName != null, "[CultureData.CalendarIds] Expected _sWindowsName to be populated by already"); int count = CalendarData.GetCalendars(_sWindowsName, _bUseOverrides, calendars); // See if we had a calendar to add. @@ -1585,9 +1775,16 @@ namespace System.Globalization } } + // Native calendar names. index of optional calendar - 1, empty if no optional calendar at that number + internal string CalendarName(CalendarId calendarId) + { + // Get the calendar + return GetCalendar(calendarId).sNativeName; + } + internal CalendarData GetCalendar(CalendarId calendarId) { - Contract.Assert(calendarId > 0 && calendarId <= CalendarId.LAST_CALENDAR, + Debug.Assert(calendarId > 0 && calendarId <= CalendarId.LAST_CALENDAR, "[CultureData.GetCalendar] Expect calendarId to be in a valid range"); // arrays are 0 based, calendarIds are 1 based @@ -1606,7 +1803,7 @@ namespace System.Globalization // Make sure that calendar has data if (calendarData == null) { - Contract.Assert(_sWindowsName != null, "[CultureData.GetCalendar] Expected _sWindowsName to be populated by already"); + Debug.Assert(_sWindowsName != null, "[CultureData.GetCalendar] Expected _sWindowsName to be populated by already"); calendarData = new CalendarData(_sWindowsName, calendarId, this.UseUserOverride); _calendars[calendarIndex] = calendarData; } @@ -1646,7 +1843,7 @@ namespace System.Globalization { if (_iReadingLayout == undef) { - Contract.Assert(_sRealName != null, "[CultureData.IsRightToLeft] Expected _sRealName to be populated by already"); + Debug.Assert(_sRealName != null, "[CultureData.IsRightToLeft] Expected _sRealName to be populated by already"); _iReadingLayout = GetLocaleInfo(LocaleNumberData.ReadingLayout); } @@ -1667,8 +1864,8 @@ namespace System.Globalization { // Note: Custom cultures might point at another culture's textinfo, however windows knows how // to redirect it to the desired textinfo culture, so this is OK. - Contract.Assert(_sWindowsName != null, "[CultureData.STEXTINFO] Expected _sWindowsName to be populated by already"); - return (_sWindowsName); + Debug.Assert(_sRealName != null, "[CultureData.STEXTINFO] Expected _sRealName to be populated by already"); + return (_sRealName); } } @@ -1677,8 +1874,8 @@ namespace System.Globalization { get { - Contract.Assert(_sWindowsName != null, "[CultureData.SCOMPAREINFO] Expected _sWindowsName to be populated by already"); - return (_sWindowsName); + Debug.Assert(_sRealName != null, "[CultureData.SCOMPAREINFO] Expected _sRealName to be populated by already"); + return (_sRealName); } } @@ -1690,10 +1887,63 @@ namespace System.Globalization } } + internal int IDEFAULTANSICODEPAGE // default ansi code page ID (ACP) + { + get + { + if (_iDefaultAnsiCodePage == undef) + { + _iDefaultAnsiCodePage = GetAnsiCodePage(_sRealName); + } + return _iDefaultAnsiCodePage; + } + } + + internal int IDEFAULTOEMCODEPAGE // default oem code page ID (OCP or OEM) + { + get + { + if (_iDefaultOemCodePage == undef) + { + _iDefaultOemCodePage = GetOemCodePage(_sRealName); + } + return _iDefaultOemCodePage; + } + } + + internal int IDEFAULTMACCODEPAGE // default macintosh code page + { + get + { + if (_iDefaultMacCodePage == undef) + { + _iDefaultMacCodePage = GetMacCodePage(_sRealName); + } + return _iDefaultMacCodePage; + } + } + + internal int IDEFAULTEBCDICCODEPAGE // default EBCDIC code page + { + get + { + if (_iDefaultEbcdicCodePage == undef) + { + _iDefaultEbcdicCodePage = GetEbcdicCodePage(_sRealName); + } + return _iDefaultEbcdicCodePage; + } + } + internal int ILANGUAGE { get { + if (_iLanguage == 0) + { + Debug.Assert(_sRealName != null, "[CultureData.ILANGUAGE] Expected this.sRealName to be populated by COMNlsInfo::nativeInitCultureData already"); + _iLanguage = LocaleNameToLCID(_sRealName); + } return _iLanguage; } } @@ -1734,21 +1984,21 @@ namespace System.Globalization // All of our era names internal String[] EraNames(CalendarId calendarId) { - Contract.Assert(calendarId > 0, "[CultureData.saEraNames] Expected Calendar.ID > 0"); + Debug.Assert(calendarId > 0, "[CultureData.saEraNames] Expected Calendar.ID > 0"); return this.GetCalendar(calendarId).saEraNames; } internal String[] AbbrevEraNames(CalendarId calendarId) { - Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0"); + Debug.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0"); return this.GetCalendar(calendarId).saAbbrevEraNames; } internal String[] AbbreviatedEnglishEraNames(CalendarId calendarId) { - Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0"); + Debug.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0"); return this.GetCalendar(calendarId).saAbbrevEnglishEraNames; } @@ -1808,9 +2058,9 @@ namespace System.Globalization //////////////////////////////////////////////////////////////////////////// private static String UnescapeNlsString(String str, int start, int end) { - Contract.Requires(str != null); - Contract.Requires(start >= 0); - Contract.Requires(end >= 0); + Debug.Assert(str != null); + Debug.Assert(start >= 0); + Debug.Assert(end >= 0); StringBuilder result = null; for (int i = start; i < str.Length && i <= end; i++) @@ -1908,8 +2158,8 @@ namespace System.Globalization private static int IndexOfTimePart(string format, int startIndex, string timeParts) { - Contract.Assert(startIndex >= 0, "startIndex cannot be negative"); - Contract.Assert(timeParts.IndexOfAny(new char[] { '\'', '\\' }) == -1, "timeParts cannot include quote characters"); + Debug.Assert(startIndex >= 0, "startIndex cannot be negative"); + Debug.Assert(timeParts.IndexOfAny(new char[] { '\'', '\\' }) == -1, "timeParts cannot include quote characters"); bool inQuote = false; for (int i = startIndex; i < format.Length; ++i) { @@ -1944,9 +2194,9 @@ namespace System.Globalization return -1; } - private static bool IsCustomCultureId(int cultureId) + internal static bool IsCustomCultureId(int cultureId) { - return (cultureId == LOCALE_CUSTOM_DEFAULT || cultureId == LOCALE_CUSTOM_UNSPECIFIED); + return (cultureId == CultureInfo.LOCALE_CUSTOM_DEFAULT || cultureId == CultureInfo.LOCALE_CUSTOM_UNSPECIFIED); } internal void GetNFIValues(NumberFormatInfo nfi) @@ -1954,7 +2204,6 @@ namespace System.Globalization if (this.IsInvariantCulture) { // FUTURE: NumberFormatInfo already has default values for many of these fields. Can we not do this? - // if we do need to do this, then why don't we set nfi.nativeDigits in this case? nfi.positiveSign = _sPositiveSign; nfi.negativeSign = _sNegativeSign; @@ -1972,7 +2221,7 @@ namespace System.Globalization } else { - Contract.Assert(_sWindowsName != null, "[CultureData.GetNFIValues] Expected _sWindowsName to be populated by already"); + Debug.Assert(_sWindowsName != null, "[CultureData.GetNFIValues] Expected _sWindowsName to be populated by already"); // String values nfi.positiveSign = GetLocaleInfo(LocaleStringData.PositiveSign); nfi.negativeSign = GetLocaleInfo(LocaleStringData.NegativeSign); @@ -1997,6 +2246,8 @@ namespace System.Globalization { nfi.nativeDigits[i] = new string(digits[i], 1); } + + nfi.digitSubstitution = GetDigitSubstitution(_sRealName); } // @@ -2043,9 +2294,26 @@ namespace System.Globalization // This is ONLY used for caching names and shouldn't be used for anything else internal static string AnsiToLower(string testString) { + int index = 0; + + while (index'Z' )) + { + index++; + } + if (index >= testString.Length) + { + return testString; // we didn't really change the string + } + StringBuilder sb = new StringBuilder(testString.Length); + for (int i=0; i= 'A' ? (char)(ch - 'A' + 'a') : ch); @@ -2072,10 +2340,14 @@ namespace System.Globalization EnglishLanguageName = 0x00001001, /// native name of language, eg "Deutsch" (coresponds to LOCALE_SNATIVELANGUAGENAME) NativeLanguageName = 0x00000004, + /// localized name of country, eg "Germany" in UI language (coresponds to LOCALE_SLOCALIZEDCOUNTRYNAME) + LocalizedCountryName = 0x00000006, /// English name of country, eg "Germany" (coresponds to LOCALE_SENGLISHCOUNTRYNAME) EnglishCountryName = 0x00001002, /// native name of country, eg "Deutschland" (coresponds to LOCALE_SNATIVECOUNTRYNAME) NativeCountryName = 0x00000008, + /// abbreviated language name (coresponds to LOCALE_SABBREVLANGNAME) + AbbreviatedWindowsLanguageName = 0x00000003, /// list item separator (coresponds to LOCALE_SLIST) ListSeparator = 0x0000000C, /// decimal separator (coresponds to LOCALE_SDECIMAL) @@ -2086,6 +2358,10 @@ namespace System.Globalization Digits = 0x00000013, /// local monetary symbol (coresponds to LOCALE_SCURRENCY) MonetarySymbol = 0x00000014, + /// English currency name (coresponds to LOCALE_SENGCURRNAME) + CurrencyEnglishName = 0x00001007, + /// Native currency name (coresponds to LOCALE_SNATIVECURRNAME) + CurrencyNativeName = 0x00001008, /// uintl monetary symbol (coresponds to LOCALE_SINTLSYMBOL) Iso4217MonetarySymbol = 0x00000015, /// monetary decimal separator (coresponds to LOCALE_SMONDECIMALSEP) @@ -2101,9 +2377,15 @@ namespace System.Globalization /// negative sign (coresponds to LOCALE_SNEGATIVESIGN) NegativeSign = 0x00000051, /// ISO abbreviated language name (coresponds to LOCALE_SISO639LANGNAME) + Iso639LanguageTwoLetterName = 0x00000059, + /// ISO abbreviated country name (coresponds to LOCALE_SISO639LANGNAME2) + Iso639LanguageThreeLetterName = 0x00000067, + /// ISO abbreviated language name (coresponds to LOCALE_SISO639LANGNAME) Iso639LanguageName = 0x00000059, /// ISO abbreviated country name (coresponds to LOCALE_SISO3166CTRYNAME) Iso3166CountryName = 0x0000005A, + /// 3 letter ISO country code (coresponds to LOCALE_SISO3166CTRYNAME2) + Iso3166CountryName2 = 0x00000068, // 3 character ISO country name /// Not a Number (coresponds to LOCALE_SNAN) NaNSymbol = 0x00000069, /// + Infinity (coresponds to LOCALE_SPOSINFINITY) @@ -2112,6 +2394,8 @@ namespace System.Globalization NegativeInfinitySymbol = 0x0000006b, /// Fallback name for resources (coresponds to LOCALE_SPARENT) ParentName = 0x0000006d, + /// Fallback name for within the console (coresponds to LOCALE_SCONSOLEFALLBACKNAME) + ConsoleFallbackName = 0x0000006e, /// Returns the percent symbol (coresponds to LOCALE_SPERCENT) PercentSymbol = 0x00000076, /// Returns the permille (U+2030) symbol (coresponds to LOCALE_SPERMILLE) @@ -2138,6 +2422,10 @@ namespace System.Globalization { /// language id (coresponds to LOCALE_ILANGUAGE) LanguageId = 0x00000001, + /// geographical location id, (coresponds to LOCALE_IGEOID) + GeoId = 0x00000008, + /// 0 = context, 1 = none, 2 = national (coresponds to LOCALE_IDIGITSUBSTITUTION) + DigitSubstitution = 0x00001014, /// 0 = metric, 1 = US (coresponds to LOCALE_IMEASURE) MeasurementSystem = 0x0000000D, /// number of fractional digits (coresponds to LOCALE_IDIGITS) @@ -2168,7 +2456,15 @@ namespace System.Globalization /// Returns 0-11 for the negative percent format (coresponds to LOCALE_INEGATIVEPERCENT) NegativePercentFormat = 0x00000074, /// Returns 0-3 for the positive percent format (coresponds to LOCALE_IPOSITIVEPERCENT) - PositivePercentFormat = 0x00000075 + PositivePercentFormat = 0x00000075, + /// default ansi code page (coresponds to LOCALE_IDEFAULTCODEPAGE) + OemCodePage = 0x0000000B, + /// default ansi code page (coresponds to LOCALE_IDEFAULTANSICODEPAGE) + AnsiCodePage = 0x00001004, + /// default mac code page (coresponds to LOCALE_IDEFAULTMACCODEPAGE) + MacCodePage = 0x00001011, + /// default ebcdic code page (coresponds to LOCALE_IDEFAULTEBCDICCODEPAGE) + EbcdicCodePage = 0x00001012, } } } diff --git a/src/mscorlib/corefx/System/Globalization/CultureInfo.Windows.cs b/src/mscorlib/corefx/System/Globalization/CultureInfo.Windows.cs index 16c8a06e08..c019eb2ceb 100644 --- a/src/mscorlib/corefx/System/Globalization/CultureInfo.Windows.cs +++ b/src/mscorlib/corefx/System/Globalization/CultureInfo.Windows.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if ENABLE_WINRT using Internal.Runtime.Augments; +#endif namespace System.Globalization { @@ -16,16 +18,18 @@ namespace System.Globalization /// private static CultureInfo GetUserDefaultCultureCacheOverride() { +#if ENABLE_WINRT WinRTInteropCallbacks callbacks = WinRTInterop.UnsafeCallbacks; if (callbacks != null && callbacks.IsAppxModel()) { return (CultureInfo)callbacks.GetUserDefaultCulture(); } +#endif return null; } - private static CultureInfo GetUserDefaultCulture() + internal static CultureInfo GetUserDefaultCulture() { const uint LOCALE_SNAME = 0x0000005c; const string LOCALE_NAME_USER_DEFAULT = null; diff --git a/src/mscorlib/corefx/System/Globalization/CultureInfo.cs b/src/mscorlib/corefx/System/Globalization/CultureInfo.cs index f2b3742ab4..da084d17f9 100644 --- a/src/mscorlib/corefx/System/Globalization/CultureInfo.cs +++ b/src/mscorlib/corefx/System/Globalization/CultureInfo.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -26,15 +26,10 @@ // //////////////////////////////////////////////////////////////////////////// -using System; -using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.Contracts; -using System.Runtime; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Serialization; -using System.Security; using System.Threading; namespace System.Globalization @@ -42,9 +37,12 @@ namespace System.Globalization #if INSIDE_CLR using StringCultureInfoDictionary = Dictionary; + using StringLcidDictionary = Dictionary; + using Lock = Object; #else using StringCultureInfoDictionary = LowLevelDictionary; + using StringLcidDictionary = LowLevelDictionary; #endif [Serializable] @@ -79,6 +77,9 @@ namespace System.Globalization [NonSerialized] internal bool m_isInherited; + [NonSerialized] + private CultureInfo m_consoleFallbackCulture; + // Names are confusing. Here are 3 names we have: // // new CultureInfo() m_name m_nonSortName m_sortName @@ -106,7 +107,6 @@ namespace System.Globalization [NonSerialized] private string m_sortName; - //--------------------------------------------------------------------// // // Static data members @@ -139,11 +139,21 @@ namespace System.Globalization private static readonly Lock m_lock = new Lock(); private static volatile StringCultureInfoDictionary s_NameCachedCultures; + private static volatile StringLcidDictionary s_LcidCachedCultures; //The parent culture. [NonSerialized] private CultureInfo m_parent; + // LOCALE constants of interest to us internally and privately for LCID functions + // (ie: avoid using these and use names if possible) + internal const int LOCALE_NEUTRAL = 0x0000; + private const int LOCALE_USER_DEFAULT = 0x0400; + private const int LOCALE_SYSTEM_DEFAULT = 0x0800; + internal const int LOCALE_CUSTOM_UNSPECIFIED = 0x1000; + internal const int LOCALE_CUSTOM_DEFAULT = 0x0c00; + internal const int LOCALE_INVARIANT = 0x007F; + static AsyncLocal s_asyncLocalCurrentCulture; static AsyncLocal s_asyncLocalCurrentUICulture; @@ -189,17 +199,55 @@ namespace System.Globalization } - internal CultureInfo(String name, bool useUserOverride) + public CultureInfo(String name, bool useUserOverride) { if (name == null) { - throw new ArgumentNullException("name", + throw new ArgumentNullException(nameof(name), SR.ArgumentNull_String); } InitializeFromName(name, useUserOverride); } + public CultureInfo(int culture) : this(culture, true) + { + } + + public CultureInfo(int culture, bool useUserOverride) + { + // We don't check for other invalid LCIDS here... + if (culture < 0) + { + throw new ArgumentOutOfRangeException(nameof(culture), SR.ArgumentOutOfRange_NeedPosNum); + } + Contract.EndContractBlock(); + + InitializeFromCultureId(culture, useUserOverride); + } + + private void InitializeFromCultureId(int culture, bool useUserOverride) + { + switch (culture) + { + case LOCALE_CUSTOM_DEFAULT: + case LOCALE_SYSTEM_DEFAULT: + case LOCALE_NEUTRAL: + case LOCALE_USER_DEFAULT: + case LOCALE_CUSTOM_UNSPECIFIED: + // Can't support unknown custom cultures and we do not support neutral or + // non-custom user locales. + throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported); + + default: + // Now see if this LCID is supported in the system default CultureData table. + m_cultureData = CultureData.GetCultureData(culture, useUserOverride); + break; + } + m_isInherited = (this.GetType() != typeof(System.Globalization.CultureInfo)); + m_name = m_cultureData.CultureName; + } + private void InitializeFromName(string name, bool useUserOverride) { // Get our data providing record @@ -207,13 +255,38 @@ namespace System.Globalization if (this.m_cultureData == null) { - throw new CultureNotFoundException("name", name, SR.Argument_CultureNotSupported); + throw new CultureNotFoundException(nameof(name), name, SR.Argument_CultureNotSupported); } this.m_name = this.m_cultureData.CultureName; this.m_isInherited = (this.GetType() != typeof(System.Globalization.CultureInfo)); } + // Constructor called by SQL Server's special munged culture - creates a culture with + // a TextInfo and CompareInfo that come from a supplied alternate source. This object + // is ALWAYS read-only. + // Note that we really cannot use an LCID version of this override as the cached + // name we create for it has to include both names, and the logic for this is in + // the GetCultureInfo override *only*. + internal CultureInfo(String cultureName, String textAndCompareCultureName) + { + if (cultureName == null) + { + throw new ArgumentNullException(nameof(cultureName),SR.ArgumentNull_String); + } + Contract.EndContractBlock(); + + m_cultureData = CultureData.GetCultureData(cultureName, false); + if (m_cultureData == null) + throw new CultureNotFoundException(nameof(cultureName), cultureName, SR.Argument_CultureNotSupported); + + m_name = m_cultureData.CultureName; + + CultureInfo altCulture = GetCultureInfo(textAndCompareCultureName); + compareInfo = altCulture.CompareInfo; + textInfo = altCulture.TextInfo; + } + // We do this to try to return the system UI language and the default user languages // This method will fallback if this fails (like Invariant) // @@ -239,6 +312,66 @@ namespace System.Globalization return ci; } + // + // Return a specific culture. A tad irrelevent now since we always return valid data + // for neutral locales. + // + // Note that there's interesting behavior that tries to find a smaller name, ala RFC4647, + // if we can't find a bigger name. That doesn't help with things like "zh" though, so + // the approach is of questionable value + // + public static CultureInfo CreateSpecificCulture(String name) + { + Contract.Ensures(Contract.Result() != null); + + CultureInfo culture; + + try + { + culture = new CultureInfo(name); + } + catch (ArgumentException) + { + // When CultureInfo throws this exception, it may be because someone passed the form + // like "az-az" because it came out of an http accept lang. We should try a little + // parsing to perhaps fall back to "az" here and use *it* to create the neutral. + + int idx; + + culture = null; + for (idx = 0; idx < name.Length; idx++) + { + if ('-' == name[idx]) + { + try + { + culture = new CultureInfo(name.Substring(0, idx)); + break; + } + catch (ArgumentException) + { + // throw the original exception so the name in the string will be right + throw; + } + } + } + + if (culture == null) + { + // nothing to save here; throw the original exception + throw; + } + } + + // In the most common case, they've given us a specific culture, so we'll just return that. + if (!(culture.IsNeutralCulture)) + { + return culture; + } + + return (new CultureInfo(culture.m_cultureData.SSPECIFICCULTURE)); + } + // // // // Return a specific culture. A tad irrelevent now since we always return valid data // // for neutral locales. @@ -296,7 +429,7 @@ namespace System.Globalization [OnDeserialized] private void OnDeserialized(StreamingContext ctx) { - Contract.Assert(m_name != null, "[CultureInfo.OnDeserialized] m_name != null"); + Debug.Assert(m_name != null, "[CultureInfo.OnDeserialized] m_name != null"); InitializeFromName(m_name, m_useUserOverride); } @@ -348,7 +481,7 @@ namespace System.Globalization Init(); } - Contract.Assert(s_userDefaultCulture != null); + Debug.Assert(s_userDefaultCulture != null); return s_userDefaultCulture; } @@ -356,7 +489,7 @@ namespace System.Globalization { if (value == null) { - throw new ArgumentNullException("value"); + throw new ArgumentNullException(nameof(value)); } if (s_asyncLocalCurrentCulture == null) @@ -396,7 +529,7 @@ namespace System.Globalization Init(); } - Contract.Assert(s_userDefaultCulture != null); + Debug.Assert(s_userDefaultCulture != null); return s_userDefaultCulture; } @@ -404,7 +537,7 @@ namespace System.Globalization { if (value == null) { - throw new ArgumentNullException("value"); + throw new ArgumentNullException(nameof(value)); } CultureInfo.VerifyCultureName(value, true); @@ -419,6 +552,20 @@ namespace System.Globalization } } + public static CultureInfo InstalledUICulture + { + get + { + Contract.Ensures(Contract.Result() != null); + if (s_userDefaultCulture == null) + { + Init(); + } + Debug.Assert(s_userDefaultCulture != null, "[CultureInfo.InstalledUICulture] s_userDefaultCulture != null"); + return s_userDefaultCulture; + } + } + public static CultureInfo DefaultThreadCurrentCulture { get { return s_DefaultThreadCurrentCulture; } @@ -513,6 +660,34 @@ namespace System.Globalization } } + public virtual int LCID + { + get + { + return (this.m_cultureData.ILANGUAGE); + } + } + + public virtual int KeyboardLayoutId + { + get + { + return m_cultureData.IINPUTLANGUAGEHANDLE; + } + } + + public static CultureInfo[] GetCultures(CultureTypes types) + { + Contract.Ensures(Contract.Result() != null); + // internally we treat UserCustomCultures as Supplementals but v2 + // treats as Supplementals and Replacements + if((types & CultureTypes.UserCustomCulture) == CultureTypes.UserCustomCulture) + { + types |= CultureTypes.ReplacementCultures; + } + return (CultureData.GetCultures(types)); + } + //////////////////////////////////////////////////////////////////////// // // Name @@ -552,6 +727,24 @@ namespace System.Globalization } } + public string IetfLanguageTag + { + get + { + Contract.Ensures(Contract.Result() != null); + + // special case the compatibility cultures + switch (this.Name) + { + case "zh-CHT": + return "zh-Hant"; + case "zh-CHS": + return "zh-Hans"; + default: + return this.Name; + } + } + } //////////////////////////////////////////////////////////////////////// // @@ -567,7 +760,7 @@ namespace System.Globalization get { Contract.Ensures(Contract.Result() != null); - Contract.Assert(m_name != null, "[CultureInfo.DisplayName] Always expect m_name to be set"); + Debug.Assert(m_name != null, "[CultureInfo.DisplayName] Always expect m_name to be set"); return m_cultureData.SLOCALIZEDDISPLAYNAME; } @@ -619,6 +812,32 @@ namespace System.Globalization } } + // ie: eng + public virtual String ThreeLetterISOLanguageName + { + get + { + Contract.Ensures(Contract.Result() != null); + return m_cultureData.SISO639LANGNAME2; + } + } + + //////////////////////////////////////////////////////////////////////// + // + // ThreeLetterWindowsLanguageName + // + // Returns the 3 letter windows language name for the current instance. eg: "ENU" + // The ISO names are much preferred + // + //////////////////////////////////////////////////////////////////////// + public virtual String ThreeLetterWindowsLanguageName + { + get + { + Contract.Ensures(Contract.Result() != null); + return m_cultureData.SABBREVLANGNAME; + } + } //////////////////////////////////////////////////////////////////////// // @@ -767,6 +986,31 @@ namespace System.Globalization } } + public CultureTypes CultureTypes + { + get + { + CultureTypes types = 0; + + if (m_cultureData.IsNeutralCulture) + types |= CultureTypes.NeutralCultures; + else + types |= CultureTypes.SpecificCultures; + + types |= m_cultureData.IsWin32Installed ? CultureTypes.InstalledWin32Cultures : 0; + +// Disable warning 618: System.Globalization.CultureTypes.FrameworkCultures' is obsolete +#pragma warning disable 618 + types |= m_cultureData.IsFramework ? CultureTypes.FrameworkCultures : 0; + +#pragma warning restore 618 + types |= m_cultureData.IsSupplementalCustomCulture ? CultureTypes.UserCustomCulture : 0; + types |= m_cultureData.IsReplacementCulture ? CultureTypes.ReplacementCultures | CultureTypes.UserCustomCulture : 0; + + return types; + } + } + public virtual NumberFormatInfo NumberFormat { get @@ -775,7 +1019,7 @@ namespace System.Globalization { NumberFormatInfo temp = new NumberFormatInfo(this.m_cultureData); temp.isReadOnly = m_isReadOnly; - numInfo = temp; + Interlocked.CompareExchange(ref numInfo, temp, null); } return (numInfo); } @@ -783,7 +1027,7 @@ namespace System.Globalization { if (value == null) { - throw new ArgumentNullException("value", SR.ArgumentNull_Obj); + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Obj); } VerifyWritable(); numInfo = value; @@ -807,8 +1051,7 @@ namespace System.Globalization // Change the calendar of DTFI to the specified calendar of this CultureInfo. DateTimeFormatInfo temp = new DateTimeFormatInfo(this.m_cultureData, this.Calendar); temp._isReadOnly = m_isReadOnly; - System.Threading.Interlocked.MemoryBarrier(); - dateTimeInfo = temp; + Interlocked.CompareExchange(ref dateTimeInfo, temp, null); } return (dateTimeInfo); } @@ -817,13 +1060,28 @@ namespace System.Globalization { if (value == null) { - throw new ArgumentNullException("value", SR.ArgumentNull_Obj); + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Obj); } VerifyWritable(); dateTimeInfo = value; } } + public void ClearCachedData() + { + s_userDefaultCulture = null; + + RegionInfo.s_currentRegionInfo = null; + #pragma warning disable 0618 // disable the obsolete warning + TimeZone.ResetTimeZone(); + #pragma warning restore 0618 + TimeZoneInfo.ClearCachedData(); + s_LcidCachedCultures = null; + s_NameCachedCultures = null; + + CultureData.ClearCachedData(); + } + /*=================================GetCalendarInstance========================== **Action: Map a Win32 CALID to an instance of supported calendar. **Returns: An instance of calendar. @@ -845,7 +1103,7 @@ namespace System.Globalization //calendars unless they're required. internal static Calendar GetCalendarInstanceRare(CalendarId calType) { - Contract.Assert(calType != CalendarId.GREGORIAN, "calType!=CalendarId.GREGORIAN"); + Debug.Assert(calType != CalendarId.GREGORIAN, "calType!=CalendarId.GREGORIAN"); switch (calType) { @@ -889,7 +1147,7 @@ namespace System.Globalization { if (calendar == null) { - Contract.Assert(this.m_cultureData.CalendarIds.Length > 0, "this.m_cultureData.CalendarIds.Length > 0"); + Debug.Assert(this.m_cultureData.CalendarIds.Length > 0, "this.m_cultureData.CalendarIds.Length > 0"); // Get the default calendar for this culture. Note that the value can be // from registry if this is a user default culture. Calendar newObj = this.m_cultureData.DefaultCalendar; @@ -929,13 +1187,26 @@ namespace System.Globalization } } - - private bool UseUserOverride + public bool UseUserOverride { get { - return (this.m_cultureData.UseUserOverride); + return m_cultureData.UseUserOverride; + } + } + + public CultureInfo GetConsoleFallbackUICulture() + { + Contract.Ensures(Contract.Result() != null); + + CultureInfo temp = m_consoleFallbackCulture; + if (temp == null) + { + temp = CreateSpecificCulture(m_cultureData.SCONSOLEFALLBACKNAME); + temp.m_isReadOnly = true; + m_consoleFallbackCulture = temp; } + return (temp); } public virtual Object Clone() @@ -979,7 +1250,7 @@ namespace System.Globalization { if (ci == null) { - throw new ArgumentNullException("ci"); + throw new ArgumentNullException(nameof(ci)); } Contract.Ensures(Contract.Result() != null); Contract.EndContractBlock(); @@ -1054,26 +1325,25 @@ namespace System.Globalization get { return Name == CultureInfo.InvariantCulture.Name; } } - // Helper function both both overloads of GetCachedReadOnlyCulture. - internal static CultureInfo GetCultureInfoHelper(string name) + // Helper function both both overloads of GetCachedReadOnlyCulture. If lcid is 0, we use the name. + // If lcid is -1, use the altName and create one of those special SQL cultures. + internal static CultureInfo GetCultureInfoHelper(int lcid, string name, string altName) { - // There is a race condition in this code with the side effect that the second thread's value - // clobbers the first in the dictionary. This is an acceptable race since the CultureInfo objects - // are content equal (but not reference equal). Since we make no guarantees there, this race is - // acceptable. - // retval is our return value. CultureInfo retval; - if (name == null) - { - return null; - } - // Temporary hashtable for the names. StringCultureInfoDictionary tempNameHT = s_NameCachedCultures; - name = CultureData.AnsiToLower(name); + if (name != null) + { + name = CultureData.AnsiToLower(name); + } + + if (altName != null) + { + altName = CultureData.AnsiToLower(altName); + } // We expect the same result for both hashtables, but will test individually for added safety. if (tempNameHT == null) @@ -1082,20 +1352,66 @@ namespace System.Globalization } else { - bool ret; - lock (m_lock) + // If we are called by name, check if the object exists in the hashtable. If so, return it. + if (lcid == -1 || lcid == 0) { - ret = tempNameHT.TryGetValue(name, out retval); + bool ret; + lock (m_lock) + { + ret = tempNameHT.TryGetValue(lcid == 0 ? name : name + '\xfffd' + altName, out retval); + } + + if (ret && retval != null) + { + return retval; + } } + } - if (ret && retval != null) + // Next, the Lcid table. + StringLcidDictionary tempLcidHT = s_LcidCachedCultures; + + if (tempLcidHT == null) + { + // Case insensitive is not an issue here, save the constructor call. + tempLcidHT = new StringLcidDictionary(); + } + else + { + // If we were called by Lcid, check if the object exists in the table. If so, return it. + if (lcid > 0) { - return retval; + bool ret; + lock (m_lock) + { + ret = tempLcidHT.TryGetValue(lcid, out retval); + } + if (ret && retval != null) + { + return retval; + } } } + + // We now have two temporary hashtables and the desired object was not found. + // We'll construct it. We catch any exceptions from the constructor call and return null. try { - retval = new CultureInfo(name, false); + switch (lcid) + { + case -1: + // call the private constructor + retval = new CultureInfo(name, altName); + break; + + case 0: + retval = new CultureInfo(name, false); + break; + + default: + retval = new CultureInfo(lcid, false); + break; + } } catch (ArgumentException) { @@ -1105,14 +1421,42 @@ namespace System.Globalization // Set it to read-only retval.m_isReadOnly = true; - // Remember our name (as constructed). Do NOT use alternate sort name versions because - // we have internal state representing the sort. (So someone would get the wrong cached version) - string newName = CultureData.AnsiToLower(retval.m_name); + if (lcid == -1) + { + lock (m_lock) + { + // This new culture will be added only to the name hash table. + tempNameHT[name + '\xfffd' + altName] = retval; + } + // when lcid == -1 then TextInfo object is already get created and we need to set it as read only. + retval.TextInfo.SetReadOnlyState(true); + } + else if (lcid == 0) + { + // Remember our name (as constructed). Do NOT use alternate sort name versions because + // we have internal state representing the sort. (So someone would get the wrong cached version) + string newName = CultureData.AnsiToLower(retval.m_name); + + // We add this new culture info object to both tables. + lock (m_lock) + { + tempNameHT[newName] = retval; + } + } + else + { + lock (m_lock) + { + tempLcidHT[lcid] = retval; + } + } - // We add this new culture info object to both tables. - lock (m_lock) + // Copy the two hashtables to the corresponding member variables. This will potentially overwrite + // new tables simultaneously created by a new thread, but maximizes thread safety. + if (-1 != lcid) { - tempNameHT[newName] = retval; + // Only when we modify the lcid hash table, is there a need to overwrite. + s_LcidCachedCultures = tempLcidHT; } s_NameCachedCultures = tempNameHT; @@ -1121,24 +1465,94 @@ namespace System.Globalization return retval; } + // Gets a cached copy of the specified culture from an internal hashtable (or creates it + // if not found). (LCID version)... use named version + public static CultureInfo GetCultureInfo(int culture) + { + // Must check for -1 now since the helper function uses the value to signal + // the altCulture code path for SQL Server. + // Also check for zero as this would fail trying to add as a key to the hash. + if (culture <= 0) + { + throw new ArgumentOutOfRangeException(nameof(culture), SR.ArgumentOutOfRange_NeedPosNum); + } + Contract.Ensures(Contract.Result() != null); + Contract.EndContractBlock(); + CultureInfo retval = GetCultureInfoHelper(culture, null, null); + if (null == retval) + { + throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported); + } + return retval; + } + // Gets a cached copy of the specified culture from an internal hashtable (or creates it // if not found). (Named version) - internal static CultureInfo GetCultureInfo(string name) + public static CultureInfo GetCultureInfo(string name) { // Make sure we have a valid, non-zero length string as name if (name == null) { - throw new ArgumentNullException("name"); + throw new ArgumentNullException(nameof(name)); } - CultureInfo retval = GetCultureInfoHelper(name); + CultureInfo retval = GetCultureInfoHelper(0, name, null); if (retval == null) { throw new CultureNotFoundException( - "name", name, SR.Argument_CultureNotSupported); + nameof(name), name, SR.Argument_CultureNotSupported); + } + return retval; + } + + // Gets a cached copy of the specified culture from an internal hashtable (or creates it + // if not found). + public static CultureInfo GetCultureInfo(string name, string altName) + { + // Make sure we have a valid, non-zero length string as name + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (altName == null) + { + throw new ArgumentNullException(nameof(altName)); + } + + Contract.Ensures(Contract.Result() != null); + Contract.EndContractBlock(); + + CultureInfo retval = GetCultureInfoHelper(-1, name, altName); + if (retval == null) + { + throw new CultureNotFoundException("name or altName", + SR.Format(SR.Argument_OneOfCulturesNotSupported, name, altName)); } return retval; } + + // This function is deprecated, we don't like it + public static CultureInfo GetCultureInfoByIetfLanguageTag(string name) + { + Contract.Ensures(Contract.Result() != null); + + // Disallow old zh-CHT/zh-CHS names + if (name == "zh-CHT" || name == "zh-CHS") + { + throw new CultureNotFoundException(nameof(name), SR.Format(SR.Argument_CultureIetfNotSupported, name)); + } + + CultureInfo ci = GetCultureInfo(name); + + // Disallow alt sorts and es-es_TS + if (ci.LCID > 0xffff || ci.LCID == 0x040a) + { + throw new CultureNotFoundException(nameof(name), SR.Format(SR.Argument_CultureIetfNotSupported, name)); + } + + return ci; + } } } diff --git a/src/mscorlib/corefx/System/Globalization/CultureNotFoundException.cs b/src/mscorlib/corefx/System/Globalization/CultureNotFoundException.cs index 740063e4d3..64782d28c0 100644 --- a/src/mscorlib/corefx/System/Globalization/CultureNotFoundException.cs +++ b/src/mscorlib/corefx/System/Globalization/CultureNotFoundException.cs @@ -13,6 +13,7 @@ namespace System.Globalization public partial class CultureNotFoundException : ArgumentException, ISerializable { private string _invalidCultureName; // unrecognized culture name + private int? _invalidCultureId; // unrecognized culture Lcid public CultureNotFoundException() : base(DefaultMessage) @@ -46,8 +47,22 @@ namespace System.Globalization _invalidCultureName = invalidCultureName; } - protected CultureNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) + public CultureNotFoundException(string message, int invalidCultureId, Exception innerException) + : base(message, innerException) + { + _invalidCultureId = invalidCultureId; + } + + public CultureNotFoundException(string paramName, int invalidCultureId, string message) + : base(message, paramName) + { + _invalidCultureId = invalidCultureId; + } + + protected CultureNotFoundException(SerializationInfo info, StreamingContext context) + : base(info, context) { + _invalidCultureId = (int?)info.GetValue("InvalidCultureId", typeof(int?)); _invalidCultureName = (string)info.GetValue("InvalidCultureName", typeof(string)); } @@ -56,13 +71,19 @@ namespace System.Globalization { if (info == null) { - throw new ArgumentNullException("info"); + throw new ArgumentNullException(nameof(info)); } base.GetObjectData(info, context); + info.AddValue("InvalidCultureId", _invalidCultureId, typeof(int?)); info.AddValue("InvalidCultureName", _invalidCultureName, typeof(string)); } + public virtual Nullable InvalidCultureId + { + get { return _invalidCultureId; } + } + public virtual string InvalidCultureName { get { return _invalidCultureName; } @@ -80,7 +101,9 @@ namespace System.Globalization { get { - return InvalidCultureName; + return InvalidCultureId != null ? + String.Format(CultureInfo.InvariantCulture, "{0} (0x{0:x4})", (int)InvalidCultureId) : + InvalidCultureName; } } @@ -89,12 +112,14 @@ namespace System.Globalization get { String s = base.Message; - if ( - _invalidCultureName != null) + if (_invalidCultureId != null || _invalidCultureName != null) { String valueMessage = SR.Format(SR.Argument_CultureInvalidIdentifier, FormatedInvalidCultureId); if (s == null) + { return valueMessage; + } + return s + Environment.NewLine + valueMessage; } return s; diff --git a/src/mscorlib/corefx/System/Globalization/CultureTypes.cs b/src/mscorlib/corefx/System/Globalization/CultureTypes.cs new file mode 100644 index 0000000000..80b588aabb --- /dev/null +++ b/src/mscorlib/corefx/System/Globalization/CultureTypes.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// The enumeration constants used in CultureInfo.GetCultures(). +// On Linux platforms, the only enum values used there is NeutralCultures and SpecificCultures +// the rest are obsolete or not valid on Linux + +namespace System.Globalization +{ + [Serializable] + [Flags] + public enum CultureTypes + { + NeutralCultures = 0x0001, // Neutral cultures are cultures like "en", "de", "zh", etc, for enumeration this includes ALL neutrals regardless of other flags + SpecificCultures = 0x0002, // Non-netural cultuers. Examples are "en-us", "zh-tw", etc., for enumeration this includes ALL specifics regardless of other flags + InstalledWin32Cultures = 0x0004, // Win32 installed cultures in the system and exists in the framework too., this is effectively all cultures + + AllCultures = NeutralCultures | SpecificCultures | InstalledWin32Cultures, + + UserCustomCulture = 0x0008, // User defined custom culture + ReplacementCultures = 0x0010, // User defined replacement custom culture. + [Obsolete("This value has been deprecated. Please use other values in CultureTypes.")] + WindowsOnlyCultures = 0x0020, // this will always return empty list. + [Obsolete("This value has been deprecated. Please use other values in CultureTypes.")] + FrameworkCultures = 0x0040, // will return only the v2 cultures marked as Framework culture. + } +} diff --git a/src/mscorlib/corefx/System/Globalization/DateTimeFormatInfo.cs b/src/mscorlib/corefx/System/Globalization/DateTimeFormatInfo.cs index da746ada88..216fc603d0 100644 --- a/src/mscorlib/corefx/System/Globalization/DateTimeFormatInfo.cs +++ b/src/mscorlib/corefx/System/Globalization/DateTimeFormatInfo.cs @@ -2,15 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.Contracts; -using System.Runtime.InteropServices; using System.Runtime.Serialization; -using System.Security; -using System.Text; -using System.Threading; namespace System.Globalization { @@ -55,8 +50,7 @@ namespace System.Globalization [Serializable] - [System.Runtime.InteropServices.ComVisible(true)] - public sealed partial class DateTimeFormatInfo : IFormatProvider, ICloneable + public sealed class DateTimeFormatInfo : IFormatProvider, ICloneable { // cache for the invariant culture. // invariantInfo is constant irrespective of your current culture. @@ -228,7 +222,7 @@ namespace System.Globalization { // Get the abbreviated day names for our current calendar this.abbreviatedDayNames = _cultureData.AbbreviatedDayNames(Calendar.ID); - Contract.Assert(this.abbreviatedDayNames.Length == 7, "[DateTimeFormatInfo.GetAbbreviatedDayOfWeekNames] Expected 7 day names in a week"); + Debug.Assert(this.abbreviatedDayNames.Length == 7, "[DateTimeFormatInfo.GetAbbreviatedDayOfWeekNames] Expected 7 day names in a week"); } return (this.abbreviatedDayNames); } @@ -252,7 +246,7 @@ namespace System.Globalization { // Get the super short day names for our current calendar this.m_superShortDayNames = _cultureData.SuperShortDayNames(Calendar.ID); - Contract.Assert(this.m_superShortDayNames.Length == 7, "[DateTimeFormatInfo.internalGetSuperShortDayNames] Expected 7 day names in a week"); + Debug.Assert(this.m_superShortDayNames.Length == 7, "[DateTimeFormatInfo.internalGetSuperShortDayNames] Expected 7 day names in a week"); } return (this.m_superShortDayNames); } @@ -269,7 +263,7 @@ namespace System.Globalization { // Get the day names for our current calendar this.dayNames = _cultureData.DayNames(Calendar.ID); - Contract.Assert(this.dayNames.Length == 7, "[DateTimeFormatInfo.GetDayOfWeekNames] Expected 7 day names in a week"); + Debug.Assert(this.dayNames.Length == 7, "[DateTimeFormatInfo.GetDayOfWeekNames] Expected 7 day names in a week"); } return (this.dayNames); } @@ -286,7 +280,7 @@ namespace System.Globalization { // Get the month names for our current calendar this.abbreviatedMonthNames = _cultureData.AbbreviatedMonthNames(Calendar.ID); - Contract.Assert(this.abbreviatedMonthNames.Length == 12 || this.abbreviatedMonthNames.Length == 13, + Debug.Assert(this.abbreviatedMonthNames.Length == 12 || this.abbreviatedMonthNames.Length == 13, "[DateTimeFormatInfo.GetAbbreviatedMonthNames] Expected 12 or 13 month names in a year"); } return (this.abbreviatedMonthNames); @@ -305,7 +299,7 @@ namespace System.Globalization { // Get the month names for our current calendar this.monthNames = _cultureData.MonthNames(Calendar.ID); - Contract.Assert(this.monthNames.Length == 12 || this.monthNames.Length == 13, + Debug.Assert(this.monthNames.Length == 12 || this.monthNames.Length == 13, "[DateTimeFormatInfo.GetMonthNames] Expected 12 or 13 month names in a year"); } @@ -324,8 +318,8 @@ namespace System.Globalization internal DateTimeFormatInfo(CultureData cultureData, Calendar cal) { - Contract.Requires(cultureData != null); - Contract.Requires(cal != null); + Debug.Assert(cultureData != null); + Debug.Assert(cal != null); // Remember our culture _cultureData = cultureData; @@ -335,8 +329,8 @@ namespace System.Globalization private void InitializeOverridableProperties(CultureData cultureData, CalendarId calendarId) { - Contract.Requires(cultureData != null); - Contract.Assert(calendarId != CalendarId.UNINITIALIZED_VALUE, "[DateTimeFormatInfo.Populate] Expected initalized calendarId"); + Debug.Assert(cultureData != null); + Debug.Assert(calendarId != CalendarId.UNINITIALIZED_VALUE, "[DateTimeFormatInfo.Populate] Expected initalized calendarId"); if (this.firstDayOfWeek == -1) { this.firstDayOfWeek = cultureData.IFIRSTDAYOFWEEK; } if (this.calendarWeekRule == -1) { this.calendarWeekRule = cultureData.IFIRSTWEEKOFYEAR; } @@ -347,19 +341,19 @@ namespace System.Globalization if (this.dateSeparator == null) { this.dateSeparator = cultureData.DateSeparator(calendarId); } this.allLongTimePatterns = _cultureData.LongTimes; - Contract.Assert(this.allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long time patterns"); + Debug.Assert(this.allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long time patterns"); this.allShortTimePatterns = _cultureData.ShortTimes; - Contract.Assert(this.allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short time patterns"); + Debug.Assert(this.allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short time patterns"); this.allLongDatePatterns = cultureData.LongDates(calendarId); - Contract.Assert(this.allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long date patterns"); + Debug.Assert(this.allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some long date patterns"); this.allShortDatePatterns = cultureData.ShortDates(calendarId); - Contract.Assert(this.allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short date patterns"); + Debug.Assert(this.allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some short date patterns"); this.allYearMonthPatterns = cultureData.YearMonths(calendarId); - Contract.Assert(this.allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some year month patterns"); + Debug.Assert(this.allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.Populate] Expected some year month patterns"); } [OptionalField(VersionAdded = 1)] @@ -527,7 +521,7 @@ namespace System.Globalization { this.amDesignator = _cultureData.SAM1159; } - Contract.Assert(this.amDesignator != null, "DateTimeFormatInfo.AMDesignator, amDesignator != null"); + Debug.Assert(this.amDesignator != null, "DateTimeFormatInfo.AMDesignator, amDesignator != null"); return (this.amDesignator); } @@ -537,7 +531,7 @@ namespace System.Globalization throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); if (value == null) { - throw new ArgumentNullException("value", + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); } Contract.EndContractBlock(); @@ -553,7 +547,7 @@ namespace System.Globalization { Contract.Ensures(Contract.Result() != null); - Contract.Assert(this.calendar != null, "DateTimeFormatInfo.Calendar: calendar != null"); + Debug.Assert(this.calendar != null, "DateTimeFormatInfo.Calendar: calendar != null"); return (this.calendar); } @@ -563,7 +557,7 @@ namespace System.Globalization throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); if (value == null) { - throw new ArgumentNullException("value", SR.ArgumentNull_Obj); + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Obj); } Contract.EndContractBlock(); if (value == calendar) @@ -641,7 +635,7 @@ namespace System.Globalization } // The assigned calendar is not a valid calendar for this culture, throw - throw new ArgumentOutOfRangeException("value", SR.Argument_InvalidCalendar); + throw new ArgumentOutOfRangeException(nameof(value), SR.Argument_InvalidCalendar); } } @@ -670,7 +664,7 @@ namespace System.Globalization { if (eraName == null) { - throw new ArgumentNullException("eraName", + throw new ArgumentNullException(nameof(eraName), SR.ArgumentNull_String); } Contract.EndContractBlock(); @@ -759,7 +753,7 @@ namespace System.Globalization { return (m_eraNames[era]); } - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } internal String[] AbbreviatedEraNames @@ -791,7 +785,7 @@ namespace System.Globalization { return (m_abbrevEraNames[era]); } - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } internal String[] AbbreviatedEnglishEraNames @@ -800,34 +794,41 @@ namespace System.Globalization { if (this.m_abbrevEnglishEraNames == null) { - Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.AbbreviatedEnglishEraNames] Expected Calendar.ID > 0"); + Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.AbbreviatedEnglishEraNames] Expected Calendar.ID > 0"); this.m_abbrevEnglishEraNames = _cultureData.AbbreviatedEnglishEraNames(Calendar.ID); } return (this.m_abbrevEnglishEraNames); } } - // Note that cultureData derives this from the short date format (unless someone's set this previously) // Note that this property is quite undesirable. - internal String DateSeparator + public string DateSeparator { get { - if (this.dateSeparator == null) + if (dateSeparator == null) { - this.dateSeparator = _cultureData.DateSeparator(Calendar.ID); + dateSeparator = _cultureData.DateSeparator(Calendar.ID); } - Contract.Assert(this.dateSeparator != null, "DateTimeFormatInfo.DateSeparator, dateSeparator != null"); - return (this.dateSeparator); + Debug.Assert(this.dateSeparator != null, "DateTimeFormatInfo.DateSeparator, dateSeparator != null"); + return dateSeparator; } set { - throw null; + if (IsReadOnly) + throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); + + if (value == null) + { + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); + } + Contract.EndContractBlock(); + ClearTokenHashTable(); + dateSeparator = value; } } - public DayOfWeek FirstDayOfWeek { get @@ -836,7 +837,7 @@ namespace System.Globalization { this.firstDayOfWeek = _cultureData.IFIRSTDAYOFWEEK; } - Contract.Assert(this.firstDayOfWeek != -1, "DateTimeFormatInfo.FirstDayOfWeek, firstDayOfWeek != -1"); + Debug.Assert(this.firstDayOfWeek != -1, "DateTimeFormatInfo.FirstDayOfWeek, firstDayOfWeek != -1"); return ((DayOfWeek)this.firstDayOfWeek); } @@ -852,7 +853,7 @@ namespace System.Globalization else { throw new ArgumentOutOfRangeException( - "value", SR.Format(SR.ArgumentOutOfRange_Range, + nameof(value), SR.Format(SR.ArgumentOutOfRange_Range, DayOfWeek.Sunday, DayOfWeek.Saturday)); } } @@ -866,7 +867,7 @@ namespace System.Globalization { this.calendarWeekRule = _cultureData.IFIRSTWEEKOFYEAR; } - Contract.Assert(this.calendarWeekRule != -1, "DateTimeFormatInfo.CalendarWeekRule, calendarWeekRule != -1"); + Debug.Assert(this.calendarWeekRule != -1, "DateTimeFormatInfo.CalendarWeekRule, calendarWeekRule != -1"); return ((CalendarWeekRule)this.calendarWeekRule); } @@ -881,7 +882,7 @@ namespace System.Globalization else { throw new ArgumentOutOfRangeException( - "value", SR.Format(SR.ArgumentOutOfRange_Range, + nameof(value), SR.Format(SR.ArgumentOutOfRange_Range, CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek)); } } @@ -904,7 +905,7 @@ namespace System.Globalization throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); if (value == null) { - throw new ArgumentNullException("value", + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); } Contract.EndContractBlock(); @@ -938,7 +939,7 @@ namespace System.Globalization throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); if (value == null) { - throw new ArgumentNullException("value", + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); } Contract.EndContractBlock(); @@ -979,7 +980,7 @@ namespace System.Globalization throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); if (value == null) { - throw new ArgumentNullException("value", + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); } Contract.EndContractBlock(); @@ -1005,10 +1006,10 @@ namespace System.Globalization { if (this.monthDayPattern == null) { - Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.MonthDayPattern] Expected calID > 0"); + Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.MonthDayPattern] Expected calID > 0"); this.monthDayPattern = _cultureData.MonthDay(Calendar.ID); } - Contract.Assert(this.monthDayPattern != null, "DateTimeFormatInfo.MonthDayPattern, monthDayPattern != null"); + Debug.Assert(this.monthDayPattern != null, "DateTimeFormatInfo.MonthDayPattern, monthDayPattern != null"); return (this.monthDayPattern); } @@ -1018,7 +1019,7 @@ namespace System.Globalization throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); if (value == null) { - throw new ArgumentNullException("value", + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); } Contract.EndContractBlock(); @@ -1037,7 +1038,7 @@ namespace System.Globalization { this.pmDesignator = _cultureData.SPM2359; } - Contract.Assert(this.pmDesignator != null, "DateTimeFormatInfo.PMDesignator, pmDesignator != null"); + Debug.Assert(this.pmDesignator != null, "DateTimeFormatInfo.PMDesignator, pmDesignator != null"); return (this.pmDesignator); } @@ -1047,7 +1048,7 @@ namespace System.Globalization throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); if (value == null) { - throw new ArgumentNullException("value", + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); } Contract.EndContractBlock(); @@ -1090,7 +1091,7 @@ namespace System.Globalization if (IsReadOnly) throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); if (value == null) - throw new ArgumentNullException("value", + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); Contract.EndContractBlock(); @@ -1132,7 +1133,7 @@ namespace System.Globalization throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); if (value == null) { - throw new ArgumentNullException("value", + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); } Contract.EndContractBlock(); @@ -1270,7 +1271,7 @@ namespace System.Globalization // Note that cultureData derives this from the long time format (unless someone's set this previously) // Note that this property is quite undesirable. - internal String TimeSeparator + public string TimeSeparator { get { @@ -1278,17 +1279,27 @@ namespace System.Globalization { timeSeparator = _cultureData.TimeSeparator; } - Contract.Assert(this.timeSeparator != null, "DateTimeFormatInfo.TimeSeparator, timeSeparator != null"); + Debug.Assert(this.timeSeparator != null, "DateTimeFormatInfo.TimeSeparator, timeSeparator != null"); return (timeSeparator); } set { - throw null; + if (IsReadOnly) + throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); + + if (value == null) + { + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); + } + + Contract.EndContractBlock(); + ClearTokenHashTable(); + + timeSeparator = value; } } - public String UniversalSortableDateTimePattern { get @@ -1321,7 +1332,7 @@ namespace System.Globalization throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); if (value == null) { - throw new ArgumentNullException("value", + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); } Contract.EndContractBlock(); @@ -1339,8 +1350,8 @@ namespace System.Globalization // private static void CheckNullValue(String[] values, int length) { - Contract.Requires(values != null, "value != null"); - Contract.Requires(values.Length >= length); + Debug.Assert(values != null, "value != null"); + Debug.Assert(values.Length >= length); for (int i = 0; i < length; i++) { if (values[i] == null) @@ -1365,12 +1376,12 @@ namespace System.Globalization throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); if (value == null) { - throw new ArgumentNullException("value", + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Array); } if (value.Length != 7) { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), "value"); + throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value)); } Contract.EndContractBlock(); CheckNullValue(value, value.Length); @@ -1381,7 +1392,6 @@ namespace System.Globalization } // Returns the string array of the one-letter day of week names. - [System.Runtime.InteropServices.ComVisible(false)] public String[] ShortestDayNames { get @@ -1395,12 +1405,12 @@ namespace System.Globalization throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); if (value == null) { - throw new ArgumentNullException("value", + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Array); } if (value.Length != 7) { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), "value"); + throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value)); } Contract.EndContractBlock(); CheckNullValue(value, value.Length); @@ -1422,12 +1432,12 @@ namespace System.Globalization throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); if (value == null) { - throw new ArgumentNullException("value", + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Array); } if (value.Length != 7) { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), "value"); + throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 7), nameof(value)); } Contract.EndContractBlock(); CheckNullValue(value, value.Length); @@ -1451,12 +1461,12 @@ namespace System.Globalization throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); if (value == null) { - throw new ArgumentNullException("value", + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Array); } if (value.Length != 13) { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), "value"); + throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value)); } Contract.EndContractBlock(); CheckNullValue(value, value.Length - 1); @@ -1479,12 +1489,12 @@ namespace System.Globalization throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); if (value == null) { - throw new ArgumentNullException("value", + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Array); } if (value.Length != 13) { - throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), "value"); + throw new ArgumentException(SR.Format(SR.Argument_InvalidArrayLength, 13), nameof(value)); } Contract.EndContractBlock(); CheckNullValue(value, value.Length - 1); @@ -1550,7 +1560,7 @@ namespace System.Globalization if ((month < 1) || (month > monthNamesArray.Length)) { throw new ArgumentOutOfRangeException( - "month", SR.Format(SR.ArgumentOutOfRange_Range, + nameof(month), SR.Format(SR.ArgumentOutOfRange_Range, 1, monthNamesArray.Length)); } return (monthNamesArray[month - 1]); @@ -1571,7 +1581,7 @@ namespace System.Globalization if (this.m_genitiveAbbreviatedMonthNames == null) { this.m_genitiveAbbreviatedMonthNames = _cultureData.AbbreviatedGenitiveMonthNames(this.Calendar.ID); - Contract.Assert(this.m_genitiveAbbreviatedMonthNames.Length == 13, + Debug.Assert(this.m_genitiveAbbreviatedMonthNames.Length == 13, "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 abbreviated genitive month names in a year"); } return (this.m_genitiveAbbreviatedMonthNames); @@ -1580,7 +1590,7 @@ namespace System.Globalization if (this.genitiveMonthNames == null) { this.genitiveMonthNames = _cultureData.GenitiveMonthNames(this.Calendar.ID); - Contract.Assert(this.genitiveMonthNames.Length == 13, + Debug.Assert(this.genitiveMonthNames.Length == 13, "[DateTimeFormatInfo.GetGenitiveMonthNames] Expected 13 genitive month names in a year"); } return (this.genitiveMonthNames); @@ -1597,9 +1607,9 @@ namespace System.Globalization { if (this.leapYearMonthNames == null) { - Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expected Calendar.ID > 0"); + Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expected Calendar.ID > 0"); this.leapYearMonthNames = _cultureData.LeapYearMonthNames(Calendar.ID); - Contract.Assert(this.leapYearMonthNames.Length == 13, + Debug.Assert(this.leapYearMonthNames.Length == 13, "[DateTimeFormatInfo.internalGetLeapYearMonthNames] Expepcted 13 leap year month names"); } return (leapYearMonthNames); @@ -1611,7 +1621,7 @@ namespace System.Globalization if ((int)dayofweek < 0 || (int)dayofweek > 6) { throw new ArgumentOutOfRangeException( - "dayofweek", SR.Format(SR.ArgumentOutOfRange_Range, + nameof(dayofweek), SR.Format(SR.ArgumentOutOfRange_Range, DayOfWeek.Sunday, DayOfWeek.Saturday)); } Contract.EndContractBlock(); @@ -1622,11 +1632,28 @@ namespace System.Globalization return (internalGetAbbreviatedDayOfWeekNames()[(int)dayofweek]); } + // Returns the super short day of week names for the specified day of week. + public string GetShortestDayName(DayOfWeek dayOfWeek) + { + if ((int)dayOfWeek < 0 || (int)dayOfWeek > 6) + { + throw new ArgumentOutOfRangeException( + nameof(dayOfWeek), SR.Format(SR.ArgumentOutOfRange_Range, + DayOfWeek.Sunday, DayOfWeek.Saturday)); + } + Contract.EndContractBlock(); + // + // Don't call the public property SuperShortDayNames here since a clone is needed in that + // property, so it will be slower. Instead, use internalGetSuperShortDayNames() directly. + // + return (internalGetSuperShortDayNames()[(int)dayOfWeek]); + } + // Get all possible combination of inputs private static String[] GetCombinedPatterns(String[] patterns1, String[] patterns2, String connectString) { - Contract.Requires(patterns1 != null); - Contract.Requires(patterns2 != null); + Debug.Assert(patterns1 != null); + Debug.Assert(patterns2 != null); // Get array size String[] result = new String[patterns1.Length * patterns2.Length]; @@ -1646,9 +1673,22 @@ namespace System.Globalization return (result); } + public string[] GetAllDateTimePatterns() + { + List results = new List(DEFAULT_ALL_DATETIMES_SIZE); + + for (int i = 0; i < DateTimeFormat.allStandardFormats.Length; i++) + { + String[] strings = GetAllDateTimePatterns(DateTimeFormat.allStandardFormats[i]); + for (int j = 0; j < strings.Length; j++) + { + results.Add(strings[j]); + } + } + return results.ToArray(); + } - // auto-generated - internal String[] GetAllDateTimePatterns(char format) + public string[] GetAllDateTimePatterns(char format) { Contract.Ensures(Contract.Result() != null); String[] result = null; @@ -1703,7 +1743,7 @@ namespace System.Globalization result = this.AllYearMonthPatterns; break; default: - throw new ArgumentException(SR.Format_BadFormatSpecifier, "format"); + throw new ArgumentException(SR.Format_BadFormatSpecifier, nameof(format)); } return (result); } @@ -1714,7 +1754,7 @@ namespace System.Globalization if ((int)dayofweek < 0 || (int)dayofweek > 6) { throw new ArgumentOutOfRangeException( - "dayofweek", SR.Format(SR.ArgumentOutOfRange_Range, + nameof(dayofweek), SR.Format(SR.ArgumentOutOfRange_Range, DayOfWeek.Sunday, DayOfWeek.Saturday)); } Contract.EndContractBlock(); @@ -1730,7 +1770,7 @@ namespace System.Globalization if (month < 1 || month > 13) { throw new ArgumentOutOfRangeException( - "month", SR.Format(SR.ArgumentOutOfRange_Range, + nameof(month), SR.Format(SR.ArgumentOutOfRange_Range, 1, 13)); } Contract.EndContractBlock(); @@ -1744,7 +1784,7 @@ namespace System.Globalization if (month < 1 || month > 13) { throw new ArgumentOutOfRangeException( - "month", SR.Format(SR.ArgumentOutOfRange_Range, + nameof(month), SR.Format(SR.ArgumentOutOfRange_Range, 1, 13)); } Contract.EndContractBlock(); @@ -1761,9 +1801,9 @@ namespace System.Globalization // The resulting [] can get returned to the calling app, so clone it. private static string[] GetMergedPatterns(string[] patterns, string defaultPattern) { - Contract.Assert(patterns != null && patterns.Length > 0, + Debug.Assert(patterns != null && patterns.Length > 0, "[DateTimeFormatInfo.GetMergedPatterns]Expected array of at least one pattern"); - Contract.Assert(defaultPattern != null, + Debug.Assert(defaultPattern != null, "[DateTimeFormatInfo.GetMergedPatterns]Expected non null default string"); // If the default happens to be the first in the list just return (a cloned) copy @@ -1864,9 +1904,9 @@ namespace System.Globalization { if (this.allYearMonthPatterns == null) { - Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected Calendar.ID > 0"); + Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected Calendar.ID > 0"); this.allYearMonthPatterns = _cultureData.YearMonths(this.Calendar.ID); - Contract.Assert(this.allYearMonthPatterns.Length > 0, + Debug.Assert(this.allYearMonthPatterns.Length > 0, "[DateTimeFormatInfo.UnclonedYearMonthPatterns] Expected some year month patterns"); } @@ -1883,9 +1923,9 @@ namespace System.Globalization { if (allShortDatePatterns == null) { - Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected Calendar.ID > 0"); + Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected Calendar.ID > 0"); this.allShortDatePatterns = _cultureData.ShortDates(this.Calendar.ID); - Contract.Assert(this.allShortDatePatterns.Length > 0, + Debug.Assert(this.allShortDatePatterns.Length > 0, "[DateTimeFormatInfo.UnclonedShortDatePatterns] Expected some short date patterns"); } @@ -1901,9 +1941,9 @@ namespace System.Globalization { if (allLongDatePatterns == null) { - Contract.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected Calendar.ID > 0"); + Debug.Assert(Calendar.ID > 0, "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected Calendar.ID > 0"); this.allLongDatePatterns = _cultureData.LongDates(this.Calendar.ID); - Contract.Assert(this.allLongDatePatterns.Length > 0, + Debug.Assert(this.allLongDatePatterns.Length > 0, "[DateTimeFormatInfo.UnclonedLongDatePatterns] Expected some long date patterns"); } @@ -1920,7 +1960,7 @@ namespace System.Globalization if (this.allShortTimePatterns == null) { this.allShortTimePatterns = _cultureData.ShortTimes; - Contract.Assert(this.allShortTimePatterns.Length > 0, + Debug.Assert(this.allShortTimePatterns.Length > 0, "[DateTimeFormatInfo.UnclonedShortTimePatterns] Expected some short time patterns"); } @@ -1937,7 +1977,7 @@ namespace System.Globalization if (this.allLongTimePatterns == null) { this.allLongTimePatterns = _cultureData.LongTimes; - Contract.Assert(this.allLongTimePatterns.Length > 0, + Debug.Assert(this.allLongTimePatterns.Length > 0, "[DateTimeFormatInfo.UnclonedLongTimePatterns] Expected some long time patterns"); } @@ -1949,7 +1989,7 @@ namespace System.Globalization { if (dtfi == null) { - throw new ArgumentNullException("dtfi", + throw new ArgumentNullException(nameof(dtfi), SR.ArgumentNull_Obj); } Contract.EndContractBlock(); @@ -1973,7 +2013,99 @@ namespace System.Globalization } } - [System.Runtime.InteropServices.ComVisible(false)] + // Return the native name for the calendar in DTFI.Calendar. The native name is referred to + // the culture used to create the DTFI. E.g. in the following example, the native language is Japanese. + // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new JapaneseCalendar(); + // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Japanese calendar. + // DateTimeFormatInfo dtfi = new CultureInfo("ja-JP", false).DateTimeFormat.Calendar = new GregorianCalendar(GregorianCalendarTypes.Localized); + // String nativeName = dtfi.NativeCalendarName; // Get the Japanese name for the Gregorian calendar. + public string NativeCalendarName + { + get + { + return _cultureData.CalendarName(Calendar.ID); + } + } + + // + // Used by custom cultures and others to set the list of available formats. Note that none of them are + // explicitly used unless someone calls GetAllDateTimePatterns and subsequently uses one of the items + // from the list. + // + // Most of the format characters that can be used in GetAllDateTimePatterns are + // not really needed since they are one of the following: + // + // r/R/s/u locale-independent constants -- cannot be changed! + // m/M/y/Y fields with a single string in them -- that can be set through props directly + // f/F/g/G/U derived fields based on combinations of various of the below formats + // + // NOTE: No special validation is done here beyond what is done when the actual respective fields + // are used (what would be the point of disallowing here what we allow in the appropriate property?) + // + // WARNING: If more validation is ever done in one place, it should be done in the other. + // + public void SetAllDateTimePatterns(String[] patterns, char format) + { + if (IsReadOnly) + throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); + + if (patterns == null) + { + throw new ArgumentNullException(nameof(patterns), SR.ArgumentNull_Array); + } + + if (patterns.Length == 0) + { + throw new ArgumentException(SR.Arg_ArrayZeroError, nameof(patterns)); + } + + Contract.EndContractBlock(); + + for (int i=0; i= TOKEN_HASH_SIZE) hashcode -= TOKEN_HASH_SIZE; } while (i < TOKEN_HASH_SIZE); - Contract.Assert(false, "The hashtable is full. This should not happen."); + Debug.Assert(false, "The hashtable is full. This should not happen."); } private bool CompareStringIgnoreCaseOptimized(string string1, int offset1, int length1, string string2, int offset2, int length2) diff --git a/src/mscorlib/corefx/System/Globalization/DayLightTime.cs b/src/mscorlib/corefx/System/Globalization/DayLightTime.cs index 2345fb5bc2..6ec9d6a43e 100644 --- a/src/mscorlib/corefx/System/Globalization/DayLightTime.cs +++ b/src/mscorlib/corefx/System/Globalization/DayLightTime.cs @@ -9,11 +9,11 @@ namespace System.Globalization // This class represents a starting/ending time for a period of daylight saving time. [Serializable] - public partial class DaylightTime + public class DaylightTime { - internal DateTime m_start; - internal DateTime m_end; - internal TimeSpan m_delta; + private readonly DateTime _start; + private readonly DateTime _end; + private readonly TimeSpan _delta; private DaylightTime() { @@ -21,36 +21,33 @@ namespace System.Globalization public DaylightTime(DateTime start, DateTime end, TimeSpan delta) { - m_start = start; - m_end = end; - m_delta = delta; + _start = start; + _end = end; + _delta = delta; } // The start date of a daylight saving period. - public DateTime Start - { - get - { - return m_start; - } - } + public DateTime Start => _start; // The end date of a daylight saving period. - public DateTime End - { - get - { - return m_end; - } - } + public DateTime End => _end; // Delta to stardard offset in ticks. - public TimeSpan Delta + public TimeSpan Delta => _delta; + } + + // Value type version of DaylightTime + internal struct DaylightTimeStruct + { + public DaylightTimeStruct(DateTime start, DateTime end, TimeSpan delta) { - get - { - return m_delta; - } + Start = start; + End = end; + Delta = delta; } + + public readonly DateTime Start; + public readonly DateTime End; + public readonly TimeSpan Delta; } } diff --git a/src/mscorlib/corefx/System/Globalization/DigitShapes.cs b/src/mscorlib/corefx/System/Globalization/DigitShapes.cs new file mode 100644 index 0000000000..7e40033a2f --- /dev/null +++ b/src/mscorlib/corefx/System/Globalization/DigitShapes.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +// The enumeration constants used in NumberFormatInfo.DigitSubstitution. +// +namespace System.Globalization +{ + [Serializable] + public enum DigitShapes : int + { + Context = 0x0000, // The shape depends on the previous text in the same output. + None = 0x0001, // Gives full Unicode compatibility. + NativeNational = 0x0002 // National shapes + } +} \ No newline at end of file diff --git a/src/mscorlib/corefx/System/Globalization/EastAsianLunisolarCalendar.cs b/src/mscorlib/corefx/System/Globalization/EastAsianLunisolarCalendar.cs index 8f2bbbc10f..84a44a990d 100644 --- a/src/mscorlib/corefx/System/Globalization/EastAsianLunisolarCalendar.cs +++ b/src/mscorlib/corefx/System/Globalization/EastAsianLunisolarCalendar.cs @@ -38,14 +38,13 @@ namespace System.Globalization internal const int DatePartMonth = 2; internal const int DatePartDay = 3; - // Return the type of the East Asian Lunisolar calendars. - // - - //public override CalendarAlgorithmType AlgorithmType { - // get { - // return CalendarAlgorithmType.LunisolarCalendar; - // } - //} + public override CalendarAlgorithmType AlgorithmType + { + get + { + return CalendarAlgorithmType.LunisolarCalendar; + } + } // Return the year number in the 60-year cycle. // @@ -69,7 +68,7 @@ namespace System.Globalization if ((sexagenaryYear < 1) || (sexagenaryYear > 60)) { throw new ArgumentOutOfRangeException( - "sexagenaryYear", + nameof(sexagenaryYear), SR.Format(SR.ArgumentOutOfRange_Range, 1, 60)); } Contract.EndContractBlock(); @@ -86,7 +85,7 @@ namespace System.Globalization if ((sexagenaryYear < 1) || (sexagenaryYear > 60)) { throw new ArgumentOutOfRangeException( - "sexagenaryYear", + nameof(sexagenaryYear), SR.Format(SR.ArgumentOutOfRange_Range, 1, 60)); } Contract.EndContractBlock(); @@ -133,7 +132,7 @@ namespace System.Globalization return (mEraInfo[i].minEraYear); } } - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } internal int MaxEraCalendarYear(int era) @@ -162,7 +161,7 @@ namespace System.Globalization return (mEraInfo[i].maxEraYear); } } - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } internal EastAsianLunisolarCalendar() @@ -190,7 +189,7 @@ namespace System.Globalization if ((era < GetEra(MinDate)) || (era > GetEra(MaxDate))) { - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } } @@ -202,7 +201,7 @@ namespace System.Globalization if ((year < MinCalendarYear) || (year > MaxCalendarYear)) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), SR.Format(SR.ArgumentOutOfRange_Range, MinEraCalendarYear(era), MaxEraCalendarYear(era))); } return year; @@ -216,12 +215,12 @@ namespace System.Globalization { //Reject if there is no leap month this year if (GetYearInfo(year, LeapMonth) == 0) - throw new ArgumentOutOfRangeException("month", SR.ArgumentOutOfRange_Month); + throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month); } if (month < 1 || month > 13) { - throw new ArgumentOutOfRangeException("month", SR.ArgumentOutOfRange_Month); + throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month); } return year; } @@ -266,7 +265,7 @@ namespace System.Globalization if (day < 1 || day > daysInMonth) { throw new ArgumentOutOfRangeException( - "day", + nameof(day), SR.Format(SR.ArgumentOutOfRange_Day, daysInMonth, month)); } @@ -447,7 +446,7 @@ namespace System.Globalization if (months < -120000 || months > 120000) { throw new ArgumentOutOfRangeException( - "months", + nameof(months), SR.Format(SR.ArgumentOutOfRange_Range, -120000, 120000)); } Contract.EndContractBlock(); @@ -627,7 +626,7 @@ namespace System.Globalization if (day < 1 || day > daysInMonth) { throw new ArgumentOutOfRangeException( - "day", + nameof(day), SR.Format(SR.ArgumentOutOfRange_Day, daysInMonth, month)); } int m = GetYearInfo(year, LeapMonth); @@ -694,7 +693,7 @@ namespace System.Globalization if (value < 99 || value > MaxCalendarYear) { throw new ArgumentOutOfRangeException( - "value", + nameof(value), SR.Format(SR.ArgumentOutOfRange_Range, 99, MaxCalendarYear)); } twoDigitYearMax = value; @@ -706,7 +705,7 @@ namespace System.Globalization { if (year < 0) { - throw new ArgumentOutOfRangeException("year", + throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedNonNegNum); } Contract.EndContractBlock(); diff --git a/src/mscorlib/corefx/System/Globalization/GregorianCalendar.cs b/src/mscorlib/corefx/System/Globalization/GregorianCalendar.cs index d0933a0fc6..c2ed2e012b 100644 --- a/src/mscorlib/corefx/System/Globalization/GregorianCalendar.cs +++ b/src/mscorlib/corefx/System/Globalization/GregorianCalendar.cs @@ -84,6 +84,15 @@ namespace System.Globalization } } + [System.Runtime.InteropServices.ComVisible(false)] + public override CalendarAlgorithmType AlgorithmType + { + get + { + return CalendarAlgorithmType.SolarCalendar; + } + } + /*=================================GetDefaultInstance========================== **Action: Internal method to provide a default intance of GregorianCalendar. Used by NLS+ implementation ** and other calendars. @@ -114,7 +123,7 @@ namespace System.Globalization if ((int)type < (int)GregorianCalendarTypes.Localized || (int)type > (int)GregorianCalendarTypes.TransliteratedFrench) { throw new ArgumentOutOfRangeException( - "type", + nameof(type), SR.Format(SR.ArgumentOutOfRange_Range, GregorianCalendarTypes.Localized, GregorianCalendarTypes.TransliteratedFrench)); } @@ -205,7 +214,7 @@ namespace System.Globalization int[] days = leapYear ? DaysToMonth366 : DaysToMonth365; // All months have less than 32 days, so n >> 5 is a good conservative // estimate for the month - int m = n >> 5 + 1; + int m = (n >> 5) + 1; // m = 1-based month number while (n >= days[m]) m++; // If month was requested, return it @@ -277,7 +286,7 @@ namespace System.Globalization if (months < -120000 || months > 120000) { throw new ArgumentOutOfRangeException( - "months", + nameof(months), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -366,17 +375,17 @@ namespace System.Globalization { if (year < 1 || year > MaxYear) { - throw new ArgumentOutOfRangeException("year", SR.Format(SR.ArgumentOutOfRange_Range, + throw new ArgumentOutOfRangeException(nameof(year), SR.Format(SR.ArgumentOutOfRange_Range, 1, MaxYear)); } if (month < 1 || month > 12) { - throw new ArgumentOutOfRangeException("month", SR.ArgumentOutOfRange_Month); + throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month); } int[] days = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? DaysToMonth366 : DaysToMonth365); return (days[month] - days[month - 1]); } - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } // Returns the number of days in the year given by the year argument for the current era. @@ -391,14 +400,14 @@ namespace System.Globalization return ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 366 : 365); } throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, 1, MaxYear)); } - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } // Returns the era for the specified DateTime value. @@ -438,14 +447,14 @@ namespace System.Globalization return (12); } throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, 1, MaxYear)); } - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } // Returns the year part of the specified DateTime. The returned value is an @@ -465,25 +474,25 @@ namespace System.Globalization { if (month < 1 || month > 12) { - throw new ArgumentOutOfRangeException("month", SR.Format(SR.ArgumentOutOfRange_Range, + throw new ArgumentOutOfRangeException(nameof(month), SR.Format(SR.ArgumentOutOfRange_Range, 1, 12)); } Contract.EndContractBlock(); if (era != CurrentEra && era != ADEra) { - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } if (year < 1 || year > MaxYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), SR.Format(SR.ArgumentOutOfRange_Range, 1, MaxYear)); } if (day < 1 || day > GetDaysInMonth(year, month)) { - throw new ArgumentOutOfRangeException("day", SR.Format(SR.ArgumentOutOfRange_Range, + throw new ArgumentOutOfRangeException(nameof(day), SR.Format(SR.ArgumentOutOfRange_Range, 1, GetDaysInMonth(year, month))); } if (!IsLeapYear(year)) @@ -506,12 +515,12 @@ namespace System.Globalization { if (era != CurrentEra && era != ADEra) { - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } if (year < 1 || year > MaxYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, 1, MaxYear)); @@ -528,13 +537,13 @@ namespace System.Globalization { if (era != CurrentEra && era != ADEra) { - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } if (year < 1 || year > MaxYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, 1, MaxYear)); @@ -542,7 +551,7 @@ namespace System.Globalization if (month < 1 || month > 12) { - throw new ArgumentOutOfRangeException("month", SR.Format(SR.ArgumentOutOfRange_Range, + throw new ArgumentOutOfRangeException(nameof(month), SR.Format(SR.ArgumentOutOfRange_Range, 1, 12)); } Contract.EndContractBlock(); @@ -563,12 +572,12 @@ namespace System.Globalization } throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, 1, MaxYear)); } - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } // Returns the date and time converted to a DateTime value. Throws an exception if the n-tuple is invalid. @@ -580,7 +589,7 @@ namespace System.Globalization { return new DateTime(year, month, day, hour, minute, second, millisecond); } - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } internal override Boolean TryToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era, out DateTime result) @@ -643,7 +652,7 @@ namespace System.Globalization { if (year < 0) { - throw new ArgumentOutOfRangeException("year", + throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedNonNegNum); } Contract.EndContractBlock(); @@ -651,7 +660,7 @@ namespace System.Globalization if (year > MaxYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, 1, MaxYear)); diff --git a/src/mscorlib/corefx/System/Globalization/GregorianCalendarHelper.cs b/src/mscorlib/corefx/System/Globalization/GregorianCalendarHelper.cs index f595e72d0d..ee8ba13894 100644 --- a/src/mscorlib/corefx/System/Globalization/GregorianCalendarHelper.cs +++ b/src/mscorlib/corefx/System/Globalization/GregorianCalendarHelper.cs @@ -148,7 +148,7 @@ namespace System.Globalization { if (year < 0) { - throw new ArgumentOutOfRangeException("year", + throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedNonNegNum); } Contract.EndContractBlock(); @@ -165,7 +165,7 @@ namespace System.Globalization if (year < m_EraInfo[i].minEraYear || year > m_EraInfo[i].maxEraYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -175,7 +175,7 @@ namespace System.Globalization return (m_EraInfo[i].yearOffset + year); } } - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } internal bool IsValidYear(int year, int era) @@ -248,7 +248,7 @@ namespace System.Globalization int[] days = leapYear ? DaysToMonth366 : DaysToMonth365; // All months have less than 32 days, so n >> 5 is a good conservative // estimate for the month - int m = n >> 5 + 1; + int m = (n >> 5) + 1; // m = 1-based month number while (n >= days[m]) m++; // If month was requested, return it @@ -308,7 +308,7 @@ namespace System.Globalization if (millisecond < 0 || millisecond >= MillisPerSecond) { throw new ArgumentOutOfRangeException( - "millisecond", + nameof(millisecond), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -358,7 +358,7 @@ namespace System.Globalization if (months < -120000 || months > 120000) { throw new ArgumentOutOfRangeException( - "months", + nameof(months), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -446,7 +446,7 @@ namespace System.Globalization year = GetGregorianYear(year, era); if (month < 1 || month > 12) { - throw new ArgumentOutOfRangeException("month", SR.ArgumentOutOfRange_Month); + throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month); } int[] days = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? DaysToMonth366 : DaysToMonth365); return (days[month] - days[month - 1]); @@ -476,7 +476,7 @@ namespace System.Globalization return (m_EraInfo[i].era); } } - throw new ArgumentOutOfRangeException("time", SR.ArgumentOutOfRange_Era); + throw new ArgumentOutOfRangeException(nameof(time), SR.ArgumentOutOfRange_Era); } @@ -558,7 +558,7 @@ namespace System.Globalization if (day < 1 || day > GetDaysInMonth(year, month, era)) { throw new ArgumentOutOfRangeException( - "day", + nameof(day), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -598,7 +598,7 @@ namespace System.Globalization if (month < 1 || month > 12) { throw new ArgumentOutOfRangeException( - "month", + nameof(month), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -640,7 +640,7 @@ namespace System.Globalization { if (year < 0) { - throw new ArgumentOutOfRangeException("year", + throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedPosNum); } Contract.EndContractBlock(); @@ -654,7 +654,7 @@ namespace System.Globalization if (year < m_minYear || year > m_maxYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, m_minYear, m_maxYear)); diff --git a/src/mscorlib/corefx/System/Globalization/HebrewCalendar.cs b/src/mscorlib/corefx/System/Globalization/HebrewCalendar.cs index 5fbf2e0f09..7e63708382 100644 --- a/src/mscorlib/corefx/System/Globalization/HebrewCalendar.cs +++ b/src/mscorlib/corefx/System/Globalization/HebrewCalendar.cs @@ -2,8 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Text; +using System.Diagnostics; using System.Diagnostics.Contracts; namespace System.Globalization @@ -65,7 +64,7 @@ namespace System.Globalization [Serializable] [System.Runtime.InteropServices.ComVisible(true)] - public partial class HebrewCalendar : Calendar + public class HebrewCalendar : Calendar { public static readonly int HebrewEra = 1; @@ -317,6 +316,14 @@ namespace System.Globalization } } + public override CalendarAlgorithmType AlgorithmType + { + get + { + return CalendarAlgorithmType.LunisolarCalendar; + } + } + public HebrewCalendar() { } @@ -371,7 +378,7 @@ namespace System.Globalization if (month < 1 || month > monthsInYear) { throw new ArgumentOutOfRangeException( - "month", + nameof(month), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -397,7 +404,7 @@ namespace System.Globalization if (day < 1 || day > daysInMonth) { throw new ArgumentOutOfRangeException( - "day", + nameof(day), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -410,7 +417,7 @@ namespace System.Globalization { if (era != CurrentEra && era != HebrewEra) { - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } } @@ -470,7 +477,7 @@ namespace System.Globalization int index = gregorianYear - FirstGregorianTableYear; if (index < 0 || index > TABLESIZE) { - throw new ArgumentOutOfRangeException("gregorianYear"); + throw new ArgumentOutOfRangeException(nameof(gregorianYear)); } index *= 2; @@ -595,7 +602,7 @@ namespace System.Globalization // is true. // NumDays -= (long)(s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + lunarDate.month] - lunarDate.day); - Contract.Assert(NumDays >= 1, "NumDays >= 1"); + Debug.Assert(NumDays >= 1, "NumDays >= 1"); // If NumDays is 1, then we are done. Otherwise, find the correct Hebrew month // and day. @@ -705,7 +712,7 @@ namespace System.Globalization catch (ArgumentException) { throw new ArgumentOutOfRangeException( - "months", + nameof(months), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_AddValue)); @@ -728,7 +735,7 @@ namespace System.Globalization int d = GetDatePart(time.Ticks, DatePartDay); y += years; - CheckHebrewYearValue(y, Calendar.CurrentEra, "years"); + CheckHebrewYearValue(y, Calendar.CurrentEra, nameof(years)); int months = GetMonthsInYear(y, CurrentEra); if (m > months) @@ -771,7 +778,7 @@ namespace System.Globalization internal static int GetHebrewYearType(int year, int era) { - CheckHebrewYearValue(year, era, "year"); + CheckHebrewYearValue(year, era, nameof(year)); // The HebrewTable is indexed by Gregorian year and starts from FirstGregorianYear. // So we need to convert year (Hebrew year value) to Gregorian Year below. return (s_hebrewTable[(year - HebrewYearOf1AD - FirstGregorianTableYear) * 2 + 1]); @@ -819,12 +826,12 @@ namespace System.Globalization int hebrewYearType = GetHebrewYearType(year, era); CheckHebrewMonthValue(year, month, era); - Contract.Assert(hebrewYearType >= 1 && hebrewYearType <= 6, + Debug.Assert(hebrewYearType >= 1 && hebrewYearType <= 6, "hebrewYearType should be from 1 to 6, but now hebrewYearType = " + hebrewYearType + " for hebrew year " + year); int monthDays = s_lunarMonthLen[hebrewYearType * MaxMonthPlusOne + month]; if (monthDays == 0) { - throw new ArgumentOutOfRangeException("month", SR.ArgumentOutOfRange_Month); + throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month); } return (monthDays); } @@ -956,7 +963,7 @@ namespace System.Globalization public override bool IsLeapYear(int year, int era) { - CheckHebrewYearValue(year, era, "year"); + CheckHebrewYearValue(year, era, nameof(year)); return (((7 * (long)year + 1) % 19) < 7); } @@ -1047,7 +1054,7 @@ namespace System.Globalization public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era) { - CheckHebrewYearValue(year, era, "year"); + CheckHebrewYearValue(year, era, nameof(year)); CheckHebrewMonthValue(year, month, era); CheckHebrewDayValue(year, month, day, era); DateTime dt = HebrewToGregorian(year, month, day, hour, minute, second, millisecond); @@ -1078,7 +1085,7 @@ namespace System.Globalization } else { - CheckHebrewYearValue(value, HebrewEra, "value"); + CheckHebrewYearValue(value, HebrewEra, nameof(value)); } twoDigitYearMax = value; } @@ -1089,7 +1096,7 @@ namespace System.Globalization { if (year < 0) { - throw new ArgumentOutOfRangeException("year", + throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedNonNegNum); } Contract.EndContractBlock(); @@ -1102,7 +1109,7 @@ namespace System.Globalization if (year > MaxHebrewYear || year < MinHebrewYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, diff --git a/src/mscorlib/corefx/System/Globalization/HebrewNumber.cs b/src/mscorlib/corefx/System/Globalization/HebrewNumber.cs index 8fc264b788..01e251d9e8 100644 --- a/src/mscorlib/corefx/System/Globalization/HebrewNumber.cs +++ b/src/mscorlib/corefx/System/Globalization/HebrewNumber.cs @@ -2,18 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Text; using System.Diagnostics; -using System.Diagnostics.Contracts; namespace System.Globalization { - -#if INSIDE_CLR - using Debug = BCLDebug; -#endif - //////////////////////////////////////////////////////////////////////////// // // Used in HebrewNumber.ParseByChar to maintain the context information ( @@ -110,7 +103,7 @@ namespace System.Globalization Number -= 5000; } - Contract.Assert(Number > 0 && Number <= 999, "Number is out of range."); ; + Debug.Assert(Number > 0 && Number <= 999, "Number is out of range."); ; // // Get the Hundreds. @@ -335,7 +328,7 @@ namespace System.Globalization // // The state machine for Hebrew number pasing. // - private readonly static HS[] s_numberPasingState = + private static readonly HS[] s_numberPasingState = { // 400 300/200 100 90~10 8~1 6, 7, 9, ' " /* 0 */ diff --git a/src/mscorlib/corefx/System/Globalization/HijriCalendar.Win32.cs b/src/mscorlib/corefx/System/Globalization/HijriCalendar.Win32.cs new file mode 100644 index 0000000000..5f46dce61d --- /dev/null +++ b/src/mscorlib/corefx/System/Globalization/HijriCalendar.Win32.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32; + +namespace System.Globalization +{ + public partial class HijriCalendar : Calendar + { + private static int GetHijriDateAdjustment() + { + if (_hijriAdvance == Int32.MinValue) { + // Never been set before. Use the system value from registry. + _hijriAdvance = GetAdvanceHijriDate(); + } + return (_hijriAdvance); + } + + private const String InternationalRegKey = "Control Panel\\International"; + private const String HijriAdvanceRegKeyEntry = "AddHijriDate"; + + /*=================================GetAdvanceHijriDate========================== + **Action: Gets the AddHijriDate value from the registry. + **Returns: + **Arguments: None. + **Exceptions: + **Note: + ** The HijriCalendar has a user-overidable calculation. That is, use can set a value from the control + ** panel, so that the calculation of the Hijri Calendar can move ahead or backwards from -2 to +2 days. + ** + ** The valid string values in the registry are: + ** "AddHijriDate-2" => Add -2 days to the current calculated Hijri date. + ** "AddHijriDate" => Add -1 day to the current calculated Hijri date. + ** "" => Add 0 day to the current calculated Hijri date. + ** "AddHijriDate+1" => Add +1 days to the current calculated Hijri date. + ** "AddHijriDate+2" => Add +2 days to the current calculated Hijri date. + ============================================================================*/ + private static int GetAdvanceHijriDate() { + int hijriAdvance = 0; + Microsoft.Win32.RegistryKey key = null; + + try { + // Open in read-only mode. + // Use InternalOpenSubKey so that we avoid the security check. + key = RegistryKey.GetBaseKey(RegistryKey.HKEY_CURRENT_USER).OpenSubKey(InternationalRegKey, false); + } + //If this fails for any reason, we'll just return 0. + catch (ObjectDisposedException) { return 0; } + catch (ArgumentException) { return 0; } + + if (key != null) { + try { + Object value = key.InternalGetValue(HijriAdvanceRegKeyEntry, null, false, false); + if (value == null) { + return (0); + } + String str = value.ToString(); + if (String.Compare(str, 0, HijriAdvanceRegKeyEntry, 0, HijriAdvanceRegKeyEntry.Length, StringComparison.OrdinalIgnoreCase) == 0) { + if (str.Length == HijriAdvanceRegKeyEntry.Length) + hijriAdvance = -1; + else { + str = str.Substring(HijriAdvanceRegKeyEntry.Length); + try { + int advance = Int32.Parse(str.ToString(), CultureInfo.InvariantCulture); + if ((advance >= MinAdvancedHijri) && (advance <= MaxAdvancedHijri)) { + hijriAdvance = advance; + } + } + // If we got garbage from registry just ignore it. + // hijriAdvance = 0 because of declaraction assignment up above. + catch (ArgumentException) { } + catch (FormatException) { } + catch (OverflowException) { } + } + } + } + finally { + key.Close(); + } + + } + return (hijriAdvance); + } + } +} diff --git a/src/mscorlib/corefx/System/Globalization/HijriCalendar.WinRT.cs b/src/mscorlib/corefx/System/Globalization/HijriCalendar.WinRT.cs new file mode 100644 index 0000000000..fb91c27ef6 --- /dev/null +++ b/src/mscorlib/corefx/System/Globalization/HijriCalendar.WinRT.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Internal.Runtime.Augments; + +namespace System.Globalization +{ + public partial class HijriCalendar : Calendar + { + private static int GetHijriDateAdjustment() + { + return WinRTInterop.Callbacks.GetHijriDateAdjustment(); + } + } +} diff --git a/src/mscorlib/corefx/System/Globalization/HijriCalendar.Windows.cs b/src/mscorlib/corefx/System/Globalization/HijriCalendar.Windows.cs deleted file mode 100644 index 185c5184be..0000000000 --- a/src/mscorlib/corefx/System/Globalization/HijriCalendar.Windows.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Internal.Runtime.Augments; - -namespace System.Globalization -{ - public partial class HijriCalendar : Calendar - { - public static int GetHijriDateAdjustment() - { - return WinRTInterop.Callbacks.GetHijriDateAdjustment(); - } - } -} diff --git a/src/mscorlib/corefx/System/Globalization/HijriCalendar.cs b/src/mscorlib/corefx/System/Globalization/HijriCalendar.cs index 72d9ab3f52..156b2104bd 100644 --- a/src/mscorlib/corefx/System/Globalization/HijriCalendar.cs +++ b/src/mscorlib/corefx/System/Globalization/HijriCalendar.cs @@ -48,7 +48,7 @@ namespace System.Globalization [System.Runtime.InteropServices.ComVisible(true)] public partial class HijriCalendar : Calendar { - internal static readonly int HijriEra = 1; + public static readonly int HijriEra = 1; internal const int DatePartYear = 0; internal const int DatePartDayOfYear = 1; @@ -91,6 +91,15 @@ namespace System.Globalization } } + [System.Runtime.InteropServices.ComVisible(false)] + public override CalendarAlgorithmType AlgorithmType + { + get + { + return CalendarAlgorithmType.LunarCalendar; + } + } + public HijriCalendar() { } @@ -221,7 +230,7 @@ namespace System.Globalization { if (era != CurrentEra && era != HijriEra) { - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } } @@ -231,7 +240,7 @@ namespace System.Globalization if (year < 1 || year > MaxCalendarYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -248,7 +257,7 @@ namespace System.Globalization if (month > MaxCalendarMonth) { throw new ArgumentOutOfRangeException( - "month", + nameof(month), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -259,7 +268,7 @@ namespace System.Globalization if (month < 1 || month > 12) { - throw new ArgumentOutOfRangeException("month", SR.ArgumentOutOfRange_Month); + throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month); } } @@ -386,7 +395,7 @@ namespace System.Globalization if (months < -120000 || months > 120000) { throw new ArgumentOutOfRangeException( - "months", + nameof(months), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -542,7 +551,7 @@ namespace System.Globalization if (day < 1 || day > daysInMonth) { throw new ArgumentOutOfRangeException( - "day", + nameof(day), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Day, @@ -593,7 +602,7 @@ namespace System.Globalization if (day < 1 || day > daysInMonth) { throw new ArgumentOutOfRangeException( - "day", + nameof(day), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Day, @@ -633,7 +642,7 @@ namespace System.Globalization if (value < 99 || value > MaxCalendarYear) { throw new ArgumentOutOfRangeException( - "value", + nameof(value), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -649,7 +658,7 @@ namespace System.Globalization { if (year < 0) { - throw new ArgumentOutOfRangeException("year", + throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedNonNegNum); } Contract.EndContractBlock(); @@ -662,7 +671,7 @@ namespace System.Globalization if (year > MaxCalendarYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, diff --git a/src/mscorlib/corefx/System/Globalization/IdnMapping.Unix.cs b/src/mscorlib/corefx/System/Globalization/IdnMapping.Unix.cs new file mode 100644 index 0000000000..58f4ccadde --- /dev/null +++ b/src/mscorlib/corefx/System/Globalization/IdnMapping.Unix.cs @@ -0,0 +1,134 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Globalization +{ + sealed partial class IdnMapping + { + private unsafe string GetAsciiCore(char* unicode, int count) + { + uint flags = Flags; + CheckInvalidIdnCharacters(unicode, count, flags, nameof(unicode)); + + const int StackallocThreshold = 512; + // Each unicode character is represented by up to 3 ASCII chars + // and the whole string is prefixed by "xn--" (length 4) + int estimatedLength = (int)Math.Min(count * 3L + 4, StackallocThreshold); + int actualLength; + if (estimatedLength < StackallocThreshold) + { + char* outputStack = stackalloc char[estimatedLength]; + actualLength = Interop.GlobalizationInterop.ToAscii(flags, unicode, count, outputStack, estimatedLength); + if (actualLength > 0 && actualLength <= estimatedLength) + { + return new string(outputStack, 0, actualLength); + } + } + else + { + actualLength = Interop.GlobalizationInterop.ToAscii(flags, unicode, count, null, 0); + } + if (actualLength == 0) + { + throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(unicode)); + } + + char[] outputHeap = new char[actualLength]; + fixed (char* pOutputHeap = outputHeap) + { + actualLength = Interop.GlobalizationInterop.ToAscii(flags, unicode, count, pOutputHeap, actualLength); + if (actualLength == 0 || actualLength > outputHeap.Length) + { + throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(unicode)); + } + return new string(pOutputHeap, 0, actualLength); + } + } + + private unsafe string GetUnicodeCore(char* ascii, int count) + { + uint flags = Flags; + CheckInvalidIdnCharacters(ascii, count, flags, nameof(ascii)); + + const int StackAllocThreshold = 512; + if (count < StackAllocThreshold) + { + char* output = stackalloc char[count]; + return GetUnicodeCore(ascii, count, flags, output, count, reattempt: true); + } + else + { + char[] output = new char[count]; + fixed (char* pOutput = output) + { + return GetUnicodeCore(ascii, count, flags, pOutput, count, reattempt: true); + } + } + } + + private unsafe string GetUnicodeCore(char* ascii, int count, uint flags, char* output, int outputLength, bool reattempt) + { + int realLen = Interop.GlobalizationInterop.ToUnicode(flags, ascii, count, output, outputLength); + + if (realLen == 0) + { + throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(ascii)); + } + else if (realLen <= outputLength) + { + return new string(output, 0, realLen); + } + else if (reattempt) + { + char[] newOutput = new char[realLen]; + fixed (char* pNewOutput = newOutput) + { + return GetUnicodeCore(ascii, count, flags, pNewOutput, realLen, reattempt: false); + } + } + + throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(ascii)); + } + + // ----------------------------- + // ---- PAL layer ends here ---- + // ----------------------------- + + private uint Flags + { + get + { + int flags = + (AllowUnassigned ? Interop.GlobalizationInterop.AllowUnassigned : 0) | + (UseStd3AsciiRules ? Interop.GlobalizationInterop.UseStd3AsciiRules : 0); + return (uint)flags; + } + } + + /// + /// ICU doesn't check for invalid characters unless the STD3 rules option + /// is enabled. + /// + /// To match Windows behavior, we walk the string ourselves looking for these + /// bad characters so we can continue to throw ArgumentException in these cases. + /// + private static unsafe void CheckInvalidIdnCharacters(char* s, int count, uint flags, string paramName) + { + if ((flags & Interop.GlobalizationInterop.UseStd3AsciiRules) == 0) + { + for (int i = 0; i < count; i++) + { + char c = s[i]; + + // These characters are prohibited regardless of the UseStd3AsciiRules property. + // See https://msdn.microsoft.com/en-us/library/system.globalization.idnmapping.usestd3asciirules(v=vs.110).aspx + if (c <= 0x1F || c == 0x7F) + { + throw new ArgumentException(SR.Argument_IdnIllegalName, paramName); + } + } + } + } + } +} diff --git a/src/mscorlib/corefx/System/Globalization/IdnMapping.Windows.cs b/src/mscorlib/corefx/System/Globalization/IdnMapping.Windows.cs new file mode 100644 index 0000000000..f39457b750 --- /dev/null +++ b/src/mscorlib/corefx/System/Globalization/IdnMapping.Windows.cs @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace System.Globalization +{ + sealed partial class IdnMapping + { + private unsafe string GetAsciiCore(char* unicode, int count) + { + uint flags = Flags; + + // Determine the required length + int length = Interop.mincore.IdnToAscii(flags, new IntPtr(unicode), count, IntPtr.Zero, 0); + if (length == 0) + { + ThrowForZeroLength(nameof(unicode), SR.Argument_IdnIllegalName, SR.Argument_InvalidCharSequenceNoIndex); + } + + // Do the conversion + const int StackAllocThreshold = 512; // arbitrary limit to switch from stack to heap allocation + if (length < StackAllocThreshold) + { + char* output = stackalloc char[length]; + return GetAsciiCore(unicode, count, flags, output, length); + } + else + { + char[] output = new char[length]; + fixed (char* pOutput = output) + { + return GetAsciiCore(unicode, count, flags, pOutput, length); + } + } + } + + private unsafe string GetAsciiCore(char* unicode, int count, uint flags, char* output, int outputLength) + { + int length = Interop.mincore.IdnToAscii(flags, new IntPtr(unicode), count, new IntPtr(output), outputLength); + if (length == 0) + { + ThrowForZeroLength(nameof(unicode), SR.Argument_IdnIllegalName, SR.Argument_InvalidCharSequenceNoIndex); + } + Debug.Assert(length == outputLength); + return new string(output, 0, length); + } + + private unsafe string GetUnicodeCore(char* ascii, int count) + { + uint flags = Flags; + + // Determine the required length + int length = Interop.mincore.IdnToUnicode(flags, new IntPtr(ascii), count, IntPtr.Zero, 0); + if (length == 0) + { + ThrowForZeroLength(nameof(ascii), SR.Argument_IdnIllegalName, SR.Argument_IdnBadPunycode); + } + + // Do the conversion + const int StackAllocThreshold = 512; // arbitrary limit to switch from stack to heap allocation + if (length < StackAllocThreshold) + { + char* output = stackalloc char[length]; + return GetUnicodeCore(ascii, count, flags, output, length); + } + else + { + char[] output = new char[length]; + fixed (char* pOutput = output) + { + return GetUnicodeCore(ascii, count, flags, pOutput, length); + } + } + } + + private unsafe string GetUnicodeCore(char* ascii, int count, uint flags, char* output, int outputLength) + { + int length = Interop.mincore.IdnToUnicode(flags, new IntPtr(ascii), count, new IntPtr(output), outputLength); + if (length == 0) + { + ThrowForZeroLength(nameof(ascii), SR.Argument_IdnIllegalName, SR.Argument_IdnBadPunycode); + } + Debug.Assert(length == outputLength); + return new string(output, 0, length); + } + + // ----------------------------- + // ---- PAL layer ends here ---- + // ----------------------------- + + private uint Flags + { + get + { + int flags = + (AllowUnassigned ? Interop.mincore.IDN_ALLOW_UNASSIGNED : 0) | + (UseStd3AsciiRules ? Interop.mincore.IDN_USE_STD3_ASCII_RULES : 0); + return (uint)flags; + } + } + + private static void ThrowForZeroLength(string paramName, string invalidNameString, string otherString) + { + throw new ArgumentException( + Marshal.GetLastWin32Error() == Interop.ERROR_INVALID_NAME ? invalidNameString : otherString, + paramName); + } + } +} + diff --git a/src/mscorlib/corefx/System/Globalization/IdnMapping.cs b/src/mscorlib/corefx/System/Globalization/IdnMapping.cs new file mode 100644 index 0000000000..8424472751 --- /dev/null +++ b/src/mscorlib/corefx/System/Globalization/IdnMapping.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// This file contains the IDN functions and implementation. +// +// This allows encoding of non-ASCII domain names in a "punycode" form, +// for example: +// +// \u5B89\u5BA4\u5948\u7F8E\u6075-with-SUPER-MONKEYS +// +// is encoded as: +// +// xn---with-SUPER-MONKEYS-pc58ag80a8qai00g7n9n +// +// Additional options are provided to allow unassigned IDN characters and +// to validate according to the Std3ASCII Rules (like DNS names). +// +// There are also rules regarding bidirectionality of text and the length +// of segments. +// +// For additional rules see also: +// RFC 3490 - Internationalizing Domain Names in Applications (IDNA) +// RFC 3491 - Nameprep: A Stringprep Profile for Internationalized Domain Names (IDN) +// RFC 3492 - Punycode: A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA) + +using System.Diagnostics.Contracts; + +namespace System.Globalization +{ + // IdnMapping class used to map names to Punycode + public sealed partial class IdnMapping + { + private bool _allowUnassigned; + private bool _useStd3AsciiRules; + + public IdnMapping() + { + } + + public bool AllowUnassigned + { + get { return _allowUnassigned; } + set { _allowUnassigned = value; } + } + + public bool UseStd3AsciiRules + { + get { return _useStd3AsciiRules; } + set { _useStd3AsciiRules = value; } + } + + // Gets ASCII (Punycode) version of the string + public string GetAscii(string unicode) + { + return GetAscii(unicode, 0); + } + + public string GetAscii(string unicode, int index) + { + if (unicode == null) + throw new ArgumentNullException(nameof(unicode)); + Contract.EndContractBlock(); + return GetAscii(unicode, index, unicode.Length - index); + } + + public string GetAscii(string unicode, int index, int count) + { + if (unicode == null) + throw new ArgumentNullException(nameof(unicode)); + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index < 0) ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); + if (index > unicode.Length) + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index); + if (index > unicode.Length - count) + throw new ArgumentOutOfRangeException(nameof(unicode), SR.ArgumentOutOfRange_IndexCountBuffer); + Contract.EndContractBlock(); + + if (count == 0) + { + throw new ArgumentException(SR.Argument_IdnBadLabelSize, nameof(unicode)); + } + if (unicode[index + count - 1] == 0) + { + throw new ArgumentException(SR.Format(SR.Argument_InvalidCharSequence, index + count - 1), nameof(unicode)); + } + + unsafe + { + fixed (char* pUnicode = unicode) + { + return GetAsciiCore(pUnicode + index, count); + } + } + } + + // Gets Unicode version of the string. Normalized and limited to IDNA characters. + public string GetUnicode(string ascii) + { + return GetUnicode(ascii, 0); + } + + public string GetUnicode(string ascii, int index) + { + if (ascii == null) + throw new ArgumentNullException(nameof(ascii)); + Contract.EndContractBlock(); + return GetUnicode(ascii, index, ascii.Length - index); + } + + public string GetUnicode(string ascii, int index, int count) + { + if (ascii == null) + throw new ArgumentNullException(nameof(ascii)); + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index < 0) ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); + if (index > ascii.Length) + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index); + if (index > ascii.Length - count) + throw new ArgumentOutOfRangeException(nameof(ascii), SR.ArgumentOutOfRange_IndexCountBuffer); + + // This is a case (i.e. explicitly null-terminated input) where behavior in .NET and Win32 intentionally differ. + // The .NET APIs should (and did in v4.0 and earlier) throw an ArgumentException on input that includes a terminating null. + // The Win32 APIs fail on an embedded null, but not on a terminating null. + if (count > 0 && ascii[index + count - 1] == (char)0) + throw new ArgumentException(SR.Argument_IdnBadPunycode, nameof(ascii)); + Contract.EndContractBlock(); + + unsafe + { + fixed (char* pAscii = ascii) + { + return GetUnicodeCore(pAscii + index, count); + } + } + } + + public override bool Equals(object obj) + { + IdnMapping that = obj as IdnMapping; + return + that != null && + _allowUnassigned == that._allowUnassigned && + _useStd3AsciiRules == that._useStd3AsciiRules; + } + + public override int GetHashCode() + { + return (_allowUnassigned ? 100 : 200) + (_useStd3AsciiRules ? 1000 : 2000); + } + } +} diff --git a/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.Unix.cs b/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.Unix.cs index 12f430c71c..b9bd94af6e 100644 --- a/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.Unix.cs +++ b/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.Unix.cs @@ -3,13 +3,11 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Security; namespace System.Globalization { public partial class JapaneseCalendar : Calendar { - [SecuritySafeCritical] private static EraInfo[] GetJapaneseEras() { string[] eraNames; @@ -66,7 +64,6 @@ namespace System.Globalization return eraNames[eraIndex].Substring(0, 1); } - [SecuritySafeCritical] private static bool GetJapaneseEraStartDate(int era, out DateTime dateTime) { dateTime = default(DateTime); diff --git a/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.Win32.cs b/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.Win32.cs new file mode 100644 index 0000000000..bbde320041 --- /dev/null +++ b/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.Win32.cs @@ -0,0 +1,209 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +using Microsoft.Win32; + +namespace System.Globalization +{ + public partial class JapaneseCalendar : Calendar + { + private const string c_japaneseErasHive = @"System\CurrentControlSet\Control\Nls\Calendars\Japanese\Eras"; + private const string c_japaneseErasHivePermissionList = @"HKEY_LOCAL_MACHINE\" + c_japaneseErasHive; + + // We know about 4 built-in eras, however users may add additional era(s) from the + // registry, by adding values to HKLM\SYSTEM\CurrentControlSet\Control\Nls\Calendars\Japanese\Eras + // + // Registry values look like: + // yyyy.mm.dd=era_abbrev_english_englishabbrev + // + // Where yyyy.mm.dd is the registry value name, and also the date of the era start. + // yyyy, mm, and dd are the year, month & day the era begins (4, 2 & 2 digits long) + // era is the Japanese Era name + // abbrev is the Abbreviated Japanese Era Name + // english is the English name for the Era (unused) + // englishabbrev is the Abbreviated English name for the era. + // . is a delimiter, but the value of . doesn't matter. + // '_' marks the space between the japanese era name, japanese abbreviated era name + // english name, and abbreviated english names. + private static EraInfo[] GetJapaneseEras() + { + // Look in the registry key and see if we can find any ranges + int iFoundEras = 0; + EraInfo[] registryEraRanges = null; + + try + { + // Need to access registry + RegistryKey key = RegistryKey.GetBaseKey(RegistryKey.HKEY_LOCAL_MACHINE).OpenSubKey(c_japaneseErasHive, false); + + // Abort if we didn't find anything + if (key == null) return null; + + // Look up the values in our reg key + String[] valueNames = key.GetValueNames(); + if (valueNames != null && valueNames.Length > 0) + { + registryEraRanges = new EraInfo[valueNames.Length]; + + // Loop through the registry and read in all the values + for (int i = 0; i < valueNames.Length; i++) + { + // See if the era is a valid date + EraInfo era = GetEraFromValue(valueNames[i], key.GetValue(valueNames[i]).ToString()); + + // continue if not valid + if (era == null) continue; + + // Remember we found one. + registryEraRanges[iFoundEras] = era; + iFoundEras++; + } + } + } + catch (System.Security.SecurityException) + { + // If we weren't allowed to read, then just ignore the error + return null; + } + catch (System.IO.IOException) + { + // If key is being deleted just ignore the error + return null; + } + catch (System.UnauthorizedAccessException) + { + // Registry access rights permissions, just ignore the error + return null; + } + + // + // If we didn't have valid eras, then fail + // should have at least 4 eras + // + if (iFoundEras < 4) return null; + + // + // Now we have eras, clean them up. + // + // Clean up array length + Array.Resize(ref registryEraRanges, iFoundEras); + + // Sort them + Array.Sort(registryEraRanges, CompareEraRanges); + + // Clean up era information + for (int i = 0; i < registryEraRanges.Length; i++) + { + // eras count backwards from length to 1 (and are 1 based indexes into string arrays) + registryEraRanges[i].era = registryEraRanges.Length - i; + + // update max era year + if (i == 0) + { + // First range is 'til the end of the calendar + registryEraRanges[0].maxEraYear = GregorianCalendar.MaxYear - registryEraRanges[0].yearOffset; + } + else + { + // Rest are until the next era (remember most recent era is first in array) + registryEraRanges[i].maxEraYear = registryEraRanges[i-1].yearOffset + 1 - registryEraRanges[i].yearOffset; + } + } + + // Return our ranges + return registryEraRanges; + } + + // + // Compare two era ranges, eg just the ticks + // Remember the era array is supposed to be in reverse chronological order + // + private static int CompareEraRanges(EraInfo a, EraInfo b) + { + return b.ticks.CompareTo(a.ticks); + } + + // + // GetEraFromValue + // + // Parse the registry value name/data pair into an era + // + // Registry values look like: + // yyyy.mm.dd=era_abbrev_english_englishabbrev + // + // Where yyyy.mm.dd is the registry value name, and also the date of the era start. + // yyyy, mm, and dd are the year, month & day the era begins (4, 2 & 2 digits long) + // era is the Japanese Era name + // abbrev is the Abbreviated Japanese Era Name + // english is the English name for the Era (unused) + // englishabbrev is the Abbreviated English name for the era. + // . is a delimiter, but the value of . doesn't matter. + // '_' marks the space between the japanese era name, japanese abbreviated era name + // english name, and abbreviated english names. + private static EraInfo GetEraFromValue(String value, String data) + { + // Need inputs + if (value == null || data == null) return null; + + // + // Get Date + // + // Need exactly 10 characters in name for date + // yyyy.mm.dd although the . can be any character + if (value.Length != 10) return null; + + int year; + int month; + int day; + + if (!Int32.TryParse(value.Substring(0,4), NumberStyles.None, NumberFormatInfo.InvariantInfo, out year) || + !Int32.TryParse(value.Substring(5,2), NumberStyles.None, NumberFormatInfo.InvariantInfo, out month) || + !Int32.TryParse(value.Substring(8,2), NumberStyles.None, NumberFormatInfo.InvariantInfo, out day)) + { + // Couldn't convert integer, fail + return null; + } + + // + // Get Strings + // + // Needs to be a certain length e_a_E_A at least (7 chars, exactly 4 groups) + String[] names = data.Split(new char[] {'_'}); + + // Should have exactly 4 parts + // 0 - Era Name + // 1 - Abbreviated Era Name + // 2 - English Era Name + // 3 - Abbreviated English Era Name + if (names.Length != 4) return null; + + // Each part should have data in it + if (names[0].Length == 0 || + names[1].Length == 0 || + names[2].Length == 0 || + names[3].Length == 0) + return null; + + // + // Now we have an era we can build + // Note that the era # and max era year need cleaned up after sorting + // Don't use the full English Era Name (names[2]) + // + return new EraInfo( 0, year, month, day, year - 1, 1, 0, + names[0], names[1], names[3]); + } + + // PAL Layer ends here + + private static string[] JapaneseErasEnglishNames = new String[] { "M", "T", "S", "H" }; + + private static string GetJapaneseEnglishEraName(int era) + { + Debug.Assert(era > 0); + return era <= JapaneseErasEnglishNames.Length ? JapaneseErasEnglishNames[era - 1] : " "; + } + } +} diff --git a/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.WinRT.cs b/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.WinRT.cs new file mode 100644 index 0000000000..6a9df97200 --- /dev/null +++ b/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.WinRT.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +using Internal.Runtime.Augments; + +namespace System.Globalization +{ + public partial class JapaneseCalendar : Calendar + { + private static EraInfo[] GetJapaneseEras() + { + int erasCount = WinRTInterop.Callbacks.GetJapaneseEraCount(); + if (erasCount < 4) + { + return null; + } + + EraInfo[] eras = new EraInfo[erasCount]; + int lastMaxYear = GregorianCalendar.MaxYear; + + for (int i = erasCount; i > 0; i--) + { + DateTimeOffset dateOffset; + + string eraName; + string abbreviatedEraName; + + if (!GetJapaneseEraInfo(i, out dateOffset, out eraName, out abbreviatedEraName)) + { + return null; + } + + DateTime dt = new DateTime(dateOffset.Ticks); + + eras[erasCount - i] = new EraInfo(i, dt.Year, dt.Month, dt.Day, dt.Year - 1, 1, lastMaxYear - dt.Year + 1, + eraName, abbreviatedEraName, GetJapaneseEnglishEraName(i)); // era #4 start year/month/day, yearOffset, minEraYear + + lastMaxYear = dt.Year; + } + + return eras; + } + + // PAL Layer ends here + + private static string[] JapaneseErasEnglishNames = new String[] { "M", "T", "S", "H" }; + + private static string GetJapaneseEnglishEraName(int era) + { + Debug.Assert(era > 0); + return era <= JapaneseErasEnglishNames.Length ? JapaneseErasEnglishNames[era - 1] : " "; + } + + private static bool GetJapaneseEraInfo(int era, out DateTimeOffset dateOffset, out string eraName, out string abbreviatedEraName) + { + return WinRTInterop.Callbacks.GetJapaneseEraInfo(era, out dateOffset, out eraName, out abbreviatedEraName); + } + } +} diff --git a/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.Windows.cs b/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.Windows.cs deleted file mode 100644 index 6a9df97200..0000000000 --- a/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.Windows.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics; - -using Internal.Runtime.Augments; - -namespace System.Globalization -{ - public partial class JapaneseCalendar : Calendar - { - private static EraInfo[] GetJapaneseEras() - { - int erasCount = WinRTInterop.Callbacks.GetJapaneseEraCount(); - if (erasCount < 4) - { - return null; - } - - EraInfo[] eras = new EraInfo[erasCount]; - int lastMaxYear = GregorianCalendar.MaxYear; - - for (int i = erasCount; i > 0; i--) - { - DateTimeOffset dateOffset; - - string eraName; - string abbreviatedEraName; - - if (!GetJapaneseEraInfo(i, out dateOffset, out eraName, out abbreviatedEraName)) - { - return null; - } - - DateTime dt = new DateTime(dateOffset.Ticks); - - eras[erasCount - i] = new EraInfo(i, dt.Year, dt.Month, dt.Day, dt.Year - 1, 1, lastMaxYear - dt.Year + 1, - eraName, abbreviatedEraName, GetJapaneseEnglishEraName(i)); // era #4 start year/month/day, yearOffset, minEraYear - - lastMaxYear = dt.Year; - } - - return eras; - } - - // PAL Layer ends here - - private static string[] JapaneseErasEnglishNames = new String[] { "M", "T", "S", "H" }; - - private static string GetJapaneseEnglishEraName(int era) - { - Debug.Assert(era > 0); - return era <= JapaneseErasEnglishNames.Length ? JapaneseErasEnglishNames[era - 1] : " "; - } - - private static bool GetJapaneseEraInfo(int era, out DateTimeOffset dateOffset, out string eraName, out string abbreviatedEraName) - { - return WinRTInterop.Callbacks.GetJapaneseEraInfo(era, out dateOffset, out eraName, out abbreviatedEraName); - } - } -} diff --git a/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.cs b/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.cs index 4130801de5..0b0fa77fc0 100644 --- a/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.cs +++ b/src/mscorlib/corefx/System/Globalization/JapaneseCalendar.cs @@ -9,11 +9,6 @@ using System.Diagnostics.Contracts; namespace System.Globalization { - -#if INSIDE_CLR - using Debug = BCLDebug; -#endif - /*=================================JapaneseCalendar========================== ** ** JapaneseCalendar is based on Gregorian calendar. The month and day values are the same as @@ -68,6 +63,15 @@ namespace System.Globalization } } + [System.Runtime.InteropServices.ComVisible(false)] + public override CalendarAlgorithmType AlgorithmType + { + get + { + return CalendarAlgorithmType.SolarCalendar; + } + } + // // Using a field initializer rather than a static constructor so that the whole class can be lazy // init. @@ -302,7 +306,7 @@ namespace System.Globalization { if (year <= 0) { - throw new ArgumentOutOfRangeException("year", + throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedPosNum); } Contract.EndContractBlock(); @@ -310,7 +314,7 @@ namespace System.Globalization if (year > helper.MaxYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, diff --git a/src/mscorlib/corefx/System/Globalization/JulianCalendar.cs b/src/mscorlib/corefx/System/Globalization/JulianCalendar.cs index 6721899ac9..a4277c6d49 100644 --- a/src/mscorlib/corefx/System/Globalization/JulianCalendar.cs +++ b/src/mscorlib/corefx/System/Globalization/JulianCalendar.cs @@ -67,17 +67,14 @@ namespace System.Globalization } } - // Return the type of the Julian calendar. - // - - //[System.Runtime.InteropServices.ComVisible(false)] - //public override CalendarAlgorithmType AlgorithmType - //{ - // get - // { - // return CalendarAlgorithmType.SolarCalendar; - // } - //} + [System.Runtime.InteropServices.ComVisible(false)] + public override CalendarAlgorithmType AlgorithmType + { + get + { + return CalendarAlgorithmType.SolarCalendar; + } + } public JulianCalendar() { @@ -97,7 +94,7 @@ namespace System.Globalization { if (era != CurrentEra && era != JulianEra) { - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } } @@ -107,7 +104,7 @@ namespace System.Globalization if (year <= 0 || year > MaxYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -120,7 +117,7 @@ namespace System.Globalization { if (month < 1 || month > 12) { - throw new ArgumentOutOfRangeException("month", SR.ArgumentOutOfRange_Month); + throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month); } } @@ -151,7 +148,7 @@ namespace System.Globalization if (day < 1 || day > monthDays) { throw new ArgumentOutOfRangeException( - "day", + nameof(day), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -196,7 +193,7 @@ namespace System.Globalization int[] days = leapYear ? s_daysToMonth366 : s_daysToMonth365; // All months have less than 32 days, so n >> 5 is a good conservative // estimate for the month - int m = n >> 5 + 1; + int m = (n >> 5) + 1; // m = 1-based month number while (n >= days[m]) m++; // If month was requested, return it @@ -223,7 +220,7 @@ namespace System.Globalization if (months < -120000 || months > 120000) { throw new ArgumentOutOfRangeException( - "months", + nameof(months), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -383,7 +380,7 @@ namespace System.Globalization if (millisecond < 0 || millisecond >= MillisPerSecond) { throw new ArgumentOutOfRangeException( - "millisecond", + nameof(millisecond), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -431,7 +428,7 @@ namespace System.Globalization { if (year < 0) { - throw new ArgumentOutOfRangeException("year", + throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedNonNegNum); } Contract.EndContractBlock(); @@ -439,7 +436,7 @@ namespace System.Globalization if (year > MaxYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Bounds_Lower_Upper, diff --git a/src/mscorlib/corefx/System/Globalization/KoreanCalendar.cs b/src/mscorlib/corefx/System/Globalization/KoreanCalendar.cs index 38a0b41fda..27d0aa812a 100644 --- a/src/mscorlib/corefx/System/Globalization/KoreanCalendar.cs +++ b/src/mscorlib/corefx/System/Globalization/KoreanCalendar.cs @@ -69,6 +69,15 @@ namespace System.Globalization } } + [System.Runtime.InteropServices.ComVisible(false)] + public override CalendarAlgorithmType AlgorithmType + { + get + { + return CalendarAlgorithmType.SolarCalendar; + } + } + public KoreanCalendar() { try @@ -253,7 +262,7 @@ namespace System.Globalization { if (year < 0) { - throw new ArgumentOutOfRangeException("year", + throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedNonNegNum); } Contract.EndContractBlock(); diff --git a/src/mscorlib/corefx/System/Globalization/KoreanLunisolarCalendar.cs b/src/mscorlib/corefx/System/Globalization/KoreanLunisolarCalendar.cs index 68c4fab58f..07d85a461e 100644 --- a/src/mscorlib/corefx/System/Globalization/KoreanLunisolarCalendar.cs +++ b/src/mscorlib/corefx/System/Globalization/KoreanLunisolarCalendar.cs @@ -1275,12 +1275,12 @@ namespace System.Globalization internal override int GetGregorianYear(int year, int era) { if (era != CurrentEra && era != GregorianEra) - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); if (year < MIN_LUNISOLAR_YEAR || year > MAX_LUNISOLAR_YEAR) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, MIN_LUNISOLAR_YEAR, MAX_LUNISOLAR_YEAR)); diff --git a/src/mscorlib/corefx/System/Globalization/LocaleData.Unix.cs b/src/mscorlib/corefx/System/Globalization/LocaleData.Unix.cs new file mode 100644 index 0000000000..d4c58d8a3d --- /dev/null +++ b/src/mscorlib/corefx/System/Globalization/LocaleData.Unix.cs @@ -0,0 +1,4572 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Diagnostics.Contracts; + +// This file contains the handling of Windows OS specific culture features. + +namespace System.Globalization +{ + internal enum LocaleDataParts + { + Lcid = 0, + AnsiCodePage = 1, + OemCodePage = 2, + MacCodePage = 3, + EbcdicCodePage = 4, + GeoId = 5, + DigitSubstitution = 6, + SpecificLocaleIndex = 7, + ConsoleLocaleIndex = 8 + } + + internal partial class LocaleData + { + // this is done rather than using a large readonly array of strings to avoid + // generating a large amount of code in the static constructor. + // Using indices from s_localeNamesIndices, we binary search this string when mapping + // an culture name to Lcid. Note that these names are all lowercase and are + // sorted alphabetically (ordinal). + private const string c_localeNames = + // culture name Lcid + "aa" + // 01000 - 0 + "aa-dj" + // 01000 - 2 + "aa-er" + // 01000 - 7 + "aa-et" + // 01000 - 12 + "af" + // 00036 - 17 + "af-na" + // 01000 - 19 + "af-za" + // 00436 - 24 + "agq" + // 01000 - 29 + "agq-cm" + // 01000 - 32 + "ak" + // 01000 - 38 + "ak-gh" + // 01000 - 40 + "am" + // 0005e - 45 + "am-et" + // 0045e - 47 + "ar" + // 00001 - 52 + "ar-001" + // 01000 - 54 + "ar-ae" + // 03801 - 60 + "ar-bh" + // 03c01 - 65 + "ar-dj" + // 01000 - 70 + "ar-dz" + // 01401 - 75 + "ar-eg" + // 00c01 - 80 + "ar-er" + // 01000 - 85 + "ar-il" + // 01000 - 90 + "ar-iq" + // 00801 - 95 + "ar-jo" + // 02c01 - 100 + "ar-km" + // 01000 - 105 + "ar-kw" + // 03401 - 110 + "ar-lb" + // 03001 - 115 + "ar-ly" + // 01001 - 120 + "ar-ma" + // 01801 - 125 + "ar-mr" + // 01000 - 130 + "ar-om" + // 02001 - 135 + "ar-ps" + // 01000 - 140 + "ar-qa" + // 04001 - 145 + "ar-sa" + // 00401 - 150 + "ar-sd" + // 01000 - 155 + "ar-so" + // 01000 - 160 + "ar-ss" + // 01000 - 165 + "ar-sy" + // 02801 - 170 + "ar-td" + // 01000 - 175 + "ar-tn" + // 01c01 - 180 + "ar-ye" + // 02401 - 185 + "arn" + // 0007a - 190 + "arn-cl" + // 0047a - 193 + "as" + // 0004d - 199 + "as-in" + // 0044d - 201 + "asa" + // 01000 - 206 + "asa-tz" + // 01000 - 209 + "ast" + // 01000 - 215 + "ast-es" + // 01000 - 218 + "az" + // 0002c - 224 + "az-cyrl" + // 0742c - 226 + "az-cyrl-az" + // 0082c - 233 + "az-latn" + // 0782c - 243 + "az-latn-az" + // 0042c - 250 + "ba" + // 0006d - 260 + "ba-ru" + // 0046d - 262 + "bas" + // 01000 - 267 + "bas-cm" + // 01000 - 270 + "be" + // 00023 - 276 + "be-by" + // 00423 - 278 + "bem" + // 01000 - 283 + "bem-zm" + // 01000 - 286 + "bez" + // 01000 - 292 + "bez-tz" + // 01000 - 295 + "bg" + // 00002 - 301 + "bg-bg" + // 00402 - 303 + "bin" + // 00066 - 308 + "bin-ng" + // 00466 - 311 + "bm" + // 01000 - 317 + "bm-latn" + // 01000 - 319 + "bm-latn-ml" + // 01000 - 326 + "bn" + // 00045 - 336 + "bn-bd" + // 00845 - 338 + "bn-in" + // 00445 - 343 + "bo" + // 00051 - 348 + "bo-cn" + // 00451 - 350 + "bo-in" + // 01000 - 355 + "br" + // 0007e - 360 + "br-fr" + // 0047e - 362 + "brx" + // 01000 - 367 + "brx-in" + // 01000 - 370 + "bs" + // 0781a - 376 + "bs-cyrl" + // 0641a - 378 + "bs-cyrl-ba" + // 0201a - 385 + "bs-latn" + // 0681a - 395 + "bs-latn-ba" + // 0141a - 402 + "byn" + // 01000 - 412 + "byn-er" + // 01000 - 415 + "ca" + // 00003 - 421 + "ca-ad" + // 01000 - 423 + "ca-es" + // 00403 - 428 + "ca-es-valencia" + // 00803 - 433 + "ca-fr" + // 01000 - 447 + "ca-it" + // 01000 - 452 + "ce" + // 01000 - 457 + "ce-ru" + // 01000 - 459 + "cgg" + // 01000 - 464 + "cgg-ug" + // 01000 - 467 + "chr" + // 0005c - 473 + "chr-cher" + // 07c5c - 476 + "chr-cher-us" + // 0045c - 484 + "co" + // 00083 - 495 + "co-fr" + // 00483 - 497 + "cs" + // 00005 - 502 + "cs-cz" + // 00405 - 504 + "cu" + // 01000 - 509 + "cu-ru" + // 01000 - 511 + "cy" + // 00052 - 516 + "cy-gb" + // 00452 - 518 + "da" + // 00006 - 523 + "da-dk" + // 00406 - 525 + "da-gl" + // 01000 - 530 + "dav" + // 01000 - 535 + "dav-ke" + // 01000 - 538 + "de" + // 00007 - 544 + "de-at" + // 00c07 - 546 + "de-be" + // 01000 - 551 + "de-ch" + // 00807 - 556 + "de-de" + // 00407 - 561 + "de-de_phoneb" + // 10407 - 566 + "de-it" + // 01000 - 578 + "de-li" + // 01407 - 583 + "de-lu" + // 01007 - 588 + "dje" + // 01000 - 593 + "dje-ne" + // 01000 - 596 + "dsb" + // 07c2e - 602 + "dsb-de" + // 0082e - 605 + "dua" + // 01000 - 611 + "dua-cm" + // 01000 - 614 + "dv" + // 00065 - 620 + "dv-mv" + // 00465 - 622 + "dyo" + // 01000 - 627 + "dyo-sn" + // 01000 - 630 + "dz" + // 01000 - 636 + "dz-bt" + // 00c51 - 638 + "ebu" + // 01000 - 643 + "ebu-ke" + // 01000 - 646 + "ee" + // 01000 - 652 + "ee-gh" + // 01000 - 654 + "ee-tg" + // 01000 - 659 + "el" + // 00008 - 664 + "el-cy" + // 01000 - 666 + "el-gr" + // 00408 - 671 + "en" + // 00009 - 676 + "en-001" + // 01000 - 678 + "en-029" + // 02409 - 684 + "en-150" + // 01000 - 690 + "en-ag" + // 01000 - 696 + "en-ai" + // 01000 - 701 + "en-as" + // 01000 - 706 + "en-at" + // 01000 - 711 + "en-au" + // 00c09 - 716 + "en-bb" + // 01000 - 721 + "en-be" + // 01000 - 726 + "en-bi" + // 01000 - 731 + "en-bm" + // 01000 - 736 + "en-bs" + // 01000 - 741 + "en-bw" + // 01000 - 746 + "en-bz" + // 02809 - 751 + "en-ca" + // 01009 - 756 + "en-cc" + // 01000 - 761 + "en-ch" + // 01000 - 766 + "en-ck" + // 01000 - 771 + "en-cm" + // 01000 - 776 + "en-cx" + // 01000 - 781 + "en-cy" + // 01000 - 786 + "en-de" + // 01000 - 791 + "en-dk" + // 01000 - 796 + "en-dm" + // 01000 - 801 + "en-er" + // 01000 - 806 + "en-fi" + // 01000 - 811 + "en-fj" + // 01000 - 816 + "en-fk" + // 01000 - 821 + "en-fm" + // 01000 - 826 + "en-gb" + // 00809 - 831 + "en-gd" + // 01000 - 836 + "en-gg" + // 01000 - 841 + "en-gh" + // 01000 - 846 + "en-gi" + // 01000 - 851 + "en-gm" + // 01000 - 856 + "en-gu" + // 01000 - 861 + "en-gy" + // 01000 - 866 + "en-hk" + // 03c09 - 871 + "en-id" + // 03809 - 876 + "en-ie" + // 01809 - 881 + "en-il" + // 01000 - 886 + "en-im" + // 01000 - 891 + "en-in" + // 04009 - 896 + "en-io" + // 01000 - 901 + "en-je" + // 01000 - 906 + "en-jm" + // 02009 - 911 + "en-ke" + // 01000 - 916 + "en-ki" + // 01000 - 921 + "en-kn" + // 01000 - 926 + "en-ky" + // 01000 - 931 + "en-lc" + // 01000 - 936 + "en-lr" + // 01000 - 941 + "en-ls" + // 01000 - 946 + "en-mg" + // 01000 - 951 + "en-mh" + // 01000 - 956 + "en-mo" + // 01000 - 961 + "en-mp" + // 01000 - 966 + "en-ms" + // 01000 - 971 + "en-mt" + // 01000 - 976 + "en-mu" + // 01000 - 981 + "en-mw" + // 01000 - 986 + "en-my" + // 04409 - 991 + "en-na" + // 01000 - 996 + "en-nf" + // 01000 - 1001 + "en-ng" + // 01000 - 1006 + "en-nl" + // 01000 - 1011 + "en-nr" + // 01000 - 1016 + "en-nu" + // 01000 - 1021 + "en-nz" + // 01409 - 1026 + "en-pg" + // 01000 - 1031 + "en-ph" + // 03409 - 1036 + "en-pk" + // 01000 - 1041 + "en-pn" + // 01000 - 1046 + "en-pr" + // 01000 - 1051 + "en-pw" + // 01000 - 1056 + "en-rw" + // 01000 - 1061 + "en-sb" + // 01000 - 1066 + "en-sc" + // 01000 - 1071 + "en-sd" + // 01000 - 1076 + "en-se" + // 01000 - 1081 + "en-sg" + // 04809 - 1086 + "en-sh" + // 01000 - 1091 + "en-si" + // 01000 - 1096 + "en-sl" + // 01000 - 1101 + "en-ss" + // 01000 - 1106 + "en-sx" + // 01000 - 1111 + "en-sz" + // 01000 - 1116 + "en-tc" + // 01000 - 1121 + "en-tk" + // 01000 - 1126 + "en-to" + // 01000 - 1131 + "en-tt" + // 02c09 - 1136 + "en-tv" + // 01000 - 1141 + "en-tz" + // 01000 - 1146 + "en-ug" + // 01000 - 1151 + "en-um" + // 01000 - 1156 + "en-us" + // 00409 - 1161 + "en-vc" + // 01000 - 1166 + "en-vg" + // 01000 - 1171 + "en-vi" + // 01000 - 1176 + "en-vu" + // 01000 - 1181 + "en-ws" + // 01000 - 1186 + "en-za" + // 01c09 - 1191 + "en-zm" + // 01000 - 1196 + "en-zw" + // 03009 - 1201 + "eo" + // 01000 - 1206 + "eo-001" + // 01000 - 1208 + "es" + // 0000a - 1214 + "es-419" + // 0580a - 1216 + "es-ar" + // 02c0a - 1222 + "es-bo" + // 0400a - 1227 + "es-br" + // 01000 - 1232 + "es-cl" + // 0340a - 1237 + "es-co" + // 0240a - 1242 + "es-cr" + // 0140a - 1247 + "es-cu" + // 05c0a - 1252 + "es-do" + // 01c0a - 1257 + "es-ec" + // 0300a - 1262 + "es-es" + // 00c0a - 1267 + "es-es_tradnl" + // 0040a - 1272 + "es-gq" + // 01000 - 1284 + "es-gt" + // 0100a - 1289 + "es-hn" + // 0480a - 1294 + "es-mx" + // 0080a - 1299 + "es-ni" + // 04c0a - 1304 + "es-pa" + // 0180a - 1309 + "es-pe" + // 0280a - 1314 + "es-ph" + // 01000 - 1319 + "es-pr" + // 0500a - 1324 + "es-py" + // 03c0a - 1329 + "es-sv" + // 0440a - 1334 + "es-us" + // 0540a - 1339 + "es-uy" + // 0380a - 1344 + "es-ve" + // 0200a - 1349 + "et" + // 00025 - 1354 + "et-ee" + // 00425 - 1356 + "eu" + // 0002d - 1361 + "eu-es" + // 0042d - 1363 + "ewo" + // 01000 - 1368 + "ewo-cm" + // 01000 - 1371 + "fa" + // 00029 - 1377 + "fa-ir" + // 00429 - 1379 + "ff" + // 00067 - 1384 + "ff-cm" + // 01000 - 1386 + "ff-gn" + // 01000 - 1391 + "ff-latn" + // 07c67 - 1396 + "ff-latn-sn" + // 00867 - 1403 + "ff-mr" + // 01000 - 1413 + "ff-ng" + // 00467 - 1418 + "fi" + // 0000b - 1423 + "fi-fi" + // 0040b - 1425 + "fil" + // 00064 - 1430 + "fil-ph" + // 00464 - 1433 + "fo" + // 00038 - 1439 + "fo-dk" + // 01000 - 1441 + "fo-fo" + // 00438 - 1446 + "fr" + // 0000c - 1451 + "fr-029" + // 01c0c - 1453 + "fr-be" + // 0080c - 1459 + "fr-bf" + // 01000 - 1464 + "fr-bi" + // 01000 - 1469 + "fr-bj" + // 01000 - 1474 + "fr-bl" + // 01000 - 1479 + "fr-ca" + // 00c0c - 1484 + "fr-cd" + // 0240c - 1489 + "fr-cf" + // 01000 - 1494 + "fr-cg" + // 01000 - 1499 + "fr-ch" + // 0100c - 1504 + "fr-ci" + // 0300c - 1509 + "fr-cm" + // 02c0c - 1514 + "fr-dj" + // 01000 - 1519 + "fr-dz" + // 01000 - 1524 + "fr-fr" + // 0040c - 1529 + "fr-ga" + // 01000 - 1534 + "fr-gf" + // 01000 - 1539 + "fr-gn" + // 01000 - 1544 + "fr-gp" + // 01000 - 1549 + "fr-gq" + // 01000 - 1554 + "fr-ht" + // 03c0c - 1559 + "fr-km" + // 01000 - 1564 + "fr-lu" + // 0140c - 1569 + "fr-ma" + // 0380c - 1574 + "fr-mc" + // 0180c - 1579 + "fr-mf" + // 01000 - 1584 + "fr-mg" + // 01000 - 1589 + "fr-ml" + // 0340c - 1594 + "fr-mq" + // 01000 - 1599 + "fr-mr" + // 01000 - 1604 + "fr-mu" + // 01000 - 1609 + "fr-nc" + // 01000 - 1614 + "fr-ne" + // 01000 - 1619 + "fr-pf" + // 01000 - 1624 + "fr-pm" + // 01000 - 1629 + "fr-re" + // 0200c - 1634 + "fr-rw" + // 01000 - 1639 + "fr-sc" + // 01000 - 1644 + "fr-sn" + // 0280c - 1649 + "fr-sy" + // 01000 - 1654 + "fr-td" + // 01000 - 1659 + "fr-tg" + // 01000 - 1664 + "fr-tn" + // 01000 - 1669 + "fr-vu" + // 01000 - 1674 + "fr-wf" + // 01000 - 1679 + "fr-yt" + // 01000 - 1684 + "fur" + // 01000 - 1689 + "fur-it" + // 01000 - 1692 + "fy" + // 00062 - 1698 + "fy-nl" + // 00462 - 1700 + "ga" + // 0003c - 1705 + "ga-ie" + // 0083c - 1707 + "gd" + // 00091 - 1712 + "gd-gb" + // 00491 - 1714 + "gl" + // 00056 - 1719 + "gl-es" + // 00456 - 1721 + "gn" + // 00074 - 1726 + "gn-py" + // 00474 - 1728 + "gsw" + // 00084 - 1733 + "gsw-ch" + // 01000 - 1736 + "gsw-fr" + // 00484 - 1742 + "gsw-li" + // 01000 - 1748 + "gu" + // 00047 - 1754 + "gu-in" + // 00447 - 1756 + "guz" + // 01000 - 1761 + "guz-ke" + // 01000 - 1764 + "gv" + // 01000 - 1770 + "gv-im" + // 01000 - 1772 + "ha" + // 00068 - 1777 + "ha-latn" + // 07c68 - 1779 + "ha-latn-gh" + // 01000 - 1786 + "ha-latn-ne" + // 01000 - 1796 + "ha-latn-ng" + // 00468 - 1806 + "haw" + // 00075 - 1816 + "haw-us" + // 00475 - 1819 + "he" + // 0000d - 1825 + "he-il" + // 0040d - 1827 + "hi" + // 00039 - 1832 + "hi-in" + // 00439 - 1834 + "hr" + // 0001a - 1839 + "hr-ba" + // 0101a - 1841 + "hr-hr" + // 0041a - 1846 + "hsb" + // 0002e - 1851 + "hsb-de" + // 0042e - 1854 + "hu" + // 0000e - 1860 + "hu-hu" + // 0040e - 1862 + "hu-hu_technl" + // 1040e - 1867 + "hy" + // 0002b - 1879 + "hy-am" + // 0042b - 1881 + "ia" + // 01000 - 1886 + "ia-001" + // 01000 - 1888 + "ia-fr" + // 01000 - 1894 + "ibb" + // 00069 - 1899 + "ibb-ng" + // 00469 - 1902 + "id" + // 00021 - 1908 + "id-id" + // 00421 - 1910 + "ig" + // 00070 - 1915 + "ig-ng" + // 00470 - 1917 + "ii" + // 00078 - 1922 + "ii-cn" + // 00478 - 1924 + "is" + // 0000f - 1929 + "is-is" + // 0040f - 1931 + "it" + // 00010 - 1936 + "it-ch" + // 00810 - 1938 + "it-it" + // 00410 - 1943 + "it-sm" + // 01000 - 1948 + "iu" + // 0005d - 1953 + "iu-cans" + // 0785d - 1955 + "iu-cans-ca" + // 0045d - 1962 + "iu-latn" + // 07c5d - 1972 + "iu-latn-ca" + // 0085d - 1979 + "ja" + // 00011 - 1989 + "ja-jp" + // 00411 - 1991 + "ja-jp_radstr" + // 40411 - 1996 + "jgo" + // 01000 - 2008 + "jgo-cm" + // 01000 - 2011 + "jmc" + // 01000 - 2017 + "jmc-tz" + // 01000 - 2020 + "jv" + // 01000 - 2026 + "jv-java" + // 01000 - 2028 + "jv-java-id" + // 01000 - 2035 + "jv-latn" + // 01000 - 2045 + "jv-latn-id" + // 01000 - 2052 + "ka" + // 00037 - 2062 + "ka-ge" + // 00437 - 2064 + "ka-ge_modern" + // 10437 - 2069 + "kab" + // 01000 - 2081 + "kab-dz" + // 01000 - 2084 + "kam" + // 01000 - 2090 + "kam-ke" + // 01000 - 2093 + "kde" + // 01000 - 2099 + "kde-tz" + // 01000 - 2102 + "kea" + // 01000 - 2108 + "kea-cv" + // 01000 - 2111 + "khq" + // 01000 - 2117 + "khq-ml" + // 01000 - 2120 + "ki" + // 01000 - 2126 + "ki-ke" + // 01000 - 2128 + "kk" + // 0003f - 2133 + "kk-kz" + // 0043f - 2135 + "kkj" + // 01000 - 2140 + "kkj-cm" + // 01000 - 2143 + "kl" + // 0006f - 2149 + "kl-gl" + // 0046f - 2151 + "kln" + // 01000 - 2156 + "kln-ke" + // 01000 - 2159 + "km" + // 00053 - 2165 + "km-kh" + // 00453 - 2167 + "kn" + // 0004b - 2172 + "kn-in" + // 0044b - 2174 + "ko" + // 00012 - 2179 + "ko-kp" + // 01000 - 2181 + "ko-kr" + // 00412 - 2186 + "kok" + // 00057 - 2191 + "kok-in" + // 00457 - 2194 + "kr" + // 00071 - 2200 + "kr-ng" + // 00471 - 2202 + "ks" + // 00060 - 2207 + "ks-arab" + // 00460 - 2209 + "ks-arab-in" + // 01000 - 2216 + "ks-deva" + // 01000 - 2226 + "ks-deva-in" + // 00860 - 2233 + "ksb" + // 01000 - 2243 + "ksb-tz" + // 01000 - 2246 + "ksf" + // 01000 - 2252 + "ksf-cm" + // 01000 - 2255 + "ksh" + // 01000 - 2261 + "ksh-de" + // 01000 - 2264 + "ku" + // 00092 - 2270 + "ku-arab" + // 07c92 - 2272 + "ku-arab-iq" + // 00492 - 2279 + "ku-arab-ir" + // 01000 - 2289 + "kw" + // 01000 - 2299 + "kw-gb" + // 01000 - 2301 + "ky" + // 00040 - 2306 + "ky-kg" + // 00440 - 2308 + "la" + // 00076 - 2313 + "la-001" + // 00476 - 2315 + "lag" + // 01000 - 2321 + "lag-tz" + // 01000 - 2324 + "lb" + // 0006e - 2330 + "lb-lu" + // 0046e - 2332 + "lg" + // 01000 - 2337 + "lg-ug" + // 01000 - 2339 + "lkt" + // 01000 - 2344 + "lkt-us" + // 01000 - 2347 + "ln" + // 01000 - 2353 + "ln-ao" + // 01000 - 2355 + "ln-cd" + // 01000 - 2360 + "ln-cf" + // 01000 - 2365 + "ln-cg" + // 01000 - 2370 + "lo" + // 00054 - 2375 + "lo-la" + // 00454 - 2377 + "lrc" + // 01000 - 2382 + "lrc-iq" + // 01000 - 2385 + "lrc-ir" + // 01000 - 2391 + "lt" + // 00027 - 2397 + "lt-lt" + // 00427 - 2399 + "lu" + // 01000 - 2404 + "lu-cd" + // 01000 - 2406 + "luo" + // 01000 - 2411 + "luo-ke" + // 01000 - 2414 + "luy" + // 01000 - 2420 + "luy-ke" + // 01000 - 2423 + "lv" + // 00026 - 2429 + "lv-lv" + // 00426 - 2431 + "mas" + // 01000 - 2436 + "mas-ke" + // 01000 - 2439 + "mas-tz" + // 01000 - 2445 + "mer" + // 01000 - 2451 + "mer-ke" + // 01000 - 2454 + "mfe" + // 01000 - 2460 + "mfe-mu" + // 01000 - 2463 + "mg" + // 01000 - 2469 + "mg-mg" + // 01000 - 2471 + "mgh" + // 01000 - 2476 + "mgh-mz" + // 01000 - 2479 + "mgo" + // 01000 - 2485 + "mgo-cm" + // 01000 - 2488 + "mi" + // 00081 - 2494 + "mi-nz" + // 00481 - 2496 + "mk" + // 0002f - 2501 + "mk-mk" + // 0042f - 2503 + "ml" + // 0004c - 2508 + "ml-in" + // 0044c - 2510 + "mn" + // 00050 - 2515 + "mn-cyrl" + // 07850 - 2517 + "mn-mn" + // 00450 - 2524 + "mn-mong" + // 07c50 - 2529 + "mn-mong-cn" + // 00850 - 2536 + "mn-mong-mn" + // 00c50 - 2546 + "mni" + // 00058 - 2556 + "mni-in" + // 00458 - 2559 + "moh" + // 0007c - 2565 + "moh-ca" + // 0047c - 2568 + "mr" + // 0004e - 2574 + "mr-in" + // 0044e - 2576 + "ms" + // 0003e - 2581 + "ms-bn" + // 0083e - 2583 + "ms-my" + // 0043e - 2588 + "ms-sg" + // 01000 - 2593 + "mt" + // 0003a - 2598 + "mt-mt" + // 0043a - 2600 + "mua" + // 01000 - 2605 + "mua-cm" + // 01000 - 2608 + "my" + // 00055 - 2614 + "my-mm" + // 00455 - 2616 + "mzn" + // 01000 - 2621 + "mzn-ir" + // 01000 - 2624 + "naq" + // 01000 - 2630 + "naq-na" + // 01000 - 2633 + "nb" + // 07c14 - 2639 + "nb-no" + // 00414 - 2641 + "nb-sj" + // 01000 - 2646 + "nd" + // 01000 - 2651 + "nd-zw" + // 01000 - 2653 + "nds" + // 01000 - 2658 + "nds-de" + // 01000 - 2661 + "nds-nl" + // 01000 - 2667 + "ne" + // 00061 - 2673 + "ne-in" + // 00861 - 2675 + "ne-np" + // 00461 - 2680 + "nl" + // 00013 - 2685 + "nl-aw" + // 01000 - 2687 + "nl-be" + // 00813 - 2692 + "nl-bq" + // 01000 - 2697 + "nl-cw" + // 01000 - 2702 + "nl-nl" + // 00413 - 2707 + "nl-sr" + // 01000 - 2712 + "nl-sx" + // 01000 - 2717 + "nmg" + // 01000 - 2722 + "nmg-cm" + // 01000 - 2725 + "nn" + // 07814 - 2731 + "nn-no" + // 00814 - 2733 + "nnh" + // 01000 - 2738 + "nnh-cm" + // 01000 - 2741 + "no" + // 00014 - 2747 + "nqo" + // 01000 - 2749 + "nqo-gn" + // 01000 - 2752 + "nr" + // 01000 - 2758 + "nr-za" + // 01000 - 2760 + "nso" + // 0006c - 2765 + "nso-za" + // 0046c - 2768 + "nus" + // 01000 - 2774 + "nus-ss" + // 01000 - 2777 + "nyn" + // 01000 - 2783 + "nyn-ug" + // 01000 - 2786 + "oc" + // 00082 - 2792 + "oc-fr" + // 00482 - 2794 + "om" + // 00072 - 2799 + "om-et" + // 00472 - 2801 + "om-ke" + // 01000 - 2806 + "or" + // 00048 - 2811 + "or-in" + // 00448 - 2813 + "os" + // 01000 - 2818 + "os-ge" + // 01000 - 2820 + "os-ru" + // 01000 - 2825 + "pa" + // 00046 - 2830 + "pa-arab" + // 07c46 - 2832 + "pa-arab-pk" + // 00846 - 2839 + "pa-in" + // 00446 - 2849 + "pap" + // 00079 - 2854 + "pap-029" + // 00479 - 2857 + "pl" + // 00015 - 2864 + "pl-pl" + // 00415 - 2866 + "prg" + // 01000 - 2871 + "prg-001" + // 01000 - 2874 + "prs" + // 0008c - 2881 + "prs-af" + // 0048c - 2884 + "ps" + // 00063 - 2890 + "ps-af" + // 00463 - 2892 + "pt" + // 00016 - 2897 + "pt-ao" + // 01000 - 2899 + "pt-br" + // 00416 - 2904 + "pt-ch" + // 01000 - 2909 + "pt-cv" + // 01000 - 2914 + "pt-gq" + // 01000 - 2919 + "pt-gw" + // 01000 - 2924 + "pt-lu" + // 01000 - 2929 + "pt-mo" + // 01000 - 2934 + "pt-mz" + // 01000 - 2939 + "pt-pt" + // 00816 - 2944 + "pt-st" + // 01000 - 2949 + "pt-tl" + // 01000 - 2954 + "qps-latn-x-sh" + // 00901 - 2959 + "qps-ploc" + // 00501 - 2972 + "qps-ploca" + // 005fe - 2980 + "qps-plocm" + // 009ff - 2989 + "quc" + // 00086 - 2998 + "quc-latn" + // 07c86 - 3001 + "quc-latn-gt" + // 00486 - 3009 + "quz" + // 0006b - 3020 + "quz-bo" + // 0046b - 3023 + "quz-ec" + // 0086b - 3029 + "quz-pe" + // 00c6b - 3035 + "rm" + // 00017 - 3041 + "rm-ch" + // 00417 - 3043 + "rn" + // 01000 - 3048 + "rn-bi" + // 01000 - 3050 + "ro" + // 00018 - 3055 + "ro-md" + // 00818 - 3057 + "ro-ro" + // 00418 - 3062 + "rof" + // 01000 - 3067 + "rof-tz" + // 01000 - 3070 + "ru" + // 00019 - 3076 + "ru-by" + // 01000 - 3078 + "ru-kg" + // 01000 - 3083 + "ru-kz" + // 01000 - 3088 + "ru-md" + // 00819 - 3093 + "ru-ru" + // 00419 - 3098 + "ru-ua" + // 01000 - 3103 + "rw" + // 00087 - 3108 + "rw-rw" + // 00487 - 3110 + "rwk" + // 01000 - 3115 + "rwk-tz" + // 01000 - 3118 + "sa" + // 0004f - 3124 + "sa-in" + // 0044f - 3126 + "sah" + // 00085 - 3131 + "sah-ru" + // 00485 - 3134 + "saq" + // 01000 - 3140 + "saq-ke" + // 01000 - 3143 + "sbp" + // 01000 - 3149 + "sbp-tz" + // 01000 - 3152 + "sd" + // 00059 - 3158 + "sd-arab" + // 07c59 - 3160 + "sd-arab-pk" + // 00859 - 3167 + "sd-deva" + // 01000 - 3177 + "sd-deva-in" + // 00459 - 3184 + "se" + // 0003b - 3194 + "se-fi" + // 00c3b - 3196 + "se-no" + // 0043b - 3201 + "se-se" + // 0083b - 3206 + "seh" + // 01000 - 3211 + "seh-mz" + // 01000 - 3214 + "ses" + // 01000 - 3220 + "ses-ml" + // 01000 - 3223 + "sg" + // 01000 - 3229 + "sg-cf" + // 01000 - 3231 + "shi" + // 01000 - 3236 + "shi-latn" + // 01000 - 3239 + "shi-latn-ma" + // 01000 - 3247 + "shi-tfng" + // 01000 - 3258 + "shi-tfng-ma" + // 01000 - 3266 + "si" + // 0005b - 3277 + "si-lk" + // 0045b - 3279 + "sk" + // 0001b - 3284 + "sk-sk" + // 0041b - 3286 + "sl" + // 00024 - 3291 + "sl-si" + // 00424 - 3293 + "sma" + // 0783b - 3298 + "sma-no" + // 0183b - 3301 + "sma-se" + // 01c3b - 3307 + "smj" + // 07c3b - 3313 + "smj-no" + // 0103b - 3316 + "smj-se" + // 0143b - 3322 + "smn" + // 0703b - 3328 + "smn-fi" + // 0243b - 3331 + "sms" + // 0743b - 3337 + "sms-fi" + // 0203b - 3340 + "sn" + // 01000 - 3346 + "sn-latn" + // 01000 - 3348 + "sn-latn-zw" + // 01000 - 3355 + "so" + // 00077 - 3365 + "so-dj" + // 01000 - 3367 + "so-et" + // 01000 - 3372 + "so-ke" + // 01000 - 3377 + "so-so" + // 00477 - 3382 + "sq" + // 0001c - 3387 + "sq-al" + // 0041c - 3389 + "sq-mk" + // 01000 - 3394 + "sq-xk" + // 01000 - 3399 + "sr" + // 07c1a - 3404 + "sr-cyrl" + // 06c1a - 3406 + "sr-cyrl-ba" + // 01c1a - 3413 + "sr-cyrl-cs" + // 00c1a - 3423 + "sr-cyrl-me" + // 0301a - 3433 + "sr-cyrl-rs" + // 0281a - 3443 + "sr-cyrl-xk" + // 01000 - 3453 + "sr-latn" + // 0701a - 3463 + "sr-latn-ba" + // 0181a - 3470 + "sr-latn-cs" + // 0081a - 3480 + "sr-latn-me" + // 02c1a - 3490 + "sr-latn-rs" + // 0241a - 3500 + "sr-latn-xk" + // 01000 - 3510 + "ss" + // 01000 - 3520 + "ss-sz" + // 01000 - 3522 + "ss-za" + // 01000 - 3527 + "ssy" + // 01000 - 3532 + "ssy-er" + // 01000 - 3535 + "st" + // 00030 - 3541 + "st-ls" + // 01000 - 3543 + "st-za" + // 00430 - 3548 + "sv" + // 0001d - 3553 + "sv-ax" + // 01000 - 3555 + "sv-fi" + // 0081d - 3560 + "sv-se" + // 0041d - 3565 + "sw" + // 00041 - 3570 + "sw-cd" + // 01000 - 3572 + "sw-ke" + // 00441 - 3577 + "sw-tz" + // 01000 - 3582 + "sw-ug" + // 01000 - 3587 + "swc" + // 01000 - 3592 + "swc-cd" + // 01000 - 3595 + "syr" + // 0005a - 3601 + "syr-sy" + // 0045a - 3604 + "ta" + // 00049 - 3610 + "ta-in" + // 00449 - 3612 + "ta-lk" + // 00849 - 3617 + "ta-my" + // 01000 - 3622 + "ta-sg" + // 01000 - 3627 + "te" + // 0004a - 3632 + "te-in" + // 0044a - 3634 + "teo" + // 01000 - 3639 + "teo-ke" + // 01000 - 3642 + "teo-ug" + // 01000 - 3648 + "tg" + // 00028 - 3654 + "tg-cyrl" + // 07c28 - 3656 + "tg-cyrl-tj" + // 00428 - 3663 + "th" + // 0001e - 3673 + "th-th" + // 0041e - 3675 + "ti" + // 00073 - 3680 + "ti-er" + // 00873 - 3682 + "ti-et" + // 00473 - 3687 + "tig" + // 01000 - 3692 + "tig-er" + // 01000 - 3695 + "tk" + // 00042 - 3701 + "tk-tm" + // 00442 - 3703 + "tn" + // 00032 - 3708 + "tn-bw" + // 00832 - 3710 + "tn-za" + // 00432 - 3715 + "to" + // 01000 - 3720 + "to-to" + // 01000 - 3722 + "tr" + // 0001f - 3727 + "tr-cy" + // 01000 - 3729 + "tr-tr" + // 0041f - 3734 + "ts" + // 00031 - 3739 + "ts-za" + // 00431 - 3741 + "tt" + // 00044 - 3746 + "tt-ru" + // 00444 - 3748 + "twq" + // 01000 - 3753 + "twq-ne" + // 01000 - 3756 + "tzm" + // 0005f - 3762 + "tzm-arab" + // 01000 - 3765 + "tzm-arab-ma" + // 0045f - 3773 + "tzm-latn" + // 07c5f - 3784 + "tzm-latn-dz" + // 0085f - 3792 + "tzm-latn-ma" + // 01000 - 3803 + "tzm-tfng" + // 0785f - 3814 + "tzm-tfng-ma" + // 0105f - 3822 + "ug" + // 00080 - 3833 + "ug-cn" + // 00480 - 3835 + "uk" + // 00022 - 3840 + "uk-ua" + // 00422 - 3842 + "ur" + // 00020 - 3847 + "ur-in" + // 00820 - 3849 + "ur-pk" + // 00420 - 3854 + "uz" + // 00043 - 3859 + "uz-arab" + // 01000 - 3861 + "uz-arab-af" + // 01000 - 3868 + "uz-cyrl" + // 07843 - 3878 + "uz-cyrl-uz" + // 00843 - 3885 + "uz-latn" + // 07c43 - 3895 + "uz-latn-uz" + // 00443 - 3902 + "vai" + // 01000 - 3912 + "vai-latn" + // 01000 - 3915 + "vai-latn-lr" + // 01000 - 3923 + "vai-vaii" + // 01000 - 3934 + "vai-vaii-lr" + // 01000 - 3942 + "ve" + // 00033 - 3953 + "ve-za" + // 00433 - 3955 + "vi" + // 0002a - 3960 + "vi-vn" + // 0042a - 3962 + "vo" + // 01000 - 3967 + "vo-001" + // 01000 - 3969 + "vun" + // 01000 - 3975 + "vun-tz" + // 01000 - 3978 + "wae" + // 01000 - 3984 + "wae-ch" + // 01000 - 3987 + "wal" + // 01000 - 3993 + "wal-et" + // 01000 - 3996 + "wo" + // 00088 - 4002 + "wo-sn" + // 00488 - 4004 + "x-iv_mathan" + // 1007f - 4009 + "xh" + // 00034 - 4020 + "xh-za" + // 00434 - 4022 + "xog" + // 01000 - 4027 + "xog-ug" + // 01000 - 4030 + "yav" + // 01000 - 4036 + "yav-cm" + // 01000 - 4039 + "yi" + // 0003d - 4045 + "yi-001" + // 0043d - 4047 + "yo" + // 0006a - 4053 + "yo-bj" + // 01000 - 4055 + "yo-ng" + // 0046a - 4060 + "yue" + // 01000 - 4065 + "yue-hk" + // 01000 - 4068 + "zgh" + // 01000 - 4074 + "zgh-tfng" + // 01000 - 4077 + "zgh-tfng-ma" + // 01000 - 4085 + "zh" + // 07804 - 4096 + "zh-chs" + // 00004 - 4098 + "zh-cht" + // 07c04 - 4104 + "zh-cn" + // 00804 - 4110 + "zh-cn_phoneb" + // 50804 - 4115 + "zh-cn_stroke" + // 20804 - 4127 + "zh-hans" + // 00004 - 4139 + "zh-hans-hk" + // 01000 - 4146 + "zh-hans-mo" + // 01000 - 4156 + "zh-hant" + // 07c04 - 4166 + "zh-hk" + // 00c04 - 4173 + "zh-hk_radstr" + // 40c04 - 4178 + "zh-mo" + // 01404 - 4190 + "zh-mo_radstr" + // 41404 - 4195 + "zh-mo_stroke" + // 21404 - 4207 + "zh-sg" + // 01004 - 4219 + "zh-sg_phoneb" + // 51004 - 4224 + "zh-sg_stroke" + // 21004 - 4236 + "zh-tw" + // 00404 - 4248 + "zh-tw_pronun" + // 30404 - 4253 + "zh-tw_radstr" + // 40404 - 4265 + "zu" + // 00035 - 4277 + "zu-za"; // 00435 - 4279 + + // c_threeLetterWindowsLanguageName is string containing 3-letter Windows language names + // every 3-characters entry is matching locale name entry in c_localeNames + + private const string c_threeLetterWindowsLanguageName = + "ZZZ" + // aa + "ZZZ" + // aa-dj + "ZZZ" + // aa-er + "ZZZ" + // aa-et + "AFK" + // af + "ZZZ" + // af-na + "AFK" + // af-za + "ZZZ" + // agq + "ZZZ" + // agq-cm + "ZZZ" + // ak + "ZZZ" + // ak-gh + "AMH" + // am + "AMH" + // am-et + "ARA" + // ar + "ZZZ" + // ar-001 + "ARU" + // ar-ae + "ARH" + // ar-bh + "ZZZ" + // ar-dj + "ARG" + // ar-dz + "ARE" + // ar-eg + "ZZZ" + // ar-er + "ZZZ" + // ar-il + "ARI" + // ar-iq + "ARJ" + // ar-jo + "ZZZ" + // ar-km + "ARK" + // ar-kw + "ARB" + // ar-lb + "ARL" + // ar-ly + "ARM" + // ar-ma + "ZZZ" + // ar-mr + "ARO" + // ar-om + "ZZZ" + // ar-ps + "ARQ" + // ar-qa + "ARA" + // ar-sa + "ZZZ" + // ar-sd + "ZZZ" + // ar-so + "ZZZ" + // ar-ss + "ARS" + // ar-sy + "ZZZ" + // ar-td + "ART" + // ar-tn + "ARY" + // ar-ye + "MPD" + // arn + "MPD" + // arn-cl + "ASM" + // as + "ASM" + // as-in + "ZZZ" + // asa + "ZZZ" + // asa-tz + "ZZZ" + // ast + "ZZZ" + // ast-es + "AZE" + // az + "AZC" + // az-cyrl + "AZC" + // az-cyrl-az + "AZE" + // az-latn + "AZE" + // az-latn-az + "BAS" + // ba + "BAS" + // ba-ru + "ZZZ" + // bas + "ZZZ" + // bas-cm + "BEL" + // be + "BEL" + // be-by + "ZZZ" + // bem + "ZZZ" + // bem-zm + "ZZZ" + // bez + "ZZZ" + // bez-tz + "BGR" + // bg + "BGR" + // bg-bg + "ZZZ" + // bin + "ZZZ" + // bin-ng + "ZZZ" + // bm + "ZZZ" + // bm-latn + "ZZZ" + // bm-latn-ml + "BNB" + // bn + "BNB" + // bn-bd + "BNG" + // bn-in + "BOB" + // bo + "BOB" + // bo-cn + "ZZZ" + // bo-in + "BRE" + // br + "BRE" + // br-fr + "ZZZ" + // brx + "ZZZ" + // brx-in + "BSB" + // bs + "BSC" + // bs-cyrl + "BSC" + // bs-cyrl-ba + "BSB" + // bs-latn + "BSB" + // bs-latn-ba + "ZZZ" + // byn + "ZZZ" + // byn-er + "CAT" + // ca + "ZZZ" + // ca-ad + "CAT" + // ca-es + "VAL" + // ca-es-valencia + "ZZZ" + // ca-fr + "ZZZ" + // ca-it + "ZZZ" + // ce + "ZZZ" + // ce-ru + "ZZZ" + // cgg + "ZZZ" + // cgg-ug + "CRE" + // chr + "CRE" + // chr-cher + "CRE" + // chr-cher-us + "COS" + // co + "COS" + // co-fr + "CSY" + // cs + "CSY" + // cs-cz + "ZZZ" + // cu + "ZZZ" + // cu-ru + "CYM" + // cy + "CYM" + // cy-gb + "DAN" + // da + "DAN" + // da-dk + "ZZZ" + // da-gl + "ZZZ" + // dav + "ZZZ" + // dav-ke + "DEU" + // de + "DEA" + // de-at + "ZZZ" + // de-be + "DES" + // de-ch + "DEU" + // de-de + "DEU" + // de-de_phoneb + "ZZZ" + // de-it + "DEC" + // de-li + "DEL" + // de-lu + "ZZZ" + // dje + "ZZZ" + // dje-ne + "DSB" + // dsb + "DSB" + // dsb-de + "ZZZ" + // dua + "ZZZ" + // dua-cm + "DIV" + // dv + "DIV" + // dv-mv + "ZZZ" + // dyo + "ZZZ" + // dyo-sn + "ZZZ" + // dz + "ZZZ" + // dz-bt + "ZZZ" + // ebu + "ZZZ" + // ebu-ke + "ZZZ" + // ee + "ZZZ" + // ee-gh + "ZZZ" + // ee-tg + "ELL" + // el + "ZZZ" + // el-cy + "ELL" + // el-gr + "ENU" + // en + "ZZZ" + // en-001 + "ENB" + // en-029 + "ZZZ" + // en-150 + "ZZZ" + // en-ag + "ZZZ" + // en-ai + "ZZZ" + // en-as + "ZZZ" + // en-at + "ENA" + // en-au + "ZZZ" + // en-bb + "ZZZ" + // en-be + "ZZZ" + // en-bi + "ZZZ" + // en-bm + "ZZZ" + // en-bs + "ZZZ" + // en-bw + "ENL" + // en-bz + "ENC" + // en-ca + "ZZZ" + // en-cc + "ZZZ" + // en-ch + "ZZZ" + // en-ck + "ZZZ" + // en-cm + "ZZZ" + // en-cx + "ZZZ" + // en-cy + "ZZZ" + // en-de + "ZZZ" + // en-dk + "ZZZ" + // en-dm + "ZZZ" + // en-er + "ZZZ" + // en-fi + "ZZZ" + // en-fj + "ZZZ" + // en-fk + "ZZZ" + // en-fm + "ENG" + // en-gb + "ZZZ" + // en-gd + "ZZZ" + // en-gg + "ZZZ" + // en-gh + "ZZZ" + // en-gi + "ZZZ" + // en-gm + "ZZZ" + // en-gu + "ZZZ" + // en-gy + "ENH" + // en-hk + "ZZZ" + // en-id + "ENI" + // en-ie + "ZZZ" + // en-il + "ZZZ" + // en-im + "ENN" + // en-in + "ZZZ" + // en-io + "ZZZ" + // en-je + "ENJ" + // en-jm + "ZZZ" + // en-ke + "ZZZ" + // en-ki + "ZZZ" + // en-kn + "ZZZ" + // en-ky + "ZZZ" + // en-lc + "ZZZ" + // en-lr + "ZZZ" + // en-ls + "ZZZ" + // en-mg + "ZZZ" + // en-mh + "ZZZ" + // en-mo + "ZZZ" + // en-mp + "ZZZ" + // en-ms + "ZZZ" + // en-mt + "ZZZ" + // en-mu + "ZZZ" + // en-mw + "ENM" + // en-my + "ZZZ" + // en-na + "ZZZ" + // en-nf + "ZZZ" + // en-ng + "ZZZ" + // en-nl + "ZZZ" + // en-nr + "ZZZ" + // en-nu + "ENZ" + // en-nz + "ZZZ" + // en-pg + "ENP" + // en-ph + "ZZZ" + // en-pk + "ZZZ" + // en-pn + "ZZZ" + // en-pr + "ZZZ" + // en-pw + "ZZZ" + // en-rw + "ZZZ" + // en-sb + "ZZZ" + // en-sc + "ZZZ" + // en-sd + "ZZZ" + // en-se + "ENE" + // en-sg + "ZZZ" + // en-sh + "ZZZ" + // en-si + "ZZZ" + // en-sl + "ZZZ" + // en-ss + "ZZZ" + // en-sx + "ZZZ" + // en-sz + "ZZZ" + // en-tc + "ZZZ" + // en-tk + "ZZZ" + // en-to + "ENT" + // en-tt + "ZZZ" + // en-tv + "ZZZ" + // en-tz + "ZZZ" + // en-ug + "ZZZ" + // en-um + "ENU" + // en-us + "ZZZ" + // en-vc + "ZZZ" + // en-vg + "ZZZ" + // en-vi + "ZZZ" + // en-vu + "ZZZ" + // en-ws + "ENS" + // en-za + "ZZZ" + // en-zm + "ENW" + // en-zw + "ZZZ" + // eo + "ZZZ" + // eo-001 + "ESN" + // es + "ESJ" + // es-419 + "ESS" + // es-ar + "ESB" + // es-bo + "ZZZ" + // es-br + "ESL" + // es-cl + "ESO" + // es-co + "ESC" + // es-cr + "ESK" + // es-cu + "ESD" + // es-do + "ESF" + // es-ec + "ESN" + // es-es + "ESP" + // es-es_tradnl + "ZZZ" + // es-gq + "ESG" + // es-gt + "ESH" + // es-hn + "ESM" + // es-mx + "ESI" + // es-ni + "ESA" + // es-pa + "ESR" + // es-pe + "ZZZ" + // es-ph + "ESU" + // es-pr + "ESZ" + // es-py + "ESE" + // es-sv + "EST" + // es-us + "ESY" + // es-uy + "ESV" + // es-ve + "ETI" + // et + "ETI" + // et-ee + "EUQ" + // eu + "EUQ" + // eu-es + "ZZZ" + // ewo + "ZZZ" + // ewo-cm + "FAR" + // fa + "FAR" + // fa-ir + "FUL" + // ff + "ZZZ" + // ff-cm + "ZZZ" + // ff-gn + "FUL" + // ff-latn + "FUL" + // ff-latn-sn + "ZZZ" + // ff-mr + "ZZZ" + // ff-ng + "FIN" + // fi + "FIN" + // fi-fi + "FPO" + // fil + "FPO" + // fil-ph + "FOS" + // fo + "ZZZ" + // fo-dk + "FOS" + // fo-fo + "FRA" + // fr + "ZZZ" + // fr-029 + "FRB" + // fr-be + "ZZZ" + // fr-bf + "ZZZ" + // fr-bi + "ZZZ" + // fr-bj + "ZZZ" + // fr-bl + "FRC" + // fr-ca + "FRD" + // fr-cd + "ZZZ" + // fr-cf + "ZZZ" + // fr-cg + "FRS" + // fr-ch + "FRI" + // fr-ci + "FRE" + // fr-cm + "ZZZ" + // fr-dj + "ZZZ" + // fr-dz + "FRA" + // fr-fr + "ZZZ" + // fr-ga + "ZZZ" + // fr-gf + "ZZZ" + // fr-gn + "ZZZ" + // fr-gp + "ZZZ" + // fr-gq + "FRH" + // fr-ht + "ZZZ" + // fr-km + "FRL" + // fr-lu + "FRO" + // fr-ma + "FRM" + // fr-mc + "ZZZ" + // fr-mf + "ZZZ" + // fr-mg + "FRF" + // fr-ml + "ZZZ" + // fr-mq + "ZZZ" + // fr-mr + "ZZZ" + // fr-mu + "ZZZ" + // fr-nc + "ZZZ" + // fr-ne + "ZZZ" + // fr-pf + "ZZZ" + // fr-pm + "FRR" + // fr-re + "ZZZ" + // fr-rw + "ZZZ" + // fr-sc + "FRN" + // fr-sn + "ZZZ" + // fr-sy + "ZZZ" + // fr-td + "ZZZ" + // fr-tg + "ZZZ" + // fr-tn + "ZZZ" + // fr-vu + "ZZZ" + // fr-wf + "ZZZ" + // fr-yt + "ZZZ" + // fur + "ZZZ" + // fur-it + "FYN" + // fy + "FYN" + // fy-nl + "IRE" + // ga + "IRE" + // ga-ie + "GLA" + // gd + "GLA" + // gd-gb + "GLC" + // gl + "GLC" + // gl-es + "GRN" + // gn + "GRN" + // gn-py + "ZZZ" + // gsw + "ZZZ" + // gsw-ch + "GSW" + // gsw-fr + "ZZZ" + // gsw-li + "GUJ" + // gu + "GUJ" + // gu-in + "ZZZ" + // guz + "ZZZ" + // guz-ke + "ZZZ" + // gv + "ZZZ" + // gv-im + "HAU" + // ha + "HAU" + // ha-latn + "ZZZ" + // ha-latn-gh + "ZZZ" + // ha-latn-ne + "HAU" + // ha-latn-ng + "HAW" + // haw + "HAW" + // haw-us + "HEB" + // he + "HEB" + // he-il + "HIN" + // hi + "HIN" + // hi-in + "HRV" + // hr + "HRB" + // hr-ba + "HRV" + // hr-hr + "HSB" + // hsb + "HSB" + // hsb-de + "HUN" + // hu + "HUN" + // hu-hu + "HUN" + // hu-hu_technl + "HYE" + // hy + "HYE" + // hy-am + "ZZZ" + // ia + "ZZZ" + // ia-001 + "ZZZ" + // ia-fr + "ZZZ" + // ibb + "ZZZ" + // ibb-ng + "IND" + // id + "IND" + // id-id + "IBO" + // ig + "IBO" + // ig-ng + "III" + // ii + "III" + // ii-cn + "ISL" + // is + "ISL" + // is-is + "ITA" + // it + "ITS" + // it-ch + "ITA" + // it-it + "ZZZ" + // it-sm + "IUK" + // iu + "IUS" + // iu-cans + "IUS" + // iu-cans-ca + "IUK" + // iu-latn + "IUK" + // iu-latn-ca + "JPN" + // ja + "JPN" + // ja-jp + "JPN" + // ja-jp_radstr + "ZZZ" + // jgo + "ZZZ" + // jgo-cm + "ZZZ" + // jmc + "ZZZ" + // jmc-tz + "JAV" + // jv + "ZZZ" + // jv-java + "ZZZ" + // jv-java-id + "JAV" + // jv-latn + "JAV" + // jv-latn-id + "KAT" + // ka + "KAT" + // ka-ge + "KAT" + // ka-ge_modern + "ZZZ" + // kab + "ZZZ" + // kab-dz + "ZZZ" + // kam + "ZZZ" + // kam-ke + "ZZZ" + // kde + "ZZZ" + // kde-tz + "ZZZ" + // kea + "ZZZ" + // kea-cv + "ZZZ" + // khq + "ZZZ" + // khq-ml + "ZZZ" + // ki + "ZZZ" + // ki-ke + "KKZ" + // kk + "KKZ" + // kk-kz + "ZZZ" + // kkj + "ZZZ" + // kkj-cm + "KAL" + // kl + "KAL" + // kl-gl + "ZZZ" + // kln + "ZZZ" + // kln-ke + "KHM" + // km + "KHM" + // km-kh + "KDI" + // kn + "KDI" + // kn-in + "KOR" + // ko + "ZZZ" + // ko-kp + "KOR" + // ko-kr + "KNK" + // kok + "KNK" + // kok-in + "ZZZ" + // kr + "ZZZ" + // kr-ng + "ZZZ" + // ks + "ZZZ" + // ks-arab + "ZZZ" + // ks-arab-in + "ZZZ" + // ks-deva + "ZZZ" + // ks-deva-in + "ZZZ" + // ksb + "ZZZ" + // ksb-tz + "ZZZ" + // ksf + "ZZZ" + // ksf-cm + "ZZZ" + // ksh + "ZZZ" + // ksh-de + "KUR" + // ku + "KUR" + // ku-arab + "KUR" + // ku-arab-iq + "ZZZ" + // ku-arab-ir + "ZZZ" + // kw + "ZZZ" + // kw-gb + "KYR" + // ky + "KYR" + // ky-kg + "ZZZ" + // la + "ZZZ" + // la-001 + "ZZZ" + // lag + "ZZZ" + // lag-tz + "LBX" + // lb + "LBX" + // lb-lu + "ZZZ" + // lg + "ZZZ" + // lg-ug + "ZZZ" + // lkt + "ZZZ" + // lkt-us + "ZZZ" + // ln + "ZZZ" + // ln-ao + "ZZZ" + // ln-cd + "ZZZ" + // ln-cf + "ZZZ" + // ln-cg + "LAO" + // lo + "LAO" + // lo-la + "ZZZ" + // lrc + "ZZZ" + // lrc-iq + "ZZZ" + // lrc-ir + "LTH" + // lt + "LTH" + // lt-lt + "ZZZ" + // lu + "ZZZ" + // lu-cd + "ZZZ" + // luo + "ZZZ" + // luo-ke + "ZZZ" + // luy + "ZZZ" + // luy-ke + "LVI" + // lv + "LVI" + // lv-lv + "ZZZ" + // mas + "ZZZ" + // mas-ke + "ZZZ" + // mas-tz + "ZZZ" + // mer + "ZZZ" + // mer-ke + "ZZZ" + // mfe + "ZZZ" + // mfe-mu + "MLG" + // mg + "MLG" + // mg-mg + "ZZZ" + // mgh + "ZZZ" + // mgh-mz + "ZZZ" + // mgo + "ZZZ" + // mgo-cm + "MRI" + // mi + "MRI" + // mi-nz + "MKI" + // mk + "MKI" + // mk-mk + "MYM" + // ml + "MYM" + // ml-in + "MNN" + // mn + "MNN" + // mn-cyrl + "MNN" + // mn-mn + "MNG" + // mn-mong + "MNG" + // mn-mong-cn + "MNM" + // mn-mong-mn + "ZZZ" + // mni + "ZZZ" + // mni-in + "MWK" + // moh + "MWK" + // moh-ca + "MAR" + // mr + "MAR" + // mr-in + "MSL" + // ms + "MSB" + // ms-bn + "MSL" + // ms-my + "ZZZ" + // ms-sg + "MLT" + // mt + "MLT" + // mt-mt + "ZZZ" + // mua + "ZZZ" + // mua-cm + "MYA" + // my + "MYA" + // my-mm + "ZZZ" + // mzn + "ZZZ" + // mzn-ir + "ZZZ" + // naq + "ZZZ" + // naq-na + "NOR" + // nb + "NOR" + // nb-no + "ZZZ" + // nb-sj + "ZZZ" + // nd + "ZZZ" + // nd-zw + "ZZZ" + // nds + "ZZZ" + // nds-de + "ZZZ" + // nds-nl + "NEP" + // ne + "NEI" + // ne-in + "NEP" + // ne-np + "NLD" + // nl + "ZZZ" + // nl-aw + "NLB" + // nl-be + "ZZZ" + // nl-bq + "ZZZ" + // nl-cw + "NLD" + // nl-nl + "ZZZ" + // nl-sr + "ZZZ" + // nl-sx + "ZZZ" + // nmg + "ZZZ" + // nmg-cm + "NON" + // nn + "NON" + // nn-no + "ZZZ" + // nnh + "ZZZ" + // nnh-cm + "NOR" + // no + "NQO" + // nqo + "NQO" + // nqo-gn + "ZZZ" + // nr + "ZZZ" + // nr-za + "NSO" + // nso + "NSO" + // nso-za + "ZZZ" + // nus + "ZZZ" + // nus-ss + "ZZZ" + // nyn + "ZZZ" + // nyn-ug + "OCI" + // oc + "OCI" + // oc-fr + "ORM" + // om + "ORM" + // om-et + "ZZZ" + // om-ke + "ORI" + // or + "ORI" + // or-in + "ZZZ" + // os + "ZZZ" + // os-ge + "ZZZ" + // os-ru + "PAN" + // pa + "PAP" + // pa-arab + "PAP" + // pa-arab-pk + "PAN" + // pa-in + "ZZZ" + // pap + "ZZZ" + // pap-029 + "PLK" + // pl + "PLK" + // pl-pl + "ZZZ" + // prg + "ZZZ" + // prg-001 + "PRS" + // prs + "PRS" + // prs-af + "PAS" + // ps + "PAS" + // ps-af + "PTB" + // pt + "PTA" + // pt-ao + "PTB" + // pt-br + "ZZZ" + // pt-ch + "ZZZ" + // pt-cv + "ZZZ" + // pt-gq + "ZZZ" + // pt-gw + "ZZZ" + // pt-lu + "ZZZ" + // pt-mo + "ZZZ" + // pt-mz + "PTG" + // pt-pt + "ZZZ" + // pt-st + "ZZZ" + // pt-tl + "ENJ" + // qps-latn-x-sh + "ENU" + // qps-ploc + "JPN" + // qps-ploca + "ARA" + // qps-plocm + "QUT" + // quc + "QUT" + // quc-latn + "QUT" + // quc-latn-gt + "QUB" + // quz + "QUB" + // quz-bo + "QUE" + // quz-ec + "QUP" + // quz-pe + "RMC" + // rm + "RMC" + // rm-ch + "ZZZ" + // rn + "ZZZ" + // rn-bi + "ROM" + // ro + "ROD" + // ro-md + "ROM" + // ro-ro + "ZZZ" + // rof + "ZZZ" + // rof-tz + "RUS" + // ru + "ZZZ" + // ru-by + "ZZZ" + // ru-kg + "ZZZ" + // ru-kz + "RUM" + // ru-md + "RUS" + // ru-ru + "ZZZ" + // ru-ua + "KIN" + // rw + "KIN" + // rw-rw + "ZZZ" + // rwk + "ZZZ" + // rwk-tz + "SAN" + // sa + "SAN" + // sa-in + "SAH" + // sah + "SAH" + // sah-ru + "ZZZ" + // saq + "ZZZ" + // saq-ke + "ZZZ" + // sbp + "ZZZ" + // sbp-tz + "SIP" + // sd + "SIP" + // sd-arab + "SIP" + // sd-arab-pk + "ZZZ" + // sd-deva + "ZZZ" + // sd-deva-in + "SME" + // se + "SMG" + // se-fi + "SME" + // se-no + "SMF" + // se-se + "ZZZ" + // seh + "ZZZ" + // seh-mz + "ZZZ" + // ses + "ZZZ" + // ses-ml + "ZZZ" + // sg + "ZZZ" + // sg-cf + "ZZZ" + // shi + "ZZZ" + // shi-latn + "ZZZ" + // shi-latn-ma + "ZZZ" + // shi-tfng + "ZZZ" + // shi-tfng-ma + "SIN" + // si + "SIN" + // si-lk + "SKY" + // sk + "SKY" + // sk-sk + "SLV" + // sl + "SLV" + // sl-si + "SMB" + // sma + "SMA" + // sma-no + "SMB" + // sma-se + "SMK" + // smj + "SMJ" + // smj-no + "SMK" + // smj-se + "SMN" + // smn + "SMN" + // smn-fi + "SMS" + // sms + "SMS" + // sms-fi + "SNA" + // sn + "SNA" + // sn-latn + "SNA" + // sn-latn-zw + "SOM" + // so + "ZZZ" + // so-dj + "ZZZ" + // so-et + "ZZZ" + // so-ke + "SOM" + // so-so + "SQI" + // sq + "SQI" + // sq-al + "ZZZ" + // sq-mk + "ZZZ" + // sq-xk + "SRM" + // sr + "SRO" + // sr-cyrl + "SRN" + // sr-cyrl-ba + "SRB" + // sr-cyrl-cs + "SRQ" + // sr-cyrl-me + "SRO" + // sr-cyrl-rs + "ZZZ" + // sr-cyrl-xk + "SRM" + // sr-latn + "SRS" + // sr-latn-ba + "SRL" + // sr-latn-cs + "SRP" + // sr-latn-me + "SRM" + // sr-latn-rs + "ZZZ" + // sr-latn-xk + "ZZZ" + // ss + "ZZZ" + // ss-sz + "ZZZ" + // ss-za + "ZZZ" + // ssy + "ZZZ" + // ssy-er + "SOT" + // st + "ZZZ" + // st-ls + "SOT" + // st-za + "SVE" + // sv + "ZZZ" + // sv-ax + "SVF" + // sv-fi + "SVE" + // sv-se + "SWK" + // sw + "ZZZ" + // sw-cd + "SWK" + // sw-ke + "ZZZ" + // sw-tz + "ZZZ" + // sw-ug + "ZZZ" + // swc + "ZZZ" + // swc-cd + "SYR" + // syr + "SYR" + // syr-sy + "TAI" + // ta + "TAI" + // ta-in + "TAM" + // ta-lk + "ZZZ" + // ta-my + "ZZZ" + // ta-sg + "TEL" + // te + "TEL" + // te-in + "ZZZ" + // teo + "ZZZ" + // teo-ke + "ZZZ" + // teo-ug + "TAJ" + // tg + "TAJ" + // tg-cyrl + "TAJ" + // tg-cyrl-tj + "THA" + // th + "THA" + // th-th + "TIR" + // ti + "TIR" + // ti-er + "TIE" + // ti-et + "ZZZ" + // tig + "ZZZ" + // tig-er + "TUK" + // tk + "TUK" + // tk-tm + "TSN" + // tn + "TSB" + // tn-bw + "TSN" + // tn-za + "ZZZ" + // to + "ZZZ" + // to-to + "TRK" + // tr + "ZZZ" + // tr-cy + "TRK" + // tr-tr + "TSO" + // ts + "TSO" + // ts-za + "TTT" + // tt + "TTT" + // tt-ru + "ZZZ" + // twq + "ZZZ" + // twq-ne + "TZA" + // tzm + "ZZZ" + // tzm-arab + "ZZZ" + // tzm-arab-ma + "TZA" + // tzm-latn + "TZA" + // tzm-latn-dz + "ZZZ" + // tzm-latn-ma + "TZM" + // tzm-tfng + "TZM" + // tzm-tfng-ma + "UIG" + // ug + "UIG" + // ug-cn + "UKR" + // uk + "UKR" + // uk-ua + "URD" + // ur + "URI" + // ur-in + "URD" + // ur-pk + "UZB" + // uz + "ZZZ" + // uz-arab + "ZZZ" + // uz-arab-af + "UZC" + // uz-cyrl + "UZC" + // uz-cyrl-uz + "UZB" + // uz-latn + "UZB" + // uz-latn-uz + "ZZZ" + // vai + "ZZZ" + // vai-latn + "ZZZ" + // vai-latn-lr + "ZZZ" + // vai-vaii + "ZZZ" + // vai-vaii-lr + "ZZZ" + // ve + "ZZZ" + // ve-za + "VIT" + // vi + "VIT" + // vi-vn + "ZZZ" + // vo + "ZZZ" + // vo-001 + "ZZZ" + // vun + "ZZZ" + // vun-tz + "ZZZ" + // wae + "ZZZ" + // wae-ch + "ZZZ" + // wal + "ZZZ" + // wal-et + "WOL" + // wo + "WOL" + // wo-sn + "IVL" + // x-iv_mathan + "XHO" + // xh + "XHO" + // xh-za + "ZZZ" + // xog + "ZZZ" + // xog-ug + "ZZZ" + // yav + "ZZZ" + // yav-cm + "ZZZ" + // yi + "ZZZ" + // yi-001 + "YOR" + // yo + "ZZZ" + // yo-bj + "YOR" + // yo-ng + "ZZZ" + // yue + "ZZZ" + // yue-hk + "ZHG" + // zgh + "ZHG" + // zgh-tfng + "ZHG" + // zgh-tfng-ma + "CHS" + // zh + "CHS" + // zh-chs + "CHT" + // zh-cht + "CHS" + // zh-cn + "CHS" + // zh-cn_phoneb + "CHS" + // zh-cn_stroke + "CHS" + // zh-hans + "ZZZ" + // zh-hans-hk + "ZZZ" + // zh-hans-mo + "ZHH" + // zh-hant + "ZHH" + // zh-hk + "ZHH" + // zh-hk_radstr + "ZHM" + // zh-mo + "ZHM" + // zh-mo_radstr + "ZHM" + // zh-mo_stroke + "ZHI" + // zh-sg + "ZHI" + // zh-sg_phoneb + "ZHI" + // zh-sg_stroke + "CHT" + // zh-tw + "CHT" + // zh-tw_pronun + "CHT" + // zh-tw_radstr + "ZUL" + // zu + "ZUL"; // zu-za + + // s_localeNamesIndices contains the start index of every culture name in the string + // s_localeNames. We infer the length of each string by looking at the start index + // of the next string. + private static readonly int[] s_localeNamesIndices = new int[] + { + // c_localeNames index, // index to this array - culture name + 0 , // 0 - aa + 2 , // 1 - aa-dj + 7 , // 2 - aa-er + 12 , // 3 - aa-et + 17 , // 4 - af + 19 , // 5 - af-na + 24 , // 6 - af-za + 29 , // 7 - agq + 32 , // 8 - agq-cm + 38 , // 9 - ak + 40 , // 10 - ak-gh + 45 , // 11 - am + 47 , // 12 - am-et + 52 , // 13 - ar + 54 , // 14 - ar-001 + 60 , // 15 - ar-ae + 65 , // 16 - ar-bh + 70 , // 17 - ar-dj + 75 , // 18 - ar-dz + 80 , // 19 - ar-eg + 85 , // 20 - ar-er + 90 , // 21 - ar-il + 95 , // 22 - ar-iq + 100 , // 23 - ar-jo + 105 , // 24 - ar-km + 110 , // 25 - ar-kw + 115 , // 26 - ar-lb + 120 , // 27 - ar-ly + 125 , // 28 - ar-ma + 130 , // 29 - ar-mr + 135 , // 30 - ar-om + 140 , // 31 - ar-ps + 145 , // 32 - ar-qa + 150 , // 33 - ar-sa + 155 , // 34 - ar-sd + 160 , // 35 - ar-so + 165 , // 36 - ar-ss + 170 , // 37 - ar-sy + 175 , // 38 - ar-td + 180 , // 39 - ar-tn + 185 , // 40 - ar-ye + 190 , // 41 - arn + 193 , // 42 - arn-cl + 199 , // 43 - as + 201 , // 44 - as-in + 206 , // 45 - asa + 209 , // 46 - asa-tz + 215 , // 47 - ast + 218 , // 48 - ast-es + 224 , // 49 - az + 226 , // 50 - az-cyrl + 233 , // 51 - az-cyrl-az + 243 , // 52 - az-latn + 250 , // 53 - az-latn-az + 260 , // 54 - ba + 262 , // 55 - ba-ru + 267 , // 56 - bas + 270 , // 57 - bas-cm + 276 , // 58 - be + 278 , // 59 - be-by + 283 , // 60 - bem + 286 , // 61 - bem-zm + 292 , // 62 - bez + 295 , // 63 - bez-tz + 301 , // 64 - bg + 303 , // 65 - bg-bg + 308 , // 66 - bin + 311 , // 67 - bin-ng + 317 , // 68 - bm + 319 , // 69 - bm-latn + 326 , // 70 - bm-latn-ml + 336 , // 71 - bn + 338 , // 72 - bn-bd + 343 , // 73 - bn-in + 348 , // 74 - bo + 350 , // 75 - bo-cn + 355 , // 76 - bo-in + 360 , // 77 - br + 362 , // 78 - br-fr + 367 , // 79 - brx + 370 , // 80 - brx-in + 376 , // 81 - bs + 378 , // 82 - bs-cyrl + 385 , // 83 - bs-cyrl-ba + 395 , // 84 - bs-latn + 402 , // 85 - bs-latn-ba + 412 , // 86 - byn + 415 , // 87 - byn-er + 421 , // 88 - ca + 423 , // 89 - ca-ad + 428 , // 90 - ca-es + 433 , // 91 - ca-es-valencia + 447 , // 92 - ca-fr + 452 , // 93 - ca-it + 457 , // 94 - ce + 459 , // 95 - ce-ru + 464 , // 96 - cgg + 467 , // 97 - cgg-ug + 473 , // 98 - chr + 476 , // 99 - chr-cher + 484 , // 100 - chr-cher-us + 495 , // 101 - co + 497 , // 102 - co-fr + 502 , // 103 - cs + 504 , // 104 - cs-cz + 509 , // 105 - cu + 511 , // 106 - cu-ru + 516 , // 107 - cy + 518 , // 108 - cy-gb + 523 , // 109 - da + 525 , // 110 - da-dk + 530 , // 111 - da-gl + 535 , // 112 - dav + 538 , // 113 - dav-ke + 544 , // 114 - de + 546 , // 115 - de-at + 551 , // 116 - de-be + 556 , // 117 - de-ch + 561 , // 118 - de-de + 566 , // 119 - de-de_phoneb + 578 , // 120 - de-it + 583 , // 121 - de-li + 588 , // 122 - de-lu + 593 , // 123 - dje + 596 , // 124 - dje-ne + 602 , // 125 - dsb + 605 , // 126 - dsb-de + 611 , // 127 - dua + 614 , // 128 - dua-cm + 620 , // 129 - dv + 622 , // 130 - dv-mv + 627 , // 131 - dyo + 630 , // 132 - dyo-sn + 636 , // 133 - dz + 638 , // 134 - dz-bt + 643 , // 135 - ebu + 646 , // 136 - ebu-ke + 652 , // 137 - ee + 654 , // 138 - ee-gh + 659 , // 139 - ee-tg + 664 , // 140 - el + 666 , // 141 - el-cy + 671 , // 142 - el-gr + 676 , // 143 - en + 678 , // 144 - en-001 + 684 , // 145 - en-029 + 690 , // 146 - en-150 + 696 , // 147 - en-ag + 701 , // 148 - en-ai + 706 , // 149 - en-as + 711 , // 150 - en-at + 716 , // 151 - en-au + 721 , // 152 - en-bb + 726 , // 153 - en-be + 731 , // 154 - en-bi + 736 , // 155 - en-bm + 741 , // 156 - en-bs + 746 , // 157 - en-bw + 751 , // 158 - en-bz + 756 , // 159 - en-ca + 761 , // 160 - en-cc + 766 , // 161 - en-ch + 771 , // 162 - en-ck + 776 , // 163 - en-cm + 781 , // 164 - en-cx + 786 , // 165 - en-cy + 791 , // 166 - en-de + 796 , // 167 - en-dk + 801 , // 168 - en-dm + 806 , // 169 - en-er + 811 , // 170 - en-fi + 816 , // 171 - en-fj + 821 , // 172 - en-fk + 826 , // 173 - en-fm + 831 , // 174 - en-gb + 836 , // 175 - en-gd + 841 , // 176 - en-gg + 846 , // 177 - en-gh + 851 , // 178 - en-gi + 856 , // 179 - en-gm + 861 , // 180 - en-gu + 866 , // 181 - en-gy + 871 , // 182 - en-hk + 876 , // 183 - en-id + 881 , // 184 - en-ie + 886 , // 185 - en-il + 891 , // 186 - en-im + 896 , // 187 - en-in + 901 , // 188 - en-io + 906 , // 189 - en-je + 911 , // 190 - en-jm + 916 , // 191 - en-ke + 921 , // 192 - en-ki + 926 , // 193 - en-kn + 931 , // 194 - en-ky + 936 , // 195 - en-lc + 941 , // 196 - en-lr + 946 , // 197 - en-ls + 951 , // 198 - en-mg + 956 , // 199 - en-mh + 961 , // 200 - en-mo + 966 , // 201 - en-mp + 971 , // 202 - en-ms + 976 , // 203 - en-mt + 981 , // 204 - en-mu + 986 , // 205 - en-mw + 991 , // 206 - en-my + 996 , // 207 - en-na + 1001, // 208 - en-nf + 1006, // 209 - en-ng + 1011, // 210 - en-nl + 1016, // 211 - en-nr + 1021, // 212 - en-nu + 1026, // 213 - en-nz + 1031, // 214 - en-pg + 1036, // 215 - en-ph + 1041, // 216 - en-pk + 1046, // 217 - en-pn + 1051, // 218 - en-pr + 1056, // 219 - en-pw + 1061, // 220 - en-rw + 1066, // 221 - en-sb + 1071, // 222 - en-sc + 1076, // 223 - en-sd + 1081, // 224 - en-se + 1086, // 225 - en-sg + 1091, // 226 - en-sh + 1096, // 227 - en-si + 1101, // 228 - en-sl + 1106, // 229 - en-ss + 1111, // 230 - en-sx + 1116, // 231 - en-sz + 1121, // 232 - en-tc + 1126, // 233 - en-tk + 1131, // 234 - en-to + 1136, // 235 - en-tt + 1141, // 236 - en-tv + 1146, // 237 - en-tz + 1151, // 238 - en-ug + 1156, // 239 - en-um + 1161, // 240 - en-us + 1166, // 241 - en-vc + 1171, // 242 - en-vg + 1176, // 243 - en-vi + 1181, // 244 - en-vu + 1186, // 245 - en-ws + 1191, // 246 - en-za + 1196, // 247 - en-zm + 1201, // 248 - en-zw + 1206, // 249 - eo + 1208, // 250 - eo-001 + 1214, // 251 - es + 1216, // 252 - es-419 + 1222, // 253 - es-ar + 1227, // 254 - es-bo + 1232, // 255 - es-br + 1237, // 256 - es-cl + 1242, // 257 - es-co + 1247, // 258 - es-cr + 1252, // 259 - es-cu + 1257, // 260 - es-do + 1262, // 261 - es-ec + 1267, // 262 - es-es + 1272, // 263 - es-es_tradnl + 1284, // 264 - es-gq + 1289, // 265 - es-gt + 1294, // 266 - es-hn + 1299, // 267 - es-mx + 1304, // 268 - es-ni + 1309, // 269 - es-pa + 1314, // 270 - es-pe + 1319, // 271 - es-ph + 1324, // 272 - es-pr + 1329, // 273 - es-py + 1334, // 274 - es-sv + 1339, // 275 - es-us + 1344, // 276 - es-uy + 1349, // 277 - es-ve + 1354, // 278 - et + 1356, // 279 - et-ee + 1361, // 280 - eu + 1363, // 281 - eu-es + 1368, // 282 - ewo + 1371, // 283 - ewo-cm + 1377, // 284 - fa + 1379, // 285 - fa-ir + 1384, // 286 - ff + 1386, // 287 - ff-cm + 1391, // 288 - ff-gn + 1396, // 289 - ff-latn + 1403, // 290 - ff-latn-sn + 1413, // 291 - ff-mr + 1418, // 292 - ff-ng + 1423, // 293 - fi + 1425, // 294 - fi-fi + 1430, // 295 - fil + 1433, // 296 - fil-ph + 1439, // 297 - fo + 1441, // 298 - fo-dk + 1446, // 299 - fo-fo + 1451, // 300 - fr + 1453, // 301 - fr-029 + 1459, // 302 - fr-be + 1464, // 303 - fr-bf + 1469, // 304 - fr-bi + 1474, // 305 - fr-bj + 1479, // 306 - fr-bl + 1484, // 307 - fr-ca + 1489, // 308 - fr-cd + 1494, // 309 - fr-cf + 1499, // 310 - fr-cg + 1504, // 311 - fr-ch + 1509, // 312 - fr-ci + 1514, // 313 - fr-cm + 1519, // 314 - fr-dj + 1524, // 315 - fr-dz + 1529, // 316 - fr-fr + 1534, // 317 - fr-ga + 1539, // 318 - fr-gf + 1544, // 319 - fr-gn + 1549, // 320 - fr-gp + 1554, // 321 - fr-gq + 1559, // 322 - fr-ht + 1564, // 323 - fr-km + 1569, // 324 - fr-lu + 1574, // 325 - fr-ma + 1579, // 326 - fr-mc + 1584, // 327 - fr-mf + 1589, // 328 - fr-mg + 1594, // 329 - fr-ml + 1599, // 330 - fr-mq + 1604, // 331 - fr-mr + 1609, // 332 - fr-mu + 1614, // 333 - fr-nc + 1619, // 334 - fr-ne + 1624, // 335 - fr-pf + 1629, // 336 - fr-pm + 1634, // 337 - fr-re + 1639, // 338 - fr-rw + 1644, // 339 - fr-sc + 1649, // 340 - fr-sn + 1654, // 341 - fr-sy + 1659, // 342 - fr-td + 1664, // 343 - fr-tg + 1669, // 344 - fr-tn + 1674, // 345 - fr-vu + 1679, // 346 - fr-wf + 1684, // 347 - fr-yt + 1689, // 348 - fur + 1692, // 349 - fur-it + 1698, // 350 - fy + 1700, // 351 - fy-nl + 1705, // 352 - ga + 1707, // 353 - ga-ie + 1712, // 354 - gd + 1714, // 355 - gd-gb + 1719, // 356 - gl + 1721, // 357 - gl-es + 1726, // 358 - gn + 1728, // 359 - gn-py + 1733, // 360 - gsw + 1736, // 361 - gsw-ch + 1742, // 362 - gsw-fr + 1748, // 363 - gsw-li + 1754, // 364 - gu + 1756, // 365 - gu-in + 1761, // 366 - guz + 1764, // 367 - guz-ke + 1770, // 368 - gv + 1772, // 369 - gv-im + 1777, // 370 - ha + 1779, // 371 - ha-latn + 1786, // 372 - ha-latn-gh + 1796, // 373 - ha-latn-ne + 1806, // 374 - ha-latn-ng + 1816, // 375 - haw + 1819, // 376 - haw-us + 1825, // 377 - he + 1827, // 378 - he-il + 1832, // 379 - hi + 1834, // 380 - hi-in + 1839, // 381 - hr + 1841, // 382 - hr-ba + 1846, // 383 - hr-hr + 1851, // 384 - hsb + 1854, // 385 - hsb-de + 1860, // 386 - hu + 1862, // 387 - hu-hu + 1867, // 388 - hu-hu_technl + 1879, // 389 - hy + 1881, // 390 - hy-am + 1886, // 391 - ia + 1888, // 392 - ia-001 + 1894, // 393 - ia-fr + 1899, // 394 - ibb + 1902, // 395 - ibb-ng + 1908, // 396 - id + 1910, // 397 - id-id + 1915, // 398 - ig + 1917, // 399 - ig-ng + 1922, // 400 - ii + 1924, // 401 - ii-cn + 1929, // 402 - is + 1931, // 403 - is-is + 1936, // 404 - it + 1938, // 405 - it-ch + 1943, // 406 - it-it + 1948, // 407 - it-sm + 1953, // 408 - iu + 1955, // 409 - iu-cans + 1962, // 410 - iu-cans-ca + 1972, // 411 - iu-latn + 1979, // 412 - iu-latn-ca + 1989, // 413 - ja + 1991, // 414 - ja-jp + 1996, // 415 - ja-jp_radstr + 2008, // 416 - jgo + 2011, // 417 - jgo-cm + 2017, // 418 - jmc + 2020, // 419 - jmc-tz + 2026, // 420 - jv + 2028, // 421 - jv-java + 2035, // 422 - jv-java-id + 2045, // 423 - jv-latn + 2052, // 424 - jv-latn-id + 2062, // 425 - ka + 2064, // 426 - ka-ge + 2069, // 427 - ka-ge_modern + 2081, // 428 - kab + 2084, // 429 - kab-dz + 2090, // 430 - kam + 2093, // 431 - kam-ke + 2099, // 432 - kde + 2102, // 433 - kde-tz + 2108, // 434 - kea + 2111, // 435 - kea-cv + 2117, // 436 - khq + 2120, // 437 - khq-ml + 2126, // 438 - ki + 2128, // 439 - ki-ke + 2133, // 440 - kk + 2135, // 441 - kk-kz + 2140, // 442 - kkj + 2143, // 443 - kkj-cm + 2149, // 444 - kl + 2151, // 445 - kl-gl + 2156, // 446 - kln + 2159, // 447 - kln-ke + 2165, // 448 - km + 2167, // 449 - km-kh + 2172, // 450 - kn + 2174, // 451 - kn-in + 2179, // 452 - ko + 2181, // 453 - ko-kp + 2186, // 454 - ko-kr + 2191, // 455 - kok + 2194, // 456 - kok-in + 2200, // 457 - kr + 2202, // 458 - kr-ng + 2207, // 459 - ks + 2209, // 460 - ks-arab + 2216, // 461 - ks-arab-in + 2226, // 462 - ks-deva + 2233, // 463 - ks-deva-in + 2243, // 464 - ksb + 2246, // 465 - ksb-tz + 2252, // 466 - ksf + 2255, // 467 - ksf-cm + 2261, // 468 - ksh + 2264, // 469 - ksh-de + 2270, // 470 - ku + 2272, // 471 - ku-arab + 2279, // 472 - ku-arab-iq + 2289, // 473 - ku-arab-ir + 2299, // 474 - kw + 2301, // 475 - kw-gb + 2306, // 476 - ky + 2308, // 477 - ky-kg + 2313, // 478 - la + 2315, // 479 - la-001 + 2321, // 480 - lag + 2324, // 481 - lag-tz + 2330, // 482 - lb + 2332, // 483 - lb-lu + 2337, // 484 - lg + 2339, // 485 - lg-ug + 2344, // 486 - lkt + 2347, // 487 - lkt-us + 2353, // 488 - ln + 2355, // 489 - ln-ao + 2360, // 490 - ln-cd + 2365, // 491 - ln-cf + 2370, // 492 - ln-cg + 2375, // 493 - lo + 2377, // 494 - lo-la + 2382, // 495 - lrc + 2385, // 496 - lrc-iq + 2391, // 497 - lrc-ir + 2397, // 498 - lt + 2399, // 499 - lt-lt + 2404, // 500 - lu + 2406, // 501 - lu-cd + 2411, // 502 - luo + 2414, // 503 - luo-ke + 2420, // 504 - luy + 2423, // 505 - luy-ke + 2429, // 506 - lv + 2431, // 507 - lv-lv + 2436, // 508 - mas + 2439, // 509 - mas-ke + 2445, // 510 - mas-tz + 2451, // 511 - mer + 2454, // 512 - mer-ke + 2460, // 513 - mfe + 2463, // 514 - mfe-mu + 2469, // 515 - mg + 2471, // 516 - mg-mg + 2476, // 517 - mgh + 2479, // 518 - mgh-mz + 2485, // 519 - mgo + 2488, // 520 - mgo-cm + 2494, // 521 - mi + 2496, // 522 - mi-nz + 2501, // 523 - mk + 2503, // 524 - mk-mk + 2508, // 525 - ml + 2510, // 526 - ml-in + 2515, // 527 - mn + 2517, // 528 - mn-cyrl + 2524, // 529 - mn-mn + 2529, // 530 - mn-mong + 2536, // 531 - mn-mong-cn + 2546, // 532 - mn-mong-mn + 2556, // 533 - mni + 2559, // 534 - mni-in + 2565, // 535 - moh + 2568, // 536 - moh-ca + 2574, // 537 - mr + 2576, // 538 - mr-in + 2581, // 539 - ms + 2583, // 540 - ms-bn + 2588, // 541 - ms-my + 2593, // 542 - ms-sg + 2598, // 543 - mt + 2600, // 544 - mt-mt + 2605, // 545 - mua + 2608, // 546 - mua-cm + 2614, // 547 - my + 2616, // 548 - my-mm + 2621, // 549 - mzn + 2624, // 550 - mzn-ir + 2630, // 551 - naq + 2633, // 552 - naq-na + 2639, // 553 - nb + 2641, // 554 - nb-no + 2646, // 555 - nb-sj + 2651, // 556 - nd + 2653, // 557 - nd-zw + 2658, // 558 - nds + 2661, // 559 - nds-de + 2667, // 560 - nds-nl + 2673, // 561 - ne + 2675, // 562 - ne-in + 2680, // 563 - ne-np + 2685, // 564 - nl + 2687, // 565 - nl-aw + 2692, // 566 - nl-be + 2697, // 567 - nl-bq + 2702, // 568 - nl-cw + 2707, // 569 - nl-nl + 2712, // 570 - nl-sr + 2717, // 571 - nl-sx + 2722, // 572 - nmg + 2725, // 573 - nmg-cm + 2731, // 574 - nn + 2733, // 575 - nn-no + 2738, // 576 - nnh + 2741, // 577 - nnh-cm + 2747, // 578 - no + 2749, // 579 - nqo + 2752, // 580 - nqo-gn + 2758, // 581 - nr + 2760, // 582 - nr-za + 2765, // 583 - nso + 2768, // 584 - nso-za + 2774, // 585 - nus + 2777, // 586 - nus-ss + 2783, // 587 - nyn + 2786, // 588 - nyn-ug + 2792, // 589 - oc + 2794, // 590 - oc-fr + 2799, // 591 - om + 2801, // 592 - om-et + 2806, // 593 - om-ke + 2811, // 594 - or + 2813, // 595 - or-in + 2818, // 596 - os + 2820, // 597 - os-ge + 2825, // 598 - os-ru + 2830, // 599 - pa + 2832, // 600 - pa-arab + 2839, // 601 - pa-arab-pk + 2849, // 602 - pa-in + 2854, // 603 - pap + 2857, // 604 - pap-029 + 2864, // 605 - pl + 2866, // 606 - pl-pl + 2871, // 607 - prg + 2874, // 608 - prg-001 + 2881, // 609 - prs + 2884, // 610 - prs-af + 2890, // 611 - ps + 2892, // 612 - ps-af + 2897, // 613 - pt + 2899, // 614 - pt-ao + 2904, // 615 - pt-br + 2909, // 616 - pt-ch + 2914, // 617 - pt-cv + 2919, // 618 - pt-gq + 2924, // 619 - pt-gw + 2929, // 620 - pt-lu + 2934, // 621 - pt-mo + 2939, // 622 - pt-mz + 2944, // 623 - pt-pt + 2949, // 624 - pt-st + 2954, // 625 - pt-tl + 2959, // 626 - qps-latn-x-sh + 2972, // 627 - qps-ploc + 2980, // 628 - qps-ploca + 2989, // 629 - qps-plocm + 2998, // 630 - quc + 3001, // 631 - quc-latn + 3009, // 632 - quc-latn-gt + 3020, // 633 - quz + 3023, // 634 - quz-bo + 3029, // 635 - quz-ec + 3035, // 636 - quz-pe + 3041, // 637 - rm + 3043, // 638 - rm-ch + 3048, // 639 - rn + 3050, // 640 - rn-bi + 3055, // 641 - ro + 3057, // 642 - ro-md + 3062, // 643 - ro-ro + 3067, // 644 - rof + 3070, // 645 - rof-tz + 3076, // 646 - ru + 3078, // 647 - ru-by + 3083, // 648 - ru-kg + 3088, // 649 - ru-kz + 3093, // 650 - ru-md + 3098, // 651 - ru-ru + 3103, // 652 - ru-ua + 3108, // 653 - rw + 3110, // 654 - rw-rw + 3115, // 655 - rwk + 3118, // 656 - rwk-tz + 3124, // 657 - sa + 3126, // 658 - sa-in + 3131, // 659 - sah + 3134, // 660 - sah-ru + 3140, // 661 - saq + 3143, // 662 - saq-ke + 3149, // 663 - sbp + 3152, // 664 - sbp-tz + 3158, // 665 - sd + 3160, // 666 - sd-arab + 3167, // 667 - sd-arab-pk + 3177, // 668 - sd-deva + 3184, // 669 - sd-deva-in + 3194, // 670 - se + 3196, // 671 - se-fi + 3201, // 672 - se-no + 3206, // 673 - se-se + 3211, // 674 - seh + 3214, // 675 - seh-mz + 3220, // 676 - ses + 3223, // 677 - ses-ml + 3229, // 678 - sg + 3231, // 679 - sg-cf + 3236, // 680 - shi + 3239, // 681 - shi-latn + 3247, // 682 - shi-latn-ma + 3258, // 683 - shi-tfng + 3266, // 684 - shi-tfng-ma + 3277, // 685 - si + 3279, // 686 - si-lk + 3284, // 687 - sk + 3286, // 688 - sk-sk + 3291, // 689 - sl + 3293, // 690 - sl-si + 3298, // 691 - sma + 3301, // 692 - sma-no + 3307, // 693 - sma-se + 3313, // 694 - smj + 3316, // 695 - smj-no + 3322, // 696 - smj-se + 3328, // 697 - smn + 3331, // 698 - smn-fi + 3337, // 699 - sms + 3340, // 700 - sms-fi + 3346, // 701 - sn + 3348, // 702 - sn-latn + 3355, // 703 - sn-latn-zw + 3365, // 704 - so + 3367, // 705 - so-dj + 3372, // 706 - so-et + 3377, // 707 - so-ke + 3382, // 708 - so-so + 3387, // 709 - sq + 3389, // 710 - sq-al + 3394, // 711 - sq-mk + 3399, // 712 - sq-xk + 3404, // 713 - sr + 3406, // 714 - sr-cyrl + 3413, // 715 - sr-cyrl-ba + 3423, // 716 - sr-cyrl-cs + 3433, // 717 - sr-cyrl-me + 3443, // 718 - sr-cyrl-rs + 3453, // 719 - sr-cyrl-xk + 3463, // 720 - sr-latn + 3470, // 721 - sr-latn-ba + 3480, // 722 - sr-latn-cs + 3490, // 723 - sr-latn-me + 3500, // 724 - sr-latn-rs + 3510, // 725 - sr-latn-xk + 3520, // 726 - ss + 3522, // 727 - ss-sz + 3527, // 728 - ss-za + 3532, // 729 - ssy + 3535, // 730 - ssy-er + 3541, // 731 - st + 3543, // 732 - st-ls + 3548, // 733 - st-za + 3553, // 734 - sv + 3555, // 735 - sv-ax + 3560, // 736 - sv-fi + 3565, // 737 - sv-se + 3570, // 738 - sw + 3572, // 739 - sw-cd + 3577, // 740 - sw-ke + 3582, // 741 - sw-tz + 3587, // 742 - sw-ug + 3592, // 743 - swc + 3595, // 744 - swc-cd + 3601, // 745 - syr + 3604, // 746 - syr-sy + 3610, // 747 - ta + 3612, // 748 - ta-in + 3617, // 749 - ta-lk + 3622, // 750 - ta-my + 3627, // 751 - ta-sg + 3632, // 752 - te + 3634, // 753 - te-in + 3639, // 754 - teo + 3642, // 755 - teo-ke + 3648, // 756 - teo-ug + 3654, // 757 - tg + 3656, // 758 - tg-cyrl + 3663, // 759 - tg-cyrl-tj + 3673, // 760 - th + 3675, // 761 - th-th + 3680, // 762 - ti + 3682, // 763 - ti-er + 3687, // 764 - ti-et + 3692, // 765 - tig + 3695, // 766 - tig-er + 3701, // 767 - tk + 3703, // 768 - tk-tm + 3708, // 769 - tn + 3710, // 770 - tn-bw + 3715, // 771 - tn-za + 3720, // 772 - to + 3722, // 773 - to-to + 3727, // 774 - tr + 3729, // 775 - tr-cy + 3734, // 776 - tr-tr + 3739, // 777 - ts + 3741, // 778 - ts-za + 3746, // 779 - tt + 3748, // 780 - tt-ru + 3753, // 781 - twq + 3756, // 782 - twq-ne + 3762, // 783 - tzm + 3765, // 784 - tzm-arab + 3773, // 785 - tzm-arab-ma + 3784, // 786 - tzm-latn + 3792, // 787 - tzm-latn-dz + 3803, // 788 - tzm-latn-ma + 3814, // 789 - tzm-tfng + 3822, // 790 - tzm-tfng-ma + 3833, // 791 - ug + 3835, // 792 - ug-cn + 3840, // 793 - uk + 3842, // 794 - uk-ua + 3847, // 795 - ur + 3849, // 796 - ur-in + 3854, // 797 - ur-pk + 3859, // 798 - uz + 3861, // 799 - uz-arab + 3868, // 800 - uz-arab-af + 3878, // 801 - uz-cyrl + 3885, // 802 - uz-cyrl-uz + 3895, // 803 - uz-latn + 3902, // 804 - uz-latn-uz + 3912, // 805 - vai + 3915, // 806 - vai-latn + 3923, // 807 - vai-latn-lr + 3934, // 808 - vai-vaii + 3942, // 809 - vai-vaii-lr + 3953, // 810 - ve + 3955, // 811 - ve-za + 3960, // 812 - vi + 3962, // 813 - vi-vn + 3967, // 814 - vo + 3969, // 815 - vo-001 + 3975, // 816 - vun + 3978, // 817 - vun-tz + 3984, // 818 - wae + 3987, // 819 - wae-ch + 3993, // 820 - wal + 3996, // 821 - wal-et + 4002, // 822 - wo + 4004, // 823 - wo-sn + 4009, // 824 - x-iv_mathan + 4020, // 825 - xh + 4022, // 826 - xh-za + 4027, // 827 - xog + 4030, // 828 - xog-ug + 4036, // 829 - yav + 4039, // 830 - yav-cm + 4045, // 831 - yi + 4047, // 832 - yi-001 + 4053, // 833 - yo + 4055, // 834 - yo-bj + 4060, // 835 - yo-ng + 4065, // 836 - yue + 4068, // 837 - yue-hk + 4074, // 838 - zgh + 4077, // 839 - zgh-tfng + 4085, // 840 - zgh-tfng-ma + 4096, // 841 - zh + 4098, // 842 - zh-chs + 4104, // 843 - zh-cht + 4110, // 844 - zh-cn + 4115, // 845 - zh-cn_phoneb + 4127, // 846 - zh-cn_stroke + 4139, // 847 - zh-hans + 4146, // 848 - zh-hans-hk + 4156, // 849 - zh-hans-mo + 4166, // 850 - zh-hant + 4173, // 851 - zh-hk + 4178, // 852 - zh-hk_radstr + 4190, // 853 - zh-mo + 4195, // 854 - zh-mo_radstr + 4207, // 855 - zh-mo_stroke + 4219, // 856 - zh-sg + 4224, // 857 - zh-sg_phoneb + 4236, // 858 - zh-sg_stroke + 4248, // 859 - zh-tw + 4253, // 860 - zh-tw_pronun + 4265, // 861 - zh-tw_radstr + 4277, // 862 - zu + 4279, // 863 - zu-za + 4284 + }; + + private const int NUMERIC_LOCALE_DATA_COUNT_PER_ROW = 9; + // s_nameIndexToNumericData is mapping from index in s_localeNamesIndices to locale data. + // each row in the table will have the following data: + // Lcid, Ansi codepage, Oem codepage, MAC codepage, EBCDIC codepage, Geo Id, Digit Substitution, specific locale index, Console locale index + private static readonly int[] s_nameIndexToNumericData = new int[] + { + // Lcid, Ansi CP, Oem CP, MAC CP, EBCDIC CP, Geo Id, digit substitution, Specific culture index, keyboard Id, Console locale index // index - locale name + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 3 , 240 , // 0 - aa + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3e , 1 , 1 , 240 , // 1 - aa-dj + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 2 , 240 , // 2 - aa-er + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 3 , 240 , // 3 - aa-et + 0x36 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 6 , 6 , // 4 - af + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xfe , 1 , 5 , 240 , // 5 - af-na + 0x436 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 6 , 6 , // 6 - af-za + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 8 , 240 , // 7 - agq + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 8 , 240 , // 8 - agq-cm + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x59 , 1 , 10 , 240 , // 9 - ak + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x59 , 1 , 10 , 240 , // 10 - ak-gh + 0x5e , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 12 , 143 , // 11 - am + 0x45e , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 12 , 143 , // 12 - am-et + 0x1 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xcd , 0 , 33 , 143 , // 13 - ar + 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x989e, 0 , 14 , 240 , // 14 - ar-001 + 0x3801 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xe0 , 0 , 15 , 143 , // 15 - ar-ae + 0x3c01 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x11 , 0 , 16 , 143 , // 16 - ar-bh + 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x3e , 0 , 17 , 240 , // 17 - ar-dj + 0x1401 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x4 , 1 , 18 , 300 , // 18 - ar-dz + 0xc01 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x43 , 0 , 19 , 143 , // 19 - ar-eg + 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x47 , 0 , 20 , 240 , // 20 - ar-er + 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x75 , 0 , 21 , 240 , // 21 - ar-il + 0x801 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x79 , 0 , 22 , 143 , // 22 - ar-iq + 0x2c01 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x7e , 0 , 23 , 143 , // 23 - ar-jo + 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x32 , 0 , 24 , 240 , // 24 - ar-km + 0x3401 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x88 , 0 , 25 , 143 , // 25 - ar-kw + 0x3001 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x8b , 0 , 26 , 143 , // 26 - ar-lb + 0x1001 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x94 , 1 , 27 , 143 , // 27 - ar-ly + 0x1801 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x9f , 1 , 28 , 300 , // 28 - ar-ma + 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xa2 , 0 , 29 , 240 , // 29 - ar-mr + 0x2001 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xa4 , 0 , 30 , 143 , // 30 - ar-om + 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xb8 , 0 , 31 , 240 , // 31 - ar-ps + 0x4001 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xc5 , 0 , 32 , 143 , // 32 - ar-qa + 0x401 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xcd , 0 , 33 , 143 , // 33 - ar-sa + 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xdb , 0 , 34 , 240 , // 34 - ar-sd + 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xd8 , 0 , 35 , 240 , // 35 - ar-so + 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x114 , 0 , 36 , 240 , // 36 - ar-ss + 0x2801 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xde , 0 , 37 , 143 , // 37 - ar-sy + 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x29 , 0 , 38 , 240 , // 38 - ar-td + 0x1c01 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xea , 1 , 39 , 300 , // 39 - ar-tn + 0x2401 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x105 , 0 , 40 , 143 , // 40 - ar-ye + 0x7a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x2e , 1 , 42 , 42 , // 41 - arn + 0x47a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x2e , 1 , 42 , 42 , // 42 - arn-cl + 0x4d , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 44 , 143 , // 43 - as + 0x44d , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 44 , 143 , // 44 - as-in + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 46 , 240 , // 45 - asa + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 46 , 240 , // 46 - asa-tz + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd9 , 1 , 48 , 240 , // 47 - ast + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd9 , 1 , 48 , 240 , // 48 - ast-es + 0x2c , 0x4e6 , 0x359 , 0x2761, 0x51a9, 0x5 , 1 , 53 , 53 , // 49 - az + 0x742c , 0x4e3 , 0x362 , 0x2717, 0x5190, 0x5 , 1 , 51 , 51 , // 50 - az-cyrl + 0x82c , 0x4e3 , 0x362 , 0x2717, 0x5190, 0x5 , 1 , 51 , 51 , // 51 - az-cyrl-az + 0x782c , 0x4e6 , 0x359 , 0x2761, 0x51a9, 0x5 , 1 , 53 , 53 , // 52 - az-latn + 0x42c , 0x4e6 , 0x359 , 0x2761, 0x51a9, 0x5 , 1 , 53 , 53 , // 53 - az-latn-az + 0x6d , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 55 , 55 , // 54 - ba + 0x46d , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 55 , 55 , // 55 - ba-ru + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 57 , 240 , // 56 - bas + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 57 , 240 , // 57 - bas-cm + 0x23 , 0x4e3 , 0x362 , 0x2717, 0x1f4 , 0x1d , 1 , 59 , 59 , // 58 - be + 0x423 , 0x4e3 , 0x362 , 0x2717, 0x1f4 , 0x1d , 1 , 59 , 59 , // 59 - be-by + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x107 , 1 , 61 , 240 , // 60 - bem + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x107 , 1 , 61 , 240 , // 61 - bem-zm + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 63 , 240 , // 62 - bez + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 63 , 240 , // 63 - bez-tz + 0x2 , 0x4e3 , 0x362 , 0x2717, 0x5221, 0x23 , 1 , 65 , 65 , // 64 - bg + 0x402 , 0x4e3 , 0x362 , 0x2717, 0x5221, 0x23 , 1 , 65 , 65 , // 65 - bg-bg + 0x66 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xaf , 1 , 67 , 240 , // 66 - bin + 0x466 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xaf , 1 , 67 , 240 , // 67 - bin-ng + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9d , 1 , 70 , 240 , // 68 - bm + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9d , 1 , 70 , 240 , // 69 - bm-latn + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9d , 1 , 70 , 240 , // 70 - bm-latn-ml + 0x45 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x17 , 1 , 72 , 143 , // 71 - bn + 0x845 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x17 , 1 , 72 , 143 , // 72 - bn-bd + 0x445 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 73 , 143 , // 73 - bn-in + 0x51 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2d , 1 , 75 , 143 , // 74 - bo + 0x451 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2d , 1 , 75 , 143 , // 75 - bo-cn + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 76 , 240 , // 76 - bo-in + 0x7e , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 78 , 78 , // 77 - br + 0x47e , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 78 , 78 , // 78 - br-fr + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 80 , 240 , // 79 - brx + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 80 , 240 , // 80 - brx-in + 0x781a , 0x4e2 , 0x354 , 0x2762, 0x366 , 0x19 , 1 , 85 , 85 , // 81 - bs + 0x641a , 0x4e3 , 0x357 , 0x2762, 0x366 , 0x19 , 1 , 83 , 83 , // 82 - bs-cyrl + 0x201a , 0x4e3 , 0x357 , 0x2762, 0x366 , 0x19 , 1 , 83 , 83 , // 83 - bs-cyrl-ba + 0x681a , 0x4e2 , 0x354 , 0x2762, 0x366 , 0x19 , 1 , 85 , 85 , // 84 - bs-latn + 0x141a , 0x4e2 , 0x354 , 0x2762, 0x366 , 0x19 , 1 , 85 , 85 , // 85 - bs-latn-ba + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 87 , 240 , // 86 - byn + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 87 , 240 , // 87 - byn-er + 0x3 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd9 , 1 , 90 , 90 , // 88 - ca + 0x1000 , 0x4e4 , 0x352 , 0x2 , 0x1f4 , 0x8 , 1 , 89 , 240 , // 89 - ca-ad + 0x403 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd9 , 1 , 90 , 90 , // 90 - ca-es + 0x803 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd9 , 1 , 91 , 90 , // 91 - ca-es-valencia + 0x1000 , 0x4e4 , 0x352 , 0x2 , 0x1f4 , 0x54 , 1 , 92 , 240 , // 92 - ca-fr + 0x1000 , 0x4e4 , 0x352 , 0x2 , 0x1f4 , 0x76 , 1 , 93 , 240 , // 93 - ca-it + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xcb , 1 , 95 , 240 , // 94 - ce + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xcb , 1 , 95 , 240 , // 95 - ce-ru + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 97 , 240 , // 96 - cgg + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 97 , 240 , // 97 - cgg-ug + 0x5c , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf4 , 1 , 100 , 240 , // 98 - chr + 0x7c5c , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf4 , 1 , 100 , 240 , // 99 - chr-cher + 0x45c , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf4 , 1 , 100 , 240 , // 100 - chr-cher-us + 0x83 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 102 , 102 , // 101 - co + 0x483 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 102 , 102 , // 102 - co-fr + 0x5 , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x4b , 1 , 104 , 104 , // 103 - cs + 0x405 , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x4b , 1 , 104 , 104 , // 104 - cs-cz + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xcb , 1 , 106 , 240 , // 105 - cu + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xcb , 1 , 106 , 240 , // 106 - cu-ru + 0x52 , 0x4e4 , 0x352 , 0x2710, 0x4f3d, 0xf2 , 1 , 108 , 108 , // 107 - cy + 0x452 , 0x4e4 , 0x352 , 0x2710, 0x4f3d, 0xf2 , 1 , 108 , 108 , // 108 - cy-gb + 0x6 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0x3d , 1 , 110 , 110 , // 109 - da + 0x406 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0x3d , 1 , 110 , 110 , // 110 - da-dk + 0x1000 , 0x4e4 , 0x352 , 0x2 , 0x1f4 , 0x5d , 1 , 111 , 240 , // 111 - da-gl + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 113 , 240 , // 112 - dav + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 113 , 240 , // 113 - dav-ke + 0x7 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0x5e , 1 , 118 , 118 , // 114 - de + 0xc07 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0xe , 1 , 115 , 115 , // 115 - de-at + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0x15 , 1 , 116 , 240 , // 116 - de-be + 0x807 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0xdf , 1 , 117 , 117 , // 117 - de-ch + 0x407 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0x5e , 1 , 118 , 118 , // 118 - de-de + 0x10407, 0x4e4 , 0x352 , 0x2710, 0x4f31, 0x5e , 1 , 118 , 118 , // 119 - de-de_phoneb + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x76 , 1 , 120 , 240 , // 120 - de-it + 0x1407 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0x91 , 1 , 121 , 121 , // 121 - de-li + 0x1007 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0x93 , 1 , 122 , 122 , // 122 - de-lu + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xad , 1 , 124 , 240 , // 123 - dje + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xad , 1 , 124 , 240 , // 124 - dje-ne + 0x7c2e , 0x4e4 , 0x352 , 0x2710, 0x366 , 0x5e , 1 , 126 , 126 , // 125 - dsb + 0x82e , 0x4e4 , 0x352 , 0x2710, 0x366 , 0x5e , 1 , 126 , 126 , // 126 - dsb-de + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 128 , 240 , // 127 - dua + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 128 , 240 , // 128 - dua-cm + 0x65 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa5 , 1 , 130 , 143 , // 129 - dv + 0x465 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa5 , 1 , 130 , 143 , // 130 - dv-mv + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd2 , 1 , 132 , 240 , // 131 - dyo + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd2 , 1 , 132 , 240 , // 132 - dyo-sn + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x22 , 2 , 134 , 240 , // 133 - dz + 0xc51 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x22 , 2 , 134 , 240 , // 134 - dz-bt + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 136 , 240 , // 135 - ebu + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 136 , 240 , // 136 - ebu-ke + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x59 , 1 , 138 , 240 , // 137 - ee + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x59 , 1 , 138 , 240 , // 138 - ee-gh + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xe8 , 1 , 139 , 240 , // 139 - ee-tg + 0x8 , 0x4e5 , 0x2e1 , 0x2716, 0x4f31, 0x62 , 1 , 142 , 142 , // 140 - el + 0x1000 , 0x4e5 , 0x2e1 , 0x2716, 0x4f31, 0x3b , 1 , 141 , 240 , // 141 - el-cy + 0x408 , 0x4e5 , 0x2e1 , 0x2716, 0x4f31, 0x62 , 1 , 142 , 142 , // 142 - el-gr + 0x9 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xf4 , 1 , 240 , 240 , // 143 - en + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x989e, 1 , 144 , 240 , // 144 - en-001 + 0x2409 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x993248, 1 , 145 , 145 , // 145 - en-029 + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x292d, 1 , 146 , 240 , // 146 - en-150 + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x2 , 1 , 147 , 240 , // 147 - en-ag + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x12c , 1 , 148 , 240 , // 148 - en-ai + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xa , 1 , 149 , 240 , // 149 - en-as + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xe , 1 , 150 , 240 , // 150 - en-at + 0xc09 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xc , 1 , 151 , 151 , // 151 - en-au + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x12 , 1 , 152 , 240 , // 152 - en-bb + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x15 , 1 , 153 , 240 , // 153 - en-be + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x26 , 1 , 154 , 240 , // 154 - en-bi + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x14 , 1 , 155 , 240 , // 155 - en-bm + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x16 , 1 , 156 , 240 , // 156 - en-bs + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x13 , 1 , 157 , 240 , // 157 - en-bw + 0x2809 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x18 , 1 , 158 , 158 , // 158 - en-bz + 0x1009 , 0x4e4 , 0x352 , 0x2710, 0x25 , 0x27 , 1 , 159 , 159 , // 159 - en-ca + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x137 , 1 , 160 , 240 , // 160 - en-cc + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xdf , 1 , 161 , 240 , // 161 - en-ch + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x138 , 1 , 162 , 240 , // 162 - en-ck + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x31 , 1 , 163 , 240 , // 163 - en-cm + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x135 , 1 , 164 , 240 , // 164 - en-cx + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3b , 1 , 165 , 240 , // 165 - en-cy + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x5e , 1 , 166 , 240 , // 166 - en-de + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3d , 1 , 167 , 240 , // 167 - en-dk + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x3f , 1 , 168 , 240 , // 168 - en-dm + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x47 , 1 , 169 , 240 , // 169 - en-er + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x4d , 1 , 170 , 240 , // 170 - en-fi + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x4e , 1 , 171 , 240 , // 171 - en-fj + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x13b , 1 , 172 , 240 , // 172 - en-fk + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x50 , 1 , 173 , 240 , // 173 - en-fm + 0x809 , 0x4e4 , 0x352 , 0x2710, 0x4f3d, 0xf2 , 1 , 174 , 174 , // 174 - en-gb + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x5b , 1 , 175 , 240 , // 175 - en-gd + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x144 , 1 , 176 , 240 , // 176 - en-gg + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x59 , 1 , 177 , 240 , // 177 - en-gh + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x5a , 1 , 178 , 240 , // 178 - en-gi + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x56 , 1 , 179 , 240 , // 179 - en-gm + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x142 , 1 , 180 , 240 , // 180 - en-gu + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x65 , 1 , 181 , 240 , // 181 - en-gy + 0x3c09 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x68 , 1 , 182 , 240 , // 182 - en-hk + 0x3809 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 183 , 240 , // 183 - en-id + 0x1809 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x44 , 1 , 184 , 184 , // 184 - en-ie + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x75 , 1 , 185 , 240 , // 185 - en-il + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x3b16, 1 , 186 , 240 , // 186 - en-im + 0x4009 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0x71 , 1 , 187 , 187 , // 187 - en-in + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x72 , 1 , 188 , 240 , // 188 - en-io + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x148 , 1 , 189 , 240 , // 189 - en-je + 0x2009 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x7c , 1 , 190 , 190 , // 190 - en-jm + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x81 , 1 , 191 , 240 , // 191 - en-ke + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x85 , 1 , 192 , 240 , // 192 - en-ki + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xcf , 1 , 193 , 240 , // 193 - en-kn + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x133 , 1 , 194 , 240 , // 194 - en-ky + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xda , 1 , 195 , 240 , // 195 - en-lc + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x8e , 1 , 196 , 240 , // 196 - en-lr + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x92 , 1 , 197 , 240 , // 197 - en-ls + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x95 , 1 , 198 , 240 , // 198 - en-mg + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xc7 , 1 , 199 , 240 , // 199 - en-mh + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x97 , 1 , 200 , 240 , // 200 - en-mo + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x151 , 1 , 201 , 240 , // 201 - en-mp + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x14c , 1 , 202 , 240 , // 202 - en-ms + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xa3 , 1 , 203 , 240 , // 203 - en-mt + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xa0 , 1 , 204 , 240 , // 204 - en-mu + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x9c , 1 , 205 , 240 , // 205 - en-mw + 0x4409 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xa7 , 1 , 206 , 206 , // 206 - en-my + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xfe , 1 , 207 , 240 , // 207 - en-na + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x150 , 1 , 208 , 240 , // 208 - en-nf + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xaf , 1 , 209 , 240 , // 209 - en-ng + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xb0 , 1 , 210 , 240 , // 210 - en-nl + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xb4 , 1 , 211 , 240 , // 211 - en-nr + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x14f , 1 , 212 , 240 , // 212 - en-nu + 0x1409 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xb7 , 1 , 213 , 213 , // 213 - en-nz + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xc2 , 1 , 214 , 240 , // 214 - en-pg + 0x3409 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0xc9 , 1 , 215 , 215 , // 215 - en-ph + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xbe , 1 , 216 , 240 , // 216 - en-pk + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x153 , 1 , 217 , 240 , // 217 - en-pn + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xca , 1 , 218 , 240 , // 218 - en-pr + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xc3 , 1 , 219 , 240 , // 219 - en-pw + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xcc , 1 , 220 , 240 , // 220 - en-rw + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x1e , 1 , 221 , 240 , // 221 - en-sb + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd0 , 1 , 222 , 240 , // 222 - en-sc + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xdb , 1 , 223 , 240 , // 223 - en-sd + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xdd , 1 , 224 , 240 , // 224 - en-se + 0x4809 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xd7 , 1 , 225 , 225 , // 225 - en-sg + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x157 , 1 , 226 , 240 , // 226 - en-sh + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd4 , 1 , 227 , 240 , // 227 - en-si + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd5 , 1 , 228 , 240 , // 228 - en-sl + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x114 , 1 , 229 , 240 , // 229 - en-ss + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x78f7, 1 , 230 , 240 , // 230 - en-sx + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x104 , 1 , 231 , 240 , // 231 - en-sz + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x15d , 1 , 232 , 240 , // 232 - en-tc + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x15b , 1 , 233 , 240 , // 233 - en-tk + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xe7 , 1 , 234 , 240 , // 234 - en-to + 0x2c09 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xe1 , 1 , 235 , 235 , // 235 - en-tt + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xec , 1 , 236 , 240 , // 236 - en-tv + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xef , 1 , 237 , 240 , // 237 - en-tz + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xf0 , 1 , 238 , 240 , // 238 - en-ug + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x9a55d40, 1 , 239 , 240 , // 239 - en-um + 0x409 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xf4 , 1 , 240 , 240 , // 240 - en-us + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xf8 , 1 , 241 , 240 , // 241 - en-vc + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x15f , 1 , 242 , 240 , // 242 - en-vg + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xfc , 1 , 243 , 240 , // 243 - en-vi + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xae , 1 , 244 , 240 , // 244 - en-vu + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x103 , 1 , 245 , 240 , // 245 - en-ws + 0x1c09 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0xd1 , 1 , 246 , 246 , // 246 - en-za + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x107 , 1 , 247 , 240 , // 247 - en-zm + 0x3009 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0x108 , 1 , 248 , 248 , // 248 - en-zw + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 250 , 240 , // 249 - eo + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 250 , 240 , // 250 - eo-001 + 0xa , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xd9 , 1 , 262 , 262 , // 251 - es + 0x580a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x9a55d41, 1 , 252 , 240 , // 252 - es-419 + 0x2c0a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xb , 1 , 253 , 253 , // 253 - es-ar + 0x400a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x1a , 1 , 254 , 254 , // 254 - es-bo + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x20 , 1 , 255 , 240 , // 255 - es-br + 0x340a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x2e , 1 , 256 , 256 , // 256 - es-cl + 0x240a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x33 , 1 , 257 , 257 , // 257 - es-co + 0x140a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x36 , 1 , 258 , 258 , // 258 - es-cr + 0x5c0a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x38 , 1 , 259 , 240 , // 259 - es-cu + 0x1c0a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x41 , 1 , 260 , 260 , // 260 - es-do + 0x300a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x42 , 1 , 261 , 261 , // 261 - es-ec + 0xc0a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xd9 , 1 , 262 , 262 , // 262 - es-es + 0x40a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xd9 , 1 , 263 , 263 , // 263 - es-es_tradnl + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x45 , 1 , 264 , 240 , // 264 - es-gq + 0x100a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x63 , 1 , 265 , 265 , // 265 - es-gt + 0x480a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x6a , 1 , 266 , 266 , // 266 - es-hn + 0x80a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xa6 , 1 , 267 , 267 , // 267 - es-mx + 0x4c0a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xb6 , 1 , 268 , 268 , // 268 - es-ni + 0x180a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xc0 , 1 , 269 , 269 , // 269 - es-pa + 0x280a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xbb , 1 , 270 , 270 , // 270 - es-pe + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xc9 , 1 , 271 , 240 , // 271 - es-ph + 0x500a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xca , 1 , 272 , 272 , // 272 - es-pr + 0x3c0a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xb9 , 1 , 273 , 273 , // 273 - es-py + 0x440a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x48 , 1 , 274 , 274 , // 274 - es-sv + 0x540a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xf4 , 1 , 275 , 275 , // 275 - es-us + 0x380a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xf6 , 1 , 276 , 276 , // 276 - es-uy + 0x200a , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xf9 , 1 , 277 , 277 , // 277 - es-ve + 0x25 , 0x4e9 , 0x307 , 0x272d, 0x1f4 , 0x46 , 1 , 279 , 279 , // 278 - et + 0x425 , 0x4e9 , 0x307 , 0x272d, 0x1f4 , 0x46 , 1 , 279 , 279 , // 279 - et-ee + 0x2d , 0x4e4 , 0x352 , 0x2 , 0x1f4 , 0xd9 , 1 , 281 , 240 , // 280 - eu + 0x42d , 0x4e4 , 0x352 , 0x2 , 0x1f4 , 0xd9 , 1 , 281 , 240 , // 281 - eu-es + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 283 , 240 , // 282 - ewo + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 283 , 240 , // 283 - ewo-cm + 0x29 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x74 , 0 , 285 , 143 , // 284 - fa + 0x429 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x74 , 0 , 285 , 143 , // 285 - fa-ir + 0x67 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xd2 , 1 , 290 , 290 , // 286 - ff + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x31 , 1 , 287 , 240 , // 287 - ff-cm + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x64 , 1 , 288 , 240 , // 288 - ff-gn + 0x7c67 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xd2 , 1 , 290 , 290 , // 289 - ff-latn + 0x867 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xd2 , 1 , 290 , 290 , // 290 - ff-latn-sn + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xa2 , 1 , 291 , 240 , // 291 - ff-mr + 0x467 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xaf , 1 , 292 , 240 , // 292 - ff-ng + 0xb , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 294 , 294 , // 293 - fi + 0x40b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 294 , 294 , // 294 - fi-fi + 0x64 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0xc9 , 1 , 296 , 296 , // 295 - fil + 0x464 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0xc9 , 1 , 296 , 296 , // 296 - fil-ph + 0x38 , 0x4e4 , 0x352 , 0x275f, 0x4f35, 0x51 , 1 , 299 , 299 , // 297 - fo + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3d , 1 , 298 , 240 , // 298 - fo-dk + 0x438 , 0x4e4 , 0x352 , 0x275f, 0x4f35, 0x51 , 1 , 299 , 299 , // 299 - fo-fo + 0xc , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 316 , 316 , // 300 - fr + 0x1c0c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x993248, 1 , 301 , 316 , // 301 - fr-029 + 0x80c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x15 , 1 , 302 , 302 , // 302 - fr-be + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xf5 , 1 , 303 , 240 , // 303 - fr-bf + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x26 , 1 , 304 , 240 , // 304 - fr-bi + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x1c , 1 , 305 , 240 , // 305 - fr-bj + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x9a55c4f, 1 , 306 , 240 , // 306 - fr-bl + 0xc0c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x27 , 1 , 307 , 307 , // 307 - fr-ca + 0x240c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x2c , 1 , 308 , 240 , // 308 - fr-cd + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x37 , 1 , 309 , 240 , // 309 - fr-cf + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x2b , 1 , 310 , 240 , // 310 - fr-cg + 0x100c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xdf , 1 , 311 , 311 , // 311 - fr-ch + 0x300c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x77 , 1 , 312 , 240 , // 312 - fr-ci + 0x2c0c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x31 , 1 , 313 , 240 , // 313 - fr-cm + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x3e , 1 , 314 , 240 , // 314 - fr-dj + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x4 , 1 , 315 , 240 , // 315 - fr-dz + 0x40c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 316 , 316 , // 316 - fr-fr + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x57 , 1 , 317 , 240 , // 317 - fr-ga + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x13d , 1 , 318 , 240 , // 318 - fr-gf + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x64 , 1 , 319 , 240 , // 319 - fr-gn + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x141 , 1 , 320 , 240 , // 320 - fr-gp + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x45 , 1 , 321 , 240 , // 321 - fr-gq + 0x3c0c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x67 , 1 , 322 , 240 , // 322 - fr-ht + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x32 , 1 , 323 , 240 , // 323 - fr-km + 0x140c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x93 , 1 , 324 , 324 , // 324 - fr-lu + 0x380c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x9f , 1 , 325 , 240 , // 325 - fr-ma + 0x180c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x9e , 1 , 326 , 326 , // 326 - fr-mc + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x7bda, 1 , 327 , 240 , // 327 - fr-mf + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x95 , 1 , 328 , 240 , // 328 - fr-mg + 0x340c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x9d , 1 , 329 , 240 , // 329 - fr-ml + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x14a , 1 , 330 , 240 , // 330 - fr-mq + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xa2 , 1 , 331 , 240 , // 331 - fr-mr + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xa0 , 1 , 332 , 240 , // 332 - fr-mu + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x14e , 1 , 333 , 240 , // 333 - fr-nc + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xad , 1 , 334 , 240 , // 334 - fr-ne + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x13e , 1 , 335 , 240 , // 335 - fr-pf + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xce , 1 , 336 , 240 , // 336 - fr-pm + 0x200c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xc6 , 1 , 337 , 240 , // 337 - fr-re + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xcc , 1 , 338 , 240 , // 338 - fr-rw + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xd0 , 1 , 339 , 240 , // 339 - fr-sc + 0x280c , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xd2 , 1 , 340 , 240 , // 340 - fr-sn + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xde , 1 , 341 , 240 , // 341 - fr-sy + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x29 , 1 , 342 , 240 , // 342 - fr-td + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xe8 , 1 , 343 , 240 , // 343 - fr-tg + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xea , 1 , 344 , 240 , // 344 - fr-tn + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xae , 1 , 345 , 240 , // 345 - fr-vu + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x160 , 1 , 346 , 240 , // 346 - fr-wf + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x14b , 1 , 347 , 240 , // 347 - fr-yt + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x76 , 1 , 349 , 240 , // 348 - fur + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x76 , 1 , 349 , 240 , // 349 - fur-it + 0x62 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xb0 , 1 , 351 , 351 , // 350 - fy + 0x462 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xb0 , 1 , 351 , 351 , // 351 - fy-nl + 0x3c , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x44 , 1 , 353 , 353 , // 352 - ga + 0x83c , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x44 , 1 , 353 , 353 , // 353 - ga-ie + 0x91 , 0x4e4 , 0x352 , 0x2710, 0x4f3d, 0xf2 , 1 , 355 , 355 , // 354 - gd + 0x491 , 0x4e4 , 0x352 , 0x2710, 0x4f3d, 0xf2 , 1 , 355 , 355 , // 355 - gd-gb + 0x56 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd9 , 1 , 357 , 357 , // 356 - gl + 0x456 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd9 , 1 , 357 , 357 , // 357 - gl-es + 0x74 , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xb9 , 1 , 359 , 359 , // 358 - gn + 0x474 , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xb9 , 1 , 359 , 359 , // 359 - gn-py + 0x84 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xdf , 1 , 361 , 240 , // 360 - gsw + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xdf , 1 , 361 , 240 , // 361 - gsw-ch + 0x484 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 362 , 362 , // 362 - gsw-fr + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x91 , 1 , 363 , 240 , // 363 - gsw-li + 0x47 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 365 , 143 , // 364 - gu + 0x447 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 365 , 143 , // 365 - gu-in + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 367 , 240 , // 366 - guz + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 367 , 240 , // 367 - guz-ke + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3b16, 1 , 369 , 240 , // 368 - gv + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3b16, 1 , 369 , 240 , // 369 - gv-im + 0x68 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xaf , 1 , 374 , 374 , // 370 - ha + 0x7c68 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xaf , 1 , 374 , 374 , // 371 - ha-latn + 0x1000 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0x59 , 1 , 372 , 240 , // 372 - ha-latn-gh + 0x1000 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0xad , 1 , 373 , 240 , // 373 - ha-latn-ne + 0x468 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xaf , 1 , 374 , 374 , // 374 - ha-latn-ng + 0x75 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xf4 , 1 , 376 , 376 , // 375 - haw + 0x475 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xf4 , 1 , 376 , 376 , // 376 - haw-us + 0xd , 0x4e7 , 0x35e , 0x2715, 0x1f4 , 0x75 , 1 , 378 , 143 , // 377 - he + 0x40d , 0x4e7 , 0x35e , 0x2715, 0x1f4 , 0x75 , 1 , 378 , 143 , // 378 - he-il + 0x39 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 380 , 143 , // 379 - hi + 0x439 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 380 , 143 , // 380 - hi-in + 0x1a , 0x4e2 , 0x354 , 0x2762, 0x1f4 , 0x6c , 1 , 383 , 383 , // 381 - hr + 0x101a , 0x4e2 , 0x354 , 0x2762, 0x366 , 0x19 , 1 , 382 , 382 , // 382 - hr-ba + 0x41a , 0x4e2 , 0x354 , 0x2762, 0x1f4 , 0x6c , 1 , 383 , 383 , // 383 - hr-hr + 0x2e , 0x4e4 , 0x352 , 0x2710, 0x366 , 0x5e , 1 , 385 , 385 , // 384 - hsb + 0x42e , 0x4e4 , 0x352 , 0x2710, 0x366 , 0x5e , 1 , 385 , 385 , // 385 - hsb-de + 0xe , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x6d , 1 , 387 , 387 , // 386 - hu + 0x40e , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x6d , 1 , 387 , 387 , // 387 - hu-hu + 0x1040e, 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x6d , 1 , 387 , 387 , // 388 - hu-hu_technl + 0x2b , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x7 , 1 , 390 , 390 , // 389 - hy + 0x42b , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x7 , 1 , 390 , 390 , // 390 - hy-am + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x54 , 1 , 393 , 240 , // 391 - ia + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 392 , 240 , // 392 - ia-001 + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x54 , 1 , 393 , 240 , // 393 - ia-fr + 0x69 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xaf , 1 , 395 , 240 , // 394 - ibb + 0x469 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xaf , 1 , 395 , 240 , // 395 - ibb-ng + 0x21 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 397 , 397 , // 396 - id + 0x421 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 397 , 397 , // 397 - id-id + 0x70 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xaf , 1 , 399 , 399 , // 398 - ig + 0x470 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xaf , 1 , 399 , 399 , // 399 - ig-ng + 0x78 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2d , 1 , 401 , 143 , // 400 - ii + 0x478 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2d , 1 , 401 , 143 , // 401 - ii-cn + 0xf , 0x4e4 , 0x352 , 0x275f, 0x5187, 0x6e , 1 , 403 , 403 , // 402 - is + 0x40f , 0x4e4 , 0x352 , 0x275f, 0x5187, 0x6e , 1 , 403 , 403 , // 403 - is-is + 0x10 , 0x4e4 , 0x352 , 0x2710, 0x4f38, 0x76 , 1 , 406 , 406 , // 404 - it + 0x810 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xdf , 1 , 405 , 405 , // 405 - it-ch + 0x410 , 0x4e4 , 0x352 , 0x2710, 0x4f38, 0x76 , 1 , 406 , 406 , // 406 - it-it + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f38, 0xd6 , 1 , 407 , 240 , // 407 - it-sm + 0x5d , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0x27 , 1 , 412 , 412 , // 408 - iu + 0x785d , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x27 , 1 , 410 , 143 , // 409 - iu-cans + 0x45d , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x27 , 1 , 410 , 143 , // 410 - iu-cans-ca + 0x7c5d , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0x27 , 1 , 412 , 412 , // 411 - iu-latn + 0x85d , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0x27 , 1 , 412 , 412 , // 412 - iu-latn-ca + 0x11 , 0x3a4 , 0x3a4 , 0x2711, 0x4f42, 0x7a , 1 , 414 , 414 , // 413 - ja + 0x411 , 0x3a4 , 0x3a4 , 0x2711, 0x4f42, 0x7a , 1 , 414 , 414 , // 414 - ja-jp + 0x40411, 0x3a4 , 0x3a4 , 0x2711, 0x4f42, 0x7a , 1 , 414 , 414 , // 415 - ja-jp_radstr + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 417 , 240 , // 416 - jgo + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 417 , 240 , // 417 - jgo-cm + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 419 , 240 , // 418 - jmc + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 419 , 240 , // 419 - jmc-tz + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 424 , 424 , // 420 - jv + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 422 , 424 , // 421 - jv-java + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 422 , 424 , // 422 - jv-java-id + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 424 , 424 , // 423 - jv-latn + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f , 1 , 424 , 424 , // 424 - jv-latn-id + 0x37 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x58 , 1 , 426 , 426 , // 425 - ka + 0x437 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x58 , 1 , 426 , 426 , // 426 - ka-ge + 0x10437, 0x0 , 0x1 , 0x2 , 0x1f4 , 0x58 , 1 , 426 , 426 , // 427 - ka-ge_modern + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x4 , 1 , 429 , 240 , // 428 - kab + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x4 , 1 , 429 , 240 , // 429 - kab-dz + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 431 , 240 , // 430 - kam + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 431 , 240 , // 431 - kam-ke + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 433 , 240 , // 432 - kde + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 433 , 240 , // 433 - kde-tz + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x39 , 1 , 435 , 240 , // 434 - kea + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x39 , 1 , 435 , 240 , // 435 - kea-cv + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9d , 1 , 437 , 240 , // 436 - khq + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9d , 1 , 437 , 240 , // 437 - khq-ml + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 439 , 240 , // 438 - ki + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 439 , 240 , // 439 - ki-ke + 0x3f , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x89 , 1 , 441 , 441 , // 440 - kk + 0x43f , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x89 , 1 , 441 , 441 , // 441 - kk-kz + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 443 , 240 , // 442 - kkj + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 443 , 240 , // 443 - kkj-cm + 0x6f , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0x5d , 1 , 445 , 445 , // 444 - kl + 0x46f , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0x5d , 1 , 445 , 445 , // 445 - kl-gl + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 447 , 240 , // 446 - kln + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 447 , 240 , // 447 - kln-ke + 0x53 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x28 , 2 , 449 , 143 , // 448 - km + 0x453 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x28 , 2 , 449 , 143 , // 449 - km-kh + 0x4b , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 451 , 143 , // 450 - kn + 0x44b , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 451 , 143 , // 451 - kn-in + 0x12 , 0x3b5 , 0x3b5 , 0x2713, 0x5161, 0x86 , 1 , 454 , 454 , // 452 - ko + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x83 , 1 , 453 , 240 , // 453 - ko-kp + 0x412 , 0x3b5 , 0x3b5 , 0x2713, 0x5161, 0x86 , 1 , 454 , 454 , // 454 - ko-kr + 0x57 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 456 , 143 , // 455 - kok + 0x457 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 456 , 143 , // 456 - kok-in + 0x71 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xaf , 1 , 458 , 240 , // 457 - kr + 0x471 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xaf , 1 , 458 , 240 , // 458 - kr-ng + 0x60 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 2 , 461 , 240 , // 459 - ks + 0x460 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 2 , 461 , 240 , // 460 - ks-arab + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 2 , 461 , 240 , // 461 - ks-arab-in + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 463 , 187 , // 462 - ks-deva + 0x860 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 463 , 187 , // 463 - ks-deva-in + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 465 , 240 , // 464 - ksb + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 465 , 240 , // 465 - ksb-tz + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 467 , 240 , // 466 - ksf + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 467 , 240 , // 467 - ksf-cm + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x5e , 1 , 469 , 240 , // 468 - ksh + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x5e , 1 , 469 , 240 , // 469 - ksh-de + 0x92 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x79 , 0 , 472 , 143 , // 470 - ku + 0x7c92 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x79 , 0 , 472 , 143 , // 471 - ku-arab + 0x492 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x79 , 0 , 472 , 143 , // 472 - ku-arab-iq + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x74 , 0 , 473 , 240 , // 473 - ku-arab-ir + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf2 , 1 , 475 , 240 , // 474 - kw + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf2 , 1 , 475 , 240 , // 475 - kw-gb + 0x40 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0x82 , 1 , 477 , 477 , // 476 - ky + 0x440 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0x82 , 1 , 477 , 477 , // 477 - ky-kg + 0x76 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0x989e, 1 , 479 , 143 , // 478 - la + 0x476 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0x989e, 1 , 479 , 143 , // 479 - la-001 + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 481 , 240 , // 480 - lag + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 481 , 240 , // 481 - lag-tz + 0x6e , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x93 , 1 , 483 , 483 , // 482 - lb + 0x46e , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x93 , 1 , 483 , 483 , // 483 - lb-lu + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 485 , 240 , // 484 - lg + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 485 , 240 , // 485 - lg-ug + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf4 , 1 , 487 , 240 , // 486 - lkt + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf4 , 1 , 487 , 240 , // 487 - lkt-us + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2c , 1 , 490 , 240 , // 488 - ln + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9 , 1 , 489 , 240 , // 489 - ln-ao + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2c , 1 , 490 , 240 , // 490 - ln-cd + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x37 , 1 , 491 , 240 , // 491 - ln-cf + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2b , 1 , 492 , 240 , // 492 - ln-cg + 0x54 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x8a , 1 , 494 , 143 , // 493 - lo + 0x454 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x8a , 1 , 494 , 143 , // 494 - lo-la + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x74 , 2 , 497 , 240 , // 495 - lrc + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x79 , 2 , 496 , 240 , // 496 - lrc-iq + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x74 , 2 , 497 , 240 , // 497 - lrc-ir + 0x27 , 0x4e9 , 0x307 , 0x272d, 0x1f4 , 0x8d , 1 , 499 , 499 , // 498 - lt + 0x427 , 0x4e9 , 0x307 , 0x272d, 0x1f4 , 0x8d , 1 , 499 , 499 , // 499 - lt-lt + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2c , 1 , 501 , 240 , // 500 - lu + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2c , 1 , 501 , 240 , // 501 - lu-cd + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 503 , 240 , // 502 - luo + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 503 , 240 , // 503 - luo-ke + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 505 , 240 , // 504 - luy + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 505 , 240 , // 505 - luy-ke + 0x26 , 0x4e9 , 0x307 , 0x272d, 0x1f4 , 0x8c , 1 , 507 , 507 , // 506 - lv + 0x426 , 0x4e9 , 0x307 , 0x272d, 0x1f4 , 0x8c , 1 , 507 , 507 , // 507 - lv-lv + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 509 , 240 , // 508 - mas + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 509 , 240 , // 509 - mas-ke + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 510 , 240 , // 510 - mas-tz + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 512 , 240 , // 511 - mer + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 512 , 240 , // 512 - mer-ke + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa0 , 1 , 514 , 240 , // 513 - mfe + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa0 , 1 , 514 , 240 , // 514 - mfe-mu + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x95 , 1 , 516 , 240 , // 515 - mg + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x95 , 1 , 516 , 240 , // 516 - mg-mg + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa8 , 1 , 518 , 240 , // 517 - mgh + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa8 , 1 , 518 , 240 , // 518 - mgh-mz + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 520 , 240 , // 519 - mgo + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 520 , 240 , // 520 - mgo-cm + 0x81 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xb7 , 1 , 522 , 522 , // 521 - mi + 0x481 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xb7 , 1 , 522 , 522 , // 522 - mi-nz + 0x2f , 0x4e3 , 0x362 , 0x2717, 0x1f4 , 0x4ca2, 1 , 524 , 524 , // 523 - mk + 0x42f , 0x4e3 , 0x362 , 0x2717, 0x1f4 , 0x4ca2, 1 , 524 , 524 , // 524 - mk-mk + 0x4c , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 526 , 143 , // 525 - ml + 0x44c , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 526 , 143 , // 526 - ml-in + 0x50 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0x9a , 1 , 529 , 529 , // 527 - mn + 0x7850 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0x9a , 1 , 529 , 529 , // 528 - mn-cyrl + 0x450 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0x9a , 1 , 529 , 529 , // 529 - mn-mn + 0x7c50 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2d , 1 , 531 , 531 , // 530 - mn-mong + 0x850 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2d , 1 , 531 , 531 , // 531 - mn-mong-cn + 0xc50 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9a , 1 , 532 , 532 , // 532 - mn-mong-mn + 0x58 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 534 , 187 , // 533 - mni + 0x458 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 534 , 187 , // 534 - mni-in + 0x7c , 0x4e4 , 0x352 , 0x2710, 0x25 , 0x27 , 1 , 536 , 240 , // 535 - moh + 0x47c , 0x4e4 , 0x352 , 0x2710, 0x25 , 0x27 , 1 , 536 , 240 , // 536 - moh-ca + 0x4e , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 538 , 143 , // 537 - mr + 0x44e , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 538 , 143 , // 538 - mr-in + 0x3e , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xa7 , 1 , 541 , 541 , // 539 - ms + 0x83e , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x25 , 1 , 540 , 540 , // 540 - ms-bn + 0x43e , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xa7 , 1 , 541 , 541 , // 541 - ms-my + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd7 , 1 , 542 , 240 , // 542 - ms-sg + 0x3a , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa3 , 1 , 544 , 544 , // 543 - mt + 0x43a , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa3 , 1 , 544 , 544 , // 544 - mt-mt + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 546 , 240 , // 545 - mua + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 546 , 240 , // 546 - mua-cm + 0x55 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x1b , 2 , 548 , 240 , // 547 - my + 0x455 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x1b , 2 , 548 , 240 , // 548 - my-mm + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x74 , 2 , 550 , 240 , // 549 - mzn + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x74 , 2 , 550 , 240 , // 550 - mzn-ir + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xfe , 1 , 552 , 240 , // 551 - naq + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xfe , 1 , 552 , 240 , // 552 - naq-na + 0x7c14 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 554 , 554 , // 553 - nb + 0x414 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 554 , 554 , // 554 - nb-no + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xdc , 1 , 555 , 240 , // 555 - nb-sj + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x108 , 1 , 557 , 240 , // 556 - nd + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x108 , 1 , 557 , 240 , // 557 - nd-zw + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x5e , 1 , 559 , 240 , // 558 - nds + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x5e , 1 , 559 , 240 , // 559 - nds-de + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xb0 , 1 , 560 , 240 , // 560 - nds-nl + 0x61 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xb2 , 1 , 563 , 143 , // 561 - ne + 0x861 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 2 , 562 , 240 , // 562 - ne-in + 0x461 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xb2 , 1 , 563 , 143 , // 563 - ne-np + 0x13 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xb0 , 1 , 569 , 569 , // 564 - nl + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x12e , 1 , 565 , 240 , // 565 - nl-aw + 0x813 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x15 , 1 , 566 , 566 , // 566 - nl-be + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x9a55d42, 1 , 567 , 240 , // 567 - nl-bq + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x111 , 1 , 568 , 240 , // 568 - nl-cw + 0x413 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xb0 , 1 , 569 , 569 , // 569 - nl-nl + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xb5 , 1 , 570 , 240 , // 570 - nl-sr + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x78f7, 1 , 571 , 240 , // 571 - nl-sx + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 573 , 240 , // 572 - nmg + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 573 , 240 , // 573 - nmg-cm + 0x7814 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 575 , 575 , // 574 - nn + 0x814 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 575 , 575 , // 575 - nn-no + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 577 , 240 , // 576 - nnh + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 577 , 240 , // 577 - nnh-cm + 0x14 , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 554 , 554 , // 578 - no + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x64 , 2 , 580 , 143 , // 579 - nqo + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x64 , 2 , 580 , 143 , // 580 - nqo-gn + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 582 , 240 , // 581 - nr + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 582 , 240 , // 582 - nr-za + 0x6c , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 584 , 584 , // 583 - nso + 0x46c , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 584 , 584 , // 584 - nso-za + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x114 , 1 , 586 , 240 , // 585 - nus + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x114 , 1 , 586 , 240 , // 586 - nus-ss + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 588 , 240 , // 587 - nyn + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 588 , 240 , // 588 - nyn-ug + 0x82 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 590 , 590 , // 589 - oc + 0x482 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x54 , 1 , 590 , 590 , // 590 - oc-fr + 0x72 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 592 , 240 , // 591 - om + 0x472 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 592 , 240 , // 592 - om-et + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 593 , 240 , // 593 - om-ke + 0x48 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 595 , 143 , // 594 - or + 0x448 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 595 , 143 , // 595 - or-in + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x58 , 1 , 597 , 240 , // 596 - os + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x58 , 1 , 597 , 240 , // 597 - os-ge + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xcb , 1 , 598 , 240 , // 598 - os-ru + 0x46 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 602 , 143 , // 599 - pa + 0x7c46 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xbe , 2 , 601 , 143 , // 600 - pa-arab + 0x846 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xbe , 2 , 601 , 143 , // 601 - pa-arab-pk + 0x446 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 602 , 143 , // 602 - pa-in + 0x79 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x993248, 1 , 604 , 145 , // 603 - pap + 0x479 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x993248, 1 , 604 , 145 , // 604 - pap-029 + 0x15 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xbf , 1 , 606 , 606 , // 605 - pl + 0x415 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xbf , 1 , 606 , 606 , // 606 - pl-pl + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 608 , 240 , // 607 - prg + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 608 , 240 , // 608 - prg-001 + 0x8c , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x3 , 2 , 610 , 143 , // 609 - prs + 0x48c , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x3 , 2 , 610 , 143 , // 610 - prs-af + 0x63 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3 , 2 , 612 , 143 , // 611 - ps + 0x463 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3 , 2 , 612 , 143 , // 612 - ps-af + 0x16 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x20 , 1 , 615 , 615 , // 613 - pt + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x9 , 1 , 614 , 240 , // 614 - pt-ao + 0x416 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x20 , 1 , 615 , 615 , // 615 - pt-br + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xdf , 1 , 616 , 240 , // 616 - pt-ch + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x39 , 1 , 617 , 240 , // 617 - pt-cv + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x45 , 1 , 618 , 240 , // 618 - pt-gq + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xc4 , 1 , 619 , 240 , // 619 - pt-gw + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x93 , 1 , 620 , 240 , // 620 - pt-lu + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x97 , 1 , 621 , 240 , // 621 - pt-mo + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xa8 , 1 , 622 , 240 , // 622 - pt-mz + 0x816 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xc1 , 1 , 623 , 623 , // 623 - pt-pt + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xe9 , 1 , 624 , 240 , // 624 - pt-st + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x6f60e7, 1 , 625 , 240 , // 625 - pt-tl + 0x901 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x7c , 1 , 626 , 190 , // 626 - qps-latn-x-sh + 0x501 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xf4 , 1 , 627 , 627 , // 627 - qps-ploc + 0x5fe , 0x3a4 , 0x3a4 , 0x2711, 0x4f42, 0x7a , 1 , 628 , 628 , // 628 - qps-ploca + 0x9ff , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xcd , 0 , 629 , 143 , // 629 - qps-plocm + 0x86 , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x63 , 1 , 632 , 632 , // 630 - quc + 0x7c86 , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x63 , 1 , 632 , 632 , // 631 - quc-latn + 0x486 , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x63 , 1 , 632 , 632 , // 632 - quc-latn-gt + 0x6b , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x1a , 1 , 634 , 634 , // 633 - quz + 0x46b , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x1a , 1 , 634 , 634 , // 634 - quz-bo + 0x86b , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0x42 , 1 , 635 , 635 , // 635 - quz-ec + 0xc6b , 0x4e4 , 0x352 , 0x2710, 0x4f3c, 0xbb , 1 , 636 , 636 , // 636 - quz-pe + 0x17 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0xdf , 1 , 638 , 638 , // 637 - rm + 0x417 , 0x4e4 , 0x352 , 0x2710, 0x4f31, 0xdf , 1 , 638 , 638 , // 638 - rm-ch + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x26 , 1 , 640 , 240 , // 639 - rn + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x26 , 1 , 640 , 240 , // 640 - rn-bi + 0x18 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xc8 , 1 , 643 , 643 , // 641 - ro + 0x818 , 0x4e2 , 0x354 , 0x2 , 0x1f4 , 0x98 , 1 , 642 , 240 , // 642 - ro-md + 0x418 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xc8 , 1 , 643 , 643 , // 643 - ro-ro + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 645 , 240 , // 644 - rof + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 645 , 240 , // 645 - rof-tz + 0x19 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 651 , 651 , // 646 - ru + 0x1000 , 0x4e3 , 0x362 , 0x2 , 0x1f4 , 0x1d , 1 , 647 , 240 , // 647 - ru-by + 0x1000 , 0x4e3 , 0x362 , 0x2 , 0x1f4 , 0x82 , 1 , 648 , 240 , // 648 - ru-kg + 0x1000 , 0x4e3 , 0x362 , 0x2 , 0x1f4 , 0x89 , 1 , 649 , 240 , // 649 - ru-kz + 0x819 , 0x4e3 , 0x362 , 0x2 , 0x1f4 , 0x98 , 1 , 650 , 240 , // 650 - ru-md + 0x419 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 651 , 651 , // 651 - ru-ru + 0x1000 , 0x4e3 , 0x362 , 0x2 , 0x1f4 , 0xf1 , 1 , 652 , 240 , // 652 - ru-ua + 0x87 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xcc , 1 , 654 , 654 , // 653 - rw + 0x487 , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xcc , 1 , 654 , 654 , // 654 - rw-rw + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 656 , 240 , // 655 - rwk + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 656 , 240 , // 656 - rwk-tz + 0x4f , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 658 , 143 , // 657 - sa + 0x44f , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 658 , 143 , // 658 - sa-in + 0x85 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 660 , 660 , // 659 - sah + 0x485 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 660 , 660 , // 660 - sah-ru + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 662 , 240 , // 661 - saq + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 662 , 240 , // 662 - saq-ke + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 664 , 240 , // 663 - sbp + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 664 , 240 , // 664 - sbp-tz + 0x59 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xbe , 2 , 667 , 143 , // 665 - sd + 0x7c59 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xbe , 2 , 667 , 143 , // 666 - sd-arab + 0x859 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xbe , 2 , 667 , 143 , // 667 - sd-arab-pk + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 669 , 187 , // 668 - sd-deva + 0x459 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 669 , 187 , // 669 - sd-deva-in + 0x3b , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 672 , 672 , // 670 - se + 0xc3b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 671 , 671 , // 671 - se-fi + 0x43b , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 672 , 672 , // 672 - se-no + 0x83b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0xdd , 1 , 673 , 673 , // 673 - se-se + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa8 , 1 , 675 , 240 , // 674 - seh + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa8 , 1 , 675 , 240 , // 675 - seh-mz + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9d , 1 , 677 , 240 , // 676 - ses + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9d , 1 , 677 , 240 , // 677 - ses-ml + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x37 , 1 , 679 , 240 , // 678 - sg + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x37 , 1 , 679 , 240 , // 679 - sg-cf + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 684 , 240 , // 680 - shi + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 682 , 240 , // 681 - shi-latn + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 682 , 240 , // 682 - shi-latn-ma + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 684 , 240 , // 683 - shi-tfng + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 684 , 240 , // 684 - shi-tfng-ma + 0x5b , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2a , 1 , 686 , 143 , // 685 - si + 0x45b , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2a , 1 , 686 , 143 , // 686 - si-lk + 0x1b , 0x4e2 , 0x354 , 0x272d, 0x5190, 0x8f , 1 , 688 , 688 , // 687 - sk + 0x41b , 0x4e2 , 0x354 , 0x272d, 0x5190, 0x8f , 1 , 688 , 688 , // 688 - sk-sk + 0x24 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xd4 , 1 , 690 , 690 , // 689 - sl + 0x424 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xd4 , 1 , 690 , 690 , // 690 - sl-si + 0x783b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0xdd , 1 , 693 , 693 , // 691 - sma + 0x183b , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 692 , 692 , // 692 - sma-no + 0x1c3b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0xdd , 1 , 693 , 693 , // 693 - sma-se + 0x7c3b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0xdd , 1 , 696 , 696 , // 694 - smj + 0x103b , 0x4e4 , 0x352 , 0x2710, 0x4f35, 0xb1 , 1 , 695 , 695 , // 695 - smj-no + 0x143b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0xdd , 1 , 696 , 696 , // 696 - smj-se + 0x703b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 698 , 698 , // 697 - smn + 0x243b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 698 , 698 , // 698 - smn-fi + 0x743b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 700 , 700 , // 699 - sms + 0x203b , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 700 , 700 , // 700 - sms-fi + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x108 , 1 , 703 , 240 , // 701 - sn + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x108 , 1 , 703 , 240 , // 702 - sn-latn + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x108 , 1 , 703 , 240 , // 703 - sn-latn-zw + 0x77 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd8 , 1 , 708 , 240 , // 704 - so + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3e , 1 , 705 , 240 , // 705 - so-dj + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 706 , 240 , // 706 - so-et + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 707 , 240 , // 707 - so-ke + 0x477 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd8 , 1 , 708 , 240 , // 708 - so-so + 0x1c , 0x4e2 , 0x354 , 0x272d, 0x5190, 0x6 , 1 , 710 , 710 , // 709 - sq + 0x41c , 0x4e2 , 0x354 , 0x272d, 0x5190, 0x6 , 1 , 710 , 710 , // 710 - sq-al + 0x1000 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0x4ca2, 1 , 711 , 240 , // 711 - sq-mk + 0x1000 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0x974941, 1 , 712 , 240 , // 712 - sq-xk + 0x7c1a , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x10f , 1 , 724 , 724 , // 713 - sr + 0x6c1a , 0x4e3 , 0x357 , 0x2717, 0x5221, 0x10f , 1 , 718 , 718 , // 714 - sr-cyrl + 0x1c1a , 0x4e3 , 0x357 , 0x2717, 0x5221, 0x19 , 1 , 715 , 715 , // 715 - sr-cyrl-ba + 0xc1a , 0x4e3 , 0x357 , 0x2717, 0x5221, 0x10d , 1 , 716 , 716 , // 716 - sr-cyrl-cs + 0x301a , 0x4e3 , 0x357 , 0x2717, 0x5221, 0x10e , 1 , 717 , 717 , // 717 - sr-cyrl-me + 0x281a , 0x4e3 , 0x357 , 0x2717, 0x5221, 0x10f , 1 , 718 , 718 , // 718 - sr-cyrl-rs + 0x1000 , 0x4e3 , 0x357 , 0x2717, 0x5221, 0x974941, 1 , 719 , 240 , // 719 - sr-cyrl-xk + 0x701a , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x10f , 1 , 724 , 724 , // 720 - sr-latn + 0x181a , 0x4e2 , 0x354 , 0x2762, 0x366 , 0x19 , 1 , 721 , 721 , // 721 - sr-latn-ba + 0x81a , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x10d , 1 , 722 , 722 , // 722 - sr-latn-cs + 0x2c1a , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x10e , 1 , 723 , 723 , // 723 - sr-latn-me + 0x241a , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x10f , 1 , 724 , 724 , // 724 - sr-latn-rs + 0x1000 , 0x4e2 , 0x354 , 0x272d, 0x1f4 , 0x974941, 1 , 725 , 240 , // 725 - sr-latn-xk + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 728 , 240 , // 726 - ss + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x104 , 1 , 727 , 240 , // 727 - ss-sz + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 728 , 240 , // 728 - ss-za + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 730 , 240 , // 729 - ssy + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 730 , 240 , // 730 - ssy-er + 0x30 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 733 , 240 , // 731 - st + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x92 , 1 , 732 , 240 , // 732 - st-ls + 0x430 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 733 , 240 , // 733 - st-za + 0x1d , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0xdd , 1 , 737 , 737 , // 734 - sv + 0x1000 , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x9906f5, 1 , 735 , 240 , // 735 - sv-ax + 0x81d , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0x4d , 1 , 736 , 736 , // 736 - sv-fi + 0x41d , 0x4e4 , 0x352 , 0x2710, 0x4f36, 0xdd , 1 , 737 , 737 , // 737 - sv-se + 0x41 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0x81 , 1 , 740 , 740 , // 738 - sw + 0x1000 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0x2c , 1 , 739 , 740 , // 739 - sw-cd + 0x441 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0x81 , 1 , 740 , 740 , // 740 - sw-ke + 0x1000 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0xef , 1 , 741 , 240 , // 741 - sw-tz + 0x1000 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0xf0 , 1 , 742 , 240 , // 742 - sw-ug + 0x1000 , 0x0 , 0x1 , 0x0 , 0x1f4 , 0x2c , 1 , 744 , 240 , // 743 - swc + 0x1000 , 0x0 , 0x1 , 0x0 , 0x1f4 , 0x2c , 1 , 744 , 240 , // 744 - swc-cd + 0x5a , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xde , 1 , 746 , 143 , // 745 - syr + 0x45a , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xde , 1 , 746 , 143 , // 746 - syr-sy + 0x49 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 748 , 143 , // 747 - ta + 0x449 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 748 , 143 , // 748 - ta-in + 0x849 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x2a , 1 , 749 , 143 , // 749 - ta-lk + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xa7 , 1 , 750 , 240 , // 750 - ta-my + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd7 , 1 , 751 , 240 , // 751 - ta-sg + 0x4a , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 753 , 143 , // 752 - te + 0x44a , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x71 , 1 , 753 , 143 , // 753 - te-in + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 756 , 240 , // 754 - teo + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x81 , 1 , 755 , 240 , // 755 - teo-ke + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 756 , 240 , // 756 - teo-ug + 0x28 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xe4 , 1 , 759 , 759 , // 757 - tg + 0x7c28 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xe4 , 1 , 759 , 759 , // 758 - tg-cyrl + 0x428 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xe4 , 1 , 759 , 759 , // 759 - tg-cyrl-tj + 0x1e , 0x36a , 0x36a , 0x2725, 0x5166, 0xe3 , 1 , 761 , 143 , // 760 - th + 0x41e , 0x36a , 0x36a , 0x2725, 0x5166, 0xe3 , 1 , 761 , 143 , // 761 - th-th + 0x73 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 763 , 143 , // 762 - ti + 0x873 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 763 , 143 , // 763 - ti-er + 0x473 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 764 , 143 , // 764 - ti-et + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 766 , 240 , // 765 - tig + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x47 , 1 , 766 , 240 , // 766 - tig-er + 0x42 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xee , 1 , 768 , 768 , // 767 - tk + 0x442 , 0x4e2 , 0x354 , 0x272d, 0x5190, 0xee , 1 , 768 , 768 , // 768 - tk-tm + 0x32 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 771 , 771 , // 769 - tn + 0x832 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0x13 , 1 , 770 , 770 , // 770 - tn-bw + 0x432 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 771 , 771 , // 771 - tn-za + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xe7 , 1 , 773 , 240 , // 772 - to + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xe7 , 1 , 773 , 240 , // 773 - to-to + 0x1f , 0x4e6 , 0x359 , 0x2761, 0x51a9, 0xeb , 1 , 776 , 776 , // 774 - tr + 0x1000 , 0x4e6 , 0x359 , 0x2761, 0x51a9, 0x3b , 1 , 775 , 240 , // 775 - tr-cy + 0x41f , 0x4e6 , 0x359 , 0x2761, 0x51a9, 0xeb , 1 , 776 , 776 , // 776 - tr-tr + 0x31 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 778 , 240 , // 777 - ts + 0x431 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 778 , 240 , // 778 - ts-za + 0x44 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 780 , 780 , // 779 - tt + 0x444 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xcb , 1 , 780 , 780 , // 780 - tt-ru + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xad , 1 , 782 , 240 , // 781 - twq + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xad , 1 , 782 , 240 , // 782 - twq-ne + 0x5f , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x4 , 1 , 787 , 787 , // 783 - tzm + 0x1000 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x9f , 1 , 785 , 240 , // 784 - tzm-arab + 0x45f , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x9f , 1 , 785 , 240 , // 785 - tzm-arab-ma + 0x7c5f , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x4 , 1 , 787 , 787 , // 786 - tzm-latn + 0x85f , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0x4 , 1 , 787 , 787 , // 787 - tzm-latn-dz + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 788 , 240 , // 788 - tzm-latn-ma + 0x785f , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 790 , 316 , // 789 - tzm-tfng + 0x105f , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 790 , 316 , // 790 - tzm-tfng-ma + 0x80 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x2d , 1 , 792 , 143 , // 791 - ug + 0x480 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0x2d , 1 , 792 , 143 , // 792 - ug-cn + 0x22 , 0x4e3 , 0x362 , 0x2721, 0x1f4 , 0xf1 , 1 , 794 , 794 , // 793 - uk + 0x422 , 0x4e3 , 0x362 , 0x2721, 0x1f4 , 0xf1 , 1 , 794 , 794 , // 794 - uk-ua + 0x20 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xbe , 1 , 797 , 143 , // 795 - ur + 0x820 , 0x4e8 , 0x2d0 , 0x2 , 0x1f4 , 0x71 , 2 , 796 , 240 , // 796 - ur-in + 0x420 , 0x4e8 , 0x2d0 , 0x2714, 0x4fc4, 0xbe , 1 , 797 , 143 , // 797 - ur-pk + 0x43 , 0x4e6 , 0x359 , 0x272d, 0x1f4 , 0xf7 , 1 , 804 , 804 , // 798 - uz + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3 , 2 , 800 , 240 , // 799 - uz-arab + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x3 , 2 , 800 , 240 , // 800 - uz-arab-af + 0x7843 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xf7 , 1 , 802 , 802 , // 801 - uz-cyrl + 0x843 , 0x4e3 , 0x362 , 0x2717, 0x5190, 0xf7 , 1 , 802 , 802 , // 802 - uz-cyrl-uz + 0x7c43 , 0x4e6 , 0x359 , 0x272d, 0x1f4 , 0xf7 , 1 , 804 , 804 , // 803 - uz-latn + 0x443 , 0x4e6 , 0x359 , 0x272d, 0x1f4 , 0xf7 , 1 , 804 , 804 , // 804 - uz-latn-uz + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x8e , 1 , 809 , 240 , // 805 - vai + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x8e , 1 , 807 , 240 , // 806 - vai-latn + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x8e , 1 , 807 , 240 , // 807 - vai-latn-lr + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x8e , 1 , 809 , 240 , // 808 - vai-vaii + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x8e , 1 , 809 , 240 , // 809 - vai-vaii-lr + 0x33 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 811 , 240 , // 810 - ve + 0x433 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xd1 , 1 , 811 , 240 , // 811 - ve-za + 0x2a , 0x4ea , 0x4ea , 0x2710, 0x1f4 , 0xfb , 1 , 813 , 143 , // 812 - vi + 0x42a , 0x4ea , 0x4ea , 0x2710, 0x1f4 , 0xfb , 1 , 813 , 143 , // 813 - vi-vn + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 815 , 240 , // 814 - vo + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 815 , 240 , // 815 - vo-001 + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 817 , 240 , // 816 - vun + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xef , 1 , 817 , 240 , // 817 - vun-tz + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xdf , 1 , 819 , 240 , // 818 - wae + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xdf , 1 , 819 , 240 , // 819 - wae-ch + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 821 , 240 , // 820 - wal + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x49 , 1 , 821 , 240 , // 821 - wal-et + 0x88 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xd2 , 1 , 823 , 823 , // 822 - wo + 0x488 , 0x4e4 , 0x352 , 0x2710, 0x4f49, 0xd2 , 1 , 823 , 823 , // 823 - wo-sn + 0x1007f, 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xf4 , 1 , -1 , -1 , // 824 - x-iv_mathan + 0x34 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 826 , 826 , // 825 - xh + 0x434 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 826 , 826 , // 826 - xh-za + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 828 , 240 , // 827 - xog + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0xf0 , 1 , 828 , 240 , // 828 - xog-ug + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 830 , 240 , // 829 - yav + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x31 , 1 , 830 , 240 , // 830 - yav-cm + 0x3d , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 832 , 240 , // 831 - yi + 0x43d , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x989e, 1 , 832 , 240 , // 832 - yi-001 + 0x6a , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xaf , 1 , 835 , 835 , // 833 - yo + 0x1000 , 0x4e4 , 0x1b5 , 0x2710, 0x1f4 , 0x1c , 1 , 834 , 240 , // 834 - yo-bj + 0x46a , 0x4e4 , 0x1b5 , 0x2710, 0x25 , 0xaf , 1 , 835 , 835 , // 835 - yo-ng + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x68 , 1 , 837 , 240 , // 836 - yue + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x68 , 1 , 837 , 240 , // 837 - yue-hk + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 840 , 316 , // 838 - zgh + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 840 , 316 , // 839 - zgh-tfng + 0x1000 , 0x0 , 0x1 , 0x2 , 0x1f4 , 0x9f , 1 , 840 , 316 , // 840 - zgh-tfng-ma + 0x7804 , 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0x2d , 1 , 844 , 844 , // 841 - zh + 0x4 , 0x3a8 , 0x3a8 , 0x0 , 0x1f4 , 0x2d , 1 , 844 , 844 , // 842 - zh-chs + 0x7c04 , 0x3b6 , 0x3b6 , 0x0 , 0x1f4 , 0x68 , 1 , 851 , 851 , // 843 - zh-cht + 0x804 , 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0x2d , 1 , 844 , 844 , // 844 - zh-cn + 0x50804, 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0x2d , 1 , 844 , 844 , // 845 - zh-cn_phoneb + 0x20804, 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0x2d , 1 , 844 , 844 , // 846 - zh-cn_stroke + 0x4 , 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0x2d , 1 , 844 , 844 , // 847 - zh-hans + 0x1000 , 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0x68 , 1 , 848 , 240 , // 848 - zh-hans-hk + 0x1000 , 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0x97 , 1 , 849 , 240 , // 849 - zh-hans-mo + 0x7c04 , 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0x68 , 1 , 851 , 851 , // 850 - zh-hant + 0xc04 , 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0x68 , 1 , 851 , 851 , // 851 - zh-hk + 0x40c04, 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0x68 , 1 , 851 , 851 , // 852 - zh-hk_radstr + 0x1404 , 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0x97 , 1 , 853 , 853 , // 853 - zh-mo + 0x41404, 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0x97 , 1 , 853 , 853 , // 854 - zh-mo_radstr + 0x21404, 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0x97 , 1 , 853 , 853 , // 855 - zh-mo_stroke + 0x1004 , 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0xd7 , 1 , 856 , 856 , // 856 - zh-sg + 0x51004, 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0xd7 , 1 , 856 , 856 , // 857 - zh-sg_phoneb + 0x21004, 0x3a8 , 0x3a8 , 0x2718, 0x1f4 , 0xd7 , 1 , 856 , 856 , // 858 - zh-sg_stroke + 0x404 , 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0xed , 1 , 859 , 859 , // 859 - zh-tw + 0x30404, 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0xed , 1 , 859 , 859 , // 860 - zh-tw_pronun + 0x40404, 0x3b6 , 0x3b6 , 0x2712, 0x1f4 , 0xed , 1 , 859 , 859 , // 861 - zh-tw_radstr + 0x35 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 863 , 863 , // 862 - zu + 0x435 , 0x4e4 , 0x352 , 0x2710, 0x1f4 , 0xd1 , 1 , 863 , 863 , // 863 - zu-za + }; + + // s_lcids list all supported lcids. used to binary search and we use the index of the matched lcid to + // get the index in s_localeNamesIndices using s_lcidToCultureNameIndices + private static readonly int[] s_lcids = new int[] + { + // Lcid , index - index in c_localeNames + 0x1 , // 0 - 52 + 0x2 , // 1 - 301 + 0x3 , // 2 - 421 + 0x4 , // 3 - 4139 + 0x5 , // 4 - 502 + 0x6 , // 5 - 523 + 0x7 , // 6 - 544 + 0x8 , // 7 - 664 + 0x9 , // 8 - 676 + 0xa , // 9 - 1214 + 0xb , // 10 - 1423 + 0xc , // 11 - 1451 + 0xd , // 12 - 1825 + 0xe , // 13 - 1860 + 0xf , // 14 - 1929 + 0x10 , // 15 - 1936 + 0x11 , // 16 - 1989 + 0x12 , // 17 - 2179 + 0x13 , // 18 - 2685 + 0x14 , // 19 - 2747 + 0x15 , // 20 - 2864 + 0x16 , // 21 - 2897 + 0x17 , // 22 - 3041 + 0x18 , // 23 - 3055 + 0x19 , // 24 - 3076 + 0x1a , // 25 - 1839 + 0x1b , // 26 - 3284 + 0x1c , // 27 - 3387 + 0x1d , // 28 - 3553 + 0x1e , // 29 - 3673 + 0x1f , // 30 - 3727 + 0x20 , // 31 - 3847 + 0x21 , // 32 - 1908 + 0x22 , // 33 - 3840 + 0x23 , // 34 - 276 + 0x24 , // 35 - 3291 + 0x25 , // 36 - 1354 + 0x26 , // 37 - 2429 + 0x27 , // 38 - 2397 + 0x28 , // 39 - 3654 + 0x29 , // 40 - 1377 + 0x2a , // 41 - 3960 + 0x2b , // 42 - 1879 + 0x2c , // 43 - 224 + 0x2d , // 44 - 1361 + 0x2e , // 45 - 1851 + 0x2f , // 46 - 2501 + 0x30 , // 47 - 3541 + 0x31 , // 48 - 3739 + 0x32 , // 49 - 3708 + 0x33 , // 50 - 3953 + 0x34 , // 51 - 4020 + 0x35 , // 52 - 4277 + 0x36 , // 53 - 17 + 0x37 , // 54 - 2062 + 0x38 , // 55 - 1439 + 0x39 , // 56 - 1832 + 0x3a , // 57 - 2598 + 0x3b , // 58 - 3194 + 0x3c , // 59 - 1705 + 0x3d , // 60 - 4045 + 0x3e , // 61 - 2581 + 0x3f , // 62 - 2133 + 0x40 , // 63 - 2306 + 0x41 , // 64 - 3570 + 0x42 , // 65 - 3701 + 0x43 , // 66 - 3859 + 0x44 , // 67 - 3746 + 0x45 , // 68 - 336 + 0x46 , // 69 - 2830 + 0x47 , // 70 - 1754 + 0x48 , // 71 - 2811 + 0x49 , // 72 - 3610 + 0x4a , // 73 - 3632 + 0x4b , // 74 - 2172 + 0x4c , // 75 - 2508 + 0x4d , // 76 - 199 + 0x4e , // 77 - 2574 + 0x4f , // 78 - 3124 + 0x50 , // 79 - 2515 + 0x51 , // 80 - 348 + 0x52 , // 81 - 516 + 0x53 , // 82 - 2165 + 0x54 , // 83 - 2375 + 0x55 , // 84 - 2614 + 0x56 , // 85 - 1719 + 0x57 , // 86 - 2191 + 0x58 , // 87 - 2556 + 0x59 , // 88 - 3158 + 0x5a , // 89 - 3601 + 0x5b , // 90 - 3277 + 0x5c , // 91 - 473 + 0x5d , // 92 - 1953 + 0x5e , // 93 - 45 + 0x5f , // 94 - 3762 + 0x60 , // 95 - 2207 + 0x61 , // 96 - 2673 + 0x62 , // 97 - 1698 + 0x63 , // 98 - 2890 + 0x64 , // 99 - 1430 + 0x65 , // 100 - 620 + 0x66 , // 101 - 308 + 0x67 , // 102 - 1384 + 0x68 , // 103 - 1777 + 0x69 , // 104 - 1899 + 0x6a , // 105 - 4053 + 0x6b , // 106 - 3020 + 0x6c , // 107 - 2765 + 0x6d , // 108 - 260 + 0x6e , // 109 - 2330 + 0x6f , // 110 - 2149 + 0x70 , // 111 - 1915 + 0x71 , // 112 - 2200 + 0x72 , // 113 - 2799 + 0x73 , // 114 - 3680 + 0x74 , // 115 - 1726 + 0x75 , // 116 - 1816 + 0x76 , // 117 - 2313 + 0x77 , // 118 - 3365 + 0x78 , // 119 - 1922 + 0x79 , // 120 - 2854 + 0x7a , // 121 - 190 + 0x7c , // 122 - 2565 + 0x7e , // 123 - 360 + 0x80 , // 124 - 3833 + 0x81 , // 125 - 2494 + 0x82 , // 126 - 2792 + 0x83 , // 127 - 495 + 0x84 , // 128 - 1733 + 0x85 , // 129 - 3131 + 0x86 , // 130 - 2998 + 0x87 , // 131 - 3108 + 0x88 , // 132 - 4002 + 0x8c , // 133 - 2881 + 0x91 , // 134 - 1712 + 0x92 , // 135 - 2270 + 0x401 , // 136 - 150 + 0x402 , // 137 - 303 + 0x403 , // 138 - 428 + 0x404 , // 139 - 4248 + 0x405 , // 140 - 504 + 0x406 , // 141 - 525 + 0x407 , // 142 - 561 + 0x408 , // 143 - 671 + 0x409 , // 144 - 1161 + 0x40a , // 145 - 1272 + 0x40b , // 146 - 1425 + 0x40c , // 147 - 1529 + 0x40d , // 148 - 1827 + 0x40e , // 149 - 1862 + 0x40f , // 150 - 1931 + 0x410 , // 151 - 1943 + 0x411 , // 152 - 1991 + 0x412 , // 153 - 2186 + 0x413 , // 154 - 2707 + 0x414 , // 155 - 2641 + 0x415 , // 156 - 2866 + 0x416 , // 157 - 2904 + 0x417 , // 158 - 3043 + 0x418 , // 159 - 3062 + 0x419 , // 160 - 3098 + 0x41a , // 161 - 1846 + 0x41b , // 162 - 3286 + 0x41c , // 163 - 3389 + 0x41d , // 164 - 3565 + 0x41e , // 165 - 3675 + 0x41f , // 166 - 3734 + 0x420 , // 167 - 3854 + 0x421 , // 168 - 1910 + 0x422 , // 169 - 3842 + 0x423 , // 170 - 278 + 0x424 , // 171 - 3293 + 0x425 , // 172 - 1356 + 0x426 , // 173 - 2431 + 0x427 , // 174 - 2399 + 0x428 , // 175 - 3663 + 0x429 , // 176 - 1379 + 0x42a , // 177 - 3962 + 0x42b , // 178 - 1881 + 0x42c , // 179 - 250 + 0x42d , // 180 - 1363 + 0x42e , // 181 - 1854 + 0x42f , // 182 - 2503 + 0x430 , // 183 - 3548 + 0x431 , // 184 - 3741 + 0x432 , // 185 - 3715 + 0x433 , // 186 - 3955 + 0x434 , // 187 - 4022 + 0x435 , // 188 - 4279 + 0x436 , // 189 - 24 + 0x437 , // 190 - 2064 + 0x438 , // 191 - 1446 + 0x439 , // 192 - 1834 + 0x43a , // 193 - 2600 + 0x43b , // 194 - 3201 + 0x43d , // 195 - 4047 + 0x43e , // 196 - 2588 + 0x43f , // 197 - 2135 + 0x440 , // 198 - 2308 + 0x441 , // 199 - 3577 + 0x442 , // 200 - 3703 + 0x443 , // 201 - 3902 + 0x444 , // 202 - 3748 + 0x445 , // 203 - 343 + 0x446 , // 204 - 2849 + 0x447 , // 205 - 1756 + 0x448 , // 206 - 2813 + 0x449 , // 207 - 3612 + 0x44a , // 208 - 3634 + 0x44b , // 209 - 2174 + 0x44c , // 210 - 2510 + 0x44d , // 211 - 201 + 0x44e , // 212 - 2576 + 0x44f , // 213 - 3126 + 0x450 , // 214 - 2524 + 0x451 , // 215 - 350 + 0x452 , // 216 - 518 + 0x453 , // 217 - 2167 + 0x454 , // 218 - 2377 + 0x455 , // 219 - 2616 + 0x456 , // 220 - 1721 + 0x457 , // 221 - 2194 + 0x458 , // 222 - 2559 + 0x459 , // 223 - 3184 + 0x45a , // 224 - 3604 + 0x45b , // 225 - 3279 + 0x45c , // 226 - 484 + 0x45d , // 227 - 1962 + 0x45e , // 228 - 47 + 0x45f , // 229 - 3773 + 0x460 , // 230 - 2209 + 0x461 , // 231 - 2680 + 0x462 , // 232 - 1700 + 0x463 , // 233 - 2892 + 0x464 , // 234 - 1433 + 0x465 , // 235 - 622 + 0x466 , // 236 - 311 + 0x467 , // 237 - 1418 + 0x468 , // 238 - 1806 + 0x469 , // 239 - 1902 + 0x46a , // 240 - 4060 + 0x46b , // 241 - 3023 + 0x46c , // 242 - 2768 + 0x46d , // 243 - 262 + 0x46e , // 244 - 2332 + 0x46f , // 245 - 2151 + 0x470 , // 246 - 1917 + 0x471 , // 247 - 2202 + 0x472 , // 248 - 2801 + 0x473 , // 249 - 3687 + 0x474 , // 250 - 1728 + 0x475 , // 251 - 1819 + 0x476 , // 252 - 2315 + 0x477 , // 253 - 3382 + 0x478 , // 254 - 1924 + 0x479 , // 255 - 2857 + 0x47a , // 256 - 193 + 0x47c , // 257 - 2568 + 0x47e , // 258 - 362 + 0x480 , // 259 - 3835 + 0x481 , // 260 - 2496 + 0x482 , // 261 - 2794 + 0x483 , // 262 - 497 + 0x484 , // 263 - 1742 + 0x485 , // 264 - 3134 + 0x486 , // 265 - 3009 + 0x487 , // 266 - 3110 + 0x488 , // 267 - 4004 + 0x48c , // 268 - 2884 + 0x491 , // 269 - 1714 + 0x492 , // 270 - 2279 + 0x501 , // 271 - 2972 + 0x5fe , // 272 - 2980 + 0x801 , // 273 - 95 + 0x803 , // 274 - 433 + 0x804 , // 275 - 4110 + 0x807 , // 276 - 556 + 0x809 , // 277 - 831 + 0x80a , // 278 - 1299 + 0x80c , // 279 - 1459 + 0x810 , // 280 - 1938 + 0x813 , // 281 - 2692 + 0x814 , // 282 - 2733 + 0x816 , // 283 - 2944 + 0x818 , // 284 - 3057 + 0x819 , // 285 - 3093 + 0x81a , // 286 - 3480 + 0x81d , // 287 - 3560 + 0x820 , // 288 - 3849 + 0x82c , // 289 - 233 + 0x82e , // 290 - 605 + 0x832 , // 291 - 3710 + 0x83b , // 292 - 3206 + 0x83c , // 293 - 1707 + 0x83e , // 294 - 2583 + 0x843 , // 295 - 3885 + 0x845 , // 296 - 338 + 0x846 , // 297 - 2839 + 0x849 , // 298 - 3617 + 0x850 , // 299 - 2536 + 0x859 , // 300 - 3167 + 0x85d , // 301 - 1979 + 0x85f , // 302 - 3792 + 0x860 , // 303 - 2233 + 0x861 , // 304 - 2675 + 0x867 , // 305 - 1403 + 0x86b , // 306 - 3029 + 0x873 , // 307 - 3682 + 0x901 , // 308 - 2959 + 0x9ff , // 309 - 2989 + 0xc01 , // 310 - 80 + 0xc04 , // 311 - 4173 + 0xc07 , // 312 - 546 + 0xc09 , // 313 - 716 + 0xc0a , // 314 - 1267 + 0xc0c , // 315 - 1484 + 0xc1a , // 316 - 3423 + 0xc3b , // 317 - 3196 + 0xc50 , // 318 - 2546 + 0xc51 , // 319 - 638 + 0xc6b , // 320 - 3035 + 0x1001 , // 321 - 120 + 0x1004 , // 322 - 4219 + 0x1007 , // 323 - 588 + 0x1009 , // 324 - 756 + 0x100a , // 325 - 1289 + 0x100c , // 326 - 1504 + 0x101a , // 327 - 1841 + 0x103b , // 328 - 3316 + 0x105f , // 329 - 3822 + 0x1401 , // 330 - 75 + 0x1404 , // 331 - 4190 + 0x1407 , // 332 - 583 + 0x1409 , // 333 - 1026 + 0x140a , // 334 - 1247 + 0x140c , // 335 - 1569 + 0x141a , // 336 - 402 + 0x143b , // 337 - 3322 + 0x1801 , // 338 - 125 + 0x1809 , // 339 - 881 + 0x180a , // 340 - 1309 + 0x180c , // 341 - 1579 + 0x181a , // 342 - 3470 + 0x183b , // 343 - 3301 + 0x1c01 , // 344 - 180 + 0x1c09 , // 345 - 1191 + 0x1c0a , // 346 - 1257 + 0x1c0c , // 347 - 1453 + 0x1c1a , // 348 - 3413 + 0x1c3b , // 349 - 3307 + 0x2001 , // 350 - 135 + 0x2009 , // 351 - 911 + 0x200a , // 352 - 1349 + 0x200c , // 353 - 1634 + 0x201a , // 354 - 385 + 0x203b , // 355 - 3340 + 0x2401 , // 356 - 185 + 0x2409 , // 357 - 684 + 0x240a , // 358 - 1242 + 0x240c , // 359 - 1489 + 0x241a , // 360 - 3500 + 0x243b , // 361 - 3331 + 0x2801 , // 362 - 170 + 0x2809 , // 363 - 751 + 0x280a , // 364 - 1314 + 0x280c , // 365 - 1649 + 0x281a , // 366 - 3443 + 0x2c01 , // 367 - 100 + 0x2c09 , // 368 - 1136 + 0x2c0a , // 369 - 1222 + 0x2c0c , // 370 - 1514 + 0x2c1a , // 371 - 3490 + 0x3001 , // 372 - 115 + 0x3009 , // 373 - 1201 + 0x300a , // 374 - 1262 + 0x300c , // 375 - 1509 + 0x301a , // 376 - 3433 + 0x3401 , // 377 - 110 + 0x3409 , // 378 - 1036 + 0x340a , // 379 - 1237 + 0x340c , // 380 - 1594 + 0x3801 , // 381 - 60 + 0x3809 , // 382 - 876 + 0x380a , // 383 - 1344 + 0x380c , // 384 - 1574 + 0x3c01 , // 385 - 65 + 0x3c09 , // 386 - 871 + 0x3c0a , // 387 - 1329 + 0x3c0c , // 388 - 1559 + 0x4001 , // 389 - 145 + 0x4009 , // 390 - 896 + 0x400a , // 391 - 1227 + 0x4409 , // 392 - 991 + 0x440a , // 393 - 1334 + 0x4809 , // 394 - 1086 + 0x480a , // 395 - 1294 + 0x4c0a , // 396 - 1304 + 0x500a , // 397 - 1324 + 0x540a , // 398 - 1339 + 0x580a , // 399 - 1216 + 0x5c0a , // 400 - 1252 + 0x641a , // 401 - 378 + 0x681a , // 402 - 395 + 0x6c1a , // 403 - 3406 + 0x701a , // 404 - 3463 + 0x703b , // 405 - 3328 + 0x742c , // 406 - 226 + 0x743b , // 407 - 3337 + 0x7804 , // 408 - 4096 + 0x7814 , // 409 - 2731 + 0x781a , // 410 - 376 + 0x782c , // 411 - 243 + 0x783b , // 412 - 3298 + 0x7843 , // 413 - 3878 + 0x7850 , // 414 - 2517 + 0x785d , // 415 - 1955 + 0x785f , // 416 - 3814 + 0x7c04 , // 417 - 4166 + 0x7c14 , // 418 - 2639 + 0x7c1a , // 419 - 3404 + 0x7c28 , // 420 - 3656 + 0x7c2e , // 421 - 602 + 0x7c3b , // 422 - 3313 + 0x7c43 , // 423 - 3895 + 0x7c46 , // 424 - 2832 + 0x7c50 , // 425 - 2529 + 0x7c59 , // 426 - 3160 + 0x7c5c , // 427 - 476 + 0x7c5d , // 428 - 1972 + 0x7c5f , // 429 - 3784 + 0x7c67 , // 430 - 1396 + 0x7c68 , // 431 - 1779 + 0x7c86 , // 432 - 3001 + 0x7c92 , // 433 - 2272 + 0x1007f, // 434 - 4009 + 0x10407, // 435 - 566 + 0x1040e, // 436 - 1867 + 0x10437, // 437 - 2069 + 0x20804, // 438 - 4127 + 0x21004, // 439 - 4236 + 0x21404, // 440 - 4207 + 0x30404, // 441 - 4253 + 0x40404, // 442 - 4265 + 0x40411, // 443 - 1996 + 0x40c04, // 444 - 4178 + 0x41404, // 445 - 4195 + 0x50804, // 446 - 4115 + 0x51004 // 447 - 4224 + }; + // each element in s_lcidToCultureNameIndices is index to s_localeNamesIndices + private static readonly int[] s_lcidToCultureNameIndices = new int[] + { + // Index to s_localeNamesIndices, index to this array - lcid - index to the c_localeNames + 13 , // 0 - 1 - 52 + 64 , // 1 - 2 - 301 + 88 , // 2 - 3 - 421 + 847 , // 3 - 4 - 4139 + 103 , // 4 - 5 - 502 + 109 , // 5 - 6 - 523 + 114 , // 6 - 7 - 544 + 140 , // 7 - 8 - 664 + 143 , // 8 - 9 - 676 + 251 , // 9 - a - 1214 + 293 , // 10 - b - 1423 + 300 , // 11 - c - 1451 + 377 , // 12 - d - 1825 + 386 , // 13 - e - 1860 + 402 , // 14 - f - 1929 + 404 , // 15 - 10 - 1936 + 413 , // 16 - 11 - 1989 + 452 , // 17 - 12 - 2179 + 564 , // 18 - 13 - 2685 + 578 , // 19 - 14 - 2747 + 605 , // 20 - 15 - 2864 + 613 , // 21 - 16 - 2897 + 637 , // 22 - 17 - 3041 + 641 , // 23 - 18 - 3055 + 646 , // 24 - 19 - 3076 + 381 , // 25 - 1a - 1839 + 687 , // 26 - 1b - 3284 + 709 , // 27 - 1c - 3387 + 734 , // 28 - 1d - 3553 + 760 , // 29 - 1e - 3673 + 774 , // 30 - 1f - 3727 + 795 , // 31 - 20 - 3847 + 396 , // 32 - 21 - 1908 + 793 , // 33 - 22 - 3840 + 58 , // 34 - 23 - 276 + 689 , // 35 - 24 - 3291 + 278 , // 36 - 25 - 1354 + 506 , // 37 - 26 - 2429 + 498 , // 38 - 27 - 2397 + 757 , // 39 - 28 - 3654 + 284 , // 40 - 29 - 1377 + 812 , // 41 - 2a - 3960 + 389 , // 42 - 2b - 1879 + 49 , // 43 - 2c - 224 + 280 , // 44 - 2d - 1361 + 384 , // 45 - 2e - 1851 + 523 , // 46 - 2f - 2501 + 731 , // 47 - 30 - 3541 + 777 , // 48 - 31 - 3739 + 769 , // 49 - 32 - 3708 + 810 , // 50 - 33 - 3953 + 825 , // 51 - 34 - 4020 + 862 , // 52 - 35 - 4277 + 4 , // 53 - 36 - 17 + 425 , // 54 - 37 - 2062 + 297 , // 55 - 38 - 1439 + 379 , // 56 - 39 - 1832 + 543 , // 57 - 3a - 2598 + 670 , // 58 - 3b - 3194 + 352 , // 59 - 3c - 1705 + 831 , // 60 - 3d - 4045 + 539 , // 61 - 3e - 2581 + 440 , // 62 - 3f - 2133 + 476 , // 63 - 40 - 2306 + 738 , // 64 - 41 - 3570 + 767 , // 65 - 42 - 3701 + 798 , // 66 - 43 - 3859 + 779 , // 67 - 44 - 3746 + 71 , // 68 - 45 - 336 + 599 , // 69 - 46 - 2830 + 364 , // 70 - 47 - 1754 + 594 , // 71 - 48 - 2811 + 747 , // 72 - 49 - 3610 + 752 , // 73 - 4a - 3632 + 450 , // 74 - 4b - 2172 + 525 , // 75 - 4c - 2508 + 43 , // 76 - 4d - 199 + 537 , // 77 - 4e - 2574 + 657 , // 78 - 4f - 3124 + 527 , // 79 - 50 - 2515 + 74 , // 80 - 51 - 348 + 107 , // 81 - 52 - 516 + 448 , // 82 - 53 - 2165 + 493 , // 83 - 54 - 2375 + 547 , // 84 - 55 - 2614 + 356 , // 85 - 56 - 1719 + 455 , // 86 - 57 - 2191 + 533 , // 87 - 58 - 2556 + 665 , // 88 - 59 - 3158 + 745 , // 89 - 5a - 3601 + 685 , // 90 - 5b - 3277 + 98 , // 91 - 5c - 473 + 408 , // 92 - 5d - 1953 + 11 , // 93 - 5e - 45 + 783 , // 94 - 5f - 3762 + 459 , // 95 - 60 - 2207 + 561 , // 96 - 61 - 2673 + 350 , // 97 - 62 - 1698 + 611 , // 98 - 63 - 2890 + 295 , // 99 - 64 - 1430 + 129 , // 100 - 65 - 620 + 66 , // 101 - 66 - 308 + 286 , // 102 - 67 - 1384 + 370 , // 103 - 68 - 1777 + 394 , // 104 - 69 - 1899 + 833 , // 105 - 6a - 4053 + 633 , // 106 - 6b - 3020 + 583 , // 107 - 6c - 2765 + 54 , // 108 - 6d - 260 + 482 , // 109 - 6e - 2330 + 444 , // 110 - 6f - 2149 + 398 , // 111 - 70 - 1915 + 457 , // 112 - 71 - 2200 + 591 , // 113 - 72 - 2799 + 762 , // 114 - 73 - 3680 + 358 , // 115 - 74 - 1726 + 375 , // 116 - 75 - 1816 + 478 , // 117 - 76 - 2313 + 704 , // 118 - 77 - 3365 + 400 , // 119 - 78 - 1922 + 603 , // 120 - 79 - 2854 + 41 , // 121 - 7a - 190 + 535 , // 122 - 7c - 2565 + 77 , // 123 - 7e - 360 + 791 , // 124 - 80 - 3833 + 521 , // 125 - 81 - 2494 + 589 , // 126 - 82 - 2792 + 101 , // 127 - 83 - 495 + 360 , // 128 - 84 - 1733 + 659 , // 129 - 85 - 3131 + 630 , // 130 - 86 - 2998 + 653 , // 131 - 87 - 3108 + 822 , // 132 - 88 - 4002 + 609 , // 133 - 8c - 2881 + 354 , // 134 - 91 - 1712 + 470 , // 135 - 92 - 2270 + 33 , // 136 - 401 - 150 + 65 , // 137 - 402 - 303 + 90 , // 138 - 403 - 428 + 859 , // 139 - 404 - 4248 + 104 , // 140 - 405 - 504 + 110 , // 141 - 406 - 525 + 118 , // 142 - 407 - 561 + 142 , // 143 - 408 - 671 + 240 , // 144 - 409 - 1161 + 263 , // 145 - 40a - 1272 + 294 , // 146 - 40b - 1425 + 316 , // 147 - 40c - 1529 + 378 , // 148 - 40d - 1827 + 387 , // 149 - 40e - 1862 + 403 , // 150 - 40f - 1931 + 406 , // 151 - 410 - 1943 + 414 , // 152 - 411 - 1991 + 454 , // 153 - 412 - 2186 + 569 , // 154 - 413 - 2707 + 554 , // 155 - 414 - 2641 + 606 , // 156 - 415 - 2866 + 615 , // 157 - 416 - 2904 + 638 , // 158 - 417 - 3043 + 643 , // 159 - 418 - 3062 + 651 , // 160 - 419 - 3098 + 383 , // 161 - 41a - 1846 + 688 , // 162 - 41b - 3286 + 710 , // 163 - 41c - 3389 + 737 , // 164 - 41d - 3565 + 761 , // 165 - 41e - 3675 + 776 , // 166 - 41f - 3734 + 797 , // 167 - 420 - 3854 + 397 , // 168 - 421 - 1910 + 794 , // 169 - 422 - 3842 + 59 , // 170 - 423 - 278 + 690 , // 171 - 424 - 3293 + 279 , // 172 - 425 - 1356 + 507 , // 173 - 426 - 2431 + 499 , // 174 - 427 - 2399 + 759 , // 175 - 428 - 3663 + 285 , // 176 - 429 - 1379 + 813 , // 177 - 42a - 3962 + 390 , // 178 - 42b - 1881 + 53 , // 179 - 42c - 250 + 281 , // 180 - 42d - 1363 + 385 , // 181 - 42e - 1854 + 524 , // 182 - 42f - 2503 + 733 , // 183 - 430 - 3548 + 778 , // 184 - 431 - 3741 + 771 , // 185 - 432 - 3715 + 811 , // 186 - 433 - 3955 + 826 , // 187 - 434 - 4022 + 863 , // 188 - 435 - 4279 + 6 , // 189 - 436 - 24 + 426 , // 190 - 437 - 2064 + 299 , // 191 - 438 - 1446 + 380 , // 192 - 439 - 1834 + 544 , // 193 - 43a - 2600 + 672 , // 194 - 43b - 3201 + 832 , // 195 - 43d - 4047 + 541 , // 196 - 43e - 2588 + 441 , // 197 - 43f - 2135 + 477 , // 198 - 440 - 2308 + 740 , // 199 - 441 - 3577 + 768 , // 200 - 442 - 3703 + 804 , // 201 - 443 - 3902 + 780 , // 202 - 444 - 3748 + 73 , // 203 - 445 - 343 + 602 , // 204 - 446 - 2849 + 365 , // 205 - 447 - 1756 + 595 , // 206 - 448 - 2813 + 748 , // 207 - 449 - 3612 + 753 , // 208 - 44a - 3634 + 451 , // 209 - 44b - 2174 + 526 , // 210 - 44c - 2510 + 44 , // 211 - 44d - 201 + 538 , // 212 - 44e - 2576 + 658 , // 213 - 44f - 3126 + 529 , // 214 - 450 - 2524 + 75 , // 215 - 451 - 350 + 108 , // 216 - 452 - 518 + 449 , // 217 - 453 - 2167 + 494 , // 218 - 454 - 2377 + 548 , // 219 - 455 - 2616 + 357 , // 220 - 456 - 1721 + 456 , // 221 - 457 - 2194 + 534 , // 222 - 458 - 2559 + 669 , // 223 - 459 - 3184 + 746 , // 224 - 45a - 3604 + 686 , // 225 - 45b - 3279 + 100 , // 226 - 45c - 484 + 410 , // 227 - 45d - 1962 + 12 , // 228 - 45e - 47 + 785 , // 229 - 45f - 3773 + 460 , // 230 - 460 - 2209 + 563 , // 231 - 461 - 2680 + 351 , // 232 - 462 - 1700 + 612 , // 233 - 463 - 2892 + 296 , // 234 - 464 - 1433 + 130 , // 235 - 465 - 622 + 67 , // 236 - 466 - 311 + 292 , // 237 - 467 - 1418 + 374 , // 238 - 468 - 1806 + 395 , // 239 - 469 - 1902 + 835 , // 240 - 46a - 4060 + 634 , // 241 - 46b - 3023 + 584 , // 242 - 46c - 2768 + 55 , // 243 - 46d - 262 + 483 , // 244 - 46e - 2332 + 445 , // 245 - 46f - 2151 + 399 , // 246 - 470 - 1917 + 458 , // 247 - 471 - 2202 + 592 , // 248 - 472 - 2801 + 764 , // 249 - 473 - 3687 + 359 , // 250 - 474 - 1728 + 376 , // 251 - 475 - 1819 + 479 , // 252 - 476 - 2315 + 708 , // 253 - 477 - 3382 + 401 , // 254 - 478 - 1924 + 604 , // 255 - 479 - 2857 + 42 , // 256 - 47a - 193 + 536 , // 257 - 47c - 2568 + 78 , // 258 - 47e - 362 + 792 , // 259 - 480 - 3835 + 522 , // 260 - 481 - 2496 + 590 , // 261 - 482 - 2794 + 102 , // 262 - 483 - 497 + 362 , // 263 - 484 - 1742 + 660 , // 264 - 485 - 3134 + 632 , // 265 - 486 - 3009 + 654 , // 266 - 487 - 3110 + 823 , // 267 - 488 - 4004 + 610 , // 268 - 48c - 2884 + 355 , // 269 - 491 - 1714 + 472 , // 270 - 492 - 2279 + 627 , // 271 - 501 - 2972 + 628 , // 272 - 5fe - 2980 + 22 , // 273 - 801 - 95 + 91 , // 274 - 803 - 433 + 844 , // 275 - 804 - 4110 + 117 , // 276 - 807 - 556 + 174 , // 277 - 809 - 831 + 267 , // 278 - 80a - 1299 + 302 , // 279 - 80c - 1459 + 405 , // 280 - 810 - 1938 + 566 , // 281 - 813 - 2692 + 575 , // 282 - 814 - 2733 + 623 , // 283 - 816 - 2944 + 642 , // 284 - 818 - 3057 + 650 , // 285 - 819 - 3093 + 722 , // 286 - 81a - 3480 + 736 , // 287 - 81d - 3560 + 796 , // 288 - 820 - 3849 + 51 , // 289 - 82c - 233 + 126 , // 290 - 82e - 605 + 770 , // 291 - 832 - 3710 + 673 , // 292 - 83b - 3206 + 353 , // 293 - 83c - 1707 + 540 , // 294 - 83e - 2583 + 802 , // 295 - 843 - 3885 + 72 , // 296 - 845 - 338 + 601 , // 297 - 846 - 2839 + 749 , // 298 - 849 - 3617 + 531 , // 299 - 850 - 2536 + 667 , // 300 - 859 - 3167 + 412 , // 301 - 85d - 1979 + 787 , // 302 - 85f - 3792 + 463 , // 303 - 860 - 2233 + 562 , // 304 - 861 - 2675 + 290 , // 305 - 867 - 1403 + 635 , // 306 - 86b - 3029 + 763 , // 307 - 873 - 3682 + 626 , // 308 - 901 - 2959 + 629 , // 309 - 9ff - 2989 + 19 , // 310 - c01 - 80 + 851 , // 311 - c04 - 4173 + 115 , // 312 - c07 - 546 + 151 , // 313 - c09 - 716 + 262 , // 314 - c0a - 1267 + 307 , // 315 - c0c - 1484 + 716 , // 316 - c1a - 3423 + 671 , // 317 - c3b - 3196 + 532 , // 318 - c50 - 2546 + 134 , // 319 - c51 - 638 + 636 , // 320 - c6b - 3035 + 27 , // 321 - 1001 - 120 + 856 , // 322 - 1004 - 4219 + 122 , // 323 - 1007 - 588 + 159 , // 324 - 1009 - 756 + 265 , // 325 - 100a - 1289 + 311 , // 326 - 100c - 1504 + 382 , // 327 - 101a - 1841 + 695 , // 328 - 103b - 3316 + 790 , // 329 - 105f - 3822 + 18 , // 330 - 1401 - 75 + 853 , // 331 - 1404 - 4190 + 121 , // 332 - 1407 - 583 + 213 , // 333 - 1409 - 1026 + 258 , // 334 - 140a - 1247 + 324 , // 335 - 140c - 1569 + 85 , // 336 - 141a - 402 + 696 , // 337 - 143b - 3322 + 28 , // 338 - 1801 - 125 + 184 , // 339 - 1809 - 881 + 269 , // 340 - 180a - 1309 + 326 , // 341 - 180c - 1579 + 721 , // 342 - 181a - 3470 + 692 , // 343 - 183b - 3301 + 39 , // 344 - 1c01 - 180 + 246 , // 345 - 1c09 - 1191 + 260 , // 346 - 1c0a - 1257 + 301 , // 347 - 1c0c - 1453 + 715 , // 348 - 1c1a - 3413 + 693 , // 349 - 1c3b - 3307 + 30 , // 350 - 2001 - 135 + 190 , // 351 - 2009 - 911 + 277 , // 352 - 200a - 1349 + 337 , // 353 - 200c - 1634 + 83 , // 354 - 201a - 385 + 700 , // 355 - 203b - 3340 + 40 , // 356 - 2401 - 185 + 145 , // 357 - 2409 - 684 + 257 , // 358 - 240a - 1242 + 308 , // 359 - 240c - 1489 + 724 , // 360 - 241a - 3500 + 698 , // 361 - 243b - 3331 + 37 , // 362 - 2801 - 170 + 158 , // 363 - 2809 - 751 + 270 , // 364 - 280a - 1314 + 340 , // 365 - 280c - 1649 + 718 , // 366 - 281a - 3443 + 23 , // 367 - 2c01 - 100 + 235 , // 368 - 2c09 - 1136 + 253 , // 369 - 2c0a - 1222 + 313 , // 370 - 2c0c - 1514 + 723 , // 371 - 2c1a - 3490 + 26 , // 372 - 3001 - 115 + 248 , // 373 - 3009 - 1201 + 261 , // 374 - 300a - 1262 + 312 , // 375 - 300c - 1509 + 717 , // 376 - 301a - 3433 + 25 , // 377 - 3401 - 110 + 215 , // 378 - 3409 - 1036 + 256 , // 379 - 340a - 1237 + 329 , // 380 - 340c - 1594 + 15 , // 381 - 3801 - 60 + 183 , // 382 - 3809 - 876 + 276 , // 383 - 380a - 1344 + 325 , // 384 - 380c - 1574 + 16 , // 385 - 3c01 - 65 + 182 , // 386 - 3c09 - 871 + 273 , // 387 - 3c0a - 1329 + 322 , // 388 - 3c0c - 1559 + 32 , // 389 - 4001 - 145 + 187 , // 390 - 4009 - 896 + 254 , // 391 - 400a - 1227 + 206 , // 392 - 4409 - 991 + 274 , // 393 - 440a - 1334 + 225 , // 394 - 4809 - 1086 + 266 , // 395 - 480a - 1294 + 268 , // 396 - 4c0a - 1304 + 272 , // 397 - 500a - 1324 + 275 , // 398 - 540a - 1339 + 252 , // 399 - 580a - 1216 + 259 , // 400 - 5c0a - 1252 + 82 , // 401 - 641a - 378 + 84 , // 402 - 681a - 395 + 714 , // 403 - 6c1a - 3406 + 720 , // 404 - 701a - 3463 + 697 , // 405 - 703b - 3328 + 50 , // 406 - 742c - 226 + 699 , // 407 - 743b - 3337 + 841 , // 408 - 7804 - 4096 + 574 , // 409 - 7814 - 2731 + 81 , // 410 - 781a - 376 + 52 , // 411 - 782c - 243 + 691 , // 412 - 783b - 3298 + 801 , // 413 - 7843 - 3878 + 528 , // 414 - 7850 - 2517 + 409 , // 415 - 785d - 1955 + 789 , // 416 - 785f - 3814 + 850 , // 417 - 7c04 - 4166 + 553 , // 418 - 7c14 - 2639 + 713 , // 419 - 7c1a - 3404 + 758 , // 420 - 7c28 - 3656 + 125 , // 421 - 7c2e - 602 + 694 , // 422 - 7c3b - 3313 + 803 , // 423 - 7c43 - 3895 + 600 , // 424 - 7c46 - 2832 + 530 , // 425 - 7c50 - 2529 + 666 , // 426 - 7c59 - 3160 + 99 , // 427 - 7c5c - 476 + 411 , // 428 - 7c5d - 1972 + 786 , // 429 - 7c5f - 3784 + 289 , // 430 - 7c67 - 1396 + 371 , // 431 - 7c68 - 1779 + 631 , // 432 - 7c86 - 3001 + 471 , // 433 - 7c92 - 2272 + 824 , // 434 - 1007f - 4009 + 119 , // 435 - 10407 - 566 + 388 , // 436 - 1040e - 1867 + 427 , // 437 - 10437 - 2069 + 846 , // 438 - 20804 - 4127 + 858 , // 439 - 21004 - 4236 + 855 , // 440 - 21404 - 4207 + 860 , // 441 - 30404 - 4253 + 861 , // 442 - 40404 - 4265 + 415 , // 443 - 40411 - 1996 + 852 , // 444 - 40c04 - 4178 + 854 , // 445 - 41404 - 4195 + 845 , // 446 - 50804 - 4115 + 857 // 447 - 51004 - 4224 + }; + + internal static string LCIDToLocaleName(int culture) + { + int left = 0; + int right = s_lcids.Length - 1; + int index; + + Debug.Assert(s_lcids.Length == s_lcidToCultureNameIndices.Length); + + while (left <= right) + { + index = (right + left) / 2; + + if (culture == s_lcids[index]) + { + int indexToLocaleNamesIndices = s_lcidToCultureNameIndices[index]; + Debug.Assert(indexToLocaleNamesIndices < s_localeNamesIndices.Length - 1); + + return c_localeNames.Substring(s_localeNamesIndices[indexToLocaleNamesIndices], + s_localeNamesIndices[indexToLocaleNamesIndices + 1] - + s_localeNamesIndices[indexToLocaleNamesIndices]); + } + else if (culture < s_lcids[index]) + { + right = index - 1; + } + else + { + left = index + 1; + } + } + + return null; + } + + internal static int GetLocaleDataNumericPart(string cultureName, LocaleDataParts part) + { + int index = SearchCultureName(cultureName); + if (index < 0) + { + return -1; + } + + Debug.Assert((s_localeNamesIndices.Length-1 == (s_nameIndexToNumericData.Length/NUMERIC_LOCALE_DATA_COUNT_PER_ROW)) && + index < s_localeNamesIndices.Length); + + return s_nameIndexToNumericData[index * NUMERIC_LOCALE_DATA_COUNT_PER_ROW + (int) part]; + } + + internal static string GetThreeLetterWindowsLangageName(string cultureName) + { + int index = SearchCultureName(cultureName); + if (index < 0) + { + return null; + } + + Debug.Assert(s_localeNamesIndices.Length-1 == (c_threeLetterWindowsLanguageName.Length / 3)); + return c_threeLetterWindowsLanguageName.Substring(index * 3, 3); + } + + internal static string GetLocaleDataMappedCulture(string cultureName, LocaleDataParts part) + { + int indexToIndicesTable = GetLocaleDataNumericPart(cultureName, part); + if (indexToIndicesTable < 0) + { + return ""; // fallback to invariant + } + + Debug.Assert(indexToIndicesTable < s_localeNamesIndices.Length-1); + + return c_localeNames.Substring(s_localeNamesIndices[indexToIndicesTable], + s_localeNamesIndices[indexToIndicesTable+1] - s_localeNamesIndices[indexToIndicesTable]); + } + + internal static string GetSpecificCultureName(string cultureName) + { + return GetLocaleDataMappedCulture(cultureName, LocaleDataParts.SpecificLocaleIndex); + } + + internal static string GetConsoleUICulture(string cultureName) + { + return GetLocaleDataMappedCulture(cultureName, LocaleDataParts.ConsoleLocaleIndex); + } + + // SearchCultureName will binary search c_localeNames using s_localeNamesIndices. + // return index in s_localeNamesIndices, or -1 if it fail finding any match + private static int SearchCultureName(string name) + { + int left = 0; + int right = s_localeNamesIndices.Length - 2; + int index; + int result; + + Debug.Assert(s_localeNamesIndices[s_localeNamesIndices.Length - 1] == c_localeNames.Length); + + name = CultureData.AnsiToLower(name); + + // Binary search the array until we have only a couple of elements left and then + // just walk those elements. + while ((right - left) > 3) + { + index = ((right - left) / 2) + left; + + Debug.Assert(index < s_localeNamesIndices.Length - 1); + result = CompareOrdinal(name, c_localeNames, s_localeNamesIndices[index], s_localeNamesIndices[index + 1] - s_localeNamesIndices[index]); + if (result == 0) + { + return index; + } + else if (result < 0) + { + right = index; + } + else + { + left = index; + } + } + + // Walk the remaining elements (it'll be 3 or fewer). + for (; left <= right; left++) + { + Debug.Assert(left < s_localeNamesIndices.Length - 1); + if (CompareOrdinal(name, c_localeNames, s_localeNamesIndices[left], s_localeNamesIndices[left + 1] - s_localeNamesIndices[left]) == 0) + { + return (left); + } + } + + // couldn't find culture name + return -1; + } + + // optimized to avoid parameters checking + private static int CompareOrdinal(string s1, string s2, int index, int length) + { + int count = s1.Length; + if (count > length) + count = length; + + int i = 0; + while (i < count && s1[i] == s2[index + i]) + i++; + + if (i < count) + return (int)(s1[i] - s2[index + i]); + + return s1.Length - length; + } + } +} diff --git a/src/mscorlib/corefx/System/Globalization/NumberFormatInfo.cs b/src/mscorlib/corefx/System/Globalization/NumberFormatInfo.cs index 6a25eb2c4d..813554fd21 100644 --- a/src/mscorlib/corefx/System/Globalization/NumberFormatInfo.cs +++ b/src/mscorlib/corefx/System/Globalization/NumberFormatInfo.cs @@ -42,8 +42,7 @@ namespace System.Globalization // [Serializable] - [System.Runtime.InteropServices.ComVisible(true)] - sealed public partial class NumberFormatInfo : IFormatProvider, ICloneable + sealed public class NumberFormatInfo : IFormatProvider, ICloneable { // invariantInfo is constant irrespective of your current culture. private static volatile NumberFormatInfo s_invariantInfo; @@ -84,6 +83,8 @@ namespace System.Globalization internal int percentNegativePattern = 0; internal int percentDecimalDigits = 2; + [OptionalField(VersionAdded = 2)] + internal int digitSubstitution = (int) DigitShapes.None; internal bool isReadOnly = false; @@ -130,6 +131,64 @@ namespace System.Globalization Contract.EndContractBlock(); } + private static void VerifyNativeDigits(string [] nativeDig, string propertyName) + { + if (nativeDig == null) + { + throw new ArgumentNullException(propertyName, SR.ArgumentNull_Array); + } + + if (nativeDig.Length != 10) + { + throw new ArgumentException(SR.Argument_InvalidNativeDigitCount, propertyName); + } + Contract.EndContractBlock(); + + for (int i = 0; i < nativeDig.Length; i++) + { + if (nativeDig[i] == null) + { + throw new ArgumentNullException(propertyName, SR.ArgumentNull_ArrayValue); + } + + if (nativeDig[i].Length != 1) + { + if (nativeDig[i].Length != 2) + { + // Not 1 or 2 UTF-16 code points + throw new ArgumentException(SR.Argument_InvalidNativeDigitValue, propertyName); + } + else if (!char.IsSurrogatePair(nativeDig[i][0], nativeDig[i][1])) + { + // 2 UTF-6 code points, but not a surrogate pair + throw new ArgumentException(SR.Argument_InvalidNativeDigitValue, propertyName); + } + } + + if (CharUnicodeInfo.GetDecimalDigitValue(nativeDig[i], 0) != i && + CharUnicodeInfo.GetUnicodeCategory(nativeDig[i], 0) != UnicodeCategory.PrivateUse) + { + // Not the appropriate digit according to the Unicode data properties + // (Digit 0 must be a 0, etc.). + throw new ArgumentException(SR.Argument_InvalidNativeDigitValue, propertyName); + } + } + } + + private static void VerifyDigitSubstitution(DigitShapes digitSub, string propertyName) + { + switch (digitSub) + { + case DigitShapes.Context: + case DigitShapes.None: + case DigitShapes.NativeNational: + // Success. + break; + + default: + throw new ArgumentException(SR.Argument_InvalidDigitSubstitution, propertyName); + } + } internal NumberFormatInfo(CultureData cultureData) { @@ -748,6 +807,28 @@ namespace System.Globalization } } + public string [] NativeDigits + { + get { return (String[]) nativeDigits.Clone(); } + set + { + VerifyWritable(); + VerifyNativeDigits(value, "NativeDigits"); + nativeDigits = value; + } + } + + public DigitShapes DigitSubstitution + { + get { return (DigitShapes) digitSubstitution; } + set + { + VerifyWritable(); + VerifyDigitSubstitution(value, "DigitSubstitution"); + digitSubstitution = (int) value; + } + } + public Object GetFormat(Type formatType) { return formatType == typeof(NumberFormatInfo) ? this : null; @@ -757,7 +838,7 @@ namespace System.Globalization { if (nfi == null) { - throw new ArgumentNullException("nfi"); + throw new ArgumentNullException(nameof(nfi)); } Contract.EndContractBlock(); if (nfi.IsReadOnly) @@ -781,7 +862,7 @@ namespace System.Globalization // Check for undefined flags if ((style & InvalidNumberStyles) != 0) { - throw new ArgumentException(SR.Argument_InvalidNumberStyles, "style"); + throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style)); } Contract.EndContractBlock(); if ((style & NumberStyles.AllowHexSpecifier) != 0) @@ -798,7 +879,7 @@ namespace System.Globalization // Check for undefined flags if ((style & InvalidNumberStyles) != 0) { - throw new ArgumentException(SR.Argument_InvalidNumberStyles, "style"); + throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style)); } Contract.EndContractBlock(); if ((style & NumberStyles.AllowHexSpecifier) != 0) diff --git a/src/mscorlib/corefx/System/Globalization/PersianCalendar.cs b/src/mscorlib/corefx/System/Globalization/PersianCalendar.cs index 57bef7eee1..b8b3da6911 100644 --- a/src/mscorlib/corefx/System/Globalization/PersianCalendar.cs +++ b/src/mscorlib/corefx/System/Globalization/PersianCalendar.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; +using System.Diagnostics; using System.Diagnostics.Contracts; namespace System.Globalization @@ -65,15 +65,13 @@ namespace System.Globalization } } - // Return the type of the Persian calendar. - // - - - //public override CalendarAlgorithmType AlgorithmType { - // get { - // return CalendarAlgorithmType.SolarCalendar; - // } - //} + public override CalendarAlgorithmType AlgorithmType + { + get + { + return CalendarAlgorithmType.SolarCalendar; + } + } // Construct an instance of Persian calendar. @@ -138,7 +136,7 @@ namespace System.Globalization { if (era != CurrentEra && era != PersianEra) { - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } } @@ -148,7 +146,7 @@ namespace System.Globalization if (year < 1 || year > MaxCalendarYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -165,7 +163,7 @@ namespace System.Globalization if (month > MaxCalendarMonth) { throw new ArgumentOutOfRangeException( - "month", + nameof(month), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -176,13 +174,13 @@ namespace System.Globalization if (month < 1 || month > 12) { - throw new ArgumentOutOfRangeException("month", SR.ArgumentOutOfRange_Month); + throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month); } } private static int MonthFromOrdinalDay(int ordinalDay) { - Contract.Assert(ordinalDay <= 366); + Debug.Assert(ordinalDay <= 366); int index = 0; while (ordinalDay > DaysToMonth[index]) index++; @@ -192,7 +190,7 @@ namespace System.Globalization private static int DaysInPreviousMonths(int month) { - Contract.Assert(1 <= month && month <= 12); + Debug.Assert(1 <= month && month <= 12); --month; // months are one based but for calculations use 0 based return DaysToMonth[month]; } @@ -223,7 +221,7 @@ namespace System.Globalization long yearStart = CalendricalCalculationsHelper.PersianNewYearOnOrBefore(NumDays); int y = (int)(Math.Floor(((yearStart - PersianEpoch) / CalendricalCalculationsHelper.MeanTropicalYearInDays) + 0.5)) + 1; - Contract.Assert(y >= 1); + Debug.Assert(y >= 1); if (part == DatePartYear) { @@ -242,16 +240,16 @@ namespace System.Globalization } int m = MonthFromOrdinalDay(ordinalDay); - Contract.Assert(ordinalDay >= 1); - Contract.Assert(m >= 1 && m <= 12); + Debug.Assert(ordinalDay >= 1); + Debug.Assert(m >= 1 && m <= 12); if (part == DatePartMonth) { return m; } int d = ordinalDay - DaysInPreviousMonths(m); - Contract.Assert(1 <= d); - Contract.Assert(d <= 31); + Debug.Assert(1 <= d); + Debug.Assert(d <= 31); // // Calculate the Persian Day. @@ -290,7 +288,7 @@ namespace System.Globalization if (months < -120000 || months > 120000) { throw new ArgumentOutOfRangeException( - "months", + nameof(months), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -387,7 +385,7 @@ namespace System.Globalization int daysInMonth = DaysToMonth[month] - DaysToMonth[month - 1]; if ((month == MonthsPerYear) && !IsLeapYear(year)) { - Contract.Assert(daysInMonth == 30); + Debug.Assert(daysInMonth == 30); --daysInMonth; } return daysInMonth; @@ -471,7 +469,7 @@ namespace System.Globalization if (day < 1 || day > daysInMonth) { throw new ArgumentOutOfRangeException( - "day", + nameof(day), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Day, @@ -531,7 +529,7 @@ namespace System.Globalization { // BCLDebug.Log("year = " + year + ", month = " + month + ", day = " + day); throw new ArgumentOutOfRangeException( - "day", + nameof(day), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Day, @@ -570,7 +568,7 @@ namespace System.Globalization if (value < 99 || value > MaxCalendarYear) { throw new ArgumentOutOfRangeException( - "value", + nameof(value), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -587,7 +585,7 @@ namespace System.Globalization { if (year < 0) { - throw new ArgumentOutOfRangeException("year", + throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedNonNegNum); } Contract.EndContractBlock(); @@ -600,7 +598,7 @@ namespace System.Globalization if (year > MaxCalendarYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, diff --git a/src/mscorlib/corefx/System/Globalization/RegionInfo.cs b/src/mscorlib/corefx/System/Globalization/RegionInfo.cs index 0669349040..0645ded0ab 100644 --- a/src/mscorlib/corefx/System/Globalization/RegionInfo.cs +++ b/src/mscorlib/corefx/System/Globalization/RegionInfo.cs @@ -14,15 +14,14 @@ // //////////////////////////////////////////////////////////////////////////// -using System; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.Serialization; namespace System.Globalization { [Serializable] - [System.Runtime.InteropServices.ComVisible(true)] - public partial class RegionInfo + public class RegionInfo { //--------------------------------------------------------------------// // Internal Information // @@ -61,11 +60,11 @@ namespace System.Globalization public RegionInfo(String name) { if (name == null) - throw new ArgumentNullException("name"); + throw new ArgumentNullException(nameof(name)); if (name.Length == 0) //The InvariantCulture has no matching region { - throw new ArgumentException(SR.Argument_NoRegionInvariantCulture, "name"); + throw new ArgumentException(SR.Argument_NoRegionInvariantCulture, nameof(name)); } Contract.EndContractBlock(); @@ -78,16 +77,46 @@ namespace System.Globalization throw new ArgumentException( String.Format( CultureInfo.CurrentCulture, - SR.Argument_InvalidCultureName, name), "name"); + SR.Argument_InvalidCultureName, name), nameof(name)); // Not supposed to be neutral if (_cultureData.IsNeutralCulture) - throw new ArgumentException(SR.Format(SR.Argument_InvalidNeutralRegionName, name), "name"); + throw new ArgumentException(SR.Format(SR.Argument_InvalidNeutralRegionName, name), nameof(name)); SetName(name); } + [System.Security.SecuritySafeCritical] // auto-generated + public RegionInfo(int culture) + { + if (culture == CultureInfo.LOCALE_INVARIANT) //The InvariantCulture has no matching region + { + throw new ArgumentException(SR.Argument_NoRegionInvariantCulture); + } + + if (culture == CultureInfo.LOCALE_NEUTRAL) + { + // Not supposed to be neutral + throw new ArgumentException(SR.Format(SR.Argument_CultureIsNeutral, culture), nameof(culture)); + } + + if (culture == CultureInfo.LOCALE_CUSTOM_DEFAULT) + { + // Not supposed to be neutral + throw new ArgumentException(SR.Format(SR.Argument_CustomCultureCannotBePassedByNumber, culture), nameof(culture)); + } + + _cultureData = CultureData.GetCultureData(culture, true); + _name = _cultureData.SREGIONNAME; + + if (_cultureData.IsNeutralCulture) + { + // Not supposed to be neutral + throw new ArgumentException(SR.Format(SR.Argument_CultureIsNeutral, culture), nameof(culture)); + } + } + internal RegionInfo(CultureData cultureData) { _cultureData = cultureData; @@ -156,7 +185,7 @@ namespace System.Globalization { get { - Contract.Assert(_name != null, "Expected RegionInfo._name to be populated already"); + Debug.Assert(_name != null, "Expected RegionInfo._name to be populated already"); return (_name); } } @@ -202,7 +231,6 @@ namespace System.Globalization // WARNING: You need a full locale name for this to make sense. // //////////////////////////////////////////////////////////////////////// - [System.Runtime.InteropServices.ComVisible(false)] public virtual String NativeName { get @@ -226,6 +254,38 @@ namespace System.Globalization } } + //////////////////////////////////////////////////////////////////////// + // + // ThreeLetterISORegionName + // + // Returns the three letter ISO region name (ie: USA) + // + //////////////////////////////////////////////////////////////////////// + public virtual String ThreeLetterISORegionName + { + get + { + return (_cultureData.SISO3166CTRYNAME2); + } + } + + //////////////////////////////////////////////////////////////////////// + // + // ThreeLetterWindowsRegionName + // + // Returns the three letter windows region name (ie: USA) + // + //////////////////////////////////////////////////////////////////////// + public virtual String ThreeLetterWindowsRegionName + { + get + { + // ThreeLetterWindowsRegionName is really same as ThreeLetterISORegionName + return ThreeLetterISORegionName; + } + } + + //////////////////////////////////////////////////////////////////////// // // IsMetric @@ -242,6 +302,45 @@ namespace System.Globalization } } + public virtual int GeoId + { + get + { + return (_cultureData.IGEOID); + } + } + + //////////////////////////////////////////////////////////////////////// + // + // CurrencyEnglishName + // + // English name for this region's currency, ie: Swiss Franc + // + //////////////////////////////////////////////////////////////////////// + public virtual string CurrencyEnglishName + { + get + { + return (_cultureData.SENGLISHCURRENCY); + } + } + + //////////////////////////////////////////////////////////////////////// + // + // CurrencyNativeName + // + // Native name for this region's currency, ie: Schweizer Franken + // WARNING: You need a full locale name for this to make sense. + // + //////////////////////////////////////////////////////////////////////// + public virtual string CurrencyNativeName + { + get + { + return (_cultureData.SNATIVECURRENCY); + } + } + //////////////////////////////////////////////////////////////////////// // // CurrencySymbol diff --git a/src/mscorlib/corefx/System/Globalization/STUBS.cs b/src/mscorlib/corefx/System/Globalization/STUBS.cs deleted file mode 100644 index 8c85e836d6..0000000000 --- a/src/mscorlib/corefx/System/Globalization/STUBS.cs +++ /dev/null @@ -1,180 +0,0 @@ -namespace System.Globalization -{ - public abstract partial class Calendar : System.ICloneable - { - [System.Runtime.InteropServices.ComVisibleAttribute(false)] - public virtual int GetLeapMonth(int year) { throw new NotImplementedException(); } - } - - [System.Runtime.InteropServices.ComVisible(true)] - public enum CalendarAlgorithmType - { - Unknown = 0, // This is the default value to return in the Calendar base class. - SolarCalendar = 1, // Solar-base calendar, such as GregorianCalendar, jaoaneseCalendar, JulianCalendar, etc. - // Solar calendars are based on the solar year and seasons. - LunarCalendar = 2, // Lunar-based calendar, such as Hijri and UmAlQuraCalendar. - // Lunar calendars are based on the path of the moon. The seasons are not accurately represented. - LunisolarCalendar = 3 // Lunisolar-based calendar which use leap month rule, such as HebrewCalendar and Asian Lunisolar calendars. - // Lunisolar calendars are based on the cycle of the moon, but consider the seasons as a secondary consideration, - // so they align with the seasons as well as lunar events. - } - - public static partial class CharUnicodeInfo - { - public static int GetDecimalDigitValue(char ch) { throw new NotImplementedException(); } - public static int GetDecimalDigitValue(string s, int index) { throw new NotImplementedException(); } - public static int GetDigitValue(char ch) { throw new NotImplementedException(); } - public static int GetDigitValue(string s, int index) { throw new NotImplementedException(); } - } - - public partial class CompareInfo : System.Runtime.Serialization.IDeserializationCallback - { - public int LCID { get { throw new NotImplementedException(); } } - public static System.Globalization.CompareInfo GetCompareInfo(int culture) { throw new NotImplementedException(); } - public static System.Globalization.CompareInfo GetCompareInfo(int culture, System.Reflection.Assembly assembly) { throw new NotImplementedException(); } - public static System.Globalization.CompareInfo GetCompareInfo(string name, System.Reflection.Assembly assembly) { throw new NotImplementedException(); } - public virtual System.Globalization.SortKey GetSortKey(string source) { throw new NotImplementedException(); } - public virtual System.Globalization.SortKey GetSortKey(string source, System.Globalization.CompareOptions options) { throw new NotImplementedException(); } - public virtual int IndexOf(string source, char value, int startIndex) { throw new NotImplementedException(); } - public virtual int IndexOf(string source, string value, int startIndex) { throw new NotImplementedException(); } - [System.Runtime.InteropServices.ComVisibleAttribute(false)] - public static bool IsSortable(char ch) { throw new NotImplementedException(); } - [System.Runtime.InteropServices.ComVisibleAttribute(false)] - [System.Security.SecuritySafeCriticalAttribute] - public static bool IsSortable(string text) { throw new NotImplementedException(); } - public virtual int LastIndexOf(string source, char value, int startIndex) { throw new NotImplementedException(); } - public virtual int LastIndexOf(string source, string value, int startIndex) { throw new NotImplementedException(); } - } - - public partial class CultureInfo : System.ICloneable, System.IFormatProvider - { - public CultureInfo(int culture) { throw new NotImplementedException(); } - public CultureInfo(int culture, bool useUserOverride) { throw new NotImplementedException(); } - public static System.Globalization.CultureInfo InstalledUICulture { get { throw new NotImplementedException(); } } - public virtual int LCID { get { throw new NotImplementedException(); } } - public virtual string ThreeLetterISOLanguageName { get { throw new NotImplementedException(); } } - public virtual string ThreeLetterWindowsLanguageName { get { throw new NotImplementedException(); } } - public void ClearCachedData() { throw new NotImplementedException(); } - public static System.Globalization.CultureInfo CreateSpecificCulture(string name) { throw new NotImplementedException(); } - public static System.Globalization.CultureInfo GetCultureInfo(int culture) { throw new NotImplementedException(); } - public static System.Globalization.CultureInfo GetCultureInfo(string name, string altName) { throw new NotImplementedException(); } - public static System.Globalization.CultureInfo GetCultureInfoByIetfLanguageTag(string name) { throw new NotImplementedException(); } - public static System.Globalization.CultureInfo[] GetCultures(System.Globalization.CultureTypes types) { throw new NotImplementedException(); } - } - - public partial class CultureNotFoundException : System.ArgumentException, System.Runtime.Serialization.ISerializable - { - public CultureNotFoundException(string message, int invalidCultureId, System.Exception innerException) { throw new NotImplementedException(); } - public CultureNotFoundException(string paramName, int invalidCultureId, string message) { throw new NotImplementedException(); } - public virtual System.Nullable InvalidCultureId { get { throw new NotImplementedException(); } } - } - - public enum CultureTypes - { - AllCultures = 7, - [System.ObsoleteAttribute("This value has been deprecated. Please use other values in CultureTypes.")] - FrameworkCultures = 64, - InstalledWin32Cultures = 4, - NeutralCultures = 1, - ReplacementCultures = 16, - SpecificCultures = 2, - UserCustomCulture = 8, - [System.ObsoleteAttribute("This value has been deprecated. Please use other values in CultureTypes.")] - WindowsOnlyCultures = 32, - } - - public sealed partial class DateTimeFormatInfo : System.ICloneable, System.IFormatProvider - { - // Can't do partial properties so add the setter for DateSeparator and TimeSeparator - [System.Runtime.InteropServices.ComVisibleAttribute(false)] - public string NativeCalendarName { get { throw new NotImplementedException(); } } - public string[] GetAllDateTimePatterns() { throw new NotImplementedException(); } - [System.Runtime.InteropServices.ComVisibleAttribute(false)] - public string GetShortestDayName(System.DayOfWeek dayOfWeek) { throw new NotImplementedException(); } - [System.Runtime.InteropServices.ComVisibleAttribute(false)] - public void SetAllDateTimePatterns(string[] patterns, char format) { throw new NotImplementedException(); } - } - - public enum DigitShapes - { - Context = 0, - NativeNational = 2, - None = 1, - } - - public sealed partial class IdnMapping - { - public IdnMapping() { } - public bool AllowUnassigned { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } - public bool UseStd3AsciiRules { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } - public override bool Equals(object obj) { throw new NotImplementedException(); } - public string GetAscii(string unicode) { throw new NotImplementedException(); } - public string GetAscii(string unicode, int index) { throw new NotImplementedException(); } - public string GetAscii(string unicode, int index, int count) { throw new NotImplementedException(); } - public override int GetHashCode() { throw new NotImplementedException(); } - public string GetUnicode(string ascii) { throw new NotImplementedException(); } - public string GetUnicode(string ascii, int index) { throw new NotImplementedException(); } - public string GetUnicode(string ascii, int index, int count) { throw new NotImplementedException(); } - } - - public sealed partial class NumberFormatInfo : System.ICloneable, System.IFormatProvider - { - [System.Runtime.InteropServices.ComVisibleAttribute(false)] - public System.Globalization.DigitShapes DigitSubstitution { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } - [System.Runtime.InteropServices.ComVisibleAttribute(false)] - public string[] NativeDigits { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } - } - - public partial class RegionInfo - { - public RegionInfo(int culture) { throw new NotImplementedException(); } - [System.Runtime.InteropServices.ComVisibleAttribute(false)] - public virtual string CurrencyEnglishName { get { throw new NotImplementedException(); } } - [System.Runtime.InteropServices.ComVisibleAttribute(false)] - public virtual string CurrencyNativeName { get { throw new NotImplementedException(); } } - [System.Runtime.InteropServices.ComVisibleAttribute(false)] - public virtual int GeoId { get { throw new NotImplementedException(); } } - public virtual string ThreeLetterISORegionName { get { throw new NotImplementedException(); } } - public virtual string ThreeLetterWindowsRegionName { get { throw new NotImplementedException(); } } - } - - public partial class SortKey - { - internal SortKey() { throw new NotImplementedException(); } - public virtual byte[] KeyData { get { throw new NotImplementedException(); } } - public virtual string OriginalString { get { throw new NotImplementedException(); } } - public static int Compare(System.Globalization.SortKey sortkey1, System.Globalization.SortKey sortkey2) { throw new NotImplementedException(); } - public override bool Equals(object value) { throw new NotImplementedException(); } - public override int GetHashCode() { throw new NotImplementedException(); } - public override string ToString() { throw new NotImplementedException(); } - } - - public sealed partial class SortVersion : System.IEquatable - { - public SortVersion(int fullVersion, System.Guid sortId) { throw new NotImplementedException(); } - public int FullVersion { get { throw new NotImplementedException(); } } - public System.Guid SortId { get { throw new NotImplementedException(); } } - public bool Equals(System.Globalization.SortVersion other) { throw new NotImplementedException(); } - public override bool Equals(object obj) { throw new NotImplementedException(); } - public override int GetHashCode() { throw new NotImplementedException(); } - public static bool operator ==(System.Globalization.SortVersion left, System.Globalization.SortVersion right) { throw new NotImplementedException(); } - public static bool operator !=(System.Globalization.SortVersion left, System.Globalization.SortVersion right) { throw new NotImplementedException(); } - } - - public partial class StringInfo - { - public string SubstringByTextElements(int startingTextElement) { throw new NotImplementedException(); } - public string SubstringByTextElements(int startingTextElement, int lengthInTextElements) { throw new NotImplementedException(); } - } - - public partial class TextInfo : System.ICloneable, System.Runtime.Serialization.IDeserializationCallback - { - public virtual int ANSICodePage { get { throw new NotImplementedException(); } } - public virtual int EBCDICCodePage { get { throw new NotImplementedException(); } } - [System.Runtime.InteropServices.ComVisibleAttribute(false)] - public int LCID { get { throw new NotImplementedException(); } } - public virtual int MacCodePage { get { throw new NotImplementedException(); } } - public virtual int OEMCodePage { get { throw new NotImplementedException(); } } - public string ToTitleCase(string str) { throw new NotImplementedException(); } - } -} \ No newline at end of file diff --git a/src/mscorlib/corefx/System/Globalization/SortKey.cs b/src/mscorlib/corefx/System/Globalization/SortKey.cs new file mode 100644 index 0000000000..fc151fa37e --- /dev/null +++ b/src/mscorlib/corefx/System/Globalization/SortKey.cs @@ -0,0 +1,209 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +//////////////////////////////////////////////////////////////////////////// +// +// +// Purpose: This class implements a set of methods for retrieving +// sort key information. +// +// +//////////////////////////////////////////////////////////////////////////// + +namespace System.Globalization { + + using System; + using System.Runtime.CompilerServices; + using System.Runtime.Serialization; + using System.Diagnostics; + using System.Diagnostics.Contracts; + + [System.Runtime.InteropServices.ComVisible(true)] + [Serializable] + public partial class SortKey + { + //--------------------------------------------------------------------// + // Internal Information // + //--------------------------------------------------------------------// + + // + // Variables. + // + + [OptionalField(VersionAdded = 3)] + internal string _localeName; // locale identifier + + [OptionalField(VersionAdded = 1)] // LCID field so serialization is Whidbey compatible though we don't officially support it + internal int _win32LCID; + // Whidbey serialization + + internal CompareOptions _options; // options + internal string _string; // original string + internal byte[] _keyData; // sortkey data + + // + // The following constructor is designed to be called from CompareInfo to get the + // the sort key of specific string for synthetic culture + // + internal SortKey(String localeName, String str, CompareOptions options, byte[] keyData) + { + _keyData = keyData; + _localeName = localeName; + _options = options; + _string = str; + } + + [OnSerializing] + private void OnSerializing(StreamingContext context) + { + //set LCID to proper value for Whidbey serialization (no other use) + if (_win32LCID == 0) + { + _win32LCID = CultureInfo.GetCultureInfo(_localeName).LCID; + } + } + + [OnDeserialized] + private void OnDeserialized(StreamingContext context) + { + //set locale name to proper value after Whidbey deserialization + if (String.IsNullOrEmpty(_localeName) && _win32LCID != 0) + { + _localeName = CultureInfo.GetCultureInfo(_win32LCID).Name; + } + } + + //////////////////////////////////////////////////////////////////////// + // + // GetOriginalString + // + // Returns the original string used to create the current instance + // of SortKey. + // + //////////////////////////////////////////////////////////////////////// + public virtual String OriginalString + { + get + { + return (_string); + } + } + + //////////////////////////////////////////////////////////////////////// + // + // GetKeyData + // + // Returns a byte array representing the current instance of the + // sort key. + // + //////////////////////////////////////////////////////////////////////// + public virtual byte[] KeyData + { + get + { + return (byte[])(_keyData.Clone()); + } + } + + //////////////////////////////////////////////////////////////////////// + // + // Compare + // + // Compares the two sort keys. Returns 0 if the two sort keys are + // equal, a number less than 0 if sortkey1 is less than sortkey2, + // and a number greater than 0 if sortkey1 is greater than sortkey2. + // + //////////////////////////////////////////////////////////////////////// + public static int Compare(SortKey sortkey1, SortKey sortkey2) + { + if (sortkey1==null || sortkey2==null) + { + throw new ArgumentNullException((sortkey1 == null ? nameof(sortkey1) : nameof(sortkey2))); + } + Contract.EndContractBlock(); + + byte[] key1Data = sortkey1._keyData; + byte[] key2Data = sortkey2._keyData; + + Debug.Assert(key1Data != null, "key1Data != null"); + Debug.Assert(key2Data != null, "key2Data != null"); + + if (key1Data.Length == 0) + { + if (key2Data.Length == 0) + { + return (0); + } + return (-1); + } + if (key2Data.Length == 0) + { + return (1); + } + + int compLen = (key1Data.Lengthkey2Data[i]) + { + return (1); + } + if (key1Data[i] + { + private int _nlsVersion; + private Guid _sortId; + + public int FullVersion + { + get + { + return _nlsVersion; + } + } + + public Guid SortId + { + get + { + return _sortId; + } + } + + public SortVersion(int fullVersion, Guid sortId) + { + _sortId = sortId; + _nlsVersion = fullVersion; + } + + internal SortVersion(int nlsVersion, int effectiveId, Guid customVersion) + { + _nlsVersion = nlsVersion; + + if (customVersion == Guid.Empty) + { + byte b1 = (byte) (effectiveId >> 24); + byte b2 = (byte) ((effectiveId & 0x00FF0000) >> 16); + byte b3 = (byte) ((effectiveId & 0x0000FF00) >> 8); + byte b4 = (byte) (effectiveId & 0xFF); + customVersion = new Guid(0,0,0,0,0,0,0,b1,b2,b3,b4); + } + + _sortId = customVersion; + } + + public override bool Equals(object obj) + { + SortVersion n = obj as SortVersion; + if (n != null) + { + return this.Equals(n); + } + + return false; + } + + public bool Equals(SortVersion other) + { + if (other == null) + { + return false; + } + + return _nlsVersion == other._nlsVersion && _sortId == other._sortId; + } + + public override int GetHashCode() + { + return _nlsVersion * 7 | _sortId.GetHashCode(); + } + + public static bool operator ==(SortVersion left, SortVersion right) + { + if (((object) left) != null) + { + return left.Equals(right); + } + + if (((object) right) != null) + { + return right.Equals(left); + } + + // Both null. + return true; + } + + public static bool operator !=(SortVersion left, SortVersion right) + { + return !(left == right); + } + } +} \ No newline at end of file diff --git a/src/mscorlib/corefx/System/Globalization/StringInfo.cs b/src/mscorlib/corefx/System/Globalization/StringInfo.cs index 102f703beb..7558002413 100644 --- a/src/mscorlib/corefx/System/Globalization/StringInfo.cs +++ b/src/mscorlib/corefx/System/Globalization/StringInfo.cs @@ -13,6 +13,7 @@ //////////////////////////////////////////////////////////////////////////// using System; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.Serialization; @@ -20,10 +21,10 @@ namespace System.Globalization { [Serializable] [System.Runtime.InteropServices.ComVisible(true)] - public partial class StringInfo + public class StringInfo { [OptionalField(VersionAdded = 2)] - private String _str; + private string _str; [NonSerialized] private int[] _indexes; @@ -32,7 +33,7 @@ namespace System.Globalization public StringInfo() : this("") { } // Primary, useful constructor - public StringInfo(String value) + public StringInfo(string value) { this.String = value; } @@ -85,7 +86,7 @@ namespace System.Globalization } } - public String String + public string String { get { @@ -119,7 +120,61 @@ namespace System.Globalization } } - public static String GetNextTextElement(String str) + public string SubstringByTextElements(int startingTextElement) + { + // If the string is empty, no sense going further. + if (null == this.Indexes) + { + // Just decide which error to give depending on the param they gave us.... + if (startingTextElement < 0) + { + throw new ArgumentOutOfRangeException(nameof(startingTextElement), SR.ArgumentOutOfRange_NeedPosNum); + } + else + { + throw new ArgumentOutOfRangeException(nameof(startingTextElement), SR.Arg_ArgumentOutOfRangeException); + } + } + return (SubstringByTextElements(startingTextElement, Indexes.Length - startingTextElement)); + } + + public string SubstringByTextElements(int startingTextElement, int lengthInTextElements) + { + if (startingTextElement < 0) + { + throw new ArgumentOutOfRangeException(nameof(startingTextElement), SR.ArgumentOutOfRange_NeedPosNum); + } + + if (this.String.Length == 0 || startingTextElement >= Indexes.Length) + { + throw new ArgumentOutOfRangeException(nameof(startingTextElement), SR.Arg_ArgumentOutOfRangeException); + } + + if (lengthInTextElements < 0) + { + throw new ArgumentOutOfRangeException(nameof(lengthInTextElements), SR.ArgumentOutOfRange_NeedPosNum); + } + + if (startingTextElement > Indexes.Length - lengthInTextElements) + { + throw new ArgumentOutOfRangeException(nameof(lengthInTextElements), SR.Arg_ArgumentOutOfRangeException); + } + + int start = Indexes[startingTextElement]; + + if (startingTextElement + lengthInTextElements == Indexes.Length) + { + // We are at the last text element in the string and because of that + // must handle the call differently. + return (this.String.Substring(start)); + } + else + { + return (this.String.Substring(start, (Indexes[lengthInTextElements + startingTextElement] - start))); + } + } + + public static string GetNextTextElement(string str) { return (GetNextTextElement(str, 0)); } @@ -157,10 +212,10 @@ namespace System.Globalization // //////////////////////////////////////////////////////////////////////// - internal static int GetCurrentTextElementLen(String str, int index, int len, ref UnicodeCategory ucCurrent, ref int currentCharCount) + internal static int GetCurrentTextElementLen(string str, int index, int len, ref UnicodeCategory ucCurrent, ref int currentCharCount) { - Contract.Assert(index >= 0 && len >= 0, "StringInfo.GetCurrentTextElementLen() : index = " + index + ", len = " + len); - Contract.Assert(index < len, "StringInfo.GetCurrentTextElementLen() : index = " + index + ", len = " + len); + Debug.Assert(index >= 0 && len >= 0, "StringInfo.GetCurrentTextElementLen() : index = " + index + ", len = " + len); + Debug.Assert(index < len, "StringInfo.GetCurrentTextElementLen() : index = " + index + ", len = " + len); if (index + currentCharCount == len) { // This is the last character/surrogate in the string. @@ -220,14 +275,14 @@ namespace System.Globalization // of str. It recognizes a base character plus one or more combining // characters or a properly formed surrogate pair as a text element. See also // the ParseCombiningCharacters() and the ParseSurrogates() methods. - public static String GetNextTextElement(String str, int index) + public static string GetNextTextElement(string str, int index) { // // Validate parameters. // if (str == null) { - throw new ArgumentNullException("str"); + throw new ArgumentNullException(nameof(str)); } Contract.EndContractBlock(); @@ -238,7 +293,7 @@ namespace System.Globalization { return (String.Empty); } - throw new ArgumentOutOfRangeException("index", SR.ArgumentOutOfRange_Index); + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index); } int charLen; @@ -246,26 +301,26 @@ namespace System.Globalization return (str.Substring(index, GetCurrentTextElementLen(str, index, len, ref uc, ref charLen))); } - public static TextElementEnumerator GetTextElementEnumerator(String str) + public static TextElementEnumerator GetTextElementEnumerator(string str) { return (GetTextElementEnumerator(str, 0)); } - public static TextElementEnumerator GetTextElementEnumerator(String str, int index) + public static TextElementEnumerator GetTextElementEnumerator(string str, int index) { // // Validate parameters. // if (str == null) { - throw new ArgumentNullException("str"); + throw new ArgumentNullException(nameof(str)); } Contract.EndContractBlock(); int len = str.Length; if (index < 0 || (index > len)) { - throw new ArgumentOutOfRangeException("index", SR.ArgumentOutOfRange_Index); + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index); } return (new TextElementEnumerator(str, index, len)); @@ -283,11 +338,11 @@ namespace System.Globalization * return the indices: 0, 2, 4. */ - public static int[] ParseCombiningCharacters(String str) + public static int[] ParseCombiningCharacters(string str) { if (str == null) { - throw new ArgumentNullException("str"); + throw new ArgumentNullException(nameof(str)); } Contract.EndContractBlock(); diff --git a/src/mscorlib/corefx/System/Globalization/TaiwanCalendar.cs b/src/mscorlib/corefx/System/Globalization/TaiwanCalendar.cs index 89b2e4a41d..36edd5b2fd 100644 --- a/src/mscorlib/corefx/System/Globalization/TaiwanCalendar.cs +++ b/src/mscorlib/corefx/System/Globalization/TaiwanCalendar.cs @@ -83,6 +83,15 @@ namespace System.Globalization } } + [System.Runtime.InteropServices.ComVisible(false)] + public override CalendarAlgorithmType AlgorithmType + { + get + { + return CalendarAlgorithmType.SolarCalendar; + } + } + // Return the type of the Taiwan calendar. // @@ -260,7 +269,7 @@ namespace System.Globalization { if (year <= 0) { - throw new ArgumentOutOfRangeException("year", + throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedPosNum); } Contract.EndContractBlock(); @@ -268,7 +277,7 @@ namespace System.Globalization if (year > helper.MaxYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, diff --git a/src/mscorlib/corefx/System/Globalization/TextElementEnumerator.cs b/src/mscorlib/corefx/System/Globalization/TextElementEnumerator.cs index 487ef11130..c0ffc65307 100644 --- a/src/mscorlib/corefx/System/Globalization/TextElementEnumerator.cs +++ b/src/mscorlib/corefx/System/Globalization/TextElementEnumerator.cs @@ -11,7 +11,7 @@ //////////////////////////////////////////////////////////////////////////// using System.Collections; -using System.Diagnostics.Contracts; +using System.Diagnostics; using System.Runtime.Serialization; namespace System.Globalization @@ -42,9 +42,9 @@ namespace System.Globalization internal TextElementEnumerator(String str, int startIndex, int strLen) { - Contract.Assert(str != null, "TextElementEnumerator(): str != null"); - Contract.Assert(startIndex >= 0 && strLen >= 0, "TextElementEnumerator(): startIndex >= 0 && strLen >= 0"); - Contract.Assert(strLen >= startIndex, "TextElementEnumerator(): strLen >= startIndex"); + Debug.Assert(str != null, "TextElementEnumerator(): str != null"); + Debug.Assert(startIndex >= 0 && strLen >= 0, "TextElementEnumerator(): startIndex >= 0 && strLen >= 0"); + Debug.Assert(strLen >= startIndex, "TextElementEnumerator(): strLen >= startIndex"); _str = str; _startIndex = startIndex; _strLen = strLen; @@ -125,7 +125,7 @@ namespace System.Globalization } // - // Get the starting _index of the current text element. + // Get the starting index of the current text element. // public int ElementIndex diff --git a/src/mscorlib/corefx/System/Globalization/TextInfo.Unix.cs b/src/mscorlib/corefx/System/Globalization/TextInfo.Unix.cs index 8490057306..3d9b777f64 100644 --- a/src/mscorlib/corefx/System/Globalization/TextInfo.Unix.cs +++ b/src/mscorlib/corefx/System/Globalization/TextInfo.Unix.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Security; using System.Text; @@ -35,7 +36,7 @@ namespace System.Globalization [SecuritySafeCritical] private unsafe string ChangeCase(string s, bool toUpper) { - Contract.Assert(s != null); + Debug.Assert(s != null); if (s.Length == 0) { @@ -93,7 +94,7 @@ namespace System.Globalization private bool NeedsTurkishCasing(string localeName) { - Contract.Assert(localeName != null); + Debug.Assert(localeName != null); return CultureInfo.GetCultureInfo(localeName).CompareInfo.Compare("\u0131", "I", CompareOptions.IgnoreCase) == 0; } diff --git a/src/mscorlib/corefx/System/Globalization/TextInfo.Windows.cs b/src/mscorlib/corefx/System/Globalization/TextInfo.Windows.cs index 238c51217b..b2f692e2d0 100644 --- a/src/mscorlib/corefx/System/Globalization/TextInfo.Windows.cs +++ b/src/mscorlib/corefx/System/Globalization/TextInfo.Windows.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Diagnostics.Contracts; +using System.Diagnostics; namespace System.Globalization { @@ -35,7 +35,7 @@ namespace System.Globalization private unsafe string ChangeCase(string s, bool toUpper) { - Contract.Assert(s != null); + Debug.Assert(s != null); // // Get the length of the string. @@ -80,7 +80,7 @@ namespace System.Globalization throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); } - Contract.Assert(ret == nLengthInput, "Expected getting the same length of the original string"); + Debug.Assert(ret == nLengthInput, "Expected getting the same length of the original string"); return result; } } diff --git a/src/mscorlib/corefx/System/Globalization/TextInfo.cs b/src/mscorlib/corefx/System/Globalization/TextInfo.cs index 6dadb5856a..5bb376f19c 100644 --- a/src/mscorlib/corefx/System/Globalization/TextInfo.cs +++ b/src/mscorlib/corefx/System/Globalization/TextInfo.cs @@ -12,15 +12,10 @@ // //////////////////////////////////////////////////////////////////////////// -using System; +using System.Diagnostics; using System.Diagnostics.Contracts; -using System.Runtime; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Serialization; -using System.Security; using System.Text; -using System.Threading; namespace System.Globalization { @@ -145,6 +140,64 @@ namespace System.Globalization return CompareInfo.LastIndexOfOrdinal(source, value, startIndex, count, ignoreCase: true); } + //////////////////////////////////////////////////////////////////////// + // + // CodePage + // + // Returns the number of the code page used by this writing system. + // The type parameter can be any of the following values: + // ANSICodePage + // OEMCodePage + // MACCodePage + // + //////////////////////////////////////////////////////////////////////// + + + public virtual int ANSICodePage + { + get + { + return (_cultureData.IDEFAULTANSICODEPAGE); + } + } + + + public virtual int OEMCodePage + { + get + { + return (_cultureData.IDEFAULTOEMCODEPAGE); + } + } + + + public virtual int MacCodePage + { + get + { + return (_cultureData.IDEFAULTMACCODEPAGE); + } + } + + + public virtual int EBCDICCodePage + { + get + { + return (_cultureData.IDEFAULTEBCDICCODEPAGE); + } + } + + [System.Runtime.InteropServices.ComVisible(false)] + public int LCID + { + get + { + // Just use the LCID from our text info name + return CultureInfo.GetCultureInfo(_textInfoName).LCID; + } + } + ////////////////////////////////////////////////////////////////////////// //// //// CultureName @@ -177,10 +230,10 @@ namespace System.Globalization //// //// Clone //// - //// Is the implementation of IColnable. + //// Is the implementation of ICloneable. //// ////////////////////////////////////////////////////////////////////////// - public virtual Object Clone() + public virtual object Clone() { object o = MemberwiseClone(); ((TextInfo)o).SetReadOnlyState(false); @@ -196,9 +249,9 @@ namespace System.Globalization // //////////////////////////////////////////////////////////////////////// [System.Runtime.InteropServices.ComVisible(false)] - internal static TextInfo ReadOnly(TextInfo textInfo) + public static TextInfo ReadOnly(TextInfo textInfo) { - if (textInfo == null) { throw new ArgumentNullException("textInfo"); } + if (textInfo == null) { throw new ArgumentNullException(nameof(textInfo)); } Contract.EndContractBlock(); if (textInfo.IsReadOnly) { return (textInfo); } @@ -244,7 +297,7 @@ namespace System.Globalization { if (value == null) { - throw new ArgumentNullException("value", SR.ArgumentNull_String); + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_String); } VerifyWritable(); _listSeparator = value; @@ -270,7 +323,7 @@ namespace System.Globalization public unsafe virtual String ToLower(String str) { - if (str == null) { throw new ArgumentNullException("str"); } + if (str == null) { throw new ArgumentNullException(nameof(str)); } return ChangeCase(str, toUpper: false); } @@ -303,7 +356,7 @@ namespace System.Globalization public unsafe virtual String ToUpper(String str) { - if (str == null) { throw new ArgumentNullException("str"); } + if (str == null) { throw new ArgumentNullException(nameof(str)); } return ChangeCase(str, toUpper: true); } @@ -317,7 +370,7 @@ namespace System.Globalization return c; } - static private bool IsAscii(Char c) + private static bool IsAscii(Char c) { return c < 0x80; } @@ -395,6 +448,245 @@ namespace System.Globalization return ("TextInfo - " + _cultureData.CultureName); } + // + // Titlecasing: + // ----------- + // Titlecasing refers to a casing practice wherein the first letter of a word is an uppercase letter + // and the rest of the letters are lowercase. The choice of which words to titlecase in headings + // and titles is dependent on language and local conventions. For example, "The Merry Wives of Windor" + // is the appropriate titlecasing of that play's name in English, with the word "of" not titlecased. + // In German, however, the title is "Die lustigen Weiber von Windsor," and both "lustigen" and "von" + // are not titlecased. In French even fewer words are titlecased: "Les joyeuses commeres de Windsor." + // + // Moreover, the determination of what actually constitutes a word is language dependent, and this can + // influence which letter or letters of a "word" are uppercased when titlecasing strings. For example + // "l'arbre" is considered two words in French, whereas "can't" is considered one word in English. + // + public unsafe String ToTitleCase(String str) + { + if (str == null) + { + throw new ArgumentNullException(nameof(str)); + } + Contract.EndContractBlock(); + if (str.Length == 0) + { + return (str); + } + + StringBuilder result = new StringBuilder(); + string lowercaseData = null; + + for (int i = 0; i < str.Length; i++) + { + UnicodeCategory charType; + int charLen; + + charType = CharUnicodeInfo.InternalGetUnicodeCategory(str, i, out charLen); + if (Char.CheckLetter(charType)) + { + // Do the titlecasing for the first character of the word. + i = AddTitlecaseLetter(ref result, ref str, i, charLen) + 1; + + // + // Convert the characters until the end of the this word + // to lowercase. + // + int lowercaseStart = i; + + // + // Use hasLowerCase flag to prevent from lowercasing acronyms (like "URT", "USA", etc) + // This is in line with Word 2000 behavior of titlecasing. + // + bool hasLowerCase = (charType == UnicodeCategory.LowercaseLetter); + // Use a loop to find all of the other letters following this letter. + while (i < str.Length) + { + charType = CharUnicodeInfo.InternalGetUnicodeCategory(str, i, out charLen); + if (IsLetterCategory(charType)) + { + if (charType == UnicodeCategory.LowercaseLetter) + { + hasLowerCase = true; + } + i += charLen; + } + else if (str[i] == '\'') + { + i++; + if (hasLowerCase) + { + if (lowercaseData == null) + { + lowercaseData = this.ToLower(str); + } + result.Append(lowercaseData, lowercaseStart, i - lowercaseStart); + } + else + { + result.Append(str, lowercaseStart, i - lowercaseStart); + } + lowercaseStart = i; + hasLowerCase = true; + } + else if (!IsWordSeparator(charType)) + { + // This category is considered to be part of the word. + // This is any category that is marked as false in wordSeprator array. + i+= charLen; + } + else + { + // A word separator. Break out of the loop. + break; + } + } + + int count = i - lowercaseStart; + + if (count > 0) + { + if (hasLowerCase) + { + if (lowercaseData == null) + { + lowercaseData = this.ToLower(str); + } + result.Append(lowercaseData, lowercaseStart, count); + } + else + { + result.Append(str, lowercaseStart, count); + } + } + + if (i < str.Length) + { + // not a letter, just append it + i = AddNonLetter(ref result, ref str, i, charLen); + } + } + else + { + // not a letter, just append it + i = AddNonLetter(ref result, ref str, i, charLen); + } + } + return (result.ToString()); + } + + private static int AddNonLetter(ref StringBuilder result, ref String input, int inputIndex, int charLen) + { + Debug.Assert(charLen == 1 || charLen == 2, "[TextInfo.AddNonLetter] CharUnicodeInfo.InternalGetUnicodeCategory returned an unexpected charLen!"); + if (charLen == 2) + { + // Surrogate pair + result.Append(input[inputIndex++]); + result.Append(input[inputIndex]); + } + else + { + result.Append(input[inputIndex]); + } + return inputIndex; + } + + private int AddTitlecaseLetter(ref StringBuilder result, ref String input, int inputIndex, int charLen) + { + Debug.Assert(charLen == 1 || charLen == 2, "[TextInfo.AddTitlecaseLetter] CharUnicodeInfo.InternalGetUnicodeCategory returned an unexpected charLen!"); + + // for surrogate pairs do a simple ToUpper operation on the substring + if (charLen == 2) + { + // Surrogate pair + result.Append(ToUpper(input.Substring(inputIndex, charLen))); + inputIndex++; + } + else + { + switch (input[inputIndex]) + { + // + // For AppCompat, the Titlecase Case Mapping data from NDP 2.0 is used below. + case (char) 0x01C4: // DZ with Caron -> Dz with Caron + case (char) 0x01C5: // Dz with Caron -> Dz with Caron + case (char) 0x01C6: // dz with Caron -> Dz with Caron + result.Append((char) 0x01C5); + break; + case (char) 0x01C7: // LJ -> Lj + case (char) 0x01C8: // Lj -> Lj + case (char) 0x01C9: // lj -> Lj + result.Append((char) 0x01C8); + break; + case (char) 0x01CA: // NJ -> Nj + case (char) 0x01CB: // Nj -> Nj + case (char) 0x01CC: // nj -> Nj + result.Append((char) 0x01CB); + break; + case (char) 0x01F1: // DZ -> Dz + case (char) 0x01F2: // Dz -> Dz + case (char) 0x01F3: // dz -> Dz + result.Append((char) 0x01F2); + break; + default: + result.Append(ToUpper(input[inputIndex])); + break; + } + } + return inputIndex; + } + + // + // Used in ToTitleCase(): + // When we find a starting letter, the following array decides if a category should be + // considered as word seprator or not. + // + private const int c_wordSeparatorMask = + /* false */ (0 << 0) | // UppercaseLetter = 0, + /* false */ (0 << 1) | // LowercaseLetter = 1, + /* false */ (0 << 2) | // TitlecaseLetter = 2, + /* false */ (0 << 3) | // ModifierLetter = 3, + /* false */ (0 << 4) | // OtherLetter = 4, + /* false */ (0 << 5) | // NonSpacingMark = 5, + /* false */ (0 << 6) | // SpacingCombiningMark = 6, + /* false */ (0 << 7) | // EnclosingMark = 7, + /* false */ (0 << 8) | // DecimalDigitNumber = 8, + /* false */ (0 << 9) | // LetterNumber = 9, + /* false */ (0 << 10) | // OtherNumber = 10, + /* true */ (1 << 11) | // SpaceSeparator = 11, + /* true */ (1 << 12) | // LineSeparator = 12, + /* true */ (1 << 13) | // ParagraphSeparator = 13, + /* true */ (1 << 14) | // Control = 14, + /* true */ (1 << 15) | // Format = 15, + /* false */ (0 << 16) | // Surrogate = 16, + /* false */ (0 << 17) | // PrivateUse = 17, + /* true */ (1 << 18) | // ConnectorPunctuation = 18, + /* true */ (1 << 19) | // DashPunctuation = 19, + /* true */ (1 << 20) | // OpenPunctuation = 20, + /* true */ (1 << 21) | // ClosePunctuation = 21, + /* true */ (1 << 22) | // InitialQuotePunctuation = 22, + /* true */ (1 << 23) | // FinalQuotePunctuation = 23, + /* true */ (1 << 24) | // OtherPunctuation = 24, + /* true */ (1 << 25) | // MathSymbol = 25, + /* true */ (1 << 26) | // CurrencySymbol = 26, + /* true */ (1 << 27) | // ModifierSymbol = 27, + /* true */ (1 << 28) | // OtherSymbol = 28, + /* false */ (0 << 29); // OtherNotAssigned = 29; + + private static bool IsWordSeparator(UnicodeCategory category) + { + return (c_wordSeparatorMask & (1 << (int) category)) != 0; + } + + private static bool IsLetterCategory(UnicodeCategory uc) + { + return (uc == UnicodeCategory.UppercaseLetter + || uc == UnicodeCategory.LowercaseLetter + || uc == UnicodeCategory.TitlecaseLetter + || uc == UnicodeCategory.ModifierLetter + || uc == UnicodeCategory.OtherLetter); + } + // // Get case-insensitive hash code for the specified string. // @@ -403,7 +695,7 @@ namespace System.Globalization // Validate inputs if (str == null) { - throw new ArgumentNullException("str"); + throw new ArgumentNullException(nameof(str)); } // This code assumes that ASCII casing is safe for whatever context is passed in. @@ -438,7 +730,7 @@ namespace System.Globalization private unsafe int GetCaseInsensitiveHashCodeSlow(String str) { - Contract.Assert(str != null); + Debug.Assert(str != null); string upper = ToUpper(str); diff --git a/src/mscorlib/corefx/System/Globalization/ThaiBuddhistCalendar.cs b/src/mscorlib/corefx/System/Globalization/ThaiBuddhistCalendar.cs index b42af30c04..8ebbfa0a69 100644 --- a/src/mscorlib/corefx/System/Globalization/ThaiBuddhistCalendar.cs +++ b/src/mscorlib/corefx/System/Globalization/ThaiBuddhistCalendar.cs @@ -56,6 +56,15 @@ namespace System.Globalization } } + [System.Runtime.InteropServices.ComVisible(false)] + public override CalendarAlgorithmType AlgorithmType + { + get + { + return CalendarAlgorithmType.SolarCalendar; + } + } + public ThaiBuddhistCalendar() { helper = new GregorianCalendarHelper(this, thaiBuddhistEraInfo); @@ -221,7 +230,7 @@ namespace System.Globalization { if (year < 0) { - throw new ArgumentOutOfRangeException("year", + throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedNonNegNum); } Contract.EndContractBlock(); diff --git a/src/mscorlib/corefx/System/Globalization/UmAlQuraCalendar.cs b/src/mscorlib/corefx/System/Globalization/UmAlQuraCalendar.cs index 1116722fe9..997c5f2316 100644 --- a/src/mscorlib/corefx/System/Globalization/UmAlQuraCalendar.cs +++ b/src/mscorlib/corefx/System/Globalization/UmAlQuraCalendar.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; +using System.Diagnostics; using System.Diagnostics.Contracts; namespace System.Globalization @@ -270,6 +270,14 @@ namespace System.Globalization } } + public override CalendarAlgorithmType AlgorithmType + { + get + { + return CalendarAlgorithmType.LunarCalendar; + } + } + public UmAlQuraCalendar() { } @@ -309,9 +317,9 @@ namespace System.Globalization =========================ConvertHijriToGregorian============================*/ private static void ConvertHijriToGregorian(int HijriYear, int HijriMonth, int HijriDay, ref int yg, ref int mg, ref int dg) { - Contract.Assert((HijriYear >= MinCalendarYear) && (HijriYear <= MaxCalendarYear), "Hijri year is out of range."); - Contract.Assert(HijriMonth >= 1, "Hijri month is out of range."); - Contract.Assert(HijriDay >= 1, "Hijri day is out of range."); + Debug.Assert((HijriYear >= MinCalendarYear) && (HijriYear <= MaxCalendarYear), "Hijri year is out of range."); + Debug.Assert(HijriMonth >= 1, "Hijri month is out of range."); + Debug.Assert(HijriDay >= 1, "Hijri day is out of range."); int index, b, nDays = HijriDay - 1; DateTime dt; @@ -368,7 +376,7 @@ namespace System.Globalization { if (era != CurrentEra && era != UmAlQuraEra) { - throw new ArgumentOutOfRangeException("era", SR.ArgumentOutOfRange_InvalidEraValue); + throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue); } } @@ -378,7 +386,7 @@ namespace System.Globalization if (year < MinCalendarYear || year > MaxCalendarYear) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -392,7 +400,7 @@ namespace System.Globalization CheckYearRange(year, era); if (month < 1 || month > 12) { - throw new ArgumentOutOfRangeException("month", SR.ArgumentOutOfRange_Month); + throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month); } } @@ -409,7 +417,7 @@ namespace System.Globalization TimeSpan ts; int yh1 = 0, mh1 = 0, dh1 = 0; - Contract.Assert((time.Ticks >= minDate.Ticks) && (time.Ticks <= maxDate.Ticks), "Gregorian date is out of range."); + Debug.Assert((time.Ticks >= minDate.Ticks) && (time.Ticks <= maxDate.Ticks), "Gregorian date is out of range."); // Find the index where we should start our search by quessing the Hijri year that we will be in HijriYearInfo. // A Hijri year is 354 or 355 days. Use 355 days so that we will search from a lower index. @@ -510,7 +518,7 @@ namespace System.Globalization if (months < -120000 || months > 120000) { throw new ArgumentOutOfRangeException( - "months", + nameof(months), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -622,7 +630,7 @@ namespace System.Globalization { int days = 0, b; - Contract.Assert((year >= MinCalendarYear) && (year <= MaxCalendarYear), "Hijri year is out of range."); + Debug.Assert((year >= MinCalendarYear) && (year <= MaxCalendarYear), "Hijri year is out of range."); b = s_hijriYearInfo[year - MinCalendarYear].HijriMonthsLengthFlags; @@ -631,7 +639,7 @@ namespace System.Globalization days = days + 29 + (b & 1); /* Add the months lengths before mh */ b = b >> 1; } - Contract.Assert((days == 354) || (days == 355), "Hijri year has to be 354 or 355 days."); + Debug.Assert((days == 354) || (days == 355), "Hijri year has to be 354 or 355 days."); return days; } @@ -711,7 +719,7 @@ namespace System.Globalization if (day < 1 || day > daysInMonth) { throw new ArgumentOutOfRangeException( - "day", + nameof(day), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Day, @@ -775,7 +783,7 @@ namespace System.Globalization if (day < 1 || day > daysInMonth) { throw new ArgumentOutOfRangeException( - "day", + nameof(day), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Day, @@ -815,7 +823,7 @@ namespace System.Globalization if (value != 99 && (value < MinCalendarYear || value > MaxCalendarYear)) { throw new ArgumentOutOfRangeException( - "value", + nameof(value), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, @@ -835,7 +843,7 @@ namespace System.Globalization { if (year < 0) { - throw new ArgumentOutOfRangeException("year", + throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedNonNegNum); } Contract.EndContractBlock(); @@ -848,7 +856,7 @@ namespace System.Globalization if ((year < MinCalendarYear) || (year > MaxCalendarYear)) { throw new ArgumentOutOfRangeException( - "year", + nameof(year), String.Format( CultureInfo.CurrentCulture, SR.ArgumentOutOfRange_Range, diff --git a/src/mscorlib/corefx/System/HResults.cs b/src/mscorlib/corefx/System/HResults.cs new file mode 100644 index 0000000000..fa0e24f00b --- /dev/null +++ b/src/mscorlib/corefx/System/HResults.cs @@ -0,0 +1,236 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +//============================================================================= +// +// +// Purpose: Define HResult constants. Every exception has one of these. +// +// +//===========================================================================*/ + +using System; + +namespace System +{ + // Note: FACILITY_URT is defined as 0x13 (0x8013xxxx). Within that + // range, 0x1yyy is for Runtime errors (used for Security, Metadata, etc). + // In that subrange, 0x15zz and 0x16zz have been allocated for classlib-type + // HResults. Also note that some of our HResults have to map to certain + // COM HR's, etc. + + // Another arbitrary decision... Feel free to change this, as long as you + // renumber the HResults yourself (and update rexcep.h). + // Reflection will use 0x1600 -> 0x161f. IO will use 0x1620 -> 0x163f. + // Security will use 0x1640 -> 0x165f + + // There are HResults files in the IO, Remoting, Reflection & + // Security/Util directories as well, so choose your HResults carefully. + internal static class HResults + { + internal const int APPMODEL_ERROR_NO_PACKAGE = unchecked((int)0x80073D54); + internal const int CLDB_E_FILE_CORRUPT = unchecked((int)0x8013110e); + internal const int CLDB_E_FILE_OLDVER = unchecked((int)0x80131107); + internal const int CLDB_E_INDEX_NOTFOUND = unchecked((int)0x80131124); + internal const int CLR_E_BIND_ASSEMBLY_NOT_FOUND = unchecked((int)0x80132004); + internal const int CLR_E_BIND_ASSEMBLY_PUBLIC_KEY_MISMATCH = unchecked((int)0x80132001); + internal const int CLR_E_BIND_ASSEMBLY_VERSION_TOO_LOW = unchecked((int)0x80132000); + internal const int CLR_E_BIND_TYPE_NOT_FOUND = unchecked((int)0x80132005); + internal const int CLR_E_BIND_UNRECOGNIZED_IDENTITY_FORMAT = unchecked((int)0x80132003); + internal const int COR_E_ABANDONEDMUTEX = unchecked((int)0x8013152D); + internal const int COR_E_AMBIGUOUSMATCH = unchecked((int)0x8000211D); + internal const int COR_E_APPDOMAINUNLOADED = unchecked((int)0x80131014); + internal const int COR_E_APPLICATION = unchecked((int)0x80131600); + internal const int COR_E_ARGUMENT = unchecked((int)0x80070057); + internal const int COR_E_ARGUMENTOUTOFRANGE = unchecked((int)0x80131502); + internal const int COR_E_ARITHMETIC = unchecked((int)0x80070216); + internal const int COR_E_ARRAYTYPEMISMATCH = unchecked((int)0x80131503); + internal const int COR_E_ASSEMBLYEXPECTED = unchecked((int)0x80131018); + internal const int COR_E_BADIMAGEFORMAT = unchecked((int)0x8007000B); + internal const int COR_E_CANNOTUNLOADAPPDOMAIN = unchecked((int)0x80131015); + internal const int COR_E_CODECONTRACTFAILED = unchecked((int)0x80131542); + internal const int COR_E_CONTEXTMARSHAL = unchecked((int)0x80131504); + internal const int COR_E_CUSTOMATTRIBUTEFORMAT = unchecked((int)0x80131605); + internal const int COR_E_DATAMISALIGNED = unchecked((int)0x80131541); + internal const int COR_E_DIVIDEBYZERO = unchecked((int)0x80020012); // DISP_E_DIVBYZERO + internal const int COR_E_DLLNOTFOUND = unchecked((int)0x80131524); + internal const int COR_E_DUPLICATEWAITOBJECT = unchecked((int)0x80131529); + internal const int COR_E_ENTRYPOINTNOTFOUND = unchecked((int)0x80131523); + internal const int COR_E_EXCEPTION = unchecked((int)0x80131500); + internal const int COR_E_EXECUTIONENGINE = unchecked((int)0x80131506); + internal const int COR_E_FIELDACCESS = unchecked((int)0x80131507); + internal const int COR_E_FIXUPSINEXE = unchecked((int)0x80131019); + internal const int COR_E_FORMAT = unchecked((int)0x80131537); + internal const int COR_E_INDEXOUTOFRANGE = unchecked((int)0x80131508); + internal const int COR_E_INSUFFICIENTEXECUTIONSTACK = unchecked((int)0x80131578); + internal const int COR_E_INVALIDCAST = unchecked((int)0x80004002); + internal const int COR_E_INVALIDCOMOBJECT = unchecked((int)0x80131527); + internal const int COR_E_INVALIDFILTERCRITERIA = unchecked((int)0x80131601); + internal const int COR_E_INVALIDOLEVARIANTTYPE = unchecked((int)0x80131531); + internal const int COR_E_INVALIDOPERATION = unchecked((int)0x80131509); + internal const int COR_E_INVALIDPROGRAM = unchecked((int)0x8013153a); + internal const int COR_E_KEYNOTFOUND = unchecked((int)0x80131577); + internal const int COR_E_LOADING_REFERENCE_ASSEMBLY = unchecked((int)0x80131058); + internal const int COR_E_MARSHALDIRECTIVE = unchecked((int)0x80131535); + internal const int COR_E_MEMBERACCESS = unchecked((int)0x8013151A); + internal const int COR_E_METHODACCESS = unchecked((int)0x80131510); + internal const int COR_E_MISSINGFIELD = unchecked((int)0x80131511); + internal const int COR_E_MISSINGMANIFESTRESOURCE = unchecked((int)0x80131532); + internal const int COR_E_MISSINGMEMBER = unchecked((int)0x80131512); + internal const int COR_E_MISSINGMETHOD = unchecked((int)0x80131513); + internal const int COR_E_MISSINGSATELLITEASSEMBLY = unchecked((int)0x80131536); + internal const int COR_E_MODULE_HASH_CHECK_FAILED = unchecked((int)0x80131039); + internal const int COR_E_MULTICASTNOTSUPPORTED = unchecked((int)0x80131514); + internal const int COR_E_NEWER_RUNTIME = unchecked((int)0x8013101b); + internal const int COR_E_NOTFINITENUMBER = unchecked((int)0x80131528); + internal const int COR_E_NOTSUPPORTED = unchecked((int)0x80131515); + internal const int COR_E_NULLREFERENCE = unchecked((int)0x80004003); + internal const int COR_E_OBJECTDISPOSED = unchecked((int)0x80131622); + internal const int COR_E_OPERATIONCANCELED = unchecked((int)0x8013153B); + internal const int COR_E_OUTOFMEMORY = unchecked((int)0x8007000E); + internal const int COR_E_OVERFLOW = unchecked((int)0x80131516); + internal const int COR_E_PLATFORMNOTSUPPORTED = unchecked((int)0x80131539); + internal const int COR_E_RANK = unchecked((int)0x80131517); + internal const int COR_E_REFLECTIONTYPELOAD = unchecked((int)0x80131602); + internal const int COR_E_REMOTING = unchecked((int)0x8013150b); + internal const int COR_E_RUNTIMEWRAPPED = unchecked((int)0x8013153e); + internal const int COR_E_SAFEARRAYRANKMISMATCH = unchecked((int)0x80131538); + internal const int COR_E_SAFEARRAYTYPEMISMATCH = unchecked((int)0x80131533); + internal const int COR_E_SECURITY = unchecked((int)0x8013150A); + internal const int COR_E_SERIALIZATION = unchecked((int)0x8013150C); + internal const int COR_E_SERVER = unchecked((int)0x8013150e); + internal const int COR_E_STACKOVERFLOW = unchecked((int)0x800703E9); + internal const int COR_E_SYNCHRONIZATIONLOCK = unchecked((int)0x80131518); + internal const int COR_E_SYSTEM = unchecked((int)0x80131501); + internal const int COR_E_TARGET = unchecked((int)0x80131603); + internal const int COR_E_TARGETINVOCATION = unchecked((int)0x80131604); + internal const int COR_E_TARGETPARAMCOUNT = unchecked((int)0x8002000e); + internal const int COR_E_THREADABORTED = unchecked((int)0x80131530); + internal const int COR_E_THREADINTERRUPTED = unchecked((int)0x80131519); + internal const int COR_E_THREADSTART = unchecked((int)0x80131525); + internal const int COR_E_THREADSTATE = unchecked((int)0x80131520); + internal const int COR_E_TIMEOUT = unchecked((int)0x80131505); + internal const int COR_E_TYPEACCESS = unchecked((int)0x80131543); + internal const int COR_E_TYPEINITIALIZATION = unchecked((int)0x80131534); + internal const int COR_E_TYPELOAD = unchecked((int)0x80131522); + internal const int COR_E_TYPEUNLOADED = unchecked((int)0x80131013); + internal const int COR_E_UNAUTHORIZEDACCESS = unchecked((int)0x80070005); + internal const int COR_E_VERIFICATION = unchecked((int)0x8013150D); + internal const int COR_E_WAITHANDLECANNOTBEOPENED = unchecked((int)0x8013152C); + internal const int CORSEC_E_CRYPTO = unchecked((int)0x80131430); + internal const int CORSEC_E_CRYPTO_UNEX_OPER = unchecked((int)0x80131431); + internal const int CORSEC_E_INVALID_IMAGE_FORMAT = unchecked((int)0x8013141d); + internal const int CORSEC_E_INVALID_PUBLICKEY = unchecked((int)0x8013141e); + internal const int CORSEC_E_INVALID_STRONGNAME = unchecked((int)0x8013141a); + internal const int CORSEC_E_MIN_GRANT_FAIL = unchecked((int)0x80131417); + internal const int CORSEC_E_MISSING_STRONGNAME = unchecked((int)0x8013141b); + internal const int CORSEC_E_NO_EXEC_PERM = unchecked((int)0x80131418); + internal const int CORSEC_E_POLICY_EXCEPTION = unchecked((int)0x80131416); + internal const int CORSEC_E_SIGNATURE_MISMATCH = unchecked((int)0x80131420); + internal const int CORSEC_E_XMLSYNTAX = unchecked((int)0x80131419); + internal const int CTL_E_DEVICEIOERROR = unchecked((int)0x800A0039); + internal const int CTL_E_DIVISIONBYZERO = unchecked((int)0x800A000B); + internal const int CTL_E_FILENOTFOUND = unchecked((int)0x800A0035); + internal const int CTL_E_OUTOFMEMORY = unchecked((int)0x800A0007); + internal const int CTL_E_OUTOFSTACKSPACE = unchecked((int)0x800A001C); + internal const int CTL_E_OVERFLOW = unchecked((int)0x800A0006); + internal const int CTL_E_PATHFILEACCESSERROR = unchecked((int)0x800A004B); + internal const int CTL_E_PATHNOTFOUND = unchecked((int)0x800A004C); + internal const int CTL_E_PERMISSIONDENIED = unchecked((int)0x800A0046); + internal const int E_ELEMENTNOTAVAILABLE = unchecked((int)0x802B001F); + internal const int E_ELEMENTNOTENABLED = unchecked((int)0x802B001E); + internal const int E_FAIL = unchecked((int)0x80004005); + internal const int E_HANDLE = unchecked((int)0x80070006); + internal const int E_ILLEGAL_DELEGATE_ASSIGNMENT = unchecked((int)0x80000018); + internal const int E_ILLEGAL_METHOD_CALL = unchecked((int)0x8000000E); + internal const int E_ILLEGAL_STATE_CHANGE = unchecked((int)0x8000000D); + internal const int E_INVALIDARG = unchecked((int)0x80070057); + internal const int E_LAYOUTCYCLE = unchecked((int)0x802B0014); + internal const int E_NOTIMPL = unchecked((int)0x80004001); + internal const int E_OUTOFMEMORY = unchecked((int)0x8007000E); + internal const int E_POINTER = unchecked((int)0x80004003L); + internal const int E_XAMLPARSEFAILED = unchecked((int)0x802B000A); + internal const int ERROR_BAD_EXE_FORMAT = unchecked((int)0x800700C1); + internal const int ERROR_BAD_NET_NAME = unchecked((int)0x80070043); + internal const int ERROR_BAD_NETPATH = unchecked((int)0x80070035); + internal const int ERROR_DISK_CORRUPT = unchecked((int)0x80070571); + internal const int ERROR_DLL_INIT_FAILED = unchecked((int)0x8007045A); + internal const int ERROR_DLL_NOT_FOUND = unchecked((int)0x80070485); + internal const int ERROR_EXE_MARKED_INVALID = unchecked((int)0x800700C0); + internal const int ERROR_FILE_CORRUPT = unchecked((int)0x80070570); + internal const int ERROR_FILE_INVALID = unchecked((int)0x800703EE); + internal const int ERROR_FILE_NOT_FOUND = unchecked((int)0x80070002); + internal const int ERROR_INVALID_DLL = unchecked((int)0x80070482); + internal const int ERROR_INVALID_NAME = unchecked((int)0x8007007B); + internal const int ERROR_INVALID_ORDINAL = unchecked((int)0x800700B6); + internal const int ERROR_INVALID_PARAMETER = unchecked((int)0x80070057); + internal const int ERROR_LOCK_VIOLATION = unchecked((int)0x80070021); + internal const int ERROR_MOD_NOT_FOUND = unchecked((int)0x8007007E); + internal const int ERROR_NO_UNICODE_TRANSLATION = unchecked((int)0x80070459); + internal const int ERROR_NOACCESS = unchecked((int)0x800703E6); + internal const int ERROR_NOT_READY = unchecked((int)0x80070015); + internal const int ERROR_OPEN_FAILED = unchecked((int)0x8007006E); + internal const int ERROR_PATH_NOT_FOUND = unchecked((int)0x80070003); + internal const int ERROR_SHARING_VIOLATION = unchecked((int)0x80070020); + internal const int ERROR_TOO_MANY_OPEN_FILES = unchecked((int)0x80070004); + internal const int ERROR_UNRECOGNIZED_VOLUME = unchecked((int)0x800703ED); + internal const int ERROR_WRONG_TARGET_NAME = unchecked((int)0x80070574); + internal const int FUSION_E_ASM_MODULE_MISSING = unchecked((int)0x80131042); + internal const int FUSION_E_CACHEFILE_FAILED = unchecked((int)0x80131052); + internal const int FUSION_E_CODE_DOWNLOAD_DISABLED = unchecked((int)0x80131048); + internal const int FUSION_E_HOST_GAC_ASM_MISMATCH = unchecked((int)0x80131050); + internal const int FUSION_E_INVALID_NAME = unchecked((int)0x80131047); + internal const int FUSION_E_INVALID_PRIVATE_ASM_LOCATION = unchecked((int)0x80131041); + internal const int FUSION_E_LOADFROM_BLOCKED = unchecked((int)0x80131051); + internal const int FUSION_E_PRIVATE_ASM_DISALLOWED = unchecked((int)0x80131044); + internal const int FUSION_E_REF_DEF_MISMATCH = unchecked((int)0x80131040); + internal const int FUSION_E_SIGNATURE_CHECK_FAILED = unchecked((int)0x80131045); + internal const int INET_E_CANNOT_CONNECT = unchecked((int)0x800C0004); + internal const int INET_E_CONNECTION_TIMEOUT = unchecked((int)0x800C000B); + internal const int INET_E_DATA_NOT_AVAILABLE = unchecked((int)0x800C0007); + internal const int INET_E_DOWNLOAD_FAILURE = unchecked((int)0x800C0008); + internal const int INET_E_OBJECT_NOT_FOUND = unchecked((int)0x800C0006); + internal const int INET_E_RESOURCE_NOT_FOUND = unchecked((int)0x800C0005); + internal const int INET_E_UNKNOWN_PROTOCOL = unchecked((int)0x800C000D); + internal const int ISS_E_ALLOC_TOO_LARGE = unchecked((int)0x80131484); + internal const int ISS_E_BLOCK_SIZE_TOO_SMALL = unchecked((int)0x80131483); + internal const int ISS_E_CALLER = unchecked((int)0x801314A1); + internal const int ISS_E_CORRUPTED_STORE_FILE = unchecked((int)0x80131480); + internal const int ISS_E_CREATE_DIR = unchecked((int)0x80131468); + internal const int ISS_E_CREATE_MUTEX = unchecked((int)0x80131464); + internal const int ISS_E_DEPRECATE = unchecked((int)0x801314A0); + internal const int ISS_E_FILE_NOT_MAPPED = unchecked((int)0x80131482); + internal const int ISS_E_FILE_WRITE = unchecked((int)0x80131466); + internal const int ISS_E_GET_FILE_SIZE = unchecked((int)0x80131463); + internal const int ISS_E_ISOSTORE = unchecked((int)0x80131450); + internal const int ISS_E_LOCK_FAILED = unchecked((int)0x80131465); + internal const int ISS_E_MACHINE = unchecked((int)0x801314A3); + internal const int ISS_E_MACHINE_DACL = unchecked((int)0x801314A4); + internal const int ISS_E_MAP_VIEW_OF_FILE = unchecked((int)0x80131462); + internal const int ISS_E_OPEN_FILE_MAPPING = unchecked((int)0x80131461); + internal const int ISS_E_OPEN_STORE_FILE = unchecked((int)0x80131460); + internal const int ISS_E_PATH_LENGTH = unchecked((int)0x801314A2); + internal const int ISS_E_SET_FILE_POINTER = unchecked((int)0x80131467); + internal const int ISS_E_STORE_NOT_OPEN = unchecked((int)0x80131469); + internal const int ISS_E_STORE_VERSION = unchecked((int)0x80131481); + internal const int ISS_E_TABLE_ROW_NOT_FOUND = unchecked((int)0x80131486); + internal const int ISS_E_USAGE_WILL_EXCEED_QUOTA = unchecked((int)0x80131485); + internal const int META_E_BAD_SIGNATURE = unchecked((int)0x80131192); + internal const int META_E_CA_FRIENDS_SN_REQUIRED = unchecked((int)0x801311e6); + internal const int MSEE_E_ASSEMBLYLOADINPROGRESS = unchecked((int)0x80131016); + internal const int RO_E_CLOSED = unchecked((int)0x80000013); + internal const int E_BOUNDS = unchecked((int)0x8000000B); + internal const int RO_E_METADATA_NAME_NOT_FOUND = unchecked((int)0x8000000F); + internal const int SECURITY_E_INCOMPATIBLE_EVIDENCE = unchecked((int)0x80131403); + internal const int SECURITY_E_INCOMPATIBLE_SHARE = unchecked((int)0x80131401); + internal const int SECURITY_E_UNVERIFIABLE = unchecked((int)0x80131402); + internal const int STG_E_PATHNOTFOUND = unchecked((int)0x80030003); + public const int COR_E_DIRECTORYNOTFOUND = unchecked((int)0x80070003); + public const int COR_E_ENDOFSTREAM = unchecked((int)0x80070026); // OS defined + public const int COR_E_FILELOAD = unchecked((int)0x80131621); + public const int COR_E_FILENOTFOUND = unchecked((int)0x80070002); + public const int COR_E_IO = unchecked((int)0x80131620); + public const int COR_E_PATHTOOLONG = unchecked((int)0x800700CE); + } +} diff --git a/src/mscorlib/corefx/System/IO/Error.cs b/src/mscorlib/corefx/System/IO/Error.cs new file mode 100644 index 0000000000..c8434ffad8 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/Error.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Globalization; +using System.Diagnostics.Contracts; + +namespace System.IO +{ + /// + /// Provides centralized methods for creating exceptions for System.IO.FileSystem. + /// + [Pure] + internal static class Error + { + internal static Exception GetEndOfFile() + { + return new EndOfStreamException(SR.IO_EOF_ReadBeyondEOF); + } + + internal static Exception GetFileNotOpen() + { + return new ObjectDisposedException(null, SR.ObjectDisposed_FileClosed); + } + + internal static Exception GetReadNotSupported() + { + return new NotSupportedException(SR.NotSupported_UnreadableStream); + } + + internal static Exception GetSeekNotSupported() + { + return new NotSupportedException(SR.NotSupported_UnseekableStream); + } + + internal static Exception GetWriteNotSupported() + { + return new NotSupportedException(SR.NotSupported_UnwritableStream); + } + } +} diff --git a/src/mscorlib/corefx/System/IO/FileStream.NetStandard17.cs b/src/mscorlib/corefx/System/IO/FileStream.NetStandard17.cs new file mode 100644 index 0000000000..dc1385fbc0 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/FileStream.NetStandard17.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32.SafeHandles; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Threading; + +namespace System.IO +{ + public partial class FileStream : Stream + { + public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback callback, object state) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum); + if (numBytes < 0) + throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_NeedNonNegNum); + if (array.Length - offset < numBytes) + throw new ArgumentException(SR.Argument_InvalidOffLen); + + if (IsClosed) throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed); + if (!CanRead) throw new NotSupportedException(SR.NotSupported_UnreadableStream); + + if (!IsAsync) + return base.BeginRead(array, offset, numBytes, callback, state); + else + return TaskToApm.Begin(ReadAsyncInternal(array, offset, numBytes, CancellationToken.None), callback, state); + } + + public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, AsyncCallback callback, object state) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum); + if (numBytes < 0) + throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_NeedNonNegNum); + if (array.Length - offset < numBytes) + throw new ArgumentException(SR.Argument_InvalidOffLen); + + if (IsClosed) throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed); + if (!CanWrite) throw new NotSupportedException(SR.NotSupported_UnwritableStream); + + if (!IsAsync) + return base.BeginWrite(array, offset, numBytes, callback, state); + else + return TaskToApm.Begin(WriteAsyncInternal(array, offset, numBytes, CancellationToken.None), callback, state); + } + + public override int EndRead(IAsyncResult asyncResult) + { + if (asyncResult == null) + throw new ArgumentNullException(nameof(asyncResult)); + + if (!IsAsync) + return base.EndRead(asyncResult); + else + return TaskToApm.End(asyncResult); + } + + public override void EndWrite(IAsyncResult asyncResult) + { + if (asyncResult == null) + throw new ArgumentNullException(nameof(asyncResult)); + + if (!IsAsync) + base.EndWrite(asyncResult); + else + TaskToApm.End(asyncResult); + } + } +} diff --git a/src/mscorlib/corefx/System/IO/FileStream.Unix.cs b/src/mscorlib/corefx/System/IO/FileStream.Unix.cs new file mode 100644 index 0000000000..f83fc84259 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/FileStream.Unix.cs @@ -0,0 +1,934 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32.SafeHandles; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace System.IO +{ + /// Provides an implementation of a file stream for Unix files. + public partial class FileStream : Stream + { + /// File mode. + private FileMode _mode; + + /// Advanced options requested when opening the file. + private FileOptions _options; + + /// If the file was opened with FileMode.Append, the length of the file when opened; otherwise, -1. + private long _appendStart = -1; + + /// + /// Extra state used by the file stream when _useAsyncIO is true. This includes + /// the semaphore used to serialize all operation, the buffer/offset/count provided by the + /// caller for ReadAsync/WriteAsync operations, and the last successful task returned + /// synchronously from ReadAsync which can be reused if the count matches the next request. + /// Only initialized when is true. + /// + private AsyncState _asyncState; + + /// Lazily-initialized value for whether the file supports seeking. + private bool? _canSeek; + + private SafeFileHandle OpenHandle(FileMode mode, FileShare share, FileOptions options) + { + // FileStream performs most of the general argument validation. We can assume here that the arguments + // are all checked and consistent (e.g. non-null-or-empty path; valid enums in mode, access, share, and options; etc.) + // Store the arguments + _mode = mode; + _options = options; + + if (_useAsyncIO) + _asyncState = new AsyncState(); + + // Translate the arguments into arguments for an open call. + Interop.Sys.OpenFlags openFlags = PreOpenConfigurationFromOptions(mode, _access, options); // FileShare currently ignored + + // If the file gets created a new, we'll select the permissions for it. Most Unix utilities by default use 666 (read and + // write for all), so we do the same (even though this doesn't match Windows, where by default it's possible to write out + // a file and then execute it). No matter what we choose, it'll be subject to the umask applied by the system, such that the + // actual permissions will typically be less than what we select here. + const Interop.Sys.Permissions OpenPermissions = + Interop.Sys.Permissions.S_IRUSR | Interop.Sys.Permissions.S_IWUSR | + Interop.Sys.Permissions.S_IRGRP | Interop.Sys.Permissions.S_IWGRP | + Interop.Sys.Permissions.S_IROTH | Interop.Sys.Permissions.S_IWOTH; + + // Open the file and store the safe handle. + return SafeFileHandle.Open(_path, openFlags, (int)OpenPermissions); + } + + /// Initializes a stream for reading or writing a Unix file. + /// How the file should be opened. + /// What other access to the file should be allowed. This is currently ignored. + private void Init(FileMode mode, FileShare share) + { + _fileHandle.IsAsync = _useAsyncIO; + + // Lock the file if requested via FileShare. This is only advisory locking. FileShare.None implies an exclusive + // lock on the file and all other modes use a shared lock. While this is not as granular as Windows, not mandatory, + // and not atomic with file opening, it's better than nothing. + Interop.Sys.LockOperations lockOperation = (share == FileShare.None) ? Interop.Sys.LockOperations.LOCK_EX : Interop.Sys.LockOperations.LOCK_SH; + if (Interop.Sys.FLock(_fileHandle, lockOperation | Interop.Sys.LockOperations.LOCK_NB) < 0) + { + // The only error we care about is EWOULDBLOCK, which indicates that the file is currently locked by someone + // else and we would block trying to access it. Other errors, such as ENOTSUP (locking isn't supported) or + // EACCES (the file system doesn't allow us to lock), will only hamper FileStream's usage without providing value, + // given again that this is only advisory / best-effort. + Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + if (errorInfo.Error == Interop.Error.EWOULDBLOCK) + { + throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false); + } + } + + // These provide hints around how the file will be accessed. Specifying both RandomAccess + // and Sequential together doesn't make sense as they are two competing options on the same spectrum, + // so if both are specified, we prefer RandomAccess (behavior on Windows is unspecified if both are provided). + Interop.Sys.FileAdvice fadv = + (_options & FileOptions.RandomAccess) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_RANDOM : + (_options & FileOptions.SequentialScan) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_SEQUENTIAL : + 0; + if (fadv != 0) + { + CheckFileCall(Interop.Sys.PosixFAdvise(_fileHandle, 0, 0, fadv), + ignoreNotSupported: true); // just a hint. + } + + // Jump to the end of the file if opened as Append. + if (_mode == FileMode.Append) + { + _appendStart = SeekCore(0, SeekOrigin.End); + } + } + + /// Initializes a stream from an already open file handle (file descriptor). + /// The handle to the file. + /// The size of the buffer to use when buffering. + /// Whether access to the stream is performed asynchronously. + private void InitFromHandle(SafeFileHandle handle) + { + if (_useAsyncIO) + _asyncState = new AsyncState(); + + if (CanSeekCore) // use non-virtual CanSeekCore rather than CanSeek to avoid making virtual call during ctor + SeekCore(0, SeekOrigin.Current); + } + + /// Translates the FileMode, FileAccess, and FileOptions values into flags to be passed when opening the file. + /// The FileMode provided to the stream's constructor. + /// The FileAccess provided to the stream's constructor + /// The FileOptions provided to the stream's constructor + /// The flags value to be passed to the open system call. + private static Interop.Sys.OpenFlags PreOpenConfigurationFromOptions(FileMode mode, FileAccess access, FileOptions options) + { + // Translate FileMode. Most of the values map cleanly to one or more options for open. + Interop.Sys.OpenFlags flags = default(Interop.Sys.OpenFlags); + switch (mode) + { + default: + case FileMode.Open: // Open maps to the default behavior for open(...). No flags needed. + break; + + case FileMode.Append: // Append is the same as OpenOrCreate, except that we'll also separately jump to the end later + case FileMode.OpenOrCreate: + flags |= Interop.Sys.OpenFlags.O_CREAT; + break; + + case FileMode.Create: + flags |= (Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_TRUNC); + break; + + case FileMode.CreateNew: + flags |= (Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL); + break; + + case FileMode.Truncate: + flags |= Interop.Sys.OpenFlags.O_TRUNC; + break; + } + + // Translate FileAccess. All possible values map cleanly to corresponding values for open. + switch (access) + { + case FileAccess.Read: + flags |= Interop.Sys.OpenFlags.O_RDONLY; + break; + + case FileAccess.ReadWrite: + flags |= Interop.Sys.OpenFlags.O_RDWR; + break; + + case FileAccess.Write: + flags |= Interop.Sys.OpenFlags.O_WRONLY; + break; + } + + // Translate some FileOptions; some just aren't supported, and others will be handled after calling open. + // - Asynchronous: Handled in ctor, setting _useAsync and SafeFileHandle.IsAsync to true + // - DeleteOnClose: Doesn't have a Unix equivalent, but we approximate it in Dispose + // - Encrypted: No equivalent on Unix and is ignored + // - RandomAccess: Implemented after open if posix_fadvise is available + // - SequentialScan: Implemented after open if posix_fadvise is available + // - WriteThrough: Handled here + if ((options & FileOptions.WriteThrough) != 0) + { + flags |= Interop.Sys.OpenFlags.O_SYNC; + } + + return flags; + } + + /// Gets a value indicating whether the current stream supports seeking. + public override bool CanSeek => CanSeekCore; + + /// Gets a value indicating whether the current stream supports seeking. + /// Separated out of CanSeek to enable making non-virtual call to this logic. + private bool CanSeekCore + { + get + { + if (_fileHandle.IsClosed) + { + return false; + } + + if (!_canSeek.HasValue) + { + // Lazily-initialize whether we're able to seek, tested by seeking to our current location. + _canSeek = Interop.Sys.LSeek(_fileHandle, 0, Interop.Sys.SeekWhence.SEEK_CUR) >= 0; + } + return _canSeek.Value; + } + } + + private long GetLengthInternal() + { + // Get the length of the file as reported by the OS + Interop.Sys.FileStatus status; + CheckFileCall(Interop.Sys.FStat(_fileHandle, out status)); + long length = status.Size; + + // But we may have buffered some data to be written that puts our length + // beyond what the OS is aware of. Update accordingly. + if (_writePos > 0 && _filePosition + _writePos > length) + { + length = _writePos + _filePosition; + } + + return length; + } + + /// Releases the unmanaged resources used by the stream. + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + try + { + if (_fileHandle != null && !_fileHandle.IsClosed) + { + // Flush any remaining data in the file + FlushWriteBuffer(); + + // If DeleteOnClose was requested when constructed, delete the file now. + // (Unix doesn't directly support DeleteOnClose, so we mimic it here.) + if (_path != null && (_options & FileOptions.DeleteOnClose) != 0) + { + // Since we still have the file open, this will end up deleting + // it (assuming we're the only link to it) once it's closed, but the + // name will be removed immediately. + Interop.Sys.Unlink(_path); // ignore errors; it's valid that the path may no longer exist + } + } + } + finally + { + if (_fileHandle != null && !_fileHandle.IsClosed) + { + _fileHandle.Dispose(); + } + base.Dispose(disposing); + } + } + + /// Flushes the OS buffer. This does not flush the internal read/write buffer. + private void FlushOSBuffer() + { + if (Interop.Sys.FSync(_fileHandle) < 0) + { + Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + switch (errorInfo.Error) + { + case Interop.Error.EROFS: + case Interop.Error.EINVAL: + case Interop.Error.ENOTSUP: + // Ignore failures due to the FileStream being bound to a special file that + // doesn't support synchronization. In such cases there's nothing to flush. + break; + default: + throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false); + } + } + } + + /// Writes any data in the write buffer to the underlying stream and resets the buffer. + private void FlushWriteBuffer() + { + AssertBufferInvariants(); + if (_writePos > 0) + { + WriteNative(GetBuffer(), 0, _writePos); + _writePos = 0; + } + } + + /// Asynchronously clears all buffers for this stream, causing any buffered data to be written to the underlying device. + /// The token to monitor for cancellation requests. + /// A task that represents the asynchronous flush operation. + private Task FlushAsyncInternal(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + if (_fileHandle.IsClosed) + { + throw Error.GetFileNotOpen(); + } + + // As with Win32FileStream, flush the buffers synchronously to avoid race conditions. + try + { + FlushInternalBuffer(); + } + catch (Exception e) + { + return Task.FromException(e); + } + + // We then separately flush to disk asynchronously. This is only + // necessary if we support writing; otherwise, we're done. + if (CanWrite) + { + return Task.Factory.StartNew( + state => ((FileStream)state).FlushOSBuffer(), + this, + cancellationToken, + TaskCreationOptions.DenyChildAttach, + TaskScheduler.Default); + } + else + { + return Task.CompletedTask; + } + } + + /// Sets the length of this stream to the given value. + /// The new length of the stream. + private void SetLengthInternal(long value) + { + FlushInternalBuffer(); + + if (_appendStart != -1 && value < _appendStart) + { + throw new IOException(SR.IO_SetLengthAppendTruncate); + } + + long origPos = _filePosition; + + VerifyOSHandlePosition(); + + if (_filePosition != value) + { + SeekCore(value, SeekOrigin.Begin); + } + + CheckFileCall(Interop.Sys.FTruncate(_fileHandle, value)); + + // Return file pointer to where it was before setting length + if (origPos != value) + { + if (origPos < value) + { + SeekCore(origPos, SeekOrigin.Begin); + } + else + { + SeekCore(0, SeekOrigin.End); + } + } + } + + /// Reads a block of bytes from the stream and writes the data in a given buffer. + /// + /// When this method returns, contains the specified byte array with the values between offset and + /// (offset + count - 1) replaced by the bytes read from the current source. + /// + /// The byte offset in array at which the read bytes will be placed. + /// The maximum number of bytes to read. + /// + /// The total number of bytes read into the buffer. This might be less than the number of bytes requested + /// if that number of bytes are not currently available, or zero if the end of the stream is reached. + /// + public override int Read(byte[] array, int offset, int count) + { + ValidateReadWriteArgs(array, offset, count); + + if (_useAsyncIO) + { + _asyncState.Wait(); + try { return ReadCore(array, offset, count); } + finally { _asyncState.Release(); } + } + else + { + return ReadCore(array, offset, count); + } + } + + /// Reads a block of bytes from the stream and writes the data in a given buffer. + /// + /// When this method returns, contains the specified byte array with the values between offset and + /// (offset + count - 1) replaced by the bytes read from the current source. + /// + /// The byte offset in array at which the read bytes will be placed. + /// The maximum number of bytes to read. + /// + /// The total number of bytes read into the buffer. This might be less than the number of bytes requested + /// if that number of bytes are not currently available, or zero if the end of the stream is reached. + /// + private int ReadCore(byte[] array, int offset, int count) + { + PrepareForReading(); + + // Are there any bytes available in the read buffer? If yes, + // we can just return from the buffer. If the buffer is empty + // or has no more available data in it, we can either refill it + // (and then read from the buffer into the user's buffer) or + // we can just go directly into the user's buffer, if they asked + // for more data than we'd otherwise buffer. + int numBytesAvailable = _readLength - _readPos; + bool readFromOS = false; + if (numBytesAvailable == 0) + { + // If we're not able to seek, then we're not able to rewind the stream (i.e. flushing + // a read buffer), in which case we don't want to use a read buffer. Similarly, if + // the user has asked for more data than we can buffer, we also want to skip the buffer. + if (!CanSeek || (count >= _bufferLength)) + { + // Read directly into the user's buffer + _readPos = _readLength = 0; + return ReadNative(array, offset, count); + } + else + { + // Read into our buffer. + _readLength = numBytesAvailable = ReadNative(GetBuffer(), 0, _bufferLength); + _readPos = 0; + if (numBytesAvailable == 0) + { + return 0; + } + + // Note that we did an OS read as part of this Read, so that later + // we don't try to do one again if what's in the buffer doesn't + // meet the user's request. + readFromOS = true; + } + } + + // Now that we know there's data in the buffer, read from it into the user's buffer. + Debug.Assert(numBytesAvailable > 0, "Data must be in the buffer to be here"); + int bytesRead = Math.Min(numBytesAvailable, count); + Buffer.BlockCopy(GetBuffer(), _readPos, array, offset, bytesRead); + _readPos += bytesRead; + + // We may not have had enough data in the buffer to completely satisfy the user's request. + // While Read doesn't require that we return as much data as the user requested (any amount + // up to the requested count is fine), FileStream on Windows tries to do so by doing a + // subsequent read from the file if we tried to satisfy the request with what was in the + // buffer but the buffer contained less than the requested count. To be consistent with that + // behavior, we do the same thing here on Unix. Note that we may still get less the requested + // amount, as the OS may give us back fewer than we request, either due to reaching the end of + // file, or due to its own whims. + if (!readFromOS && bytesRead < count) + { + Debug.Assert(_readPos == _readLength, "bytesToRead should only be < count if numBytesAvailable < count"); + _readPos = _readLength = 0; // no data left in the read buffer + bytesRead += ReadNative(array, offset + bytesRead, count - bytesRead); + } + + return bytesRead; + } + + /// Unbuffered, reads a block of bytes from the stream and writes the data in a given buffer. + /// + /// When this method returns, contains the specified byte array with the values between offset and + /// (offset + count - 1) replaced by the bytes read from the current source. + /// + /// The byte offset in array at which the read bytes will be placed. + /// The maximum number of bytes to read. + /// + /// The total number of bytes read into the buffer. This might be less than the number of bytes requested + /// if that number of bytes are not currently available, or zero if the end of the stream is reached. + /// + private unsafe int ReadNative(byte[] array, int offset, int count) + { + FlushWriteBuffer(); // we're about to read; dump the write buffer + + VerifyOSHandlePosition(); + + int bytesRead; + fixed (byte* bufPtr = array) + { + bytesRead = CheckFileCall(Interop.Sys.Read(_fileHandle, bufPtr + offset, count)); + Debug.Assert(bytesRead <= count); + } + _filePosition += bytesRead; + return bytesRead; + } + + /// + /// Asynchronously reads a sequence of bytes from the current stream and advances + /// the position within the stream by the number of bytes read. + /// + /// The buffer to write the data into. + /// The byte offset in buffer at which to begin writing data from the stream. + /// The maximum number of bytes to read. + /// The token to monitor for cancellation requests. + /// A task that represents the asynchronous read operation. + private Task ReadAsyncInternal(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (_useAsyncIO) + { + if (!CanRead) // match Windows behavior; this gets thrown synchronously + { + throw Error.GetReadNotSupported(); + } + + // Serialize operations using the semaphore. + Task waitTask = _asyncState.WaitAsync(); + + // If we got ownership immediately, and if there's enough data in our buffer + // to satisfy the full request of the caller, hand back the buffered data. + // While it would be a legal implementation of the Read contract, we don't + // hand back here less than the amount requested so as to match the behavior + // in ReadCore that will make a native call to try to fulfill the remainder + // of the request. + if (waitTask.Status == TaskStatus.RanToCompletion) + { + int numBytesAvailable = _readLength - _readPos; + if (numBytesAvailable >= count) + { + try + { + PrepareForReading(); + + Buffer.BlockCopy(GetBuffer(), _readPos, buffer, offset, count); + _readPos += count; + + return _asyncState._lastSuccessfulReadTask != null && _asyncState._lastSuccessfulReadTask.Result == count ? + _asyncState._lastSuccessfulReadTask : + (_asyncState._lastSuccessfulReadTask = Task.FromResult(count)); + } + catch (Exception exc) + { + return Task.FromException(exc); + } + finally + { + _asyncState.Release(); + } + } + } + + // Otherwise, issue the whole request asynchronously. + _asyncState.Update(buffer, offset, count); + return waitTask.ContinueWith((t, s) => + { + // The options available on Unix for writing asynchronously to an arbitrary file + // handle typically amount to just using another thread to do the synchronous write, + // which is exactly what this implementation does. This does mean there are subtle + // differences in certain FileStream behaviors between Windows and Unix when multiple + // asynchronous operations are issued against the stream to execute concurrently; on + // Unix the operations will be serialized due to the usage of a semaphore, but the + // position /length information won't be updated until after the write has completed, + // whereas on Windows it may happen before the write has completed. + + Debug.Assert(t.Status == TaskStatus.RanToCompletion); + var thisRef = (FileStream)s; + try + { + byte[] b = thisRef._asyncState._buffer; + thisRef._asyncState._buffer = null; // remove reference to user's buffer + return thisRef.ReadCore(b, thisRef._asyncState._offset, thisRef._asyncState._count); + } + finally { thisRef._asyncState.Release(); } + }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default); + } + else + { + return base.ReadAsync(buffer, offset, count, cancellationToken); + } + } + + /// + /// Reads a byte from the stream and advances the position within the stream + /// by one byte, or returns -1 if at the end of the stream. + /// + /// The unsigned byte cast to an Int32, or -1 if at the end of the stream. + public override int ReadByte() + { + if (_useAsyncIO) + { + _asyncState.Wait(); + try { return ReadByteCore(); } + finally { _asyncState.Release(); } + } + else + { + return ReadByteCore(); + } + } + + /// Writes a block of bytes to the file stream. + /// The buffer containing data to write to the stream. + /// The zero-based byte offset in array from which to begin copying bytes to the stream. + /// The maximum number of bytes to write. + public override void Write(byte[] array, int offset, int count) + { + ValidateReadWriteArgs(array, offset, count); + + if (_useAsyncIO) + { + _asyncState.Wait(); + try { WriteCore(array, offset, count); } + finally { _asyncState.Release(); } + } + else + { + WriteCore(array, offset, count); + } + } + + /// Writes a block of bytes to the file stream. + /// The buffer containing data to write to the stream. + /// The zero-based byte offset in array from which to begin copying bytes to the stream. + /// The maximum number of bytes to write. + private void WriteCore(byte[] array, int offset, int count) + { + PrepareForWriting(); + + // If no data is being written, nothing more to do. + if (count == 0) + { + return; + } + + // If there's already data in our write buffer, then we need to go through + // our buffer to ensure data isn't corrupted. + if (_writePos > 0) + { + // If there's space remaining in the buffer, then copy as much as + // we can from the user's buffer into ours. + int spaceRemaining = _bufferLength - _writePos; + if (spaceRemaining > 0) + { + int bytesToCopy = Math.Min(spaceRemaining, count); + Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, bytesToCopy); + _writePos += bytesToCopy; + + // If we've successfully copied all of the user's data, we're done. + if (count == bytesToCopy) + { + return; + } + + // Otherwise, keep track of how much more data needs to be handled. + offset += bytesToCopy; + count -= bytesToCopy; + } + + // At this point, the buffer is full, so flush it out. + FlushWriteBuffer(); + } + + // Our buffer is now empty. If using the buffer would slow things down (because + // the user's looking to write more data than we can store in the buffer), + // skip the buffer. Otherwise, put the remaining data into the buffer. + Debug.Assert(_writePos == 0); + if (count >= _bufferLength) + { + WriteNative(array, offset, count); + } + else + { + Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, count); + _writePos = count; + } + } + + /// Unbuffered, writes a block of bytes to the file stream. + /// The buffer containing data to write to the stream. + /// The zero-based byte offset in array from which to begin copying bytes to the stream. + /// The maximum number of bytes to write. + private unsafe void WriteNative(byte[] array, int offset, int count) + { + VerifyOSHandlePosition(); + + fixed (byte* bufPtr = array) + { + while (count > 0) + { + int bytesWritten = CheckFileCall(Interop.Sys.Write(_fileHandle, bufPtr + offset, count)); + Debug.Assert(bytesWritten <= count); + + _filePosition += bytesWritten; + count -= bytesWritten; + offset += bytesWritten; + } + } + } + + /// + /// Asynchronously writes a sequence of bytes to the current stream, advances + /// the current position within this stream by the number of bytes written, and + /// monitors cancellation requests. + /// + /// The buffer to write data from. + /// The zero-based byte offset in buffer from which to begin copying bytes to the stream. + /// The maximum number of bytes to write. + /// The token to monitor for cancellation requests. + /// A task that represents the asynchronous write operation. + private Task WriteAsyncInternal(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + if (_fileHandle.IsClosed) + throw Error.GetFileNotOpen(); + + if (_useAsyncIO) + { + if (!CanWrite) // match Windows behavior; this gets thrown synchronously + { + throw Error.GetWriteNotSupported(); + } + + // Serialize operations using the semaphore. + Task waitTask = _asyncState.WaitAsync(); + + // If we got ownership immediately, and if there's enough space in our buffer + // to buffer the entire write request, then do so and we're done. + if (waitTask.Status == TaskStatus.RanToCompletion) + { + int spaceRemaining = _bufferLength - _writePos; + if (spaceRemaining >= count) + { + try + { + PrepareForWriting(); + + Buffer.BlockCopy(buffer, offset, GetBuffer(), _writePos, count); + _writePos += count; + + return Task.CompletedTask; + } + catch (Exception exc) + { + return Task.FromException(exc); + } + finally + { + _asyncState.Release(); + } + } + } + + // Otherwise, issue the whole request asynchronously. + _asyncState.Update(buffer, offset, count); + return waitTask.ContinueWith((t, s) => + { + // The options available on Unix for writing asynchronously to an arbitrary file + // handle typically amount to just using another thread to do the synchronous write, + // which is exactly what this implementation does. This does mean there are subtle + // differences in certain FileStream behaviors between Windows and Unix when multiple + // asynchronous operations are issued against the stream to execute concurrently; on + // Unix the operations will be serialized due to the usage of a semaphore, but the + // position /length information won't be updated until after the write has completed, + // whereas on Windows it may happen before the write has completed. + + Debug.Assert(t.Status == TaskStatus.RanToCompletion); + var thisRef = (FileStream)s; + try + { + byte[] b = thisRef._asyncState._buffer; + thisRef._asyncState._buffer = null; // remove reference to user's buffer + thisRef.WriteCore(b, thisRef._asyncState._offset, thisRef._asyncState._count); + } + finally { thisRef._asyncState.Release(); } + }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default); + } + else + { + return base.WriteAsync(buffer, offset, count, cancellationToken); + } + } + + /// + /// Writes a byte to the current position in the stream and advances the position + /// within the stream by one byte. + /// + /// The byte to write to the stream. + public override void WriteByte(byte value) // avoids an array allocation in the base implementation + { + if (_useAsyncIO) + { + _asyncState.Wait(); + try { WriteByteCore(value); } + finally { _asyncState.Release(); } + } + else + { + WriteByteCore(value); + } + } + + /// Prevents other processes from reading from or writing to the FileStream. + /// The beginning of the range to lock. + /// The range to be locked. + private void LockInternal(long position, long length) + { + CheckFileCall(Interop.Sys.LockFileRegion(_fileHandle, position, length, Interop.Sys.LockType.F_WRLCK)); + } + + /// Allows access by other processes to all or part of a file that was previously locked. + /// The beginning of the range to unlock. + /// The range to be unlocked. + private void UnlockInternal(long position, long length) + { + CheckFileCall(Interop.Sys.LockFileRegion(_fileHandle, position, length, Interop.Sys.LockType.F_UNLCK)); + } + + /// Sets the current position of this stream to the given value. + /// The point relative to origin from which to begin seeking. + /// + /// Specifies the beginning, the end, or the current position as a reference + /// point for offset, using a value of type SeekOrigin. + /// + /// The new position in the stream. + public override long Seek(long offset, SeekOrigin origin) + { + if (origin < SeekOrigin.Begin || origin > SeekOrigin.End) + { + throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(origin)); + } + if (_fileHandle.IsClosed) + { + throw Error.GetFileNotOpen(); + } + if (!CanSeek) + { + throw Error.GetSeekNotSupported(); + } + + VerifyOSHandlePosition(); + + // Flush our write/read buffer. FlushWrite will output any write buffer we have and reset _bufferWritePos. + // We don't call FlushRead, as that will do an unnecessary seek to rewind the read buffer, and since we're + // about to seek and update our position, we can simply update the offset as necessary and reset our read + // position and length to 0. (In the future, for some simple cases we could potentially add an optimization + // here to just move data around in the buffer for short jumps, to avoid re-reading the data from disk.) + FlushWriteBuffer(); + if (origin == SeekOrigin.Current) + { + offset -= (_readLength - _readPos); + } + _readPos = _readLength = 0; + + // Keep track of where we were, in case we're in append mode and need to verify + long oldPos = 0; + if (_appendStart >= 0) + { + oldPos = SeekCore(0, SeekOrigin.Current); + } + + // Jump to the new location + long pos = SeekCore(offset, origin); + + // Prevent users from overwriting data in a file that was opened in append mode. + if (_appendStart != -1 && pos < _appendStart) + { + SeekCore(oldPos, SeekOrigin.Begin); + throw new IOException(SR.IO_SeekAppendOverwrite); + } + + // Return the new position + return pos; + } + + /// Sets the current position of this stream to the given value. + /// The point relative to origin from which to begin seeking. + /// + /// Specifies the beginning, the end, or the current position as a reference + /// point for offset, using a value of type SeekOrigin. + /// + /// The new position in the stream. + private long SeekCore(long offset, SeekOrigin origin) + { + Debug.Assert(!_fileHandle.IsClosed && (GetType() != typeof(FileStream) || CanSeek)); // verify that we can seek, but only if CanSeek won't be a virtual call (which could happen in the ctor) + Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End); + + long pos = CheckFileCall(Interop.Sys.LSeek(_fileHandle, offset, (Interop.Sys.SeekWhence)(int)origin)); // SeekOrigin values are the same as Interop.libc.SeekWhence values + _filePosition = pos; + return pos; + } + + private long CheckFileCall(long result, bool ignoreNotSupported = false) + { + if (result < 0) + { + Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + if (!(ignoreNotSupported && errorInfo.Error == Interop.Error.ENOTSUP)) + { + throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false); + } + } + + return result; + } + + private int CheckFileCall(int result, bool ignoreNotSupported = false) + { + CheckFileCall((long)result, ignoreNotSupported); + + return result; + } + + /// State used when the stream is in async mode. + private sealed class AsyncState : SemaphoreSlim + { + /// The caller's buffer currently being used by the active async operation. + internal byte[] _buffer; + /// The caller's offset currently being used by the active async operation. + internal int _offset; + /// The caller's count currently being used by the active async operation. + internal int _count; + /// The last task successfully, synchronously returned task from ReadAsync. + internal Task _lastSuccessfulReadTask; + + /// Initialize the AsyncState. + internal AsyncState() : base(initialCount: 1, maxCount: 1) { } + + /// Sets the active buffer, offset, and count. + internal void Update(byte[] buffer, int offset, int count) + { + _buffer = buffer; + _offset = offset; + _count = count; + } + } + } +} diff --git a/src/mscorlib/corefx/System/IO/FileStream.Win32.cs b/src/mscorlib/corefx/System/IO/FileStream.Win32.cs new file mode 100644 index 0000000000..350d948b00 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/FileStream.Win32.cs @@ -0,0 +1,1770 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Buffers; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Win32.SafeHandles; +using System.Runtime.CompilerServices; + +/* + * Win32FileStream supports different modes of accessing the disk - async mode + * and sync mode. They are two completely different codepaths in the + * sync & async methods (i.e. Read/Write vs. ReadAsync/WriteAsync). File + * handles in NT can be opened in only sync or overlapped (async) mode, + * and we have to deal with this pain. Stream has implementations of + * the sync methods in terms of the async ones, so we'll + * call through to our base class to get those methods when necessary. + * + * Also buffering is added into Win32FileStream as well. Folded in the + * code from BufferedStream, so all the comments about it being mostly + * aggressive (and the possible perf improvement) apply to Win32FileStream as + * well. Also added some buffering to the async code paths. + * + * Class Invariants: + * The class has one buffer, shared for reading & writing. It can only be + * used for one or the other at any point in time - not both. The following + * should be true: + * 0 <= _readPos <= _readLen < _bufferSize + * 0 <= _writePos < _bufferSize + * _readPos == _readLen && _readPos > 0 implies the read buffer is valid, + * but we're at the end of the buffer. + * _readPos == _readLen == 0 means the read buffer contains garbage. + * Either _writePos can be greater than 0, or _readLen & _readPos can be + * greater than zero, but neither can be greater than zero at the same time. + * + */ + +namespace System.IO +{ + public partial class FileStream : Stream + { + private bool _canSeek; + private bool _isPipe; // Whether to disable async buffering code. + private long _appendStart; // When appending, prevent overwriting file. + + private static unsafe IOCompletionCallback s_ioCallback = FileStreamCompletionSource.IOCallback; + + private Task _lastSynchronouslyCompletedTask = null; // cached task for read ops that complete synchronously + private Task _activeBufferOperation = null; // tracks in-progress async ops using the buffer + private PreAllocatedOverlapped _preallocatedOverlapped; // optimization for async ops to avoid per-op allocations + private FileStreamCompletionSource _currentOverlappedOwner; // async op currently using the preallocated overlapped + + private SafeFileHandle OpenHandle(FileMode mode, FileShare share, FileOptions options) + { + Interop.mincore.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); + + int fAccess = + ((_access & FileAccess.Read) == FileAccess.Read ? GENERIC_READ : 0) | + ((_access & FileAccess.Write) == FileAccess.Write ? GENERIC_WRITE : 0); + + // Our Inheritable bit was stolen from Windows, but should be set in + // the security attributes class. Don't leave this bit set. + share &= ~FileShare.Inheritable; + + // Must use a valid Win32 constant here... + if (mode == FileMode.Append) + mode = FileMode.OpenOrCreate; + + int flagsAndAttributes = (int)options; + + // For mitigating local elevation of privilege attack through named pipes + // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the + // named pipe server can't impersonate a high privileged client security context + flagsAndAttributes |= (Interop.mincore.SecurityOptions.SECURITY_SQOS_PRESENT | Interop.mincore.SecurityOptions.SECURITY_ANONYMOUS); + + // Don't pop up a dialog for reading from an empty floppy drive + uint oldMode = Interop.mincore.SetErrorMode(Interop.mincore.SEM_FAILCRITICALERRORS); + try + { + SafeFileHandle fileHandle = Interop.mincore.SafeCreateFile(_path, fAccess, share, ref secAttrs, mode, flagsAndAttributes, IntPtr.Zero); + fileHandle.IsAsync = _useAsyncIO; + + if (fileHandle.IsInvalid) + { + // Return a meaningful exception with the full path. + + // NT5 oddity - when trying to open "C:\" as a Win32FileStream, + // we usually get ERROR_PATH_NOT_FOUND from the OS. We should + // probably be consistent w/ every other directory. + int errorCode = Marshal.GetLastWin32Error(); + + if (errorCode == Interop.mincore.Errors.ERROR_PATH_NOT_FOUND && _path.Equals(Directory.InternalGetDirectoryRoot(_path))) + errorCode = Interop.mincore.Errors.ERROR_ACCESS_DENIED; + + throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); + } + + return fileHandle; + } + finally + { + Interop.mincore.SetErrorMode(oldMode); + } + } + + private void Init(FileMode mode, FileShare share) + { + // Disallow access to all non-file devices from the Win32FileStream + // constructors that take a String. Everyone else can call + // CreateFile themselves then use the constructor that takes an + // IntPtr. Disallows "con:", "com1:", "lpt1:", etc. + int fileType = Interop.mincore.GetFileType(_fileHandle); + if (fileType != Interop.mincore.FileTypes.FILE_TYPE_DISK) + { + _fileHandle.Dispose(); + throw new NotSupportedException(SR.NotSupported_FileStreamOnNonFiles); + } + + // This is necessary for async IO using IO Completion ports via our + // managed Threadpool API's. This (theoretically) calls the OS's + // BindIoCompletionCallback method, and passes in a stub for the + // LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped + // struct for this request and gets a delegate to a managed callback + // from there, which it then calls on a threadpool thread. (We allocate + // our native OVERLAPPED structs 2 pointers too large and store EE state + // & GC handles there, one to an IAsyncResult, the other to a delegate.) + if (_useAsyncIO) + { + try + { + _fileHandle.ThreadPoolBinding = ThreadPoolBoundHandle.BindHandle(_fileHandle); + } + catch (ArgumentException ex) + { + throw new IOException(SR.IO_BindHandleFailed, ex); + } + finally + { + if (_fileHandle.ThreadPoolBinding == null) + { + // We should close the handle so that the handle is not open until SafeFileHandle GC + Debug.Assert(!_exposedHandle, "Are we closing handle that we exposed/not own, how?"); + _fileHandle.Dispose(); + } + } + } + + _canSeek = true; + + // For Append mode... + if (mode == FileMode.Append) + { + _appendStart = SeekCore(0, SeekOrigin.End); + } + else + { + _appendStart = -1; + } + } + + private void InitFromHandle(SafeFileHandle handle) + { + int handleType = Interop.mincore.GetFileType(_fileHandle); + Debug.Assert(handleType == Interop.mincore.FileTypes.FILE_TYPE_DISK || handleType == Interop.mincore.FileTypes.FILE_TYPE_PIPE || handleType == Interop.mincore.FileTypes.FILE_TYPE_CHAR, "FileStream was passed an unknown file type!"); + + _canSeek = handleType == Interop.mincore.FileTypes.FILE_TYPE_DISK; + _isPipe = handleType == Interop.mincore.FileTypes.FILE_TYPE_PIPE; + + // This is necessary for async IO using IO Completion ports via our + // managed Threadpool API's. This calls the OS's + // BindIoCompletionCallback method, and passes in a stub for the + // LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped + // struct for this request and gets a delegate to a managed callback + // from there, which it then calls on a threadpool thread. (We allocate + // our native OVERLAPPED structs 2 pointers too large and store EE + // state & a handle to a delegate there.) + // + // If, however, we've already bound this file handle to our completion port, + // don't try to bind it again because it will fail. A handle can only be + // bound to a single completion port at a time. + if (_useAsyncIO && !GetSuppressBindHandle(handle)) + { + try + { + _fileHandle.ThreadPoolBinding = ThreadPoolBoundHandle.BindHandle(_fileHandle); + } + catch (Exception ex) + { + // If you passed in a synchronous handle and told us to use + // it asynchronously, throw here. + throw new ArgumentException(SR.Arg_HandleNotAsync, nameof(handle), ex); + } + } + else if (!_useAsyncIO) + { + if (handleType != Interop.mincore.FileTypes.FILE_TYPE_PIPE) + VerifyHandleIsSync(); + } + + if (_canSeek) + SeekCore(0, SeekOrigin.Current); + else + _filePosition = 0; + } + + private static bool GetSuppressBindHandle(SafeFileHandle handle) + { + return handle.IsAsync.HasValue ? handle.IsAsync.Value : false; + } + + private unsafe static Interop.mincore.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share) + { + Interop.mincore.SECURITY_ATTRIBUTES secAttrs = default(Interop.mincore.SECURITY_ATTRIBUTES); + if ((share & FileShare.Inheritable) != 0) + { + secAttrs = new Interop.mincore.SECURITY_ATTRIBUTES(); + secAttrs.nLength = (uint)sizeof(Interop.mincore.SECURITY_ATTRIBUTES); + + secAttrs.bInheritHandle = Interop.BOOL.TRUE; + } + return secAttrs; + } + + // Verifies that this handle supports synchronous IO operations (unless you + // didn't open it for either reading or writing). + private unsafe void VerifyHandleIsSync() + { + Debug.Assert(!_useAsyncIO); + + // Do NOT use this method on pipes. Reading or writing to a pipe may + // cause an app to block incorrectly, introducing a deadlock (depending + // on whether a write will wake up an already-blocked thread or this + // Win32FileStream's thread). + Debug.Assert(Interop.mincore.GetFileType(_fileHandle) != Interop.mincore.FileTypes.FILE_TYPE_PIPE); + + byte* bytes = stackalloc byte[1]; + int numBytesReadWritten; + int r = -1; + + // If the handle is a pipe, ReadFile will block until there + // has been a write on the other end. We'll just have to deal with it, + // For the read end of a pipe, you can mess up and + // accidentally read synchronously from an async pipe. + if ((_access & FileAccess.Read) != 0) // don't use the virtual CanRead or CanWrite, as this may be used in the ctor + { + r = Interop.mincore.ReadFile(_fileHandle, bytes, 0, out numBytesReadWritten, IntPtr.Zero); + } + else if ((_access & FileAccess.Write) != 0) // don't use the virtual CanRead or CanWrite, as this may be used in the ctor + { + r = Interop.mincore.WriteFile(_fileHandle, bytes, 0, out numBytesReadWritten, IntPtr.Zero); + } + + if (r == 0) + { + int errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid(throwIfInvalidHandle: true); + if (errorCode == ERROR_INVALID_PARAMETER) + throw new ArgumentException(SR.Arg_HandleNotSync, "handle"); + } + } + + private bool HasActiveBufferOperation + { + get { return _activeBufferOperation != null && !_activeBufferOperation.IsCompleted; } + } + + public override bool CanSeek + { + get { return _canSeek; } + } + + private long GetLengthInternal() + { + Interop.mincore.FILE_STANDARD_INFO info = new Interop.mincore.FILE_STANDARD_INFO(); + + if (!Interop.mincore.GetFileInformationByHandleEx(_fileHandle, Interop.mincore.FILE_INFO_BY_HANDLE_CLASS.FileStandardInfo, out info, (uint)Marshal.SizeOf())) + throw Win32Marshal.GetExceptionForLastWin32Error(); + long len = info.EndOfFile; + // If we're writing near the end of the file, we must include our + // internal buffer in our Length calculation. Don't flush because + // we use the length of the file in our async write method. + if (_writePos > 0 && _filePosition + _writePos > len) + len = _writePos + _filePosition; + return len; + } + + protected override void Dispose(bool disposing) + { + // Nothing will be done differently based on whether we are + // disposing vs. finalizing. This is taking advantage of the + // weak ordering between normal finalizable objects & critical + // finalizable objects, which I included in the SafeHandle + // design for Win32FileStream, which would often "just work" when + // finalized. + try + { + if (_fileHandle != null && !_fileHandle.IsClosed) + { + // Flush data to disk iff we were writing. After + // thinking about this, we also don't need to flush + // our read position, regardless of whether the handle + // was exposed to the user. They probably would NOT + // want us to do this. + if (_writePos > 0) + { + FlushWriteBuffer(!disposing); + } + } + } + finally + { + if (_fileHandle != null && !_fileHandle.IsClosed) + { + if (_fileHandle.ThreadPoolBinding != null) + _fileHandle.ThreadPoolBinding.Dispose(); + + _fileHandle.Dispose(); + } + + if (_preallocatedOverlapped != null) + _preallocatedOverlapped.Dispose(); + + _canSeek = false; + + // Don't set the buffer to null, to avoid a NullReferenceException + // when users have a race condition in their code (i.e. they call + // Close when calling another method on Stream like Read). + //_buffer = null; + base.Dispose(disposing); + } + } + + private void FlushOSBuffer() + { + if (!Interop.mincore.FlushFileBuffers(_fileHandle)) + { + throw Win32Marshal.GetExceptionForLastWin32Error(); + } + } + + // Returns a task that flushes the internal write buffer + private Task FlushWriteAsync(CancellationToken cancellationToken) + { + Debug.Assert(_useAsyncIO); + Debug.Assert(_readPos == 0 && _readLength == 0, "FileStream: Read buffer must be empty in FlushWriteAsync!"); + + // If the buffer is already flushed, don't spin up the OS write + if (_writePos == 0) return Task.CompletedTask; + + Task flushTask = WriteInternalCoreAsync(GetBuffer(), 0, _writePos, cancellationToken); + _writePos = 0; + + // Update the active buffer operation + _activeBufferOperation = HasActiveBufferOperation ? + Task.WhenAll(_activeBufferOperation, flushTask) : + flushTask; + + return flushTask; + } + + // Writes are buffered. Anytime the buffer fills up + // (_writePos + delta > _bufferSize) or the buffer switches to reading + // and there is left over data (_writePos > 0), this function must be called. + private void FlushWriteBuffer(bool calledFromFinalizer = false) + { + if (_writePos == 0) return; + Debug.Assert(_readPos == 0 && _readLength == 0, "FileStream: Read buffer must be empty in FlushWrite!"); + + if (_useAsyncIO) + { + Task writeTask = FlushWriteAsync(CancellationToken.None); + // With our Whidbey async IO & overlapped support for AD unloads, + // we don't strictly need to block here to release resources + // since that support takes care of the pinning & freeing the + // overlapped struct. We need to do this when called from + // Close so that the handle is closed when Close returns, but + // we don't need to call EndWrite from the finalizer. + // Additionally, if we do call EndWrite, we block forever + // because AD unloads prevent us from running the managed + // callback from the IO completion port. Blocking here when + // called from the finalizer during AD unload is clearly wrong, + // but we can't use any sort of test for whether the AD is + // unloading because if we weren't unloading, an AD unload + // could happen on a separate thread before we call EndWrite. + if (!calledFromFinalizer) + { + writeTask.GetAwaiter().GetResult(); + } + } + else + { + WriteCore(GetBuffer(), 0, _writePos); + } + + _writePos = 0; + } + + private void SetLengthInternal(long value) + { + // Handle buffering updates. + if (_writePos > 0) + { + FlushWriteBuffer(); + } + else if (_readPos < _readLength) + { + FlushReadBuffer(); + } + _readPos = 0; + _readLength = 0; + + if (_appendStart != -1 && value < _appendStart) + throw new IOException(SR.IO_SetLengthAppendTruncate); + SetLengthCore(value); + } + + // We absolutely need this method broken out so that WriteInternalCoreAsync can call + // a method without having to go through buffering code that might call FlushWrite. + private void SetLengthCore(long value) + { + Debug.Assert(value >= 0, "value >= 0"); + long origPos = _filePosition; + + VerifyOSHandlePosition(); + if (_filePosition != value) + SeekCore(value, SeekOrigin.Begin); + if (!Interop.mincore.SetEndOfFile(_fileHandle)) + { + int errorCode = Marshal.GetLastWin32Error(); + if (errorCode == Interop.mincore.Errors.ERROR_INVALID_PARAMETER) + throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_FileLengthTooBig); + throw Win32Marshal.GetExceptionForWin32Error(errorCode); + } + // Return file pointer to where it was before setting length + if (origPos != value) + { + if (origPos < value) + SeekCore(origPos, SeekOrigin.Begin); + else + SeekCore(0, SeekOrigin.End); + } + } + + // Instance method to help code external to this MarshalByRefObject avoid + // accessing its fields by ref. This avoids a compiler warning. + private FileStreamCompletionSource CompareExchangeCurrentOverlappedOwner(FileStreamCompletionSource newSource, FileStreamCompletionSource existingSource) => Interlocked.CompareExchange(ref _currentOverlappedOwner, newSource, existingSource); + + public override int Read(byte[] array, int offset, int count) + { + ValidateReadWriteArgs(array, offset, count); + return ReadCore(array, offset, count); + } + + private int ReadCore(byte[] array, int offset, int count) + { + Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), + "We're either reading or writing, but not both."); + + bool isBlocked = false; + int n = _readLength - _readPos; + // if the read buffer is empty, read into either user's array or our + // buffer, depending on number of bytes user asked for and buffer size. + if (n == 0) + { + if (!CanRead) throw Error.GetReadNotSupported(); + if (_writePos > 0) FlushWriteBuffer(); + if (!CanSeek || (count >= _bufferLength)) + { + n = ReadNative(array, offset, count); + // Throw away read buffer. + _readPos = 0; + _readLength = 0; + return n; + } + n = ReadNative(GetBuffer(), 0, _bufferLength); + if (n == 0) return 0; + isBlocked = n < _bufferLength; + _readPos = 0; + _readLength = n; + } + // Now copy min of count or numBytesAvailable (i.e. near EOF) to array. + if (n > count) n = count; + Buffer.BlockCopy(GetBuffer(), _readPos, array, offset, n); + _readPos += n; + + // We may have read less than the number of bytes the user asked + // for, but that is part of the Stream contract. Reading again for + // more data may cause us to block if we're using a device with + // no clear end of file, such as a serial port or pipe. If we + // blocked here & this code was used with redirected pipes for a + // process's standard output, this can lead to deadlocks involving + // two processes. But leave this here for files to avoid what would + // probably be a breaking change. -- + + // If we are reading from a device with no clear EOF like a + // serial port or a pipe, this will cause us to block incorrectly. + if (!_isPipe) + { + // If we hit the end of the buffer and didn't have enough bytes, we must + // read some more from the underlying stream. However, if we got + // fewer bytes from the underlying stream than we asked for (i.e. we're + // probably blocked), don't ask for more bytes. + if (n < count && !isBlocked) + { + Debug.Assert(_readPos == _readLength, "Read buffer should be empty!"); + int moreBytesRead = ReadNative(array, offset + n, count - n); + n += moreBytesRead; + // We've just made our buffer inconsistent with our position + // pointer. We must throw away the read buffer. + _readPos = 0; + _readLength = 0; + } + } + + return n; + } + + [Conditional("DEBUG")] + private void AssertCanRead(byte[] buffer, int offset, int count) + { + Debug.Assert(!_fileHandle.IsClosed, "!_fileHandle.IsClosed"); + Debug.Assert(CanRead, "CanRead"); + Debug.Assert(buffer != null, "buffer != null"); + Debug.Assert(_writePos == 0, "_writePos == 0"); + Debug.Assert(offset >= 0, "offset is negative"); + Debug.Assert(count >= 0, "count is negative"); + } + + private unsafe int ReadNative(byte[] buffer, int offset, int count) + { + AssertCanRead(buffer, offset, count); + + if (_useAsyncIO) + return ReadNativeAsync(buffer, offset, count, 0, CancellationToken.None).GetAwaiter().GetResult(); + + // Make sure we are reading from the right spot + VerifyOSHandlePosition(); + + int errorCode = 0; + int r = ReadFileNative(_fileHandle, buffer, offset, count, null, out errorCode); + + if (r == -1) + { + // For pipes, ERROR_BROKEN_PIPE is the normal end of the pipe. + if (errorCode == ERROR_BROKEN_PIPE) + { + r = 0; + } + else + { + if (errorCode == ERROR_INVALID_PARAMETER) + throw new ArgumentException(SR.Arg_HandleNotSync, "_fileHandle"); + + throw Win32Marshal.GetExceptionForWin32Error(errorCode); + } + } + Debug.Assert(r >= 0, "FileStream's ReadNative is likely broken."); + _filePosition += r; + + return r; + } + + public override long Seek(long offset, SeekOrigin origin) + { + if (origin < SeekOrigin.Begin || origin > SeekOrigin.End) + throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(origin)); + if (_fileHandle.IsClosed) throw Error.GetFileNotOpen(); + if (!CanSeek) throw Error.GetSeekNotSupported(); + + Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both."); + + // If we've got bytes in our buffer to write, write them out. + // If we've read in and consumed some bytes, we'll have to adjust + // our seek positions ONLY IF we're seeking relative to the current + // position in the stream. This simulates doing a seek to the new + // position, then a read for the number of bytes we have in our buffer. + if (_writePos > 0) + { + FlushWriteBuffer(); + } + else if (origin == SeekOrigin.Current) + { + // Don't call FlushRead here, which would have caused an infinite + // loop. Simply adjust the seek origin. This isn't necessary + // if we're seeking relative to the beginning or end of the stream. + offset -= (_readLength - _readPos); + } + _readPos = _readLength = 0; + + // Verify that internal position is in sync with the handle + VerifyOSHandlePosition(); + + long oldPos = _filePosition + (_readPos - _readLength); + long pos = SeekCore(offset, origin); + + // Prevent users from overwriting data in a file that was opened in + // append mode. + if (_appendStart != -1 && pos < _appendStart) + { + SeekCore(oldPos, SeekOrigin.Begin); + throw new IOException(SR.IO_SeekAppendOverwrite); + } + + // We now must update the read buffer. We can in some cases simply + // update _readPos within the buffer, copy around the buffer so our + // Position property is still correct, and avoid having to do more + // reads from the disk. Otherwise, discard the buffer's contents. + if (_readLength > 0) + { + // We can optimize the following condition: + // oldPos - _readPos <= pos < oldPos + _readLen - _readPos + if (oldPos == pos) + { + if (_readPos > 0) + { + //Console.WriteLine("Seek: seeked for 0, adjusting buffer back by: "+_readPos+" _readLen: "+_readLen); + Buffer.BlockCopy(GetBuffer(), _readPos, GetBuffer(), 0, _readLength - _readPos); + _readLength -= _readPos; + _readPos = 0; + } + // If we still have buffered data, we must update the stream's + // position so our Position property is correct. + if (_readLength > 0) + SeekCore(_readLength, SeekOrigin.Current); + } + else if (oldPos - _readPos < pos && pos < oldPos + _readLength - _readPos) + { + int diff = (int)(pos - oldPos); + //Console.WriteLine("Seek: diff was "+diff+", readpos was "+_readPos+" adjusting buffer - shrinking by "+ (_readPos + diff)); + Buffer.BlockCopy(GetBuffer(), _readPos + diff, GetBuffer(), 0, _readLength - (_readPos + diff)); + _readLength -= (_readPos + diff); + _readPos = 0; + if (_readLength > 0) + SeekCore(_readLength, SeekOrigin.Current); + } + else + { + // Lose the read buffer. + _readPos = 0; + _readLength = 0; + } + Debug.Assert(_readLength >= 0 && _readPos <= _readLength, "_readLen should be nonnegative, and _readPos should be less than or equal _readLen"); + Debug.Assert(pos == Position, "Seek optimization: pos != Position! Buffer math was mangled."); + } + return pos; + } + + // This doesn't do argument checking. Necessary for SetLength, which must + // set the file pointer beyond the end of the file. This will update the + // internal position + // This is called during construction so it should avoid any virtual + // calls + private long SeekCore(long offset, SeekOrigin origin) + { + Debug.Assert(!_fileHandle.IsClosed && _canSeek, "!_handle.IsClosed && _parent.CanSeek"); + Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End, "origin>=SeekOrigin.Begin && origin<=SeekOrigin.End"); + long ret = 0; + + if (!Interop.mincore.SetFilePointerEx(_fileHandle, offset, out ret, (uint)origin)) + { + int errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid(); + throw Win32Marshal.GetExceptionForWin32Error(errorCode); + } + + _filePosition = ret; + return ret; + } + + partial void OnBufferAllocated() + { + Debug.Assert(_buffer != null); + Debug.Assert(_preallocatedOverlapped == null); + + if (_useAsyncIO) + _preallocatedOverlapped = new PreAllocatedOverlapped(s_ioCallback, this, _buffer); + } + + public override void Write(byte[] array, int offset, int count) + { + ValidateReadWriteArgs(array, offset, count); + + if (_writePos == 0) + { + // Ensure we can write to the stream, and ready buffer for writing. + if (!CanWrite) throw Error.GetWriteNotSupported(); + if (_readPos < _readLength) FlushReadBuffer(); + _readPos = 0; + _readLength = 0; + } + + // If our buffer has data in it, copy data from the user's array into + // the buffer, and if we can fit it all there, return. Otherwise, write + // the buffer to disk and copy any remaining data into our buffer. + // The assumption here is memcpy is cheaper than disk (or net) IO. + // (10 milliseconds to disk vs. ~20-30 microseconds for a 4K memcpy) + // So the extra copying will reduce the total number of writes, in + // non-pathological cases (i.e. write 1 byte, then write for the buffer + // size repeatedly) + if (_writePos > 0) + { + int numBytes = _bufferLength - _writePos; // space left in buffer + if (numBytes > 0) + { + if (numBytes > count) + numBytes = count; + Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, numBytes); + _writePos += numBytes; + if (count == numBytes) return; + offset += numBytes; + count -= numBytes; + } + // Reset our buffer. We essentially want to call FlushWrite + // without calling Flush on the underlying Stream. + + if (_useAsyncIO) + { + WriteInternalCoreAsync(GetBuffer(), 0, _writePos, CancellationToken.None).GetAwaiter().GetResult(); + } + else + { + WriteCore(GetBuffer(), 0, _writePos); + } + _writePos = 0; + } + // If the buffer would slow writes down, avoid buffer completely. + if (count >= _bufferLength) + { + Debug.Assert(_writePos == 0, "FileStream cannot have buffered data to write here! Your stream will be corrupted."); + WriteCore(array, offset, count); + return; + } + else if (count == 0) + { + return; // Don't allocate a buffer then call memcpy for 0 bytes. + } + + // Copy remaining bytes into buffer, to write at a later date. + Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, count); + _writePos = count; + return; + } + + private unsafe void WriteCore(byte[] buffer, int offset, int count) + { + Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); + Debug.Assert(CanWrite, "_parent.CanWrite"); + + Debug.Assert(buffer != null, "buffer != null"); + Debug.Assert(_readPos == _readLength, "_readPos == _readLen"); + Debug.Assert(offset >= 0, "offset is negative"); + Debug.Assert(count >= 0, "count is negative"); + if (_useAsyncIO) + { + WriteInternalCoreAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); + return; + } + + // Make sure we are writing to the position that we think we are + VerifyOSHandlePosition(); + + int errorCode = 0; + int r = WriteFileNative(_fileHandle, buffer, offset, count, null, out errorCode); + + if (r == -1) + { + // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing. + if (errorCode == ERROR_NO_DATA) + { + r = 0; + } + else + { + // ERROR_INVALID_PARAMETER may be returned for writes + // where the position is too large (i.e. writing at Int64.MaxValue + // on Win9x) OR for synchronous writes to a handle opened + // asynchronously. + if (errorCode == ERROR_INVALID_PARAMETER) + throw new IOException(SR.IO_FileTooLongOrHandleNotSync); + throw Win32Marshal.GetExceptionForWin32Error(errorCode); + } + } + Debug.Assert(r >= 0, "FileStream's WriteCore is likely broken."); + _filePosition += r; + return; + } + + private Task ReadAsyncInternal(byte[] array, int offset, int numBytes, CancellationToken cancellationToken) + { + // If async IO is not supported on this platform or + // if this Win32FileStream was not opened with FileOptions.Asynchronous. + if (!_useAsyncIO) + { + return base.ReadAsync(array, offset, numBytes, cancellationToken); + } + + if (!CanRead) throw Error.GetReadNotSupported(); + + Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both."); + + if (_isPipe) + { + // Pipes are tricky, at least when you have 2 different pipes + // that you want to use simultaneously. When redirecting stdout + // & stderr with the Process class, it's easy to deadlock your + // parent & child processes when doing writes 4K at a time. The + // OS appears to use a 4K buffer internally. If you write to a + // pipe that is full, you will block until someone read from + // that pipe. If you try reading from an empty pipe and + // Win32FileStream's ReadAsync blocks waiting for data to fill it's + // internal buffer, you will be blocked. In a case where a child + // process writes to stdout & stderr while a parent process tries + // reading from both, you can easily get into a deadlock here. + // To avoid this deadlock, don't buffer when doing async IO on + // pipes. But don't completely ignore buffered data either. + if (_readPos < _readLength) + { + int n = _readLength - _readPos; + if (n > numBytes) n = numBytes; + Buffer.BlockCopy(GetBuffer(), _readPos, array, offset, n); + _readPos += n; + + // Return a completed task + return TaskFromResultOrCache(n); + } + else + { + Debug.Assert(_writePos == 0, "Win32FileStream must not have buffered write data here! Pipes should be unidirectional."); + return ReadNativeAsync(array, offset, numBytes, 0, cancellationToken); + } + } + + Debug.Assert(!_isPipe, "Should not be a pipe."); + + // Handle buffering. + if (_writePos > 0) FlushWriteBuffer(); + if (_readPos == _readLength) + { + // I can't see how to handle buffering of async requests when + // filling the buffer asynchronously, without a lot of complexity. + // The problems I see are issuing an async read, we do an async + // read to fill the buffer, then someone issues another read + // (either synchronously or asynchronously) before the first one + // returns. This would involve some sort of complex buffer locking + // that we probably don't want to get into, at least not in V1. + // If we did a sync read to fill the buffer, we could avoid the + // problem, and any async read less than 64K gets turned into a + // synchronous read by NT anyways... -- + + if (numBytes < _bufferLength) + { + Task readTask = ReadNativeAsync(GetBuffer(), 0, _bufferLength, 0, cancellationToken); + _readLength = readTask.GetAwaiter().GetResult(); + int n = _readLength; + if (n > numBytes) n = numBytes; + Buffer.BlockCopy(GetBuffer(), 0, array, offset, n); + _readPos = n; + + // Return a completed task (recycling the one above if possible) + return (_readLength == n ? readTask : TaskFromResultOrCache(n)); + } + else + { + // Here we're making our position pointer inconsistent + // with our read buffer. Throw away the read buffer's contents. + _readPos = 0; + _readLength = 0; + return ReadNativeAsync(array, offset, numBytes, 0, cancellationToken); + } + } + else + { + int n = _readLength - _readPos; + if (n > numBytes) n = numBytes; + Buffer.BlockCopy(GetBuffer(), _readPos, array, offset, n); + _readPos += n; + + if (n >= numBytes) + { + // Return a completed task + return TaskFromResultOrCache(n); + } + else + { + // For streams with no clear EOF like serial ports or pipes + // we cannot read more data without causing an app to block + // incorrectly. Pipes don't go down this path + // though. This code needs to be fixed. + // Throw away read buffer. + _readPos = 0; + _readLength = 0; + return ReadNativeAsync(array, offset + n, numBytes - n, n, cancellationToken); + } + } + } + + unsafe private Task ReadNativeAsync(byte[] bytes, int offset, int numBytes, int numBufferedBytesRead, CancellationToken cancellationToken) + { + AssertCanRead(bytes, offset, numBytes); + Debug.Assert(_useAsyncIO, "ReadNativeAsync doesn't work on synchronous file streams!"); + + // Create and store async stream class library specific data in the async result + + FileStreamCompletionSource completionSource = new FileStreamCompletionSource(this, numBufferedBytesRead, bytes, cancellationToken); + NativeOverlapped* intOverlapped = completionSource.Overlapped; + + // Calculate position in the file we should be at after the read is done + if (CanSeek) + { + long len = Length; + + // Make sure we are reading from the position that we think we are + VerifyOSHandlePosition(); + + if (_filePosition + numBytes > len) + { + if (_filePosition <= len) + numBytes = (int)(len - _filePosition); + else + numBytes = 0; + } + + // Now set the position to read from in the NativeOverlapped struct + // For pipes, we should leave the offset fields set to 0. + intOverlapped->OffsetLow = unchecked((int)_filePosition); + intOverlapped->OffsetHigh = (int)(_filePosition >> 32); + + // When using overlapped IO, the OS is not supposed to + // touch the file pointer location at all. We will adjust it + // ourselves. This isn't threadsafe. + + // WriteFile should not update the file pointer when writing + // in overlapped mode, according to MSDN. But it does update + // the file pointer when writing to a UNC path! + // So changed the code below to seek to an absolute + // location, not a relative one. ReadFile seems consistent though. + SeekCore(numBytes, SeekOrigin.Current); + } + + // queue an async ReadFile operation and pass in a packed overlapped + int errorCode = 0; + int r = ReadFileNative(_fileHandle, bytes, offset, numBytes, intOverlapped, out errorCode); + // ReadFile, the OS version, will return 0 on failure. But + // my ReadFileNative wrapper returns -1. My wrapper will return + // the following: + // On error, r==-1. + // On async requests that are still pending, r==-1 w/ errorCode==ERROR_IO_PENDING + // on async requests that completed sequentially, r==0 + // You will NEVER RELIABLY be able to get the number of bytes + // read back from this call when using overlapped structures! You must + // not pass in a non-null lpNumBytesRead to ReadFile when using + // overlapped structures! This is by design NT behavior. + if (r == -1 && numBytes != -1) + { + // For pipes, when they hit EOF, they will come here. + if (errorCode == ERROR_BROKEN_PIPE) + { + // Not an error, but EOF. AsyncFSCallback will NOT be + // called. Call the user callback here. + + // We clear the overlapped status bit for this special case. + // Failure to do so looks like we are freeing a pending overlapped later. + intOverlapped->InternalLow = IntPtr.Zero; + completionSource.SetCompletedSynchronously(0); + } + else if (errorCode != ERROR_IO_PENDING) + { + if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere. + { + SeekCore(0, SeekOrigin.Current); + } + + completionSource.ReleaseNativeResource(); + + if (errorCode == ERROR_HANDLE_EOF) + { + throw Error.GetEndOfFile(); + } + else + { + throw Win32Marshal.GetExceptionForWin32Error(errorCode); + } + } + else + { + // Only once the IO is pending do we register for cancellation + completionSource.RegisterForCancellation(); + } + } + else + { + // Due to a workaround for a race condition in NT's ReadFile & + // WriteFile routines, we will always be returning 0 from ReadFileNative + // when we do async IO instead of the number of bytes read, + // irregardless of whether the operation completed + // synchronously or asynchronously. We absolutely must not + // set asyncResult._numBytes here, since will never have correct + // results. + //Console.WriteLine("ReadFile returned: "+r+" (0x"+Int32.Format(r, "x")+") The IO completed synchronously, but the user callback was called on a separate thread"); + } + + return completionSource.Task; + } + + // Reads a byte from the file stream. Returns the byte cast to an int + // or -1 if reading from the end of the stream. + public override int ReadByte() + { + return ReadByteCore(); + } + + private Task WriteAsyncInternal(byte[] array, int offset, int numBytes, CancellationToken cancellationToken) + { + // If async IO is not supported on this platform or + // if this Win32FileStream was not opened with FileOptions.Asynchronous. + if (!_useAsyncIO) + { + return base.WriteAsync(array, offset, numBytes, cancellationToken); + } + + if (!CanWrite) throw Error.GetWriteNotSupported(); + + Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both."); + Debug.Assert(!_isPipe || (_readPos == 0 && _readLength == 0), "Win32FileStream must not have buffered data here! Pipes should be unidirectional."); + + bool writeDataStoredInBuffer = false; + if (!_isPipe) // avoid async buffering with pipes, as doing so can lead to deadlocks (see comments in ReadInternalAsyncCore) + { + // Ensure the buffer is clear for writing + if (_writePos == 0) + { + if (_readPos < _readLength) + { + FlushReadBuffer(); + } + _readPos = 0; + _readLength = 0; + } + + // Determine how much space remains in the buffer + int remainingBuffer = _bufferLength - _writePos; + Debug.Assert(remainingBuffer >= 0); + + // Simple/common case: + // - The write is smaller than our buffer, such that it's worth considering buffering it. + // - There's no active flush operation, such that we don't have to worry about the existing buffer being in use. + // - And the data we're trying to write fits in the buffer, meaning it wasn't already filled by previous writes. + // In that case, just store it in the buffer. + if (numBytes < _bufferLength && !HasActiveBufferOperation && numBytes <= remainingBuffer) + { + Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, numBytes); + _writePos += numBytes; + writeDataStoredInBuffer = true; + + // There is one special-but-common case, common because devs often use + // byte[] sizes that are powers of 2 and thus fit nicely into our buffer, which is + // also a power of 2. If after our write the buffer still has remaining space, + // then we're done and can return a completed task now. But if we filled the buffer + // completely, we want to do the asynchronous flush/write as part of this operation + // rather than waiting until the next write that fills the buffer. + if (numBytes != remainingBuffer) + return Task.CompletedTask; + + Debug.Assert(_writePos == _bufferLength); + } + } + + // At this point, at least one of the following is true: + // 1. There was an active flush operation (it could have completed by now, though). + // 2. The data doesn't fit in the remaining buffer (or it's a pipe and we chose not to try). + // 3. We wrote all of the data to the buffer, filling it. + // + // If there's an active operation, we can't touch the current buffer because it's in use. + // That gives us a choice: we can either allocate a new buffer, or we can skip the buffer + // entirely (even if the data would otherwise fit in it). For now, for simplicity, we do + // the latter; it could also have performance wins due to OS-level optimizations, and we could + // potentially add support for PreAllocatedOverlapped due to having a single buffer. (We can + // switch to allocating a new buffer, potentially experimenting with buffer pooling, should + // performance data suggest it's appropriate.) + // + // If the data doesn't fit in the remaining buffer, it could be because it's so large + // it's greater than the entire buffer size, in which case we'd always skip the buffer, + // or it could be because there's more data than just the space remaining. For the latter + // case, we need to issue an asynchronous write to flush that data, which then turns this into + // the first case above with an active operation. + // + // If we already stored the data, then we have nothing additional to write beyond what + // we need to flush. + // + // In any of these cases, we have the same outcome: + // - If there's data in the buffer, flush it by writing it out asynchronously. + // - Then, if there's any data to be written, issue a write for it concurrently. + // We return a Task that represents one or both. + + // Flush the buffer asynchronously if there's anything to flush + Task flushTask = null; + if (_writePos > 0) + { + flushTask = FlushWriteAsync(cancellationToken); + + // If we already copied all of the data into the buffer, + // simply return the flush task here. Same goes for if the task has + // already completed and was unsuccessful. + if (writeDataStoredInBuffer || + flushTask.IsFaulted || + flushTask.IsCanceled) + { + return flushTask; + } + } + + Debug.Assert(!writeDataStoredInBuffer); + Debug.Assert(_writePos == 0); + + // Finally, issue the write asynchronously, and return a Task that logically + // represents the write operation, including any flushing done. + Task writeTask = WriteInternalCoreAsync(array, offset, numBytes, cancellationToken); + return + (flushTask == null || flushTask.Status == TaskStatus.RanToCompletion) ? writeTask : + (writeTask.Status == TaskStatus.RanToCompletion) ? flushTask : + Task.WhenAll(flushTask, writeTask); + } + + private unsafe Task WriteInternalCoreAsync(byte[] bytes, int offset, int numBytes, CancellationToken cancellationToken) + { + Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); + Debug.Assert(CanWrite, "_parent.CanWrite"); + Debug.Assert(bytes != null, "bytes != null"); + Debug.Assert(_readPos == _readLength, "_readPos == _readLen"); + Debug.Assert(_useAsyncIO, "WriteInternalCoreAsync doesn't work on synchronous file streams!"); + Debug.Assert(offset >= 0, "offset is negative"); + Debug.Assert(numBytes >= 0, "numBytes is negative"); + + // Create and store async stream class library specific data in the async result + FileStreamCompletionSource completionSource = new FileStreamCompletionSource(this, 0, bytes, cancellationToken); + NativeOverlapped* intOverlapped = completionSource.Overlapped; + + if (CanSeek) + { + // Make sure we set the length of the file appropriately. + long len = Length; + //Console.WriteLine("WriteInternalCoreAsync - Calculating end pos. pos: "+pos+" len: "+len+" numBytes: "+numBytes); + + // Make sure we are writing to the position that we think we are + VerifyOSHandlePosition(); + + if (_filePosition + numBytes > len) + { + //Console.WriteLine("WriteInternalCoreAsync - Setting length to: "+(pos + numBytes)); + SetLengthCore(_filePosition + numBytes); + } + + // Now set the position to read from in the NativeOverlapped struct + // For pipes, we should leave the offset fields set to 0. + intOverlapped->OffsetLow = (int)_filePosition; + intOverlapped->OffsetHigh = (int)(_filePosition >> 32); + + // When using overlapped IO, the OS is not supposed to + // touch the file pointer location at all. We will adjust it + // ourselves. This isn't threadsafe. + SeekCore(numBytes, SeekOrigin.Current); + } + + //Console.WriteLine("WriteInternalCoreAsync finishing. pos: "+pos+" numBytes: "+numBytes+" _pos: "+_pos+" Position: "+Position); + + int errorCode = 0; + // queue an async WriteFile operation and pass in a packed overlapped + int r = WriteFileNative(_fileHandle, bytes, offset, numBytes, intOverlapped, out errorCode); + + // WriteFile, the OS version, will return 0 on failure. But + // my WriteFileNative wrapper returns -1. My wrapper will return + // the following: + // On error, r==-1. + // On async requests that are still pending, r==-1 w/ errorCode==ERROR_IO_PENDING + // On async requests that completed sequentially, r==0 + // You will NEVER RELIABLY be able to get the number of bytes + // written back from this call when using overlapped IO! You must + // not pass in a non-null lpNumBytesWritten to WriteFile when using + // overlapped structures! This is ByDesign NT behavior. + if (r == -1 && numBytes != -1) + { + //Console.WriteLine("WriteFile returned 0; Write will complete asynchronously (if errorCode==3e5) errorCode: 0x{0:x}", errorCode); + + // For pipes, when they are closed on the other side, they will come here. + if (errorCode == ERROR_NO_DATA) + { + // Not an error, but EOF. AsyncFSCallback will NOT be called. + // Completing TCS and return cached task allowing the GC to collect TCS. + completionSource.SetCompletedSynchronously(0); + return Task.CompletedTask; + } + else if (errorCode != ERROR_IO_PENDING) + { + if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere. + { + SeekCore(0, SeekOrigin.Current); + } + + completionSource.ReleaseNativeResource(); + + if (errorCode == ERROR_HANDLE_EOF) + { + throw Error.GetEndOfFile(); + } + else + { + throw Win32Marshal.GetExceptionForWin32Error(errorCode); + } + } + else // ERROR_IO_PENDING + { + // Only once the IO is pending do we register for cancellation + completionSource.RegisterForCancellation(); + } + } + else + { + // Due to a workaround for a race condition in NT's ReadFile & + // WriteFile routines, we will always be returning 0 from WriteFileNative + // when we do async IO instead of the number of bytes written, + // irregardless of whether the operation completed + // synchronously or asynchronously. We absolutely must not + // set asyncResult._numBytes here, since will never have correct + // results. + //Console.WriteLine("WriteFile returned: "+r+" (0x"+Int32.Format(r, "x")+") The IO completed synchronously, but the user callback was called on another thread."); + } + + return completionSource.Task; + } + + public override void WriteByte(byte value) + { + WriteByteCore(value); + } + + // Windows API definitions, from winbase.h and others + + private const int FILE_ATTRIBUTE_NORMAL = 0x00000080; + private const int FILE_ATTRIBUTE_ENCRYPTED = 0x00004000; + private const int FILE_FLAG_OVERLAPPED = 0x40000000; + internal const int GENERIC_READ = unchecked((int)0x80000000); + private const int GENERIC_WRITE = 0x40000000; + + private const int FILE_BEGIN = 0; + private const int FILE_CURRENT = 1; + private const int FILE_END = 2; + + // Error codes (not HRESULTS), from winerror.h + internal const int ERROR_BROKEN_PIPE = 109; + internal const int ERROR_NO_DATA = 232; + private const int ERROR_HANDLE_EOF = 38; + private const int ERROR_INVALID_PARAMETER = 87; + private const int ERROR_IO_PENDING = 997; + + // __ConsoleStream also uses this code. + private unsafe int ReadFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int errorCode) + { + Debug.Assert(handle != null, "handle != null"); + Debug.Assert(offset >= 0, "offset >= 0"); + Debug.Assert(count >= 0, "count >= 0"); + Debug.Assert(bytes != null, "bytes != null"); + // Don't corrupt memory when multiple threads are erroneously writing + // to this stream simultaneously. + if (bytes.Length - offset < count) + throw new IndexOutOfRangeException(SR.IndexOutOfRange_IORaceCondition); + + Debug.Assert((_useAsyncIO && overlapped != null) || (!_useAsyncIO && overlapped == null), "Async IO and overlapped parameters inconsistent in call to ReadFileNative."); + + // You can't use the fixed statement on an array of length 0. + if (bytes.Length == 0) + { + errorCode = 0; + return 0; + } + + int r = 0; + int numBytesRead = 0; + + fixed (byte* p = bytes) + { + if (_useAsyncIO) + r = Interop.mincore.ReadFile(handle, p + offset, count, IntPtr.Zero, overlapped); + else + r = Interop.mincore.ReadFile(handle, p + offset, count, out numBytesRead, IntPtr.Zero); + } + + if (r == 0) + { + errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid(); + return -1; + } + else + { + errorCode = 0; + return numBytesRead; + } + } + + private unsafe int WriteFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int errorCode) + { + Debug.Assert(handle != null, "handle != null"); + Debug.Assert(offset >= 0, "offset >= 0"); + Debug.Assert(count >= 0, "count >= 0"); + Debug.Assert(bytes != null, "bytes != null"); + // Don't corrupt memory when multiple threads are erroneously writing + // to this stream simultaneously. (the OS is reading from + // the array we pass to WriteFile, but if we read beyond the end and + // that memory isn't allocated, we could get an AV.) + if (bytes.Length - offset < count) + throw new IndexOutOfRangeException(SR.IndexOutOfRange_IORaceCondition); + + Debug.Assert((_useAsyncIO && overlapped != null) || (!_useAsyncIO && overlapped == null), "Async IO and overlapped parameters inconsistent in call to WriteFileNative."); + + // You can't use the fixed statement on an array of length 0. + if (bytes.Length == 0) + { + errorCode = 0; + return 0; + } + + int numBytesWritten = 0; + int r = 0; + + fixed (byte* p = bytes) + { + if (_useAsyncIO) + r = Interop.mincore.WriteFile(handle, p + offset, count, IntPtr.Zero, overlapped); + else + r = Interop.mincore.WriteFile(handle, p + offset, count, out numBytesWritten, IntPtr.Zero); + } + + if (r == 0) + { + errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid(); + return -1; + } + else + { + errorCode = 0; + return numBytesWritten; + } + } + + private int GetLastWin32ErrorAndDisposeHandleIfInvalid(bool throwIfInvalidHandle = false) + { + int errorCode = Marshal.GetLastWin32Error(); + + // If ERROR_INVALID_HANDLE is returned, it doesn't suffice to set + // the handle as invalid; the handle must also be closed. + // + // Marking the handle as invalid but not closing the handle + // resulted in exceptions during finalization and locked column + // values (due to invalid but unclosed handle) in SQL Win32FileStream + // scenarios. + // + // A more mainstream scenario involves accessing a file on a + // network share. ERROR_INVALID_HANDLE may occur because the network + // connection was dropped and the server closed the handle. However, + // the client side handle is still open and even valid for certain + // operations. + // + // Note that _parent.Dispose doesn't throw so we don't need to special case. + // SetHandleAsInvalid only sets _closed field to true (without + // actually closing handle) so we don't need to call that as well. + if (errorCode == Interop.mincore.Errors.ERROR_INVALID_HANDLE) + { + _fileHandle.Dispose(); + + if (throwIfInvalidHandle) + throw Win32Marshal.GetExceptionForWin32Error(errorCode); + } + + return errorCode; + } + + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + // If we're in sync mode, just use the shared CopyToAsync implementation that does + // typical read/write looping. We also need to take this path if this is a derived + // instance from FileStream, as a derived type could have overridden ReadAsync, in which + // case our custom CopyToAsync implementation isn't necessarily correct. + if (!_useAsyncIO || GetType() != typeof(FileStream)) + { + return base.CopyToAsync(destination, bufferSize, cancellationToken); + } + + StreamHelpers.ValidateCopyToArgs(this, destination, bufferSize); + + // Bail early for cancellation if cancellation has been requested + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + // Fail if the file was closed + if (_fileHandle.IsClosed) + { + throw Error.GetFileNotOpen(); + } + + // Do the async copy, with differing implementations based on whether the FileStream was opened as async or sync + Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both."); + return AsyncModeCopyToAsync(destination, bufferSize, cancellationToken); + } + + private async Task AsyncModeCopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + Debug.Assert(_useAsyncIO, "This implementation is for async mode only"); + Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); + Debug.Assert(CanRead, "_parent.CanRead"); + + // Make sure any pending writes have been flushed before we do a read. + if (_writePos > 0) + { + await FlushWriteAsync(cancellationToken).ConfigureAwait(false); + } + + // Typically CopyToAsync would be invoked as the only "read" on the stream, but it's possible some reading is + // done and then the CopyToAsync is issued. For that case, see if we have any data available in the buffer. + if (GetBuffer() != null) + { + int bufferedBytes = _readLength - _readPos; + if (bufferedBytes > 0) + { + await destination.WriteAsync(GetBuffer(), _readPos, bufferedBytes, cancellationToken).ConfigureAwait(false); + _readPos = _readLength = 0; + } + } + + // For efficiency, we avoid creating a new task and associated state for each asynchronous read. + // Instead, we create a single reusable awaitable object that will be triggered when an await completes + // and reset before going again. + var readAwaitable = new AsyncCopyToAwaitable(this); + + // Make sure we are reading from the position that we think we are. + // Only set the position in the awaitable if we can seek (e.g. not for pipes). + bool canSeek = CanSeek; + if (canSeek) + { + VerifyOSHandlePosition(); + readAwaitable._position = _filePosition; + } + + // Get the buffer to use for the copy operation, as the base CopyToAsync does. We don't try to use + // _buffer here, even if it's not null, as concurrent operations are allowed, and another operation may + // actually be using the buffer already. Plus, it'll be rare for _buffer to be non-null, as typically + // CopyToAsync is used as the only operation performed on the stream, and the buffer is lazily initialized. + // Further, typically the CopyToAsync buffer size will be larger than that used by the FileStream, such that + // we'd likely be unable to use it anyway. Instead, we rent the buffer from a pool. + byte[] copyBuffer = ArrayPool.Shared.Rent(bufferSize); + bufferSize = 0; // repurpose bufferSize to be the high water mark for the buffer, to avoid an extra field in the state machine + + // Allocate an Overlapped we can use repeatedly for all operations + var awaitableOverlapped = new PreAllocatedOverlapped(AsyncCopyToAwaitable.s_callback, readAwaitable, copyBuffer); + var cancellationReg = default(CancellationTokenRegistration); + try + { + // Register for cancellation. We do this once for the whole copy operation, and just try to cancel + // whatever read operation may currently be in progress, if there is one. It's possible the cancellation + // request could come in between operations, in which case we flag that with explicit calls to ThrowIfCancellationRequested + // in the read/write copy loop. + if (cancellationToken.CanBeCanceled) + { + cancellationReg = cancellationToken.Register(s => + { + var innerAwaitable = (AsyncCopyToAwaitable)s; + unsafe + { + lock (innerAwaitable.CancellationLock) // synchronize with cleanup of the overlapped + { + if (innerAwaitable._nativeOverlapped != null) + { + // Try to cancel the I/O. We ignore the return value, as cancellation is opportunistic and we + // don't want to fail the operation because we couldn't cancel it. + Interop.mincore.CancelIoEx(innerAwaitable._fileStream._fileHandle, innerAwaitable._nativeOverlapped); + } + } + } + }, readAwaitable); + } + + // Repeatedly read from this FileStream and write the results to the destination stream. + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + readAwaitable.ResetForNextOperation(); + + try + { + bool synchronousSuccess; + int errorCode; + unsafe + { + // Allocate a native overlapped for our reusable overlapped, and set position to read based on the next + // desired address stored in the awaitable. (This position may be 0, if either we're at the beginning or + // if the stream isn't seekable.) + readAwaitable._nativeOverlapped = _fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(awaitableOverlapped); + if (canSeek) + { + readAwaitable._nativeOverlapped->OffsetLow = unchecked((int)readAwaitable._position); + readAwaitable._nativeOverlapped->OffsetHigh = (int)(readAwaitable._position >> 32); + } + + // Kick off the read. + synchronousSuccess = ReadFileNative(_fileHandle, copyBuffer, 0, copyBuffer.Length, readAwaitable._nativeOverlapped, out errorCode) >= 0; + } + + // If the operation did not synchronously succeed, it either failed or initiated the asynchronous operation. + if (!synchronousSuccess) + { + switch (errorCode) + { + case ERROR_IO_PENDING: + // Async operation in progress. + break; + case ERROR_BROKEN_PIPE: + case ERROR_HANDLE_EOF: + // We're at or past the end of the file, and the overlapped callback + // won't be raised in these cases. Mark it as completed so that the await + // below will see it as such. + readAwaitable.MarkCompleted(); + break; + default: + // Everything else is an error (and there won't be a callback). + throw Win32Marshal.GetExceptionForWin32Error(errorCode); + } + } + + // Wait for the async operation (which may or may not have already completed), then throw if it failed. + await readAwaitable; + switch (readAwaitable._errorCode) + { + case 0: // success + Debug.Assert(readAwaitable._numBytes >= 0, $"Expected non-negative numBytes, got {readAwaitable._numBytes}"); + break; + case ERROR_BROKEN_PIPE: // logically success with 0 bytes read (write end of pipe closed) + case ERROR_HANDLE_EOF: // logically success with 0 bytes read (read at end of file) + Debug.Assert(readAwaitable._numBytes == 0, $"Expected 0 bytes read, got {readAwaitable._numBytes}"); + break; + case Interop.mincore.Errors.ERROR_OPERATION_ABORTED: // canceled + throw new OperationCanceledException(cancellationToken.IsCancellationRequested ? cancellationToken : new CancellationToken(true)); + default: // error + throw Win32Marshal.GetExceptionForWin32Error((int)readAwaitable._errorCode); + } + + // Successful operation. If we got zero bytes, we're done: exit the read/write loop. + int numBytesRead = (int)readAwaitable._numBytes; + if (numBytesRead == 0) + { + break; + } + + // Otherwise, update the read position for next time accordingly. + if (canSeek) + { + readAwaitable._position += numBytesRead; + } + + // (and keep track of the maximum number of bytes in the buffer we used, to avoid excessive and unnecessary + // clearing of the buffer before we return it to the pool) + if (numBytesRead > bufferSize) + { + bufferSize = numBytesRead; + } + } + finally + { + // Free the resources for this read operation + unsafe + { + NativeOverlapped* overlapped; + lock (readAwaitable.CancellationLock) // just an Exchange, but we need this to be synchronized with cancellation, so using the same lock + { + overlapped = readAwaitable._nativeOverlapped; + readAwaitable._nativeOverlapped = null; + } + if (overlapped != null) + { + _fileHandle.ThreadPoolBinding.FreeNativeOverlapped(overlapped); + } + } + } + + // Write out the read data. + await destination.WriteAsync(copyBuffer, 0, (int)readAwaitable._numBytes, cancellationToken).ConfigureAwait(false); + } + } + finally + { + // Cleanup from the whole copy operation + cancellationReg.Dispose(); + awaitableOverlapped.Dispose(); + + Array.Clear(copyBuffer, 0, bufferSize); + ArrayPool.Shared.Return(copyBuffer, clearArray: false); + + // Make sure the stream's current position reflects where we ended up + if (!_fileHandle.IsClosed && CanSeek) + { + SeekCore(0, SeekOrigin.End); + } + } + } + + /// Used by CopyToAsync to enable awaiting the result of an overlapped I/O operation with minimal overhead. + private sealed unsafe class AsyncCopyToAwaitable : ICriticalNotifyCompletion + { + /// Sentinel object used to indicate that the I/O operation has completed before being awaited. + private readonly static Action s_sentinel = () => { }; + /// Cached delegate to IOCallback. + internal static readonly IOCompletionCallback s_callback = IOCallback; + + /// The FileStream that owns this instance. + internal readonly FileStream _fileStream; + + /// Tracked position representing the next location from which to read. + internal long _position; + /// The current native overlapped pointer. This changes for each operation. + internal NativeOverlapped* _nativeOverlapped; + /// + /// null if the operation is still in progress, + /// s_sentinel if the I/O operation completed before the await, + /// s_callback if it completed after the await yielded. + /// + internal Action _continuation; + /// Last error code from completed operation. + internal uint _errorCode; + /// Last number of read bytes from completed operation. + internal uint _numBytes; + + /// Lock object used to protect cancellation-related access to _nativeOverlapped. + internal object CancellationLock => this; + + /// Initialize the awaitable. + internal unsafe AsyncCopyToAwaitable(FileStream fileStream) + { + _fileStream = fileStream; + } + + /// Reset state to prepare for the next read operation. + internal void ResetForNextOperation() + { + Debug.Assert(_position >= 0, $"Expected non-negative position, got {_position}"); + _continuation = null; + _errorCode = 0; + _numBytes = 0; + } + + /// Overlapped callback: store the results, then invoke the continuation delegate. + internal unsafe static void IOCallback(uint errorCode, uint numBytes, NativeOverlapped* pOVERLAP) + { + var awaitable = (AsyncCopyToAwaitable)ThreadPoolBoundHandle.GetNativeOverlappedState(pOVERLAP); + + Debug.Assert(awaitable._continuation != s_sentinel, "Sentinel must not have already been set as the continuation"); + awaitable._errorCode = errorCode; + awaitable._numBytes = numBytes; + + (awaitable._continuation ?? Interlocked.CompareExchange(ref awaitable._continuation, s_sentinel, null))?.Invoke(); + } + + /// + /// Called when it's known that the I/O callback for an operation will not be invoked but we'll + /// still be awaiting the awaitable. + /// + internal void MarkCompleted() + { + Debug.Assert(_continuation == null, "Expected null continuation"); + _continuation = s_sentinel; + } + + public AsyncCopyToAwaitable GetAwaiter() => this; + public bool IsCompleted => _continuation == s_sentinel; + public void GetResult() { } + public void OnCompleted(Action continuation) => UnsafeOnCompleted(continuation); + public void UnsafeOnCompleted(Action continuation) + { + if (_continuation == s_sentinel || + Interlocked.CompareExchange(ref _continuation, continuation, null) != null) + { + Debug.Assert(_continuation == s_sentinel, $"Expected continuation set to s_sentinel, got ${_continuation}"); + Task.Run(continuation); + } + } + } + + // Unlike Flush(), FlushAsync() always flushes to disk. This is intentional. + // Legend is that we chose not to flush the OS file buffers in Flush() in fear of + // perf problems with frequent, long running FlushFileBuffers() calls. But we don't + // have that problem with FlushAsync() because we will call FlushFileBuffers() in the background. + private Task FlushAsyncInternal(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + if (_fileHandle.IsClosed) + throw Error.GetFileNotOpen(); + + // The always synchronous data transfer between the OS and the internal buffer is intentional + // because this is needed to allow concurrent async IO requests. Concurrent data transfer + // between the OS and the internal buffer will result in race conditions. Since FlushWrite and + // FlushRead modify internal state of the stream and transfer data between the OS and the + // internal buffer, they cannot be truly async. We will, however, flush the OS file buffers + // asynchronously because it doesn't modify any internal state of the stream and is potentially + // a long running process. + try + { + FlushInternalBuffer(); + } + catch (Exception e) + { + return Task.FromException(e); + } + + if (CanWrite) + { + return Task.Factory.StartNew( + state => ((FileStream)state).FlushOSBuffer(), + this, + cancellationToken, + TaskCreationOptions.DenyChildAttach, + TaskScheduler.Default); + } + else + { + return Task.CompletedTask; + } + } + + private Task TaskFromResultOrCache(int result) + { + Task completedTask = _lastSynchronouslyCompletedTask; + Debug.Assert(completedTask == null || completedTask.Status == TaskStatus.RanToCompletion, "Cached task should have completed successfully"); + + if ((completedTask == null) || (completedTask.Result != result)) + { + completedTask = Task.FromResult(result); + _lastSynchronouslyCompletedTask = completedTask; + } + + return completedTask; + } + + private void LockInternal(long position, long length) + { + int positionLow = unchecked((int)(position)); + int positionHigh = unchecked((int)(position >> 32)); + int lengthLow = unchecked((int)(length)); + int lengthHigh = unchecked((int)(length >> 32)); + + if (!Interop.mincore.LockFile(_fileHandle, positionLow, positionHigh, lengthLow, lengthHigh)) + { + throw Win32Marshal.GetExceptionForLastWin32Error(); + } + } + + private void UnlockInternal(long position, long length) + { + int positionLow = unchecked((int)(position)); + int positionHigh = unchecked((int)(position >> 32)); + int lengthLow = unchecked((int)(length)); + int lengthHigh = unchecked((int)(length >> 32)); + + if (!Interop.mincore.UnlockFile(_fileHandle, positionLow, positionHigh, lengthLow, lengthHigh)) + { + throw Win32Marshal.GetExceptionForLastWin32Error(); + } + } + } +} diff --git a/src/mscorlib/corefx/System/IO/FileStream.cs b/src/mscorlib/corefx/System/IO/FileStream.cs new file mode 100644 index 0000000000..398f5a6162 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/FileStream.cs @@ -0,0 +1,654 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Win32.SafeHandles; +using System.Diagnostics; + +namespace System.IO +{ + public partial class FileStream : Stream + { + private const FileShare DefaultShare = FileShare.Read; + private const bool DefaultIsAsync = false; + internal const int DefaultBufferSize = 4096; + + private byte[] _buffer; + private int _bufferLength; + private readonly SafeFileHandle _fileHandle; + + /// Whether the file is opened for reading, writing, or both. + private readonly FileAccess _access; + + /// The path to the opened file. + private readonly string _path; + + /// The next available byte to be read from the _buffer. + private int _readPos; + + /// The number of valid bytes in _buffer. + private int _readLength; + + /// The next location in which a write should occur to the buffer. + private int _writePos; + + /// + /// Whether asynchronous read/write/flush operations should be performed using async I/O. + /// On Windows FileOptions.Asynchronous controls how the file handle is configured, + /// and then as a result how operations are issued against that file handle. On Unix, + /// there isn't any distinction around how file descriptors are created for async vs + /// sync, but we still differentiate how the operations are issued in order to provide + /// similar behavioral semantics and performance characteristics as on Windows. On + /// Windows, if non-async, async read/write requests just delegate to the base stream, + /// and no attempt is made to synchronize between sync and async operations on the stream; + /// if async, then async read/write requests are implemented specially, and sync read/write + /// requests are coordinated with async ones by implementing the sync ones over the async + /// ones. On Unix, we do something similar. If non-async, async read/write requests just + /// delegate to the base stream, and no attempt is made to synchronize. If async, we use + /// a semaphore to coordinate both sync and async operations. + /// + private readonly bool _useAsyncIO; + + /// + /// Currently cached position in the stream. This should always mirror the underlying file's actual position, + /// and should only ever be out of sync if another stream with access to this same file manipulates it, at which + /// point we attempt to error out. + /// + private long _filePosition; + + /// Whether the file stream's handle has been exposed. + private bool _exposedHandle; + + [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead. http://go.microsoft.com/fwlink/?linkid=14202")] + public FileStream(IntPtr handle, FileAccess access) + : this(handle, access, true, DefaultBufferSize, false) + { + } + + [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")] + public FileStream(IntPtr handle, FileAccess access, bool ownsHandle) + : this(handle, access, ownsHandle, DefaultBufferSize, false) + { + } + + [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")] + public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize) + : this(handle, access, ownsHandle, bufferSize, false) + { + } + + [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")] + public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync) + : this(new SafeFileHandle(handle, ownsHandle), access, bufferSize, isAsync) + { + } + + public FileStream(SafeFileHandle handle, FileAccess access) + : this(handle, access, DefaultBufferSize) + { + } + + public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) + : this(handle, access, bufferSize, GetDefaultIsAsync(handle)) + { + } + + public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) + { + if (handle.IsInvalid) + throw new ArgumentException(SR.Arg_InvalidHandle, nameof(handle)); + + if (access < FileAccess.Read || access > FileAccess.ReadWrite) + throw new ArgumentOutOfRangeException(nameof(access), SR.ArgumentOutOfRange_Enum); + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum); + + if (handle.IsClosed) + throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed); + if (handle.IsAsync.HasValue && isAsync != handle.IsAsync.Value) + throw new ArgumentException(SR.Arg_HandleNotAsync, nameof(handle)); + + _access = access; + _useAsyncIO = isAsync; + _exposedHandle = true; + _bufferLength = bufferSize; + _fileHandle = handle; + + InitFromHandle(handle); + } + + public FileStream(string path, FileMode mode) : + this(path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), DefaultShare, DefaultBufferSize, DefaultIsAsync) + { } + + public FileStream(string path, FileMode mode, FileAccess access) : + this(path, mode, access, DefaultShare, DefaultBufferSize, DefaultIsAsync) + { } + + public FileStream(string path, FileMode mode, FileAccess access, FileShare share) : + this(path, mode, access, share, DefaultBufferSize, DefaultIsAsync) + { } + + public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize) : + this(path, mode, access, share, bufferSize, DefaultIsAsync) + { } + + public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync) : + this(path, mode, access, share, bufferSize, useAsync ? FileOptions.Asynchronous : FileOptions.None) + { } + + internal FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, string msgPath, bool bFromProxy) + : this(path, mode, access, share, bufferSize, options, msgPath, bFromProxy, useLongPath: false) + { + } + + internal FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, string msgPath, bool bFromProxy, bool useLongPath) + : this(path, mode, access, share, bufferSize, options) + { + // msgPath is the path that is handed back to untrusted code, CoreCLR is always full trust + // bFromProxy is also related to asserting rights for limited trust and also can be ignored + // useLongPath was used to get around the legacy MaxPath check, this is no longer applicable as everything supports long paths + // checkHost is also related to limited trust scenarios + } + + public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) + { + if (path == null) + throw new ArgumentNullException(nameof(path), SR.ArgumentNull_Path); + if (path.Length == 0) + throw new ArgumentException(SR.Argument_EmptyPath, nameof(path)); + + // don't include inheritable in our bounds check for share + FileShare tempshare = share & ~FileShare.Inheritable; + string badArg = null; + + if (mode < FileMode.CreateNew || mode > FileMode.Append) + badArg = nameof(mode); + else if (access < FileAccess.Read || access > FileAccess.ReadWrite) + badArg = nameof(access); + else if (tempshare < FileShare.None || tempshare > (FileShare.ReadWrite | FileShare.Delete)) + badArg = nameof(share); + + if (badArg != null) + throw new ArgumentOutOfRangeException(badArg, SR.ArgumentOutOfRange_Enum); + + // NOTE: any change to FileOptions enum needs to be matched here in the error validation + if (options != FileOptions.None && (options & ~(FileOptions.WriteThrough | FileOptions.Asynchronous | FileOptions.RandomAccess | FileOptions.DeleteOnClose | FileOptions.SequentialScan | FileOptions.Encrypted | (FileOptions)0x20000000 /* NoBuffering */)) != 0) + throw new ArgumentOutOfRangeException(nameof(options), SR.ArgumentOutOfRange_Enum); + + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum); + + // Write access validation + if ((access & FileAccess.Write) == 0) + { + if (mode == FileMode.Truncate || mode == FileMode.CreateNew || mode == FileMode.Create || mode == FileMode.Append) + { + // No write access, mode and access disagree but flag access since mode comes first + throw new ArgumentException(SR.Format(SR.Argument_InvalidFileModeAndAccessCombo, mode, access), nameof(access)); + } + } + + if ((access & FileAccess.Read) != 0 && mode == FileMode.Append) + throw new ArgumentException(SR.Argument_InvalidAppendMode, nameof(access)); + + string fullPath = Path.GetFullPath(path); + + _path = fullPath; + _access = access; + _bufferLength = bufferSize; + + if ((options & FileOptions.Asynchronous) != 0) + _useAsyncIO = true; + + _fileHandle = OpenHandle(mode, share, options); + + try + { + Init(mode, share); + } + catch + { + // If anything goes wrong while setting up the stream, make sure we deterministically dispose + // of the opened handle. + _fileHandle.Dispose(); + _fileHandle = null; + throw; + } + } + + private static bool GetDefaultIsAsync(SafeFileHandle handle) + { + // This will eventually get more complicated as we can actually check the underlying handle type on Windows + return handle.IsAsync.HasValue ? handle.IsAsync.Value : false; + } + + // InternalOpen, InternalCreate, and InternalAppend: + // Factory methods for FileStream used by File, FileInfo, and ReadLinesIterator + // Specifies default access and sharing options for FileStreams created by those classes + internal static FileStream InternalOpen(string path, int bufferSize = DefaultBufferSize, bool useAsync = DefaultIsAsync) + { + return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, useAsync); + } + + internal static FileStream InternalCreate(string path, int bufferSize = DefaultBufferSize, bool useAsync = DefaultIsAsync) + { + return new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, bufferSize, useAsync); + } + + internal static FileStream InternalAppend(string path, int bufferSize = DefaultBufferSize, bool useAsync = DefaultIsAsync) + { + return new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, bufferSize, useAsync); + } + + [Obsolete("This property has been deprecated. Please use FileStream's SafeFileHandle property instead. http://go.microsoft.com/fwlink/?linkid=14202")] + public virtual IntPtr Handle { get { return SafeFileHandle.DangerousGetHandle(); } } + + public virtual void Lock(long position, long length) + { + if (position < 0 || length < 0) + { + throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum); + } + + if (_fileHandle.IsClosed) + { + throw Error.GetFileNotOpen(); + } + + LockInternal(position, length); + } + + public virtual void Unlock(long position, long length) + { + if (position < 0 || length < 0) + { + throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum); + } + + if (_fileHandle.IsClosed) + { + throw Error.GetFileNotOpen(); + } + + UnlockInternal(position, length); + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + // If we have been inherited into a subclass, the following implementation could be incorrect + // since it does not call through to Flush() which a subclass might have overridden. To be safe + // we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Flush) when we are not sure. + if (GetType() != typeof(FileStream)) + return base.FlushAsync(cancellationToken); + + return FlushAsyncInternal(cancellationToken); + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); + if (buffer.Length - offset < count) + throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/); + + // If we have been inherited into a subclass, the following implementation could be incorrect + // since it does not call through to Read() or ReadAsync() which a subclass might have overridden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Read/ReadAsync) when we are not sure. + if (GetType() != typeof(FileStream)) + return base.ReadAsync(buffer, offset, count, cancellationToken); + + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + if (IsClosed) + throw Error.GetFileNotOpen(); + + return ReadAsyncInternal(buffer, offset, count, cancellationToken); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); + if (buffer.Length - offset < count) + throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/); + + // If we have been inherited into a subclass, the following implementation could be incorrect + // since it does not call through to Write() or WriteAsync() which a subclass might have overridden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Write/WriteAsync) when we are not sure. + if (GetType() != typeof(FileStream)) + return base.WriteAsync(buffer, offset, count, cancellationToken); + + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + if (IsClosed) + throw Error.GetFileNotOpen(); + + return WriteAsyncInternal(buffer, offset, count, cancellationToken); + } + + /// + /// Clears buffers for this stream and causes any buffered data to be written to the file. + /// + public override void Flush() + { + // Make sure that we call through the public virtual API + Flush(flushToDisk: false); + } + + /// + /// Clears buffers for this stream, and if is true, + /// causes any buffered data to be written to the file. + /// + public virtual void Flush(bool flushToDisk) + { + if (IsClosed) throw Error.GetFileNotOpen(); + + FlushInternalBuffer(); + + if (flushToDisk && CanWrite) + { + FlushOSBuffer(); + } + } + + /// Gets a value indicating whether the current stream supports reading. + public override bool CanRead + { + get { return !_fileHandle.IsClosed && (_access & FileAccess.Read) != 0; } + } + + /// Gets a value indicating whether the current stream supports writing. + public override bool CanWrite + { + get { return !_fileHandle.IsClosed && (_access & FileAccess.Write) != 0; } + } + + /// Validates arguments to Read and Write and throws resulting exceptions. + /// The buffer to read from or write to. + /// The zero-based offset into the array. + /// The maximum number of bytes to read or write. + private void ValidateReadWriteArgs(byte[] array, int offset, int count) + { + if (array == null) + throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); + if (array.Length - offset < count) + throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/); + if (_fileHandle.IsClosed) + throw Error.GetFileNotOpen(); + } + + /// Sets the length of this stream to the given value. + /// The new length of the stream. + public override void SetLength(long value) + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum); + if (_fileHandle.IsClosed) + throw Error.GetFileNotOpen(); + if (!CanSeek) + throw Error.GetSeekNotSupported(); + if (!CanWrite) + throw Error.GetWriteNotSupported(); + + SetLengthInternal(value); + } + + public virtual SafeFileHandle SafeFileHandle + { + get + { + Flush(); + _exposedHandle = true; + return _fileHandle; + } + } + + /// Gets the path that was passed to the constructor. + public virtual string Name { get { return _path ?? SR.IO_UnknownFileName; } } + + /// Gets a value indicating whether the stream was opened for I/O to be performed synchronously or asynchronously. + public virtual bool IsAsync + { + get { return _useAsyncIO; } + } + + /// Gets the length of the stream in bytes. + public override long Length + { + get + { + if (_fileHandle.IsClosed) throw Error.GetFileNotOpen(); + if (!CanSeek) throw Error.GetSeekNotSupported(); + return GetLengthInternal(); + } + } + + /// + /// Verify that the actual position of the OS's handle equals what we expect it to. + /// This will fail if someone else moved the UnixFileStream's handle or if + /// our position updating code is incorrect. + /// + private void VerifyOSHandlePosition() + { + bool verifyPosition = _exposedHandle; // in release, only verify if we've given out the handle such that someone else could be manipulating it +#if DEBUG + verifyPosition = true; // in debug, always make sure our position matches what the OS says it should be +#endif + if (verifyPosition && CanSeek) + { + long oldPos = _filePosition; // SeekCore will override the current _position, so save it now + long curPos = SeekCore(0, SeekOrigin.Current); + if (oldPos != curPos) + { + // For reads, this is non-fatal but we still could have returned corrupted + // data in some cases, so discard the internal buffer. For writes, + // this is a problem; discard the buffer and error out. + _readPos = _readLength = 0; + if (_writePos > 0) + { + _writePos = 0; + throw new IOException(SR.IO_FileStreamHandlePosition); + } + } + } + } + + /// Verifies that state relating to the read/write buffer is consistent. + [Conditional("DEBUG")] + private void AssertBufferInvariants() + { + // Read buffer values must be in range: 0 <= _bufferReadPos <= _bufferReadLength <= _bufferLength + Debug.Assert(0 <= _readPos && _readPos <= _readLength && _readLength <= _bufferLength); + + // Write buffer values must be in range: 0 <= _bufferWritePos <= _bufferLength + Debug.Assert(0 <= _writePos && _writePos <= _bufferLength); + + // Read buffering and write buffering can't both be active + Debug.Assert((_readPos == 0 && _readLength == 0) || _writePos == 0); + } + + /// Validates that we're ready to read from the stream. + private void PrepareForReading() + { + if (_fileHandle.IsClosed) + throw Error.GetFileNotOpen(); + if (_readLength == 0 && !CanRead) + throw Error.GetReadNotSupported(); + + AssertBufferInvariants(); + } + + /// Gets or sets the position within the current stream + public override long Position + { + get + { + if (_fileHandle.IsClosed) + throw Error.GetFileNotOpen(); + + if (!CanSeek) + throw Error.GetSeekNotSupported(); + + AssertBufferInvariants(); + VerifyOSHandlePosition(); + + // We may have read data into our buffer from the handle, such that the handle position + // is artificially further along than the consumer's view of the stream's position. + // Thus, when reading, our position is really starting from the handle position negatively + // offset by the number of bytes in the buffer and positively offset by the number of + // bytes into that buffer we've read. When writing, both the read length and position + // must be zero, and our position is just the handle position offset positive by how many + // bytes we've written into the buffer. + return (_filePosition - _readLength) + _readPos + _writePos; + } + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum); + + Seek(value, SeekOrigin.Begin); + } + } + + internal virtual bool IsClosed => _fileHandle.IsClosed; + + /// + /// Gets the array used for buffering reading and writing. + /// If the array hasn't been allocated, this will lazily allocate it. + /// + /// The buffer. + private byte[] GetBuffer() + { + Debug.Assert(_buffer == null || _buffer.Length == _bufferLength); + if (_buffer == null) + { + _buffer = new byte[_bufferLength]; + OnBufferAllocated(); + } + + return _buffer; + } + + partial void OnBufferAllocated(); + + /// + /// Flushes the internal read/write buffer for this stream. If write data has been buffered, + /// that data is written out to the underlying file. Or if data has been buffered for + /// reading from the stream, the data is dumped and our position in the underlying file + /// is rewound as necessary. This does not flush the OS buffer. + /// + private void FlushInternalBuffer() + { + AssertBufferInvariants(); + if (_writePos > 0) + { + FlushWriteBuffer(); + } + else if (_readPos < _readLength && CanSeek) + { + FlushReadBuffer(); + } + } + + /// Dumps any read data in the buffer and rewinds our position in the stream, accordingly, as necessary. + private void FlushReadBuffer() + { + // Reading is done by blocks from the file, but someone could read + // 1 byte from the buffer then write. At that point, the OS's file + // pointer is out of sync with the stream's position. All write + // functions should call this function to preserve the position in the file. + + AssertBufferInvariants(); + Debug.Assert(_writePos == 0, "FileStream: Write buffer must be empty in FlushReadBuffer!"); + + int rewind = _readPos - _readLength; + if (rewind != 0) + { + Debug.Assert(CanSeek, "FileStream will lose buffered read data now."); + SeekCore(rewind, SeekOrigin.Current); + } + _readPos = _readLength = 0; + } + + private int ReadByteCore() + { + PrepareForReading(); + + byte[] buffer = GetBuffer(); + if (_readPos == _readLength) + { + FlushWriteBuffer(); + Debug.Assert(_bufferLength > 0, "_bufferSize > 0"); + + _readLength = ReadNative(buffer, 0, _bufferLength); + _readPos = 0; + if (_readLength == 0) + { + return -1; + } + } + + return buffer[_readPos++]; + } + + private void WriteByteCore(byte value) + { + PrepareForWriting(); + + // Flush the write buffer if it's full + if (_writePos == _bufferLength) + FlushWriteBuffer(); + + // We now have space in the buffer. Store the byte. + GetBuffer()[_writePos++] = value; + } + + /// + /// Validates that we're ready to write to the stream, + /// including flushing a read buffer if necessary. + /// + private void PrepareForWriting() + { + if (_fileHandle.IsClosed) + throw Error.GetFileNotOpen(); + + // Make sure we're good to write. We only need to do this if there's nothing already + // in our write buffer, since if there is something in the buffer, we've already done + // this checking and flushing. + if (_writePos == 0) + { + if (!CanWrite) throw Error.GetWriteNotSupported(); + FlushReadBuffer(); + Debug.Assert(_bufferLength > 0, "_bufferSize > 0"); + } + } + + ~FileStream() + { + // Preserved for compatibility since FileStream has defined a + // finalizer in past releases and derived classes may depend + // on Dispose(false) call. + Dispose(false); + } + } +} diff --git a/src/mscorlib/corefx/System/IO/FileStreamCompletionSource.Win32.cs b/src/mscorlib/corefx/System/IO/FileStreamCompletionSource.Win32.cs new file mode 100644 index 0000000000..532dbb0615 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/FileStreamCompletionSource.Win32.cs @@ -0,0 +1,221 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Security; +using System.Threading; +using System.Threading.Tasks; +using System.Runtime.InteropServices; +using System.Diagnostics; + +namespace System.IO +{ + public partial class FileStream : Stream + { + // This is an internal object extending TaskCompletionSource with fields + // for all of the relevant data necessary to complete the IO operation. + // This is used by IOCallback and all of the async methods. + unsafe private sealed class FileStreamCompletionSource : TaskCompletionSource + { + private const long NoResult = 0; + private const long ResultSuccess = (long)1 << 32; + private const long ResultError = (long)2 << 32; + private const long RegisteringCancellation = (long)4 << 32; + private const long CompletedCallback = (long)8 << 32; + private const ulong ResultMask = ((ulong)uint.MaxValue) << 32; + + private static Action s_cancelCallback; + + private readonly FileStream _stream; + private readonly int _numBufferedBytes; + private readonly CancellationToken _cancellationToken; + private CancellationTokenRegistration _cancellationRegistration; +#if DEBUG + private bool _cancellationHasBeenRegistered; +#endif + private NativeOverlapped* _overlapped; // Overlapped class responsible for operations in progress when an appdomain unload occurs + private long _result; // Using long since this needs to be used in Interlocked APIs + + // Using RunContinuationsAsynchronously for compat reasons (old API used Task.Factory.StartNew for continuations) + internal FileStreamCompletionSource(FileStream stream, int numBufferedBytes, byte[] bytes, CancellationToken cancellationToken) + : base(TaskCreationOptions.RunContinuationsAsynchronously) + { + _numBufferedBytes = numBufferedBytes; + _stream = stream; + _result = NoResult; + _cancellationToken = cancellationToken; + + // Create the native overlapped. We try to use the preallocated overlapped if possible: + // it's possible if the byte buffer is the same one that's associated with the preallocated overlapped + // and if no one else is currently using the preallocated overlapped. This is the fast-path for cases + // where the user-provided buffer is smaller than the FileStream's buffer (such that the FileStream's + // buffer is used) and where operations on the FileStream are not being performed concurrently. + _overlapped = ReferenceEquals(bytes, _stream._buffer) && _stream.CompareExchangeCurrentOverlappedOwner(this, null) == null ? + _stream._fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(_stream._preallocatedOverlapped) : + _stream._fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(s_ioCallback, this, bytes); + Debug.Assert(_overlapped != null, "AllocateNativeOverlapped returned null"); + } + + internal NativeOverlapped* Overlapped + { + get { return _overlapped; } + } + + public void SetCompletedSynchronously(int numBytes) + { + ReleaseNativeResource(); + TrySetResult(numBytes + _numBufferedBytes); + } + + public void RegisterForCancellation() + { +#if DEBUG + Debug.Assert(!_cancellationHasBeenRegistered, "Cannot register for cancellation twice"); + _cancellationHasBeenRegistered = true; +#endif + + // Quick check to make sure that the cancellation token supports cancellation, and that the IO hasn't completed + if ((_cancellationToken.CanBeCanceled) && (_overlapped != null)) + { + var cancelCallback = s_cancelCallback; + if (cancelCallback == null) s_cancelCallback = cancelCallback = Cancel; + + // Register the cancellation only if the IO hasn't completed + long packedResult = Interlocked.CompareExchange(ref _result, RegisteringCancellation, NoResult); + if (packedResult == NoResult) + { + _cancellationRegistration = _cancellationToken.Register(cancelCallback, this); + + // Switch the result, just in case IO completed while we were setting the registration + packedResult = Interlocked.Exchange(ref _result, NoResult); + } + else if (packedResult != CompletedCallback) + { + // Failed to set the result, IO is in the process of completing + // Attempt to take the packed result + packedResult = Interlocked.Exchange(ref _result, NoResult); + } + + // If we have a callback that needs to be completed + if ((packedResult != NoResult) && (packedResult != CompletedCallback) && (packedResult != RegisteringCancellation)) + { + CompleteCallback((ulong)packedResult); + } + } + } + + internal void ReleaseNativeResource() + { + // Ensure that cancellation has been completed and cleaned up. + _cancellationRegistration.Dispose(); + + // Free the overlapped. + // NOTE: The cancellation must *NOT* be running at this point, or it may observe freed memory + // (this is why we disposed the registration above). + if (_overlapped != null) + { + _stream._fileHandle.ThreadPoolBinding.FreeNativeOverlapped(_overlapped); + _overlapped = null; + } + + // Ensure we're no longer set as the current completion source (we may not have been to begin with). + // Only one operation at a time is eligible to use the preallocated overlapped, + _stream.CompareExchangeCurrentOverlappedOwner(null, this); + } + + // When doing IO asynchronously (i.e. _isAsync==true), this callback is + // called by a free thread in the threadpool when the IO operation + // completes. + internal static unsafe void IOCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped) + { + // Extract the completion source from the overlapped. The state in the overlapped + // will either be a Win32FileStream (in the case where the preallocated overlapped was used), + // in which case the operation being completed is its _currentOverlappedOwner, or it'll + // be directly the FileStreamCompletion that's completing (in the case where the preallocated + // overlapped was already in use by another operation). + object state = ThreadPoolBoundHandle.GetNativeOverlappedState(pOverlapped); + FileStream fs = state as FileStream; + FileStreamCompletionSource completionSource = fs != null ? + fs._currentOverlappedOwner : + (FileStreamCompletionSource)state; + Debug.Assert(completionSource._overlapped == pOverlapped, "Overlaps don't match"); + + // Handle reading from & writing to closed pipes. While I'm not sure + // this is entirely necessary anymore, maybe it's possible for + // an async read on a pipe to be issued and then the pipe is closed, + // returning this error. This may very well be necessary. + ulong packedResult; + if (errorCode != 0 && errorCode != ERROR_BROKEN_PIPE && errorCode != ERROR_NO_DATA) + { + packedResult = ((ulong)ResultError | errorCode); + } + else + { + packedResult = ((ulong)ResultSuccess | numBytes); + } + + // Stow the result so that other threads can observe it + // And, if no other thread is registering cancellation, continue + if (NoResult == Interlocked.Exchange(ref completionSource._result, (long)packedResult)) + { + // Successfully set the state, attempt to take back the callback + if (Interlocked.Exchange(ref completionSource._result, CompletedCallback) != NoResult) + { + // Successfully got the callback, finish the callback + completionSource.CompleteCallback(packedResult); + } + // else: Some other thread stole the result, so now it is responsible to finish the callback + } + // else: Some other thread is registering a cancellation, so it *must* finish the callback + } + + private void CompleteCallback(ulong packedResult) { + // Free up the native resource and cancellation registration + ReleaseNativeResource(); + + // Unpack the result and send it to the user + long result = (long)(packedResult & ResultMask); + if (result == ResultError) + { + int errorCode = unchecked((int)(packedResult & uint.MaxValue)); + if (errorCode == Interop.mincore.Errors.ERROR_OPERATION_ABORTED) + { + TrySetCanceled(_cancellationToken.IsCancellationRequested ? _cancellationToken : new CancellationToken(true)); + } + else + { + TrySetException(Win32Marshal.GetExceptionForWin32Error(errorCode)); + } + } + else + { + Debug.Assert(result == ResultSuccess, "Unknown result"); + TrySetResult((int)(packedResult & uint.MaxValue) + _numBufferedBytes); + } + } + + private static void Cancel(object state) + { + // WARNING: This may potentially be called under a lock (during cancellation registration) + + FileStreamCompletionSource completionSource = state as FileStreamCompletionSource; + Debug.Assert(completionSource != null, "Unknown state passed to cancellation"); + Debug.Assert(completionSource._overlapped != null && !completionSource.Task.IsCompleted, "IO should not have completed yet"); + + // If the handle is still valid, attempt to cancel the IO + if (!completionSource._stream._fileHandle.IsInvalid && + !Interop.mincore.CancelIoEx(completionSource._stream._fileHandle, completionSource._overlapped)) + { + int errorCode = Marshal.GetLastWin32Error(); + + // ERROR_NOT_FOUND is returned if CancelIoEx cannot find the request to cancel. + // This probably means that the IO operation has completed. + if (errorCode != Interop.mincore.Errors.ERROR_NOT_FOUND) + { + throw Win32Marshal.GetExceptionForWin32Error(errorCode); + } + } + } + } + } +} diff --git a/src/mscorlib/corefx/System/IO/Path.Unix.cs b/src/mscorlib/corefx/System/IO/Path.Unix.cs new file mode 100644 index 0000000000..2dd1907007 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/Path.Unix.cs @@ -0,0 +1,256 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace System.IO +{ + public static partial class Path + { + public static readonly char DirectorySeparatorChar = '/'; + public static readonly char VolumeSeparatorChar = '/'; + public static readonly char PathSeparator = ':'; + + private const string DirectorySeparatorCharAsString = "/"; + + public static char[] GetInvalidFileNameChars() => new char[] { '\0', '/' }; + + internal static readonly int MaxPath = Interop.Sys.MaxPath; + private static readonly int MaxLongPath = MaxPath; + + private static readonly bool s_isMac = Interop.Sys.GetUnixName() == "OSX"; + + // Expands the given path to a fully qualified path. + public static string GetFullPath(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + + if (path.Length == 0) + throw new ArgumentException(SR.Arg_PathIllegal); + + PathInternal.CheckInvalidPathChars(path); + + // Expand with current directory if necessary + if (!IsPathRooted(path)) + { + path = Combine(Interop.Sys.GetCwd(), path); + } + + // We would ideally use realpath to do this, but it resolves symlinks, requires that the file actually exist, + // and turns it into a full path, which we only want if fullCheck is true. + string collapsedString = RemoveRelativeSegments(path); + + Debug.Assert(collapsedString.Length < path.Length || collapsedString.ToString() == path, + "Either we've removed characters, or the string should be unmodified from the input path."); + + if (collapsedString.Length > MaxPath) + { + throw new PathTooLongException(SR.IO_PathTooLong); + } + + string result = collapsedString.Length == 0 ? DirectorySeparatorCharAsString : collapsedString; + + return result; + } + + /// + /// Try to remove relative segments from the given path (without combining with a root). + /// + /// Skip the specified number of characters before evaluating. + private static string RemoveRelativeSegments(string path, int skip = 0) + { + bool flippedSeparator = false; + + // Remove "//", "/./", and "/../" from the path by copying each character to the output, + // except the ones we're removing, such that the builder contains the normalized path + // at the end. + var sb = StringBuilderCache.Acquire(path.Length); + if (skip > 0) + { + sb.Append(path, 0, skip); + } + + int componentCharCount = 0; + for (int i = skip; i < path.Length; i++) + { + char c = path[i]; + + if (PathInternal.IsDirectorySeparator(c) && i + 1 < path.Length) + { + componentCharCount = 0; + + // Skip this character if it's a directory separator and if the next character is, too, + // e.g. "parent//child" => "parent/child" + if (PathInternal.IsDirectorySeparator(path[i + 1])) + { + continue; + } + + // Skip this character and the next if it's referring to the current directory, + // e.g. "parent/./child" =? "parent/child" + if ((i + 2 == path.Length || PathInternal.IsDirectorySeparator(path[i + 2])) && + path[i + 1] == '.') + { + i++; + continue; + } + + // Skip this character and the next two if it's referring to the parent directory, + // e.g. "parent/child/../grandchild" => "parent/grandchild" + if (i + 2 < path.Length && + (i + 3 == path.Length || PathInternal.IsDirectorySeparator(path[i + 3])) && + path[i + 1] == '.' && path[i + 2] == '.') + { + // Unwind back to the last slash (and if there isn't one, clear out everything). + int s; + for (s = sb.Length - 1; s >= 0; s--) + { + if (PathInternal.IsDirectorySeparator(sb[s])) + { + sb.Length = s; + break; + } + } + if (s < 0) + { + sb.Length = 0; + } + + i += 2; + continue; + } + } + + if (++componentCharCount > PathInternal.MaxComponentLength) + { + throw new PathTooLongException(SR.IO_PathTooLong); + } + + // Normalize the directory separator if needed + if (c != Path.DirectorySeparatorChar && c == Path.AltDirectorySeparatorChar) + { + c = Path.DirectorySeparatorChar; + flippedSeparator = true; + } + + sb.Append(c); + } + + if (flippedSeparator || sb.Length != path.Length) + { + return StringBuilderCache.GetStringAndRelease(sb); + } + else + { + // We haven't changed the source path, return the original + StringBuilderCache.Release(sb); + return path; + } + } + + private static string RemoveLongPathPrefix(string path) + { + return path; // nop. There's nothing special about "long" paths on Unix. + } + + public static string GetTempPath() + { + const string TempEnvVar = "TMPDIR"; + const string DefaultTempPath = "/tmp/"; + + // Get the temp path from the TMPDIR environment variable. + // If it's not set, just return the default path. + // If it is, return it, ensuring it ends with a slash. + string path = Environment.GetEnvironmentVariable(TempEnvVar); + return + string.IsNullOrEmpty(path) ? DefaultTempPath : + PathInternal.IsDirectorySeparator(path[path.Length - 1]) ? path : + path + DirectorySeparatorChar; + } + + public static string GetTempFileName() + { + const string Suffix = ".tmp"; + const int SuffixByteLength = 4; + + // mkstemps takes a char* and overwrites the XXXXXX with six characters + // that'll result in a unique file name. + string template = GetTempPath() + "tmpXXXXXX" + Suffix + "\0"; + byte[] name = Encoding.UTF8.GetBytes(template); + + // Create, open, and close the temp file. + IntPtr fd = Interop.CheckIo(Interop.Sys.MksTemps(name, SuffixByteLength)); + Interop.Sys.Close(fd); // ignore any errors from close; nothing to do if cleanup isn't possible + + // 'name' is now the name of the file + Debug.Assert(name[name.Length - 1] == '\0'); + return Encoding.UTF8.GetString(name, 0, name.Length - 1); // trim off the trailing '\0' + } + + public static bool IsPathRooted(string path) + { + if (path == null) + return false; + + PathInternal.CheckInvalidPathChars(path); + return path.Length > 0 && path[0] == DirectorySeparatorChar; + } + + public static string GetPathRoot(string path) + { + if (path == null) return null; + return IsPathRooted(path) ? DirectorySeparatorCharAsString : String.Empty; + } + + private static unsafe void GetCryptoRandomBytes(byte* bytes, int byteCount) + { +#if FEATURE_CORECLR + // We want to avoid dependencies on the Crypto library when compiling in CoreCLR. This + // will use the existing PAL implementation. + byte[] buffer = new byte[KeyLength]; + Microsoft.Win32.Win32Native.Random(bStrong: true, buffer: buffer, length: KeyLength); + Runtime.InteropServices.Marshal.Copy(buffer, 0, (IntPtr)bytes, KeyLength); +#else + if (s_isMac) + { + GetCryptoRandomBytesApple(bytes, byteCount); + } + else + { + GetCryptoRandomBytesOpenSsl(bytes, byteCount); + } +#endif + } + +#if !FEATURE_CORECLR + private static unsafe void GetCryptoRandomBytesApple(byte* bytes, int byteCount) + { + Debug.Assert(bytes != null); + Debug.Assert(byteCount >= 0); + + if (Interop.CommonCrypto.CCRandomGenerateBytes(bytes, byteCount) != 0) + { + throw new InvalidOperationException(SR.InvalidOperation_Cryptography); + } + } + + private static unsafe void GetCryptoRandomBytesOpenSsl(byte* bytes, int byteCount) + { + Debug.Assert(bytes != null); + Debug.Assert(byteCount >= 0); + + if (!Interop.Crypto.GetRandomBytes(bytes, byteCount)) + { + throw new InvalidOperationException(SR.InvalidOperation_Cryptography); + } + } +#endif + + /// Gets whether the system is case-sensitive. + internal static bool IsCaseSensitive { get { return !s_isMac; } } + } +} diff --git a/src/mscorlib/corefx/System/IO/Path.Win32.cs b/src/mscorlib/corefx/System/IO/Path.Win32.cs new file mode 100644 index 0000000000..8a9e62e6e5 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/Path.Win32.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace System.IO +{ + public static partial class Path + { + private static unsafe void GetCryptoRandomBytes(byte* bytes, int byteCount) + { + // We need to fill a byte array with cryptographically-strong random bytes, but we can't reference + // System.Security.Cryptography.RandomNumberGenerator.dll due to layering. Instead, we just + // call to BCryptGenRandom directly, which is all that RandomNumberGenerator does. + + Debug.Assert(bytes != null); + Debug.Assert(byteCount >= 0); + + Interop.BCrypt.NTSTATUS status = Interop.BCrypt.BCryptGenRandom(bytes, byteCount); + if (status == Interop.BCrypt.NTSTATUS.STATUS_SUCCESS) + { + return; + } + else if (status == Interop.BCrypt.NTSTATUS.STATUS_NO_MEMORY) + { + throw new OutOfMemoryException(); + } + else + { + Debug.Fail("BCryptGenRandom should only fail due to OOM or invalid args / handle inputs."); + throw new InvalidOperationException(); + } + } + } +} diff --git a/src/mscorlib/corefx/System/IO/Path.Windows.cs b/src/mscorlib/corefx/System/IO/Path.Windows.cs new file mode 100644 index 0000000000..b597efc54e --- /dev/null +++ b/src/mscorlib/corefx/System/IO/Path.Windows.cs @@ -0,0 +1,153 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Text; + +namespace System.IO +{ + public static partial class Path + { + public static readonly char DirectorySeparatorChar = '\\'; + public static readonly char VolumeSeparatorChar = ':'; + public static readonly char PathSeparator = ';'; + + private const string DirectorySeparatorCharAsString = "\\"; + + public static char[] GetInvalidFileNameChars() => new char[] + { + '\"', '<', '>', '|', '\0', + (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, + (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, + (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, + (char)31, ':', '*', '?', '\\', '/' + }; + + // The max total path is 260, and the max individual component length is 255. + // For example, D:\<256 char file name> isn't legal, even though it's under 260 chars. + internal static readonly int MaxPath = 260; + internal static readonly int MaxLongPath = short.MaxValue; + + // Expands the given path to a fully qualified path. + public static string GetFullPath(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + + // Embedded null characters are the only invalid character case we want to check up front. + // This is because the nulls will signal the end of the string to Win32 and therefore have + // unpredictable results. Other invalid characters we give a chance to be normalized out. + if (path.IndexOf('\0') != -1) + throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); + + if (PathInternal.IsExtended(path)) + { + // We can't really know what is valid for all cases of extended paths. + // + // - object names can include other characters as well (':', '/', etc.) + // - even file objects have different rules (pipe names can contain most characters) + // + // As such we will do no further analysis of extended paths to avoid blocking known and unknown + // scenarios as well as minimizing compat breaks should we block now and need to unblock later. + return path; + } + + bool isDevice = PathInternal.IsDevice(path); + if (!isDevice) + { + // Toss out paths with colons that aren't a valid drive specifier. + // Cannot start with a colon and can only be of the form "C:". + // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.) + int startIndex = PathInternal.PathStartSkip(path); + + // Move past the colon + startIndex += 2; + + if ((path.Length > 0 && path[0] == VolumeSeparatorChar) + || (path.Length >= startIndex && path[startIndex - 1] == VolumeSeparatorChar && !PathInternal.IsValidDriveChar(path[startIndex - 2])) + || (path.Length > startIndex && path.IndexOf(VolumeSeparatorChar, startIndex) != -1)) + { + throw new NotSupportedException(SR.Argument_PathFormatNotSupported); + } + } + + // Technically this doesn't matter but we used to throw for this case + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException(SR.Arg_PathIllegal); + + // We don't want to check invalid characters for device format- see comments for extended above + string fullPath = PathHelper.Normalize(path, checkInvalidCharacters: !isDevice, expandShortPaths: true); + + if (!isDevice) + { + // Emulate FileIOPermissions checks, retained for compatibility (normal invalid characters have already been checked) + if (PathInternal.HasWildCardCharacters(fullPath)) + throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); + } + + return fullPath; + } + + public static string GetTempPath() + { + StringBuilder sb = StringBuilderCache.Acquire(MaxPath); + uint r = Interop.mincore.GetTempPathW(MaxPath, sb); + if (r == 0) + throw Win32Marshal.GetExceptionForLastWin32Error(); + return GetFullPath(StringBuilderCache.GetStringAndRelease(sb)); + } + + // Returns a unique temporary file name, and creates a 0-byte file by that + // name on disk. + public static string GetTempFileName() + { + string path = GetTempPath(); + + StringBuilder sb = StringBuilderCache.Acquire(MaxPath); + uint r = Interop.mincore.GetTempFileNameW(path, "tmp", 0, sb); + if (r == 0) + throw Win32Marshal.GetExceptionForLastWin32Error(); + return StringBuilderCache.GetStringAndRelease(sb); + } + + // Tests if the given path contains a root. A path is considered rooted + // if it starts with a backslash ("\") or a drive letter and a colon (":"). + public static bool IsPathRooted(string path) + { + if (path != null) + { + PathInternal.CheckInvalidPathChars(path); + + int length = path.Length; + if ((length >= 1 && PathInternal.IsDirectorySeparator(path[0])) || + (length >= 2 && path[1] == VolumeSeparatorChar)) + return true; + } + return false; + } + + // Returns the root portion of the given path. The resulting string + // consists of those rightmost characters of the path that constitute the + // root of the path. Possible patterns for the resulting string are: An + // empty string (a relative path on the current drive), "\" (an absolute + // path on the current drive), "X:" (a relative path on a given drive, + // where X is the drive letter), "X:\" (an absolute path on a given drive), + // and "\\server\share" (a UNC path for a given server and share name). + // The resulting string is null if path is null. + public static string GetPathRoot(string path) + { + if (path == null) return null; + PathInternal.CheckInvalidPathChars(path); + + // Need to return the normalized directory separator + path = PathInternal.NormalizeDirectorySeparators(path); + + int pathRoot = PathInternal.GetRootLength(path); + return pathRoot <= 0 ? string.Empty : path.Substring(0, pathRoot); + } + + /// Gets whether the system is case-sensitive. + internal static bool IsCaseSensitive { get { return false; } } + } +} diff --git a/src/mscorlib/corefx/System/IO/Path.cs b/src/mscorlib/corefx/System/IO/Path.cs new file mode 100644 index 0000000000..3b1ba6b07d --- /dev/null +++ b/src/mscorlib/corefx/System/IO/Path.cs @@ -0,0 +1,578 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Text; + +namespace System.IO +{ + // Provides methods for processing file system strings in a cross-platform manner. + // Most of the methods don't do a complete parsing (such as examining a UNC hostname), + // but they will handle most string operations. + public static partial class Path + { + // Platform specific alternate directory separator character. + // There is only one directory separator char on Unix, which is the same + // as the alternate separator on Windows, so same definition is used for both. + public static readonly char AltDirectorySeparatorChar = '/'; + + // For generating random file names + // 8 random bytes provides 12 chars in our encoding for the 8.3 name. + const int KeyLength = 8; + + [Obsolete("Please use GetInvalidPathChars or GetInvalidFileNameChars instead.")] + public static readonly char[] InvalidPathChars = GetInvalidPathChars(); + + // Changes the extension of a file path. The path parameter + // specifies a file path, and the extension parameter + // specifies a file extension (with a leading period, such as + // ".exe" or ".cs"). + // + // The function returns a file path with the same root, directory, and base + // name parts as path, but with the file extension changed to + // the specified extension. If path is null, the function + // returns null. If path does not contain a file extension, + // the new file extension is appended to the path. If extension + // is null, any existing extension is removed from path. + public static string ChangeExtension(string path, string extension) + { + if (path != null) + { + PathInternal.CheckInvalidPathChars(path); + + string s = path; + for (int i = path.Length - 1; i >= 0; i--) + { + char ch = path[i]; + if (ch == '.') + { + s = path.Substring(0, i); + break; + } + if (PathInternal.IsDirectoryOrVolumeSeparator(ch)) break; + } + + if (extension != null && path.Length != 0) + { + s = (extension.Length == 0 || extension[0] != '.') ? + s + "." + extension : + s + extension; + } + + return s; + } + return null; + } + + // Returns the directory path of a file path. This method effectively + // removes the last element of the given file path, i.e. it returns a + // string consisting of all characters up to but not including the last + // backslash ("\") in the file path. The returned value is null if the file + // path is null or if the file path denotes a root (such as "\", "C:", or + // "\\server\share"). + public static string GetDirectoryName(string path) + { + if (path != null) + { + PathInternal.CheckInvalidPathChars(path); + path = PathInternal.NormalizeDirectorySeparators(path); + int root = PathInternal.GetRootLength(path); + + int i = path.Length; + if (i > root) + { + while (i > root && !PathInternal.IsDirectorySeparator(path[--i])) ; + return path.Substring(0, i); + } + } + return null; + } + + public static char[] GetInvalidPathChars() + { + return PathInternal.GetInvalidPathChars(); + } + + // Returns the extension of the given path. The returned value includes the + // period (".") character of the extension except when you have a terminal period when you get string.Empty, such as ".exe" or + // ".cpp". The returned value is null if the given path is + // null or if the given path does not include an extension. + [Pure] + public static string GetExtension(string path) + { + if (path == null) + return null; + + PathInternal.CheckInvalidPathChars(path); + int length = path.Length; + for (int i = length - 1; i >= 0; i--) + { + char ch = path[i]; + if (ch == '.') + { + if (i != length - 1) + return path.Substring(i, length - i); + else + return string.Empty; + } + if (PathInternal.IsDirectoryOrVolumeSeparator(ch)) + break; + } + return string.Empty; + } + + // Returns the name and extension parts of the given path. The resulting + // string contains the characters of path that follow the last + // separator in path. The resulting string is null if path is null. + [Pure] + public static string GetFileName(string path) + { + if (path == null) + return null; + + int offset = PathInternal.FindFileNameIndex(path); + int count = path.Length - offset; + return path.Substring(offset, count); + } + + [Pure] + public static string GetFileNameWithoutExtension(string path) + { + if (path == null) + return null; + + int length = path.Length; + int offset = PathInternal.FindFileNameIndex(path); + + int end = path.LastIndexOf('.', length - 1, length - offset); + return end == -1 ? + path.Substring(offset) : // No extension was found + path.Substring(offset, end - offset); + } + + // Returns a cryptographically strong random 8.3 string that can be + // used as either a folder name or a file name. + public static unsafe string GetRandomFileName() + { + + byte* pKey = stackalloc byte[KeyLength]; + GetCryptoRandomBytes(pKey, KeyLength); + + const int RandomFileNameLength = 12; + char* pRandomFileName = stackalloc char[RandomFileNameLength]; + Populate83FileNameFromRandomBytes(pKey, KeyLength, pRandomFileName, RandomFileNameLength); + return new string(pRandomFileName, 0, RandomFileNameLength); + } + + // Tests if a path includes a file extension. The result is + // true if the characters that follow the last directory + // separator ('\\' or '/') or volume separator (':') in the path include + // a period (".") other than a terminal period. The result is false otherwise. + [Pure] + public static bool HasExtension(string path) + { + if (path != null) + { + PathInternal.CheckInvalidPathChars(path); + + for (int i = path.Length - 1; i >= 0; i--) + { + char ch = path[i]; + if (ch == '.') + { + return i != path.Length - 1; + } + if (PathInternal.IsDirectoryOrVolumeSeparator(ch)) break; + } + } + return false; + } + + public static string Combine(string path1, string path2) + { + if (path1 == null || path2 == null) + throw new ArgumentNullException((path1 == null) ? nameof(path1): nameof(path2)); + Contract.EndContractBlock(); + + PathInternal.CheckInvalidPathChars(path1); + PathInternal.CheckInvalidPathChars(path2); + + return CombineNoChecks(path1, path2); + } + + public static string Combine(string path1, string path2, string path3) + { + if (path1 == null || path2 == null || path3 == null) + throw new ArgumentNullException((path1 == null) ? nameof(path1): (path2 == null) ? nameof(path2): nameof(path3)); + Contract.EndContractBlock(); + + PathInternal.CheckInvalidPathChars(path1); + PathInternal.CheckInvalidPathChars(path2); + PathInternal.CheckInvalidPathChars(path3); + + return CombineNoChecks(path1, path2, path3); + } + + public static string Combine(string path1, string path2, string path3, string path4) + { + if (path1 == null || path2 == null || path3 == null || path4 == null) + throw new ArgumentNullException((path1 == null) ? nameof(path1): (path2 == null) ? nameof(path2): (path3 == null) ? nameof(path3): nameof(path4)); + Contract.EndContractBlock(); + + PathInternal.CheckInvalidPathChars(path1); + PathInternal.CheckInvalidPathChars(path2); + PathInternal.CheckInvalidPathChars(path3); + PathInternal.CheckInvalidPathChars(path4); + + return CombineNoChecks(path1, path2, path3, path4); + } + + public static string Combine(params string[] paths) + { + if (paths == null) + { + throw new ArgumentNullException(nameof(paths)); + } + Contract.EndContractBlock(); + + int finalSize = 0; + int firstComponent = 0; + + // We have two passes, the first calculates how large a buffer to allocate and does some precondition + // checks on the paths passed in. The second actually does the combination. + + for (int i = 0; i < paths.Length; i++) + { + if (paths[i] == null) + { + throw new ArgumentNullException(nameof(paths)); + } + + if (paths[i].Length == 0) + { + continue; + } + + PathInternal.CheckInvalidPathChars(paths[i]); + + if (IsPathRooted(paths[i])) + { + firstComponent = i; + finalSize = paths[i].Length; + } + else + { + finalSize += paths[i].Length; + } + + char ch = paths[i][paths[i].Length - 1]; + if (!PathInternal.IsDirectoryOrVolumeSeparator(ch)) + finalSize++; + } + + StringBuilder finalPath = StringBuilderCache.Acquire(finalSize); + + for (int i = firstComponent; i < paths.Length; i++) + { + if (paths[i].Length == 0) + { + continue; + } + + if (finalPath.Length == 0) + { + finalPath.Append(paths[i]); + } + else + { + char ch = finalPath[finalPath.Length - 1]; + if (!PathInternal.IsDirectoryOrVolumeSeparator(ch)) + { + finalPath.Append(DirectorySeparatorChar); + } + + finalPath.Append(paths[i]); + } + } + + return StringBuilderCache.GetStringAndRelease(finalPath); + } + + private static string CombineNoChecks(string path1, string path2) + { + if (path2.Length == 0) + return path1; + + if (path1.Length == 0) + return path2; + + if (IsPathRooted(path2)) + return path2; + + char ch = path1[path1.Length - 1]; + return PathInternal.IsDirectoryOrVolumeSeparator(ch) ? + path1 + path2 : + path1 + DirectorySeparatorCharAsString + path2; + } + + private static string CombineNoChecks(string path1, string path2, string path3) + { + if (path1.Length == 0) + return CombineNoChecks(path2, path3); + if (path2.Length == 0) + return CombineNoChecks(path1, path3); + if (path3.Length == 0) + return CombineNoChecks(path1, path2); + + if (IsPathRooted(path3)) + return path3; + if (IsPathRooted(path2)) + return CombineNoChecks(path2, path3); + + bool hasSep1 = PathInternal.IsDirectoryOrVolumeSeparator(path1[path1.Length - 1]); + bool hasSep2 = PathInternal.IsDirectoryOrVolumeSeparator(path2[path2.Length - 1]); + + if (hasSep1 && hasSep2) + { + return path1 + path2 + path3; + } + else if (hasSep1) + { + return path1 + path2 + DirectorySeparatorCharAsString + path3; + } + else if (hasSep2) + { + return path1 + DirectorySeparatorCharAsString + path2 + path3; + } + else + { + // string.Concat only has string-based overloads up to four arguments; after that requires allocating + // a params string[]. Instead, try to use a cached StringBuilder. + StringBuilder sb = StringBuilderCache.Acquire(path1.Length + path2.Length + path3.Length + 2); + sb.Append(path1) + .Append(DirectorySeparatorChar) + .Append(path2) + .Append(DirectorySeparatorChar) + .Append(path3); + return StringBuilderCache.GetStringAndRelease(sb); + } + } + + private static string CombineNoChecks(string path1, string path2, string path3, string path4) + { + if (path1.Length == 0) + return CombineNoChecks(path2, path3, path4); + if (path2.Length == 0) + return CombineNoChecks(path1, path3, path4); + if (path3.Length == 0) + return CombineNoChecks(path1, path2, path4); + if (path4.Length == 0) + return CombineNoChecks(path1, path2, path3); + + if (IsPathRooted(path4)) + return path4; + if (IsPathRooted(path3)) + return CombineNoChecks(path3, path4); + if (IsPathRooted(path2)) + return CombineNoChecks(path2, path3, path4); + + bool hasSep1 = PathInternal.IsDirectoryOrVolumeSeparator(path1[path1.Length - 1]); + bool hasSep2 = PathInternal.IsDirectoryOrVolumeSeparator(path2[path2.Length - 1]); + bool hasSep3 = PathInternal.IsDirectoryOrVolumeSeparator(path3[path3.Length - 1]); + + if (hasSep1 && hasSep2 && hasSep3) + { + // Use string.Concat overload that takes four strings + return path1 + path2 + path3 + path4; + } + else + { + // string.Concat only has string-based overloads up to four arguments; after that requires allocating + // a params string[]. Instead, try to use a cached StringBuilder. + StringBuilder sb = StringBuilderCache.Acquire(path1.Length + path2.Length + path3.Length + path4.Length + 3); + + sb.Append(path1); + if (!hasSep1) + { + sb.Append(DirectorySeparatorChar); + } + + sb.Append(path2); + if (!hasSep2) + { + sb.Append(DirectorySeparatorChar); + } + + sb.Append(path3); + if (!hasSep3) + { + sb.Append(DirectorySeparatorChar); + } + + sb.Append(path4); + + return StringBuilderCache.GetStringAndRelease(sb); + } + } + + private static readonly char[] s_base32Char = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '0', '1', '2', '3', '4', '5'}; + + private static unsafe void Populate83FileNameFromRandomBytes(byte* bytes, int byteCount, char* chars, int charCount) + { + Debug.Assert(bytes != null); + Debug.Assert(chars != null); + + // This method requires bytes of length 8 and chars of length 12. + Debug.Assert(byteCount == 8, $"Unexpected {nameof(byteCount)}"); + Debug.Assert(charCount == 12, $"Unexpected {nameof(charCount)}"); + + byte b0 = bytes[0]; + byte b1 = bytes[1]; + byte b2 = bytes[2]; + byte b3 = bytes[3]; + byte b4 = bytes[4]; + + // Consume the 5 Least significant bits of the first 5 bytes + chars[0] = s_base32Char[b0 & 0x1F]; + chars[1] = s_base32Char[b1 & 0x1F]; + chars[2] = s_base32Char[b2 & 0x1F]; + chars[3] = s_base32Char[b3 & 0x1F]; + chars[4] = s_base32Char[b4 & 0x1F]; + + // Consume 3 MSB of b0, b1, MSB bits 6, 7 of b3, b4 + chars[5] = s_base32Char[( + ((b0 & 0xE0) >> 5) | + ((b3 & 0x60) >> 2))]; + + chars[6] = s_base32Char[( + ((b1 & 0xE0) >> 5) | + ((b4 & 0x60) >> 2))]; + + // Consume 3 MSB bits of b2, 1 MSB bit of b3, b4 + b2 >>= 5; + + Debug.Assert(((b2 & 0xF8) == 0), "Unexpected set bits"); + + if ((b3 & 0x80) != 0) + b2 |= 0x08; + if ((b4 & 0x80) != 0) + b2 |= 0x10; + + chars[7] = s_base32Char[b2]; + + // Set the file extension separator + chars[8] = '.'; + + // Consume the 5 Least significant bits of the remaining 3 bytes + chars[9] = s_base32Char[(bytes[5] & 0x1F)]; + chars[10] = s_base32Char[(bytes[6] & 0x1F)]; + chars[11] = s_base32Char[(bytes[7] & 0x1F)]; + } + + /// + /// Create a relative path from one path to another. Paths will be resolved before calculating the difference. + /// Default path comparison for the active platform will be used (OrdinalIgnoreCase for Windows or Mac, Ordinal for Unix). + /// + /// The source path the output should be relative to. This path is always considered to be a directory. + /// The destination path. + /// The relative path or if the paths don't share the same root. + /// Thrown if or is null or an empty string. + public static string GetRelativePath(string relativeTo, string path) + { + return GetRelativePath(relativeTo, path, StringComparison); + } + + private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType) + { + if (string.IsNullOrEmpty(relativeTo)) throw new ArgumentNullException(nameof(relativeTo)); + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path)); + Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase); + + relativeTo = GetFullPath(relativeTo); + path = GetFullPath(path); + + // Need to check if the roots are different- if they are we need to return the "to" path. + if (!PathInternal.AreRootsEqual(relativeTo, path, comparisonType)) + return path; + + int commonLength = PathInternal.GetCommonPathLength(relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase); + + // If there is nothing in common they can't share the same root, return the "to" path as is. + if (commonLength == 0) + return path; + + // Trailing separators aren't significant for comparison + int relativeToLength = relativeTo.Length; + if (PathInternal.EndsInDirectorySeparator(relativeTo)) + relativeToLength--; + + bool pathEndsInSeparator = PathInternal.EndsInDirectorySeparator(path); + int pathLength = path.Length; + if (pathEndsInSeparator) + pathLength--; + + // If we have effectively the same path, return "." + if (relativeToLength == pathLength && commonLength >= relativeToLength) return "."; + + // We have the same root, we need to calculate the difference now using the + // common Length and Segment count past the length. + // + // Some examples: + // + // C:\Foo C:\Bar L3, S1 -> ..\Bar + // C:\Foo C:\Foo\Bar L6, S0 -> Bar + // C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar + // C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar + + StringBuilder sb = StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length)); + + // Add parent segments for segments past the common on the "from" path + if (commonLength < relativeToLength) + { + sb.Append(PathInternal.ParentDirectoryPrefix); + + for (int i = commonLength; i < relativeToLength; i++) + { + if (PathInternal.IsDirectorySeparator(relativeTo[i])) + { + sb.Append(PathInternal.ParentDirectoryPrefix); + } + } + } + else if (PathInternal.IsDirectorySeparator(path[commonLength])) + { + // No parent segments and we need to eat the initial separator + // (C:\Foo C:\Foo\Bar case) + commonLength++; + } + + // Now add the rest of the "to" path, adding back the trailing separator + int count = pathLength - commonLength; + if (pathEndsInSeparator) + count++; + + sb.Append(path, commonLength, count); + return StringBuilderCache.GetStringAndRelease(sb); + } + + // StringComparison and IsCaseSensitive are also available in PathInternal.CaseSensitivity but we are + // too low in System.Runtime.Extensions to use it (no FileStream, etc.) + + /// Returns a comparison that can be used to compare file and directory names for equality. + internal static StringComparison StringComparison + { + get + { + return IsCaseSensitive ? + StringComparison.Ordinal : + StringComparison.OrdinalIgnoreCase; + } + } + } +} diff --git a/src/mscorlib/corefx/System/IO/PathHelper.Windows.cs b/src/mscorlib/corefx/System/IO/PathHelper.Windows.cs new file mode 100644 index 0000000000..4c2cdff45e --- /dev/null +++ b/src/mscorlib/corefx/System/IO/PathHelper.Windows.cs @@ -0,0 +1,389 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.IO +{ + /// + /// Wrapper to help with path normalization. + /// + unsafe internal class PathHelper + { + // Can't be over 8.3 and be a short name + private const int MaxShortName = 12; + + private const char LastAnsi = (char)255; + private const char Delete = (char)127; + + [ThreadStatic] + private static StringBuffer t_fullPathBuffer; + + /// + /// Normalize the given path. + /// + /// + /// Normalizes via Win32 GetFullPathName(). It will also trim all "typical" whitespace at the end of the path (see s_trimEndChars). Will also trim initial + /// spaces if the path is determined to be rooted. + /// + /// Note that invalid characters will be checked after the path is normalized, which could remove bad characters. (C:\|\..\a.txt -- C:\a.txt) + /// + /// Path to normalize + /// True to check for invalid characters + /// Attempt to expand short paths if true + /// Thrown if the path is an illegal UNC (does not contain a full server/share) or contains illegal characters. + /// Thrown if the path or a path segment exceeds the filesystem limits. + /// Thrown if Windows returns ERROR_FILE_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error) + /// Thrown if Windows returns ERROR_PATH_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error) + /// Thrown if Windows returns ERROR_ACCESS_DENIED. (See Win32Marshal.GetExceptionForWin32Error) + /// Thrown if Windows returns an error that doesn't map to the above. (See Win32Marshal.GetExceptionForWin32Error) + /// Normalized path + internal static string Normalize(string path, bool checkInvalidCharacters, bool expandShortPaths) + { + // Get the full path + StringBuffer fullPath = t_fullPathBuffer ?? (t_fullPathBuffer = new StringBuffer(PathInternal.MaxShortPath)); + try + { + GetFullPathName(path, fullPath); + + // Trim whitespace off the end of the string. Win32 normalization trims only U+0020. + fullPath.TrimEnd(PathInternal.s_trimEndChars); + + if (fullPath.Length >= PathInternal.MaxLongPath) + { + // Fullpath is genuinely too long + throw new PathTooLongException(SR.IO_PathTooLong); + } + + // Checking path validity used to happen before getting the full path name. To avoid additional input allocation + // (to trim trailing whitespace) we now do it after the Win32 call. This will allow legitimate paths through that + // used to get kicked back (notably segments with invalid characters might get removed via ".."). + // + // There is no way that GetLongPath can invalidate the path so we'll do this (cheaper) check before we attempt to + // expand short file names. + + // Scan the path for: + // + // - Illegal path characters. + // - Invalid UNC paths like \\, \\server, \\server\. + // - Segments that are too long (over MaxComponentLength) + + // As the path could be > 30K, we'll combine the validity scan. None of these checks are performed by the Win32 + // GetFullPathName() API. + + bool possibleShortPath = false; + bool foundTilde = false; + + // We can get UNCs as device paths through this code (e.g. \\.\UNC\), we won't validate them as there isn't + // an easy way to normalize without extensive cost (we'd have to hunt down the canonical name for any device + // path that contains UNC or to see if the path was doing something like \\.\GLOBALROOT\Device\Mup\, + // \\.\GLOBAL\UNC\, \\.\GLOBALROOT\GLOBAL??\UNC\, etc. + bool specialPath = fullPath.Length > 1 && fullPath[0] == '\\' && fullPath[1] == '\\'; + bool isDevice = PathInternal.IsDevice(fullPath); + bool possibleBadUnc = specialPath && !isDevice; + uint index = specialPath ? 2u : 0; + uint lastSeparator = specialPath ? 1u : 0; + uint segmentLength; + char* start = fullPath.CharPointer; + char current; + + while (index < fullPath.Length) + { + current = start[index]; + + // Try to skip deeper analysis. '?' and higher are valid/ignorable except for '\', '|', and '~' + if (current < '?' || current == '\\' || current == '|' || current == '~') + { + switch (current) + { + case '|': + case '>': + case '<': + case '\"': + if (checkInvalidCharacters) throw new ArgumentException(SR.Argument_InvalidPathChars); + foundTilde = false; + break; + case '~': + foundTilde = true; + break; + case '\\': + segmentLength = index - lastSeparator - 1; + if (segmentLength > (uint)PathInternal.MaxComponentLength) + throw new PathTooLongException(SR.IO_PathTooLong + fullPath.ToString()); + lastSeparator = index; + + if (foundTilde) + { + if (segmentLength <= MaxShortName) + { + // Possibly a short path. + possibleShortPath = true; + } + + foundTilde = false; + } + + if (possibleBadUnc) + { + // If we're at the end of the path and this is the first separator, we're missing the share. + // Otherwise we're good, so ignore UNC tracking from here. + if (index == fullPath.Length - 1) + throw new ArgumentException(SR.Arg_PathIllegalUNC); + else + possibleBadUnc = false; + } + + break; + + default: + if (checkInvalidCharacters && current < ' ') throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); + break; + } + } + + index++; + } + + if (possibleBadUnc) + throw new ArgumentException(SR.Arg_PathIllegalUNC); + + segmentLength = fullPath.Length - lastSeparator - 1; + if (segmentLength > (uint)PathInternal.MaxComponentLength) + throw new PathTooLongException(SR.IO_PathTooLong); + + if (foundTilde && segmentLength <= MaxShortName) + possibleShortPath = true; + + // Check for a short filename path and try and expand it. Technically you don't need to have a tilde for a short name, but + // this is how we've always done this. This expansion is costly so we'll continue to let other short paths slide. + if (expandShortPaths && possibleShortPath) + { + return TryExpandShortFileName(fullPath, originalPath: path); + } + else + { + if (fullPath.Length == (uint)path.Length && fullPath.StartsWith(path)) + { + // If we have the exact same string we were passed in, don't bother to allocate another string from the StringBuilder. + return path; + } + else + { + return fullPath.ToString(); + } + } + } + finally + { + // Clear the buffer + fullPath.Free(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsDosUnc(StringBuffer buffer) + { + return !PathInternal.IsDevice(buffer) && buffer.Length > 1 && buffer[0] == '\\' && buffer[1] == '\\'; + } + + private static void GetFullPathName(string path, StringBuffer fullPath) + { + // If the string starts with an extended prefix we would need to remove it from the path before we call GetFullPathName as + // it doesn't root extended paths correctly. We don't currently resolve extended paths, so we'll just assert here. + Debug.Assert(PathInternal.IsPartiallyQualified(path) || !PathInternal.IsExtended(path)); + + // Historically we would skip leading spaces *only* if the path started with a drive " C:" or a UNC " \\" + int startIndex = PathInternal.PathStartSkip(path); + + fixed (char* pathStart = path) + { + uint result = 0; + while ((result = Interop.mincore.GetFullPathNameW(pathStart + startIndex, fullPath.CharCapacity, fullPath.GetHandle(), IntPtr.Zero)) > fullPath.CharCapacity) + { + // Reported size (which does not include the null) is greater than the buffer size. Increase the capacity. + fullPath.EnsureCharCapacity(result); + } + + if (result == 0) + { + // Failure, get the error and throw + int errorCode = Marshal.GetLastWin32Error(); + if (errorCode == 0) + errorCode = Interop.mincore.Errors.ERROR_BAD_PATHNAME; + throw Win32Marshal.GetExceptionForWin32Error(errorCode, path); + } + + fullPath.Length = result; + } + } + + private static uint GetInputBuffer(StringBuffer content, bool isDosUnc, out StringBuffer buffer) + { + uint length = content.Length; + + length += isDosUnc + ? (uint)PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength + : PathInternal.DevicePrefixLength; + + buffer = new StringBuffer(length); + + if (isDosUnc) + { + // Put the extended UNC prefix (\\?\UNC\) in front of the path + buffer.CopyFrom(bufferIndex: 0, source: PathInternal.UncExtendedPathPrefix); + + // Copy the source buffer over after the existing UNC prefix + content.CopyTo( + bufferIndex: PathInternal.UncPrefixLength, + destination: buffer, + destinationIndex: PathInternal.UncExtendedPrefixLength, + count: content.Length - PathInternal.UncPrefixLength); + + // Return the prefix difference + return (uint)PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength; + } + else + { + uint prefixSize = (uint)PathInternal.ExtendedPathPrefix.Length; + buffer.CopyFrom(bufferIndex: 0, source: PathInternal.ExtendedPathPrefix); + content.CopyTo(bufferIndex: 0, destination: buffer, destinationIndex: prefixSize, count: content.Length); + return prefixSize; + } + } + + private static string TryExpandShortFileName(StringBuffer outputBuffer, string originalPath) + { + // We guarantee we'll expand short names for paths that only partially exist. As such, we need to find the part of the path that actually does exist. To + // avoid allocating like crazy we'll create only one input array and modify the contents with embedded nulls. + + Debug.Assert(!PathInternal.IsPartiallyQualified(outputBuffer), "should have resolved by now"); + + // We'll have one of a few cases by now (the normalized path will have already: + // + // 1. Dos path (C:\) + // 2. Dos UNC (\\Server\Share) + // 3. Dos device path (\\.\C:\, \\?\C:\) + // + // We want to put the extended syntax on the front if it doesn't already have it, which may mean switching from \\.\. + // + // Note that we will never get \??\ here as GetFullPathName() does not recognize \??\ and will return it as C:\??\ (or whatever the current drive is). + + uint rootLength = PathInternal.GetRootLength(outputBuffer); + bool isDevice = PathInternal.IsDevice(outputBuffer); + + StringBuffer inputBuffer = null; + bool isDosUnc = false; + uint rootDifference = 0; + bool wasDotDevice = false; + + // Add the extended prefix before expanding to allow growth over MAX_PATH + if (isDevice) + { + // We have one of the following (\\?\ or \\.\) + inputBuffer = new StringBuffer(); + inputBuffer.Append(outputBuffer); + + if (outputBuffer[2] == '.') + { + wasDotDevice = true; + inputBuffer[2] = '?'; + } + } + else + { + isDosUnc = IsDosUnc(outputBuffer); + rootDifference = GetInputBuffer(outputBuffer, isDosUnc, out inputBuffer); + } + + rootLength += rootDifference; + uint inputLength = inputBuffer.Length; + + bool success = false; + uint foundIndex = inputBuffer.Length - 1; + + while (!success) + { + uint result = Interop.mincore.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity); + + // Replace any temporary null we added + if (inputBuffer[foundIndex] == '\0') inputBuffer[foundIndex] = '\\'; + + if (result == 0) + { + // Look to see if we couldn't find the file + int error = Marshal.GetLastWin32Error(); + if (error != Interop.mincore.Errors.ERROR_FILE_NOT_FOUND && error != Interop.mincore.Errors.ERROR_PATH_NOT_FOUND) + { + // Some other failure, give up + break; + } + + // We couldn't find the path at the given index, start looking further back in the string. + foundIndex--; + + for (; foundIndex > rootLength && inputBuffer[foundIndex] != '\\'; foundIndex--) ; + if (foundIndex == rootLength) + { + // Can't trim the path back any further + break; + } + else + { + // Temporarily set a null in the string to get Windows to look further up the path + inputBuffer[foundIndex] = '\0'; + } + } + else if (result > outputBuffer.CharCapacity) + { + // Not enough space. The result count for this API does not include the null terminator. + outputBuffer.EnsureCharCapacity(result); + result = Interop.mincore.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity); + } + else + { + // Found the path + success = true; + outputBuffer.Length = result; + if (foundIndex < inputLength - 1) + { + // It was a partial find, put the non-existent part of the path back + outputBuffer.Append(inputBuffer, foundIndex, inputBuffer.Length - foundIndex); + } + } + } + + // Strip out the prefix and return the string + StringBuffer bufferToUse = success ? outputBuffer : inputBuffer; + + // Switch back from \\?\ to \\.\ if necessary + if (wasDotDevice) + bufferToUse[2] = '.'; + + string returnValue = null; + + int newLength = (int)(bufferToUse.Length - rootDifference); + if (isDosUnc) + { + // Need to go from \\?\UNC\ to \\?\UN\\ + bufferToUse[PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength] = '\\'; + } + + // We now need to strip out any added characters at the front of the string + if (bufferToUse.SubstringEquals(originalPath, rootDifference, newLength)) + { + // Use the original path to avoid allocating + returnValue = originalPath; + } + else + { + returnValue = bufferToUse.Substring(rootDifference, newLength); + } + + inputBuffer.Dispose(); + return returnValue; + } + } +} diff --git a/src/mscorlib/corefx/System/IO/PathInternal.CaseSensitivity.cs b/src/mscorlib/corefx/System/IO/PathInternal.CaseSensitivity.cs new file mode 100644 index 0000000000..bea2df93b9 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/PathInternal.CaseSensitivity.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace System.IO +{ + /// Contains internal path helpers that are shared between many projects. + internal static partial class PathInternal + { + private enum Tristate : byte + { + NotInitialized, + True, + False, + } + + private static Tristate s_isCaseSensitive = Tristate.NotInitialized; + + /// Returns a comparison that can be used to compare file and directory names for equality. + internal static StringComparison StringComparison + { + get + { + return IsCaseSensitive ? + StringComparison.Ordinal : + StringComparison.OrdinalIgnoreCase; + } + } + + /// Gets whether the system is case-sensitive. + internal static bool IsCaseSensitive + { + get + { + // This must be lazily initialized as there are dependencies on PathInternal's static constructor + // being fully initialized. (GetIsCaseSensitive() calls GetFullPath() which needs to use PathInternal) + if (s_isCaseSensitive == Tristate.NotInitialized) + s_isCaseSensitive = GetIsCaseSensitive() ? Tristate.True : Tristate.False; + + return s_isCaseSensitive == Tristate.True; + } + } + + /// + /// Determines whether the file system is case sensitive. + /// + /// + /// Ideally we'd use something like pathconf with _PC_CASE_SENSITIVE, but that is non-portable, + /// not supported on Windows or Linux, etc. For now, this function creates a tmp file with capital letters + /// and then tests for its existence with lower-case letters. This could return invalid results in corner + /// cases where, for example, different file systems are mounted with differing sensitivities. + /// + private static bool GetIsCaseSensitive() + { + try + { + string pathWithUpperCase = Path.Combine(Path.GetTempPath(), "CASESENSITIVETEST" + Guid.NewGuid().ToString("N")); + using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose)) + { + string lowerCased = pathWithUpperCase.ToLowerInvariant(); + return !File.Exists(lowerCased); + } + } + catch (Exception exc) + { + // In case something goes terribly wrong, we don't want to fail just because + // of a casing test, so we assume case-insensitive-but-preserving. + Debug.Fail("Casing test failed: " + exc); + return false; + } + } + } +} diff --git a/src/mscorlib/corefx/System/IO/PathInternal.Unix.cs b/src/mscorlib/corefx/System/IO/PathInternal.Unix.cs new file mode 100644 index 0000000000..6c39f99556 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/PathInternal.Unix.cs @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Text; + +namespace System.IO +{ + /// Contains internal path helpers that are shared between many projects. + internal static partial class PathInternal + { + // There is only one invalid path character in Unix + private const char InvalidPathChar = '\0'; + internal static char[] GetInvalidPathChars() => new char[] { InvalidPathChar }; + + internal static readonly int MaxComponentLength = Interop.Sys.MaxName; + + internal const string ParentDirectoryPrefix = @"../"; + + /// Returns a value indicating if the given path contains invalid characters. + internal static bool HasIllegalCharacters(string path) + { + Debug.Assert(path != null); + return path.IndexOf(InvalidPathChar) >= 0; + } + + internal static int GetRootLength(string path) + { + return path.Length > 0 && IsDirectorySeparator(path[0]) ? 1 : 0; + } + + internal static bool IsDirectorySeparator(char c) + { + // The alternate directory separator char is the same as the directory separator, + // so we only need to check one. + Debug.Assert(Path.DirectorySeparatorChar == Path.AltDirectorySeparatorChar); + return c == Path.DirectorySeparatorChar; + } + + /// + /// Returns true if the path is too long + /// + internal static bool IsPathTooLong(string fullPath) + { + return fullPath.Length >= Interop.Sys.MaxPath; + } + + /// + /// Returns true if the directory is too long + /// + internal static bool IsDirectoryTooLong(string fullPath) + { + return fullPath.Length >= Interop.Sys.MaxPath; + } + + /// + /// Normalize separators in the given path. Compresses forward slash runs. + /// + internal static string NormalizeDirectorySeparators(string path) + { + if (string.IsNullOrEmpty(path)) return path; + + // Make a pass to see if we need to normalize so we can potentially skip allocating + bool normalized = true; + + for (int i = 0; i < path.Length; i++) + { + if (IsDirectorySeparator(path[i]) + && (i + 1 < path.Length && IsDirectorySeparator(path[i + 1]))) + { + normalized = false; + break; + } + } + + if (normalized) return path; + + StringBuilder builder = new StringBuilder(path.Length); + + for (int i = 0; i < path.Length; i++) + { + char current = path[i]; + + // Skip if we have another separator following + if (IsDirectorySeparator(current) + && (i + 1 < path.Length && IsDirectorySeparator(path[i + 1]))) + continue; + + builder.Append(current); + } + + return builder.ToString(); + } + + /// + /// Returns true if the character is a directory or volume separator. + /// + /// The character to test. + internal static bool IsDirectoryOrVolumeSeparator(char ch) + { + // The directory separator, volume separator, and the alternate directory + // separator should be the same on Unix, so we only need to check one. + Debug.Assert(Path.DirectorySeparatorChar == Path.AltDirectorySeparatorChar); + Debug.Assert(Path.DirectorySeparatorChar == Path.VolumeSeparatorChar); + return ch == Path.DirectorySeparatorChar; + } + + internal static bool HasInvalidVolumeSeparator(string path) + { + // This is only ever true for Windows + return false; + } + + internal static bool IsPartiallyQualified(string path) + { + // This is much simpler than Windows where paths can be rooted, but not fully qualified (such as Drive Relative) + // As long as the path is rooted in Unix it doesn't use the current directory and therefore is fully qualified. + return !Path.IsPathRooted(path); + } + } +} diff --git a/src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs b/src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs new file mode 100644 index 0000000000..fec2218844 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace System.IO +{ + /// Contains internal path helpers that are shared between many projects. + internal static partial class PathInternal + { + /// + /// Returns true if the path uses the extended syntax (\\?\) + /// + internal static bool IsExtended(StringBuffer path) + { + // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths. + // Skipping of normalization will *only* occur if back slashes ('\') are used. + return path.Length >= DevicePrefixLength + && path[0] == '\\' + && (path[1] == '\\' || path[1] == '?') + && path[2] == '?' + && path[3] == '\\'; + } + + /// + /// Gets the length of the root of the path (drive, share, etc.). + /// + internal unsafe static uint GetRootLength(StringBuffer path) + { + if (path.Length == 0) return 0; + return GetRootLength(path.CharPointer, path.Length); + } + + /// + /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\") + /// + internal static bool IsDevice(StringBuffer path) + { + // If the path begins with any two separators is will be recognized and normalized and prepped with + // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not. + return IsExtended(path) + || + ( + path.Length >= DevicePrefixLength + && IsDirectorySeparator(path[0]) + && IsDirectorySeparator(path[1]) + && (path[2] == '.' || path[2] == '?') + && IsDirectorySeparator(path[3]) + ); + } + + /// + /// Returns true if the path specified is relative to the current drive or working directory. + /// Returns false if the path is fixed to a specific drive or UNC path. This method does no + /// validation of the path (URIs will be returned as relative as a result). + /// + /// + /// Handles paths that use the alternate directory separator. It is a frequent mistake to + /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case. + /// "C:a" is drive relative- meaning that it will be resolved against the current directory + /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory + /// will not be used to modify the path). + /// + internal static bool IsPartiallyQualified(StringBuffer path) + { + if (path.Length < 2) + { + // It isn't fixed, it must be relative. There is no way to specify a fixed + // path with one character (or less). + return true; + } + + if (IsDirectorySeparator(path[0])) + { + // There is no valid way to specify a relative path with two initial slashes or + // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\ + return !(path[1] == '?' || IsDirectorySeparator(path[1])); + } + + // The only way to specify a fixed path that doesn't begin with two slashes + // is the drive, colon, slash format- i.e. C:\ + return !((path.Length >= 3) + && (path[1] == Path.VolumeSeparatorChar) + && IsDirectorySeparator(path[2])); + } + } +} diff --git a/src/mscorlib/corefx/System/IO/PathInternal.Windows.cs b/src/mscorlib/corefx/System/IO/PathInternal.Windows.cs new file mode 100644 index 0000000000..bd7f1eae41 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/PathInternal.Windows.cs @@ -0,0 +1,482 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Text; + +namespace System.IO +{ + /// Contains internal path helpers that are shared between many projects. + internal static partial class PathInternal + { + // All paths in Win32 ultimately end up becoming a path to a File object in the Windows object manager. Passed in paths get mapped through + // DosDevice symbolic links in the object tree to actual File objects under \Devices. To illustrate, this is what happens with a typical + // path "Foo" passed as a filename to any Win32 API: + // + // 1. "Foo" is recognized as a relative path and is appended to the current directory (say, "C:\" in our example) + // 2. "C:\Foo" is prepended with the DosDevice namespace "\??\" + // 3. CreateFile tries to create an object handle to the requested file "\??\C:\Foo" + // 4. The Object Manager recognizes the DosDevices prefix and looks + // a. First in the current session DosDevices ("\Sessions\1\DosDevices\" for example, mapped network drives go here) + // b. If not found in the session, it looks in the Global DosDevices ("\GLOBAL??\") + // 5. "C:" is found in DosDevices (in our case "\GLOBAL??\C:", which is a symbolic link to "\Device\HarddiskVolume6") + // 6. The full path is now "\Device\HarddiskVolume6\Foo", "\Device\HarddiskVolume6" is a File object and parsing is handed off + // to the registered parsing method for Files + // 7. The registered open method for File objects is invoked to create the file handle which is then returned + // + // There are multiple ways to directly specify a DosDevices path. The final format of "\??\" is one way. It can also be specified + // as "\\.\" (the most commonly documented way) and "\\?\". If the question mark syntax is used the path will skip normalization + // (essentially GetFullPathName()) and path length checks. + + // Windows Kernel-Mode Object Manager + // https://msdn.microsoft.com/en-us/library/windows/hardware/ff565763.aspx + // https://channel9.msdn.com/Shows/Going+Deep/Windows-NT-Object-Manager + // + // Introduction to MS-DOS Device Names + // https://msdn.microsoft.com/en-us/library/windows/hardware/ff548088.aspx + // + // Local and Global MS-DOS Device Names + // https://msdn.microsoft.com/en-us/library/windows/hardware/ff554302.aspx + + internal const string ExtendedPathPrefix = @"\\?\"; + internal const string UncPathPrefix = @"\\"; + internal const string UncExtendedPrefixToInsert = @"?\UNC\"; + internal const string UncExtendedPathPrefix = @"\\?\UNC\"; + internal const string DevicePathPrefix = @"\\.\"; + internal const string ParentDirectoryPrefix = @"..\"; + + internal const int MaxShortPath = 260; + internal const int MaxShortDirectoryPath = 248; + internal const int MaxLongPath = short.MaxValue; + // \\?\, \\.\, \??\ + internal const int DevicePrefixLength = 4; + // \\ + internal const int UncPrefixLength = 2; + // \\?\UNC\, \\.\UNC\ + internal const int UncExtendedPrefixLength = 8; + internal const int MaxComponentLength = 255; + + internal static char[] GetInvalidPathChars() => new char[] + { + '|', '\0', + (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, + (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, + (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, + (char)31 + }; + + // [MS - FSA] 2.1.4.4 Algorithm for Determining if a FileName Is in an Expression + // https://msdn.microsoft.com/en-us/library/ff469270.aspx + private static readonly char[] s_wildcardChars = + { + '\"', '<', '>', '*', '?' + }; + + /// + /// Returns true if the given character is a valid drive letter + /// + internal static bool IsValidDriveChar(char value) + { + return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z')); + } + + /// + /// Returns true if the path is too long + /// + internal static bool IsPathTooLong(string fullPath) + { + // We'll never know precisely what will fail as paths get changed internally in Windows and + // may grow to exceed MaxLongPath. + return fullPath.Length >= MaxLongPath; + } + + /// + /// Returns true if the directory is too long + /// + internal static bool IsDirectoryTooLong(string fullPath) + { + return IsPathTooLong(fullPath); + } + + /// + /// Adds the extended path prefix (\\?\) if not already a device path, IF the path is not relative, + /// AND the path is more than 259 characters. (> MAX_PATH + null) + /// + internal static string EnsureExtendedPrefixOverMaxPath(string path) + { + if (path != null && path.Length >= MaxShortPath) + { + return EnsureExtendedPrefix(path); + } + else + { + return path; + } + } + + /// + /// Adds the extended path prefix (\\?\) if not relative or already a device path. + /// + internal static string EnsureExtendedPrefix(string path) + { + // Putting the extended prefix on the path changes the processing of the path. It won't get normalized, which + // means adding to relative paths will prevent them from getting the appropriate current directory inserted. + + // If it already has some variant of a device path (\??\, \\?\, \\.\, //./, etc.) we don't need to change it + // as it is either correct or we will be changing the behavior. When/if Windows supports long paths implicitly + // in the future we wouldn't want normalization to come back and break existing code. + + // In any case, all internal usages should be hitting normalize path (Path.GetFullPath) before they hit this + // shimming method. (Or making a change that doesn't impact normalization, such as adding a filename to a + // normalized base path.) + if (IsPartiallyQualified(path) || IsDevice(path)) + return path; + + // Given \\server\share in longpath becomes \\?\UNC\server\share + if (path.StartsWith(UncPathPrefix, StringComparison.OrdinalIgnoreCase)) + return path.Insert(2, UncExtendedPrefixToInsert); + + return ExtendedPathPrefix + path; + } + + /// + /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\") + /// + internal static bool IsDevice(string path) + { + // If the path begins with any two separators is will be recognized and normalized and prepped with + // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not. + return IsExtended(path) + || + ( + path.Length >= DevicePrefixLength + && IsDirectorySeparator(path[0]) + && IsDirectorySeparator(path[1]) + && (path[2] == '.' || path[2] == '?') + && IsDirectorySeparator(path[3]) + ); + } + + /// + /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the + /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization + /// and path length checks. + /// + internal static bool IsExtended(string path) + { + // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths. + // Skipping of normalization will *only* occur if back slashes ('\') are used. + return path.Length >= DevicePrefixLength + && path[0] == '\\' + && (path[1] == '\\' || path[1] == '?') + && path[2] == '?' + && path[3] == '\\'; + } + + /// + /// Returns a value indicating if the given path contains invalid characters (", <, >, | + /// NUL, or any ASCII char whose integer representation is in the range of 1 through 31). + /// Does not check for wild card characters ? and *. + /// + internal static bool HasIllegalCharacters(string path) + { + // This is equivalent to IndexOfAny(InvalidPathChars) >= 0, + // except faster since IndexOfAny grows slower as the input + // array grows larger. + // Since we know that some of the characters we're looking + // for are contiguous in the alphabet-- the path cannot contain + // characters 0-31-- we can optimize this for our specific use + // case and use simple comparison operations. + + for (int i = 0; i < path.Length; i++) + { + char c = path[i]; + + if (c <= '\u001f' || c == '|') + { + return true; + } + } + + return false; + } + + /// + /// Check for known wildcard characters. '*' and '?' are the most common ones. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal unsafe static bool HasWildCardCharacters(string path) + { + // Question mark is part of dos device syntax so we have to skip if we are + int startIndex = IsDevice(path) ? ExtendedPathPrefix.Length : 0; + + return path.IndexOfAny(s_wildcardChars, startIndex) >= 0; + } + + /// + /// Gets the length of the root of the path (drive, share, etc.). + /// + internal unsafe static int GetRootLength(string path) + { + fixed(char* value = path) + { + return (int)GetRootLength(value, (uint)path.Length); + } + } + + private unsafe static uint GetRootLength(char* path, uint pathLength) + { + uint i = 0; + uint volumeSeparatorLength = 2; // Length to the colon "C:" + uint uncRootLength = 2; // Length to the start of the server name "\\" + + bool extendedSyntax = StartsWithOrdinal(path, pathLength, ExtendedPathPrefix); + bool extendedUncSyntax = StartsWithOrdinal(path, pathLength, UncExtendedPathPrefix); + if (extendedSyntax) + { + // Shift the position we look for the root from to account for the extended prefix + if (extendedUncSyntax) + { + // "\\" -> "\\?\UNC\" + uncRootLength = (uint)UncExtendedPathPrefix.Length; + } + else + { + // "C:" -> "\\?\C:" + volumeSeparatorLength += (uint)ExtendedPathPrefix.Length; + } + } + + if ((!extendedSyntax || extendedUncSyntax) && pathLength > 0 && IsDirectorySeparator(path[0])) + { + // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo") + + i = 1; // Drive rooted (\foo) is one character + if (extendedUncSyntax || (pathLength > 1 && IsDirectorySeparator(path[1]))) + { + // UNC (\\?\UNC\ or \\), scan past the next two directory separators at most + // (e.g. to \\?\UNC\Server\Share or \\Server\Share\) + i = uncRootLength; + int n = 2; // Maximum separators to skip + while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0)) i++; + } + } + else if (pathLength >= volumeSeparatorLength && path[volumeSeparatorLength - 1] == Path.VolumeSeparatorChar) + { + // Path is at least longer than where we expect a colon, and has a colon (\\?\A:, A:) + // If the colon is followed by a directory separator, move past it + i = volumeSeparatorLength; + if (pathLength >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength])) i++; + } + return i; + } + + private unsafe static bool StartsWithOrdinal(char* source, uint sourceLength, string value) + { + if (sourceLength < (uint)value.Length) return false; + for (int i = 0; i < value.Length; i++) + { + if (value[i] != source[i]) return false; + } + return true; + } + + /// + /// Returns true if the path specified is relative to the current drive or working directory. + /// Returns false if the path is fixed to a specific drive or UNC path. This method does no + /// validation of the path (URIs will be returned as relative as a result). + /// + /// + /// Handles paths that use the alternate directory separator. It is a frequent mistake to + /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case. + /// "C:a" is drive relative- meaning that it will be resolved against the current directory + /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory + /// will not be used to modify the path). + /// + internal static bool IsPartiallyQualified(string path) + { + if (path.Length < 2) + { + // It isn't fixed, it must be relative. There is no way to specify a fixed + // path with one character (or less). + return true; + } + + if (IsDirectorySeparator(path[0])) + { + // There is no valid way to specify a relative path with two initial slashes or + // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\ + return !(path[1] == '?' || IsDirectorySeparator(path[1])); + } + + // The only way to specify a fixed path that doesn't begin with two slashes + // is the drive, colon, slash format- i.e. C:\ + return !((path.Length >= 3) + && (path[1] == Path.VolumeSeparatorChar) + && IsDirectorySeparator(path[2]) + // To match old behavior we'll check the drive character for validity as the path is technically + // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream. + && IsValidDriveChar(path[0])); + } + + /// + /// Returns the characters to skip at the start of the path if it starts with space(s) and a drive or directory separator. + /// (examples are " C:", " \") + /// This is a legacy behavior of Path.GetFullPath(). + /// + /// + /// Note that this conflicts with IsPathRooted() which doesn't (and never did) such a skip. + /// + internal static int PathStartSkip(string path) + { + int startIndex = 0; + while (startIndex < path.Length && path[startIndex] == ' ') startIndex++; + + if (startIndex > 0 && (startIndex < path.Length && IsDirectorySeparator(path[startIndex])) + || (startIndex + 1 < path.Length && path[startIndex + 1] == ':' && IsValidDriveChar(path[startIndex]))) + { + // Go ahead and skip spaces as we're either " C:" or " \" + return startIndex; + } + + return 0; + } + + /// + /// True if the given character is a directory separator. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsDirectorySeparator(char c) + { + return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar; + } + + /// + /// Normalize separators in the given path. Converts forward slashes into back slashes and compresses slash runs, keeping initial 2 if present. + /// Also trims initial whitespace in front of "rooted" paths (see PathStartSkip). + /// + /// This effectively replicates the behavior of the legacy NormalizePath when it was called with fullCheck=false and expandShortpaths=false. + /// The current NormalizePath gets directory separator normalization from Win32's GetFullPathName(), which will resolve relative paths and as + /// such can't be used here (and is overkill for our uses). + /// + /// Like the current NormalizePath this will not try and analyze periods/spaces within directory segments. + /// + /// + /// The only callers that used to use Path.Normalize(fullCheck=false) were Path.GetDirectoryName() and Path.GetPathRoot(). Both usages do + /// not need trimming of trailing whitespace here. + /// + /// GetPathRoot() could technically skip normalizing separators after the second segment- consider as a future optimization. + /// + /// For legacy desktop behavior with ExpandShortPaths: + /// - It has no impact on GetPathRoot() so doesn't need consideration. + /// - It could impact GetDirectoryName(), but only if the path isn't relative (C:\ or \\Server\Share). + /// + /// In the case of GetDirectoryName() the ExpandShortPaths behavior was undocumented and provided inconsistent results if the path was + /// fixed/relative. For example: "C:\PROGRA~1\A.TXT" would return "C:\Program Files" while ".\PROGRA~1\A.TXT" would return ".\PROGRA~1". If you + /// ultimately call GetFullPath() this doesn't matter, but if you don't or have any intermediate string handling could easily be tripped up by + /// this undocumented behavior. + /// + /// We won't match this old behavior because: + /// + /// 1. It was undocumented + /// 2. It was costly (extremely so if it actually contained '~') + /// 3. Doesn't play nice with string logic + /// 4. Isn't a cross-plat friendly concept/behavior + /// + internal static string NormalizeDirectorySeparators(string path) + { + if (string.IsNullOrEmpty(path)) return path; + + char current; + int start = PathStartSkip(path); + + if (start == 0) + { + // Make a pass to see if we need to normalize so we can potentially skip allocating + bool normalized = true; + + for (int i = 0; i < path.Length; i++) + { + current = path[i]; + if (IsDirectorySeparator(current) + && (current != Path.DirectorySeparatorChar + // Check for sequential separators past the first position (we need to keep initial two for UNC/extended) + || (i > 0 && i + 1 < path.Length && IsDirectorySeparator(path[i + 1])))) + { + normalized = false; + break; + } + } + + if (normalized) return path; + } + + StringBuilder builder = new StringBuilder(path.Length); + + if (IsDirectorySeparator(path[start])) + { + start++; + builder.Append(Path.DirectorySeparatorChar); + } + + for (int i = start; i < path.Length; i++) + { + current = path[i]; + + // If we have a separator + if (IsDirectorySeparator(current)) + { + // If the next is a separator, skip adding this + if (i + 1 < path.Length && IsDirectorySeparator(path[i + 1])) + { + continue; + } + + // Ensure it is the primary separator + current = Path.DirectorySeparatorChar; + } + + builder.Append(current); + } + + return builder.ToString(); + } + + /// + /// Returns true if the character is a directory or volume separator. + /// + /// The character to test. + internal static bool IsDirectoryOrVolumeSeparator(char ch) + { + return IsDirectorySeparator(ch) || Path.VolumeSeparatorChar == ch; + } + + /// + /// Validates volume separator only occurs as C: or \\?\C:. This logic is meant to filter out Alternate Data Streams. + /// + /// True if the path has an invalid volume separator. + internal static bool HasInvalidVolumeSeparator(string path) + { + // Toss out paths with colons that aren't a valid drive specifier. + // Cannot start with a colon and can only be of the form "C:" or "\\?\C:". + // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.) + + // We don't care about skipping starting space for extended paths. Assume no knowledge of extended paths if we're forcing old path behavior. + int startIndex = IsExtended(path) ? ExtendedPathPrefix.Length : PathStartSkip(path); + + // If we start with a colon + if ((path.Length > startIndex && path[startIndex] == Path.VolumeSeparatorChar) + // Or have an invalid drive letter and colon + || (path.Length >= startIndex + 2 && path[startIndex + 1] == Path.VolumeSeparatorChar && !IsValidDriveChar(path[startIndex])) + // Or have any colons beyond the drive colon + || (path.Length > startIndex + 2 && path.IndexOf(Path.VolumeSeparatorChar, startIndex + 2) != -1)) + { + return true; + } + + return false; + } + } +} diff --git a/src/mscorlib/corefx/System/IO/PathInternal.cs b/src/mscorlib/corefx/System/IO/PathInternal.cs new file mode 100644 index 0000000000..ee67680df5 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/PathInternal.cs @@ -0,0 +1,230 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Text; + +namespace System.IO +{ + /// Contains internal path helpers that are shared between many projects. + internal static partial class PathInternal + { + // Trim trailing white spaces, tabs etc but don't be aggressive in removing everything that has UnicodeCategory of trailing space. + // string.WhitespaceChars will trim more aggressively than what the underlying FS does (for ex, NTFS, FAT). + // + // (This is for compatibility with old behavior.) + internal static readonly char[] s_trimEndChars = + { + (char)0x9, // Horizontal tab + (char)0xA, // Line feed + (char)0xB, // Vertical tab + (char)0xC, // Form feed + (char)0xD, // Carriage return + (char)0x20, // Space + (char)0x85, // Next line + (char)0xA0 // Non breaking space + }; + + /// + /// Checks for invalid path characters in the given path. + /// + /// Thrown if the path is null. + /// Thrown if the path has invalid characters. + /// The path to check for invalid characters. + internal static void CheckInvalidPathChars(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + + if (HasIllegalCharacters(path)) + throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); + } + + + /// + /// Returns true if the given StringBuilder starts with the given value. + /// + /// The string to compare against the start of the StringBuilder. + internal static bool StartsWithOrdinal(this StringBuilder builder, string value) + { + if (value == null || builder.Length < value.Length) + return false; + + for (int i = 0; i < value.Length; i++) + { + if (builder[i] != value[i]) return false; + } + return true; + } + + /// + /// Returns true if the given string starts with the given value. + /// + /// The string to compare against the start of the source string. + internal static bool StartsWithOrdinal(this string source, string value) + { + if (value == null || source.Length < value.Length) + return false; + + return source.StartsWith(value, StringComparison.Ordinal); + } + + /// + /// Trims the specified characters from the end of the StringBuilder. + /// + internal static StringBuilder TrimEnd(this StringBuilder builder, params char[] trimChars) + { + if (trimChars == null || trimChars.Length == 0) + return builder; + + int end = builder.Length - 1; + + for (; end >= 0; end--) + { + int i = 0; + char ch = builder[end]; + for (; i < trimChars.Length; i++) + { + if (trimChars[i] == ch) break; + } + if (i == trimChars.Length) + { + // Not a trim char + break; + } + } + + builder.Length = end + 1; + return builder; + } + + /// + /// Returns the start index of the filename + /// in the given path, or 0 if no directory + /// or volume separator is found. + /// + /// The path in which to find the index of the filename. + /// + /// This method returns path.Length for + /// inputs like "/usr/foo/" on Unix. As such, + /// it is not safe for being used to index + /// the string without additional verification. + /// + internal static int FindFileNameIndex(string path) + { + Debug.Assert(path != null); + CheckInvalidPathChars(path); + + for (int i = path.Length - 1; i >= 0; i--) + { + char ch = path[i]; + if (IsDirectoryOrVolumeSeparator(ch)) + return i + 1; + } + + return 0; // the whole path is the filename + } + + /// + /// Returns true if the path ends in a directory separator. + /// + internal static bool EndsInDirectorySeparator(string path) => + !string.IsNullOrEmpty(path) && IsDirectorySeparator(path[path.Length - 1]); + + /// + /// Get the common path length from the start of the string. + /// + internal static int GetCommonPathLength(string first, string second, bool ignoreCase) + { + int commonChars = EqualStartingCharacterCount(first, second, ignoreCase: ignoreCase); + + // If nothing matches + if (commonChars == 0) + return commonChars; + + // Or we're a full string and equal length or match to a separator + if (commonChars == first.Length + && (commonChars == second.Length || IsDirectorySeparator(second[commonChars]))) + return commonChars; + + if (commonChars == second.Length && IsDirectorySeparator(first[commonChars])) + return commonChars; + + // It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar. + while (commonChars > 0 && !IsDirectorySeparator(first[commonChars - 1])) + commonChars--; + + return commonChars; + } + + /// + /// Gets the count of common characters from the left optionally ignoring case + /// + unsafe internal static int EqualStartingCharacterCount(string first, string second, bool ignoreCase) + { + if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) return 0; + + int commonChars = 0; + + fixed (char* f = first) + fixed (char* s = second) + { + char* l = f; + char* r = s; + char* leftEnd = l + first.Length; + char* rightEnd = r + second.Length; + + while (l != leftEnd && r != rightEnd + && (*l == *r || (ignoreCase && char.ToUpperInvariant((*l)) == char.ToUpperInvariant((*r))))) + { + commonChars++; + l++; + r++; + } + } + + return commonChars; + } + + /// + /// Returns true if the two paths have the same root + /// + internal static bool AreRootsEqual(string first, string second, StringComparison comparisonType) + { + int firstRootLength = GetRootLength(first); + int secondRootLength = GetRootLength(second); + + return firstRootLength == secondRootLength + && string.Compare( + strA: first, + indexA: 0, + strB: second, + indexB: 0, + length: firstRootLength, + comparisonType: comparisonType) == 0; + } + + /// + /// Returns false for ".." unless it is specified as a part of a valid File/Directory name. + /// (Used to avoid moving up directories.) + /// + /// Valid: a..b abc..d + /// Invalid: ..ab ab.. .. abc..d\abc.. + /// + internal static void CheckSearchPattern(string searchPattern) + { + int index; + while ((index = searchPattern.IndexOf("..", StringComparison.Ordinal)) != -1) + { + // Terminal ".." . Files names cannot end in ".." + if (index + 2 == searchPattern.Length + || IsDirectorySeparator(searchPattern[index + 2])) + throw new ArgumentException(SR.Arg_InvalidSearchPattern); + + searchPattern = searchPattern.Substring(index + 2); + } + + } + } +} diff --git a/src/mscorlib/corefx/System/IO/Win32Marshal.cs b/src/mscorlib/corefx/System/IO/Win32Marshal.cs new file mode 100644 index 0000000000..b4dfa04468 --- /dev/null +++ b/src/mscorlib/corefx/System/IO/Win32Marshal.cs @@ -0,0 +1,134 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace System.IO +{ + /// + /// Provides static methods for converting from Win32 errors codes to exceptions, HRESULTS and error messages. + /// + internal static class Win32Marshal + { + /// + /// Converts, resetting it, the last Win32 error into a corresponding object. + /// + internal static Exception GetExceptionForLastWin32Error() + { + int errorCode = Marshal.GetLastWin32Error(); + return GetExceptionForWin32Error(errorCode, string.Empty); + } + + /// + /// Converts, resetting it, the last Win32 error into a corresponding object, optionally + /// including the specified path in the error message. + /// + internal static Exception GetExceptionForLastWin32Error(string path) + { + int errorCode = Marshal.GetLastWin32Error(); + return GetExceptionForWin32Error(errorCode, path); + } + + /// + /// Converts the specified Win32 error into a corresponding object. + /// + internal static Exception GetExceptionForWin32Error(int errorCode) + { + return GetExceptionForWin32Error(errorCode, string.Empty); + } + + /// + /// Converts the specified Win32 error into a corresponding object, optionally + /// including the specified path in the error message. + /// + internal static Exception GetExceptionForWin32Error(int errorCode, string path) + { + switch (errorCode) + { + case Interop.mincore.Errors.ERROR_FILE_NOT_FOUND: + if (path.Length == 0) + return new FileNotFoundException(SR.IO_FileNotFound); + else + return new FileNotFoundException(SR.Format(SR.IO_FileNotFound_FileName, path), path); + + case Interop.mincore.Errors.ERROR_PATH_NOT_FOUND: + if (path.Length == 0) + return new DirectoryNotFoundException(SR.IO_PathNotFound_NoPathName); + else + return new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, path)); + + case Interop.mincore.Errors.ERROR_ACCESS_DENIED: + if (path.Length == 0) + return new UnauthorizedAccessException(SR.UnauthorizedAccess_IODenied_NoPathName); + else + return new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, path)); + + case Interop.mincore.Errors.ERROR_ALREADY_EXISTS: + if (path.Length == 0) + goto default; + + return new IOException(SR.Format(SR.IO_AlreadyExists_Name, path), MakeHRFromErrorCode(errorCode)); + + case Interop.mincore.Errors.ERROR_FILENAME_EXCED_RANGE: + return new PathTooLongException(SR.IO_PathTooLong); + + case Interop.mincore.Errors.ERROR_INVALID_PARAMETER: + return new IOException(GetMessage(errorCode), MakeHRFromErrorCode(errorCode)); + + case Interop.mincore.Errors.ERROR_SHARING_VIOLATION: + if (path.Length == 0) + return new IOException(SR.IO_SharingViolation_NoFileName, MakeHRFromErrorCode(errorCode)); + else + return new IOException(SR.Format(SR.IO_SharingViolation_File, path), MakeHRFromErrorCode(errorCode)); + + case Interop.mincore.Errors.ERROR_FILE_EXISTS: + if (path.Length == 0) + goto default; + + return new IOException(SR.Format(SR.IO_FileExists_Name, path), MakeHRFromErrorCode(errorCode)); + + case Interop.mincore.Errors.ERROR_OPERATION_ABORTED: + return new OperationCanceledException(); + + default: + return new IOException(GetMessage(errorCode), MakeHRFromErrorCode(errorCode)); + } + } + + /// + /// Returns a HRESULT for the specified Win32 error code. + /// + internal static int MakeHRFromErrorCode(int errorCode) + { + Debug.Assert((0xFFFF0000 & errorCode) == 0, "This is an HRESULT, not an error code!"); + + return unchecked(((int)0x80070000) | errorCode); + } + + /// + /// Returns a Win32 error code for the specified HRESULT if it came from FACILITY_WIN32 + /// If not, returns the HRESULT unchanged + /// + internal static int TryMakeWin32ErrorCodeFromHR(int hr) + { + if ((0xFFFF0000 & hr) == 0x80070000) + { + // Win32 error, Win32Marshal.GetExceptionForWin32Error expects the Win32 format + hr &= 0x0000FFFF; + } + + return hr; + } + + /// + /// Returns a string message for the specified Win32 error code. + /// + internal static string GetMessage(int errorCode) + { + return Interop.mincore.GetMessage(errorCode); + } + } +} diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs new file mode 100644 index 0000000000..875009aee2 --- /dev/null +++ b/src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices +{ + /// + /// Wrapper for access to the native heap. Dispose to free the memory. Try to use with using statements. + /// Does not allocate zero size buffers, and will free the existing native buffer if capacity is dropped to zero. + /// + /// NativeBuffer utilizes a cache of heap buffers. + /// + /// + /// Suggested use through P/Invoke: define DllImport arguments that take a byte buffer as SafeHandle. + /// + /// Using SafeHandle will ensure that the buffer will not get collected during a P/Invoke. + /// (Notably AddRef and ReleaseRef will be called by the interop layer.) + /// + /// This class is not threadsafe, changing the capacity or disposing on multiple threads risks duplicate heap + /// handles or worse. + /// + internal class NativeBuffer : IDisposable + { + private readonly static SafeHeapHandleCache s_handleCache = new SafeHeapHandleCache(); + private readonly static SafeHandle s_emptyHandle = new EmptySafeHandle(); + private SafeHeapHandle _handle; + private ulong _capacity; + + /// + /// Create a buffer with at least the specified initial capacity in bytes. + /// + public NativeBuffer(ulong initialMinCapacity = 0) + { + EnsureByteCapacity(initialMinCapacity); + } + + protected unsafe void* VoidPointer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return _handle == null ? null : _handle.DangerousGetHandle().ToPointer(); + } + } + + protected unsafe byte* BytePointer + { + get + { + return (byte*)VoidPointer; + } + } + + /// + /// Get the handle for the buffer. + /// + public SafeHandle GetHandle() + { + // Marshalling code will throw on null for SafeHandle + return _handle ?? s_emptyHandle; + } + + /// + /// The capacity of the buffer in bytes. + /// + public ulong ByteCapacity + { + get { return _capacity; } + } + + /// + /// Ensure capacity in bytes is at least the given minimum. + /// + /// Thrown if unable to allocate memory when setting. + /// Thrown if attempting to set to a value that is larger than the maximum addressable memory. + public void EnsureByteCapacity(ulong minCapacity) + { + if (_capacity < minCapacity) + { + Resize(minCapacity); + _capacity = minCapacity; + } + } + + public unsafe byte this[ulong index] + { + get + { + if (index >= _capacity) throw new ArgumentOutOfRangeException(); + return BytePointer[index]; + } + set + { + if (index >= _capacity) throw new ArgumentOutOfRangeException(); + BytePointer[index] = value; + } + } + + private unsafe void Resize(ulong byteLength) + { + if (byteLength == 0) + { + ReleaseHandle(); + return; + } + + if (_handle == null) + { + _handle = s_handleCache.Acquire(byteLength); + } + else + { + _handle.Resize(byteLength); + } + } + + private void ReleaseHandle() + { + if (_handle != null) + { + s_handleCache.Release(_handle); + _capacity = 0; + _handle = null; + } + } + + /// + /// Release the backing buffer + /// + public virtual void Free() + { + ReleaseHandle(); + } + + public void Dispose() + { + Free(); + } + + private sealed class EmptySafeHandle : SafeHandle + { + public EmptySafeHandle() : base(IntPtr.Zero, true) { } + + public override bool IsInvalid + { + get { return true; } + } + + protected override bool ReleaseHandle() + { + return true; + } + } + } +} diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs new file mode 100644 index 0000000000..92b3d980db --- /dev/null +++ b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + /// + /// Handle for heap memory that allows tracking of capacity and reallocating. + /// + internal sealed class SafeHeapHandle : SafeBuffer + { + /// + /// Allocate a buffer of the given size if requested. + /// + /// Required size in bytes. Must be less than UInt32.MaxValue for 32 bit or UInt64.MaxValue for 64 bit. + /// Thrown if the requested memory size cannot be allocated. + /// Thrown if size is greater than the maximum memory size. + public SafeHeapHandle(ulong byteLength) : base(ownsHandle: true) + { + Resize(byteLength); + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + /// + /// Resize the buffer to the given size if requested. + /// + /// Required size in bytes. Must be less than UInt32.MaxValue for 32 bit or UInt64.MaxValue for 64 bit. + /// Thrown if the requested memory size cannot be allocated. + /// Thrown if size is greater than the maximum memory size. + public void Resize(ulong byteLength) + { + if (IsClosed) throw new ObjectDisposedException(nameof(SafeHeapHandle)); + + ulong originalLength = 0; + if (handle == IntPtr.Zero) + { + handle = Marshal.AllocHGlobal((IntPtr)byteLength); + } + else + { + originalLength = ByteLength; + + // This may or may not be the same handle, may realloc in place. If the + // handle changes Windows will deal with the old handle, trying to free it will + // cause an error. + handle = Marshal.ReAllocHGlobal(pv: handle, cb: (IntPtr)byteLength); + } + + if (handle == IntPtr.Zero) + { + // Only real plausible answer + throw new OutOfMemoryException(); + } + + if (byteLength > originalLength) + { + // Add pressure + ulong addedBytes = byteLength - originalLength; + if (addedBytes > long.MaxValue) + { + GC.AddMemoryPressure(long.MaxValue); + GC.AddMemoryPressure((long)(addedBytes - long.MaxValue)); + } + else + { + GC.AddMemoryPressure((long)addedBytes); + } + } + else + { + // Shrank or did nothing, release pressure if needed + RemoveMemoryPressure(originalLength - byteLength); + } + + Initialize(byteLength); + } + + private void RemoveMemoryPressure(ulong removedBytes) + { + if (removedBytes == 0) return; + + if (removedBytes > long.MaxValue) + { + GC.RemoveMemoryPressure(long.MaxValue); + GC.RemoveMemoryPressure((long)(removedBytes - long.MaxValue)); + } + else + { + GC.RemoveMemoryPressure((long)removedBytes); + } + } + + protected override bool ReleaseHandle() + { + if (handle != IntPtr.Zero) + { + RemoveMemoryPressure(ByteLength); + Marshal.FreeHGlobal(handle); + } + + handle = IntPtr.Zero; + return true; + } + } +} diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs new file mode 100644 index 0000000000..725076ed66 --- /dev/null +++ b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Runtime.InteropServices +{ + /// + /// Allows limited thread safe reuse of heap buffers to limit memory pressure. + /// + /// This cache does not ensure that multiple copies of handles are not released back into the cache. + /// + internal sealed class SafeHeapHandleCache : IDisposable + { + private readonly ulong _minSize; + private readonly ulong _maxSize; + + // internal for testing + internal readonly SafeHeapHandle[] _handleCache; + + /// Smallest buffer size to allocate in bytes. + /// The largest buffer size to cache in bytes. + /// The maximum number of handles to cache. + public SafeHeapHandleCache(ulong minSize = 64, ulong maxSize = 1024 * 2, int maxHandles = 0) + { + _minSize = minSize; + _maxSize = maxSize; + _handleCache = new SafeHeapHandle[maxHandles > 0 ? maxHandles : Environment.ProcessorCount * 4]; + } + + /// + /// Get a HeapHandle + /// + public SafeHeapHandle Acquire(ulong minSize = 0) + { + if (minSize < _minSize) minSize = _minSize; + + SafeHeapHandle handle = null; + + for (int i = 0; i < _handleCache.Length; i++) + { + handle = Interlocked.Exchange(ref _handleCache[i], null); + if (handle != null) break; + } + + if (handle != null) + { + // One possible future consideration is to attempt cycling through to + // find one that might already have sufficient capacity + if (handle.ByteLength < minSize) + handle.Resize(minSize); + } + else + { + handle = new SafeHeapHandle(minSize); + } + + return handle; + } + + /// + /// Give a HeapHandle back for potential reuse + /// + public void Release(SafeHeapHandle handle) + { + if (handle.ByteLength <= _maxSize) + { + for (int i = 0; i < _handleCache.Length; i++) + { + // Push the handles down, walking the last one off the end to keep + // the top of the "stack" fresh + handle = Interlocked.Exchange(ref _handleCache[i], handle); + if (handle == null) return; + } + } + + handle.Dispose(); + } + + public void Dispose() + { + Dispose(disposing: true); + } + + private void Dispose(bool disposing) + { + if (disposing && _handleCache != null) + { + foreach (SafeHeapHandle handle in _handleCache) + { + if (handle != null) handle.Dispose(); + } + } + } + } +} diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs new file mode 100644 index 0000000000..29cef08b6c --- /dev/null +++ b/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs @@ -0,0 +1,354 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + /// + /// Native buffer that deals in char size increments. Dispose to free memory. Allows buffers larger + /// than a maximum size string to enable working with very large string arrays. Always makes ordinal + /// comparisons. + /// + /// A more performant replacement for StringBuilder when performing native interop. + /// + /// + /// Suggested use through P/Invoke: define DllImport arguments that take a character buffer as SafeHandle and pass StringBuffer.GetHandle(). + /// + internal class StringBuffer : NativeBuffer + { + private uint _length; + + /// + /// Instantiate the buffer with capacity for at least the specified number of characters. Capacity + /// includes the trailing null character. + /// + public StringBuffer(uint initialCapacity = 0) + : base(initialCapacity * (ulong)sizeof(char)) + { + } + + /// + /// Get/set the character at the given index. + /// + /// Thrown if attempting to index outside of the buffer length. + public unsafe char this[uint index] + { + get + { + if (index >= _length) throw new ArgumentOutOfRangeException(nameof(index)); + return CharPointer[index]; + } + set + { + if (index >= _length) throw new ArgumentOutOfRangeException(nameof(index)); + CharPointer[index] = value; + } + } + + /// + /// Character capacity of the buffer. Includes the count for the trailing null character. + /// + public uint CharCapacity + { + get + { + ulong byteCapacity = ByteCapacity; + ulong charCapacity = byteCapacity == 0 ? 0 : byteCapacity / sizeof(char); + return charCapacity > uint.MaxValue ? uint.MaxValue : (uint)charCapacity; + } + } + + /// + /// Ensure capacity in characters is at least the given minimum. + /// + /// Thrown if unable to allocate memory when setting. + public void EnsureCharCapacity(uint minCapacity) + { + EnsureByteCapacity(minCapacity * (ulong)sizeof(char)); + } + + /// + /// The logical length of the buffer in characters. (Does not include the final null.) Will automatically attempt to increase capacity. + /// This is where the usable data ends. + /// + /// Thrown if unable to allocate memory when setting. + /// Thrown if the set size in bytes is uint.MaxValue (as space is implicitly reserved for the trailing null). + public unsafe uint Length + { + get { return _length; } + set + { + if (value == uint.MaxValue) throw new ArgumentOutOfRangeException(nameof(Length)); + + // Null terminate + EnsureCharCapacity(value + 1); + CharPointer[value] = '\0'; + + _length = value; + } + } + + /// + /// For use when the native api null terminates but doesn't return a length. + /// If no null is found, the length will not be changed. + /// + public unsafe void SetLengthToFirstNull() + { + char* buffer = CharPointer; + uint capacity = CharCapacity; + for (uint i = 0; i < capacity; i++) + { + if (buffer[i] == '\0') + { + _length = i; + break; + } + } + } + + internal unsafe char* CharPointer + { + get + { + return (char*)VoidPointer; + } + } + + /// + /// True if the buffer contains the given character. + /// + public unsafe bool Contains(char value) + { + char* start = CharPointer; + uint length = _length; + + for (uint i = 0; i < length; i++) + { + if (*start++ == value) return true; + } + + return false; + } + + /// + /// Returns true if the buffer starts with the given string. + /// + public bool StartsWith(string value) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + if (_length < (uint)value.Length) return false; + return SubstringEquals(value, startIndex: 0, count: value.Length); + } + + /// + /// Returns true if the specified StringBuffer substring equals the given value. + /// + /// The value to compare against the specified substring. + /// Start index of the sub string. + /// Length of the substring, or -1 to check all remaining. + /// + /// Thrown if or are outside the range + /// of the buffer's length. + /// + public unsafe bool SubstringEquals(string value, uint startIndex = 0, int count = -1) + { + if (value == null) return false; + if (count < -1) throw new ArgumentOutOfRangeException(nameof(count)); + if (startIndex > _length) throw new ArgumentOutOfRangeException(nameof(startIndex)); + + uint realCount = count == -1 ? _length - startIndex : (uint)count; + if (checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException(nameof(count)); + + int length = value.Length; + + // Check the substring length against the input length + if (realCount != (uint)length) return false; + + fixed (char* valueStart = value) + { + char* bufferStart = CharPointer + startIndex; + for (int i = 0; i < length; i++) + { + // Note that indexing in this case generates faster code than trying to copy the pointer and increment it + if (*bufferStart++ != valueStart[i]) return false; + } + } + + return true; + } + + /// + /// Append the given string. + /// + /// The string to append. + /// The index in the input string to start appending from. + /// The count of characters to copy from the input string, or -1 for all remaining. + /// Thrown if is null. + /// + /// Thrown if or are outside the range + /// of characters. + /// + public void Append(string value, int startIndex = 0, int count = -1) + { + CopyFrom( + bufferIndex: _length, + source: value, + sourceIndex: startIndex, + count: count); + } + + /// + /// Append the given buffer. + /// + /// The buffer to append. + /// The index in the input buffer to start appending from. + /// The count of characters to copy from the buffer string. + /// Thrown if is null. + /// + /// Thrown if or are outside the range + /// of characters. + /// + public void Append(StringBuffer value, uint startIndex = 0) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + if (value.Length == 0) return; + + value.CopyTo( + bufferIndex: startIndex, + destination: this, + destinationIndex: _length, + count: value.Length); + } + + /// + /// Append the given buffer. + /// + /// The buffer to append. + /// The index in the input buffer to start appending from. + /// The count of characters to copy from the buffer string. + /// Thrown if is null. + /// + /// Thrown if or are outside the range + /// of characters. + /// + public void Append(StringBuffer value, uint startIndex, uint count) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + if (count == 0) return; + + value.CopyTo( + bufferIndex: startIndex, + destination: this, + destinationIndex: _length, + count: count); + } + + /// + /// Copy contents to the specified buffer. Destination index must be within current destination length. + /// Will grow the destination buffer if needed. + /// + /// + /// Thrown if or or are outside the range + /// of characters. + /// + /// Thrown if is null. + public unsafe void CopyTo(uint bufferIndex, StringBuffer destination, uint destinationIndex, uint count) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (destinationIndex > destination._length) throw new ArgumentOutOfRangeException(nameof(destinationIndex)); + if (bufferIndex >= _length) throw new ArgumentOutOfRangeException(nameof(bufferIndex)); + if (_length < checked(bufferIndex + count)) throw new ArgumentOutOfRangeException(nameof(count)); + + if (count == 0) return; + uint lastIndex = checked(destinationIndex + count); + if (destination._length < lastIndex) destination.Length = lastIndex; + + Buffer.MemoryCopy( + source: CharPointer + bufferIndex, + destination: destination.CharPointer + destinationIndex, + destinationSizeInBytes: checked((long)(destination.ByteCapacity - (destinationIndex * sizeof(char)))), + sourceBytesToCopy: checked((long)count * sizeof(char))); + } + + /// + /// Copy contents from the specified string into the buffer at the given index. Start index must be within the current length of + /// the buffer, will grow as necessary. + /// + public unsafe void CopyFrom(uint bufferIndex, string source, int sourceIndex = 0, int count = -1) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (bufferIndex > _length) throw new ArgumentOutOfRangeException(nameof(bufferIndex)); + if (sourceIndex < 0 || sourceIndex > source.Length) throw new ArgumentOutOfRangeException(nameof(sourceIndex)); + if (count == -1) count = source.Length - sourceIndex; + if (count < 0 || source.Length - count < sourceIndex) throw new ArgumentOutOfRangeException(nameof(count)); + + if (count == 0) return; + uint lastIndex = bufferIndex + (uint)count; + if (_length < lastIndex) Length = lastIndex; + + fixed (char* content = source) + { + Buffer.MemoryCopy( + source: content + sourceIndex, + destination: CharPointer + bufferIndex, + destinationSizeInBytes: checked((long)(ByteCapacity - (bufferIndex * sizeof(char)))), + sourceBytesToCopy: (long)count * sizeof(char)); + } + } + + /// + /// Trim the specified values from the end of the buffer. If nothing is specified, nothing is trimmed. + /// + public unsafe void TrimEnd(char[] values) + { + if (values == null || values.Length == 0 || _length == 0) return; + + char* end = CharPointer + _length - 1; + + while (_length > 0 && Array.IndexOf(values, *end) >= 0) + { + Length = _length - 1; + end--; + } + } + + /// + /// String representation of the entire buffer. If the buffer is larger than the maximum size string (int.MaxValue) this will throw. + /// + /// Thrown if the buffer is too big to fit into a string. + public unsafe override string ToString() + { + if (_length == 0) return string.Empty; + if (_length > int.MaxValue) throw new InvalidOperationException(); + return new string(CharPointer, startIndex: 0, length: (int)_length); + } + + /// + /// Get the given substring in the buffer. + /// + /// Count of characters to take, or remaining characters from if -1. + /// + /// Thrown if or are outside the range of the buffer's length + /// or count is greater than the maximum string size (int.MaxValue). + /// + public unsafe string Substring(uint startIndex, int count = -1) + { + if (startIndex > (_length == 0 ? 0 : _length - 1)) throw new ArgumentOutOfRangeException(nameof(startIndex)); + if (count < -1) throw new ArgumentOutOfRangeException(nameof(count)); + + uint realCount = count == -1 ? _length - startIndex : (uint)count; + if (realCount > int.MaxValue || checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException(nameof(count)); + if (realCount == 0) return string.Empty; + + // The buffer could be bigger than will fit into a string, but the substring might fit. As the starting + // index might be bigger than int we need to index ourselves. + return new string(value: CharPointer + startIndex, startIndex: 0, length: (int)realCount); + } + + public override void Free() + { + base.Free(); + _length = 0; + } + } +} diff --git a/src/mscorlib/corefx/System/Security/CryptographicException.cs b/src/mscorlib/corefx/System/Security/CryptographicException.cs new file mode 100644 index 0000000000..89cb658aa9 --- /dev/null +++ b/src/mscorlib/corefx/System/Security/CryptographicException.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; +using System.Runtime.Serialization; + +namespace System.Security.Cryptography +{ + [Serializable] + public class CryptographicException : SystemException + { + public CryptographicException() + : base(SR.Arg_CryptographyException) + { + } + + public CryptographicException(int hr) + : base(SR.Arg_CryptographyException) + { + HResult = hr; + } + + public CryptographicException(string message) + : base(message) + { + } + + public CryptographicException(string message, Exception inner) + : base(message, inner) + { + } + + public CryptographicException(string format, string insert) + : base(string.Format(CultureInfo.CurrentCulture, format, insert)) + { + } + + protected CryptographicException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/mscorlib/corefx/System/Security/SafeBSTRHandle.cs b/src/mscorlib/corefx/System/Security/SafeBSTRHandle.cs new file mode 100644 index 0000000000..19d63d41e4 --- /dev/null +++ b/src/mscorlib/corefx/System/Security/SafeBSTRHandle.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace System.Security +{ + internal sealed class SafeBSTRHandle : SafeBuffer + { + internal SafeBSTRHandle() : base(true) { } + + internal static SafeBSTRHandle Allocate(uint lenInChars) + { + uint lenInBytes = lenInChars * sizeof(char); + SafeBSTRHandle bstr = Interop.OleAut32.SysAllocStringLen(IntPtr.Zero, lenInChars); + if (bstr.IsInvalid) // SysAllocStringLen returns a NULL ptr when there's insufficient memory + { + throw new OutOfMemoryException(); + } + bstr.Initialize(lenInBytes); + return bstr; + } + + override protected bool ReleaseHandle() + { + Interop.NtDll.ZeroMemory(handle, (UIntPtr)(Interop.OleAut32.SysStringLen(handle) * sizeof(char))); + Interop.OleAut32.SysFreeString(handle); + return true; + } + + internal unsafe void ClearBuffer() + { + byte* bufferPtr = null; + try + { + AcquirePointer(ref bufferPtr); + Interop.NtDll.ZeroMemory((IntPtr)bufferPtr, (UIntPtr)(Interop.OleAut32.SysStringLen((IntPtr)bufferPtr) * sizeof(char))); + } + finally + { + if (bufferPtr != null) + { + ReleasePointer(); + } + } + } + + internal unsafe uint Length => Interop.OleAut32.SysStringLen(this); + + internal unsafe static void Copy(SafeBSTRHandle source, SafeBSTRHandle target, uint bytesToCopy) + { + if (bytesToCopy == 0) + { + return; + } + + byte* sourcePtr = null, targetPtr = null; + try + { + source.AcquirePointer(ref sourcePtr); + target.AcquirePointer(ref targetPtr); + + Debug.Assert(source.ByteLength >= bytesToCopy, "Source buffer is too small."); + Buffer.MemoryCopy(sourcePtr, targetPtr, target.ByteLength, bytesToCopy); + } + finally + { + if (targetPtr != null) + { + target.ReleasePointer(); + } + if (sourcePtr != null) + { + source.ReleasePointer(); + } + } + } + } +} diff --git a/src/mscorlib/corefx/System/Security/SecureString.Unix.cs b/src/mscorlib/corefx/System/Security/SecureString.Unix.cs new file mode 100644 index 0000000000..0ef38e40ee --- /dev/null +++ b/src/mscorlib/corefx/System/Security/SecureString.Unix.cs @@ -0,0 +1,295 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace System.Security +{ + // SecureString attempts to provide a defense-in-depth solution. + // + // On Windows, this is done with several mechanisms: + // 1. keeping the data in unmanaged memory so that copies of it aren't implicitly made by the GC moving it around + // 2. zero'ing out that unmanaged memory so that the string is reliably removed from memory when done with it + // 3. encrypting the data while it's not being used (it's unencrypted to manipulate and use it) + // + // On Unix, we do 1 and 2, but we don't do 3 as there's no CryptProtectData equivalent. + + public sealed partial class SecureString + { + private UnmanagedBuffer _buffer; + + internal SecureString(SecureString str) + { + // Allocate enough space to store the provided string + EnsureCapacity(str._decryptedLength); + _decryptedLength = str._decryptedLength; + + // Copy the string into the newly allocated space + if (_decryptedLength > 0) + { + UnmanagedBuffer.Copy(str._buffer, _buffer, (ulong)(str._decryptedLength * sizeof(char))); + } + } + + private unsafe void InitializeSecureString(char* value, int length) + { + // Allocate enough space to store the provided string + EnsureCapacity(length); + _decryptedLength = length; + if (length == 0) + { + return; + } + + // Copy the string into the newly allocated space + byte* ptr = null; + try + { + _buffer.AcquirePointer(ref ptr); + Buffer.MemoryCopy(value, ptr, _buffer.ByteLength, (ulong)(length * sizeof(char))); + } + finally + { + if (ptr != null) + { + _buffer.ReleasePointer(); + } + } + } + + private void DisposeCore() + { + if (_buffer != null && !_buffer.IsInvalid) + { + _buffer.Dispose(); + _buffer = null; + } + } + + private void EnsureNotDisposed() + { + if (_buffer == null) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + private void ClearCore() + { + _decryptedLength = 0; + _buffer.Clear(); + } + + private unsafe void AppendCharCore(char c) + { + // Make sure we have enough space for the new character, then write it at the end. + EnsureCapacity(_decryptedLength + 1); + _buffer.Write((ulong)(_decryptedLength * sizeof(char)), c); + _decryptedLength++; + } + + private unsafe void InsertAtCore(int index, char c) + { + // Make sure we have enough space for the new character, then shift all of the characters above it and insert it. + EnsureCapacity(_decryptedLength + 1); + byte* ptr = null; + try + { + _buffer.AcquirePointer(ref ptr); + ptr += index * sizeof(char); + long bytesToShift = (_decryptedLength - index) * sizeof(char); + Buffer.MemoryCopy(ptr, ptr + sizeof(char), bytesToShift, bytesToShift); + *((char*)ptr) = c; + ++_decryptedLength; + } + finally + { + if (ptr != null) + { + _buffer.ReleasePointer(); + } + } + } + + private unsafe void RemoveAtCore(int index) + { + // Shift down all values above the specified index, then null out the empty space at the end. + byte* ptr = null; + try + { + _buffer.AcquirePointer(ref ptr); + ptr += index * sizeof(char); + long bytesToShift = (_decryptedLength - index - 1) * sizeof(char); + Buffer.MemoryCopy(ptr + sizeof(char), ptr, bytesToShift, bytesToShift); + *((char*)(ptr + bytesToShift)) = (char)0; + --_decryptedLength; + } + finally + { + if (ptr != null) + { + _buffer.ReleasePointer(); + } + } + } + + private void SetAtCore(int index, char c) + { + // Overwrite the character at the specified index + _buffer.Write((ulong)(index * sizeof(char)), c); + } + + internal unsafe IntPtr MarshalToStringCore(bool globalAlloc, bool unicode) + { + int length = _decryptedLength; + + byte* bufferPtr = null; + IntPtr stringPtr = IntPtr.Zero, result = IntPtr.Zero; + try + { + _buffer.AcquirePointer(ref bufferPtr); + if (unicode) + { + int resultLength = (length + 1) * sizeof(char); + stringPtr = globalAlloc ? Marshal.AllocHGlobal(resultLength) : Marshal.AllocCoTaskMem(resultLength); + Buffer.MemoryCopy( + source: bufferPtr, + destination: (byte*)stringPtr.ToPointer(), + destinationSizeInBytes: resultLength, + sourceBytesToCopy: length * sizeof(char)); + *(length + (char*)stringPtr) = '\0'; + } + else + { + int resultLength = Encoding.UTF8.GetByteCount((char*)bufferPtr, length) + 1; + stringPtr = globalAlloc ? Marshal.AllocHGlobal(resultLength) : Marshal.AllocCoTaskMem(resultLength); + int encodedLength = Encoding.UTF8.GetBytes((char*)bufferPtr, length, (byte*)stringPtr, resultLength); + Debug.Assert(encodedLength + 1 == resultLength, $"Expected encoded length to match result, got {encodedLength} != {resultLength}"); + *(resultLength - 1 + (byte*)stringPtr) = 0; + } + + result = stringPtr; + } + finally + { + // If there was a failure, such that result isn't initialized, + // release the string if we had one. + if (stringPtr != IntPtr.Zero && result == IntPtr.Zero) + { + UnmanagedBuffer.ZeroMemory((byte*)stringPtr, (ulong)(length * sizeof(char))); + MarshalFree(stringPtr, globalAlloc); + } + + if (bufferPtr != null) + { + _buffer.ReleasePointer(); + } + } + + return result; + } + + // ----------------------------- + // ---- PAL layer ends here ---- + // ----------------------------- + + private void EnsureCapacity(int capacity) + { + // Make sure the requested capacity doesn't exceed SecureString's defined limit + if (capacity > MaxLength) + { + throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_Capacity); + } + + // If we already have enough space allocated, we're done + if (_buffer != null && (capacity * sizeof(char)) <= (int)_buffer.ByteLength) + { + return; + } + + // We need more space, so allocate a new buffer, copy all our data into it, + // and then swap the new for the old. + UnmanagedBuffer newBuffer = UnmanagedBuffer.Allocate(capacity * sizeof(char)); + if (_buffer != null) + { + UnmanagedBuffer.Copy(_buffer, newBuffer, _buffer.ByteLength); + _buffer.Dispose(); + } + _buffer = newBuffer; + } + + /// SafeBuffer for managing memory meant to be kept confidential. + private sealed class UnmanagedBuffer : SafeBuffer + { + internal UnmanagedBuffer() : base(true) { } + + internal static UnmanagedBuffer Allocate(int bytes) + { + Debug.Assert(bytes >= 0); + UnmanagedBuffer buffer = new UnmanagedBuffer(); + buffer.SetHandle(Marshal.AllocHGlobal(bytes)); + buffer.Initialize((ulong)bytes); + return buffer; + } + + internal unsafe void Clear() + { + byte* ptr = null; + try + { + AcquirePointer(ref ptr); + ZeroMemory(ptr, ByteLength); + } + finally + { + if (ptr != null) + { + ReleasePointer(); + } + } + } + + internal static unsafe void Copy(UnmanagedBuffer source, UnmanagedBuffer destination, ulong bytesLength) + { + if (bytesLength == 0) + { + return; + } + + byte* srcPtr = null, dstPtr = null; + try + { + source.AcquirePointer(ref srcPtr); + destination.AcquirePointer(ref dstPtr); + Buffer.MemoryCopy(srcPtr, dstPtr, destination.ByteLength, bytesLength); + } + finally + { + if (dstPtr != null) + { + destination.ReleasePointer(); + } + if (srcPtr != null) + { + source.ReleasePointer(); + } + } + } + + protected override unsafe bool ReleaseHandle() + { + Marshal.FreeHGlobal(handle); + return true; + } + + internal static unsafe void ZeroMemory(byte* ptr, ulong len) + { + for (ulong i = 0; i < len; i++) *ptr++ = 0; + } + } + + } +} diff --git a/src/mscorlib/corefx/System/Security/SecureString.Windows.cs b/src/mscorlib/corefx/System/Security/SecureString.Windows.cs new file mode 100644 index 0000000000..5f56353647 --- /dev/null +++ b/src/mscorlib/corefx/System/Security/SecureString.Windows.cs @@ -0,0 +1,310 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using Microsoft.Win32; + +namespace System.Security +{ + public sealed partial class SecureString + { + internal SecureString(SecureString str) + { + Debug.Assert(str != null, "Expected non-null SecureString"); + Debug.Assert(str._buffer != null, "Expected other SecureString's buffer to be non-null"); + Debug.Assert(str._encrypted, "Expected to be used only on encrypted SecureStrings"); + + AllocateBuffer(str._buffer.Length); + SafeBSTRHandle.Copy(str._buffer, _buffer, str._buffer.Length * sizeof(char)); + + _decryptedLength = str._decryptedLength; + _encrypted = str._encrypted; + } + + private unsafe void InitializeSecureString(char* value, int length) + { + Debug.Assert(length >= 0, $"Expected non-negative length, got {length}"); + + AllocateBuffer((uint)length); + _decryptedLength = length; + + byte* bufferPtr = null; + try + { + _buffer.AcquirePointer(ref bufferPtr); + Buffer.MemoryCopy((byte*)value, bufferPtr, (long)_buffer.ByteLength, length * sizeof(char)); + } + finally + { + if (bufferPtr != null) + { + _buffer.ReleasePointer(); + } + } + + ProtectMemory(); + } + + private void AppendCharCore(char c) + { + UnprotectMemory(); + try + { + EnsureCapacity(_decryptedLength + 1); + _buffer.Write((uint)_decryptedLength * sizeof(char), c); + _decryptedLength++; + } + finally + { + ProtectMemory(); + } + } + + private void ClearCore() + { + _decryptedLength = 0; + _buffer.ClearBuffer(); + } + + private void DisposeCore() + { + if (_buffer != null) + { + _buffer.Dispose(); + _buffer = null; + } + } + + private unsafe void InsertAtCore(int index, char c) + { + byte* bufferPtr = null; + UnprotectMemory(); + try + { + EnsureCapacity(_decryptedLength + 1); + + _buffer.AcquirePointer(ref bufferPtr); + char* pBuffer = (char*)bufferPtr; + + for (int i = _decryptedLength; i > index; i--) + { + pBuffer[i] = pBuffer[i - 1]; + } + pBuffer[index] = c; + ++_decryptedLength; + } + finally + { + ProtectMemory(); + if (bufferPtr != null) + { + _buffer.ReleasePointer(); + } + } + } + + private unsafe void RemoveAtCore(int index) + { + byte* bufferPtr = null; + UnprotectMemory(); + try + { + _buffer.AcquirePointer(ref bufferPtr); + char* pBuffer = (char*)bufferPtr; + + for (int i = index; i < _decryptedLength - 1; i++) + { + pBuffer[i] = pBuffer[i + 1]; + } + pBuffer[--_decryptedLength] = (char)0; + } + finally + { + ProtectMemory(); + if (bufferPtr != null) + { + _buffer.ReleasePointer(); + } + } + } + + private void SetAtCore(int index, char c) + { + UnprotectMemory(); + try + { + _buffer.Write((uint)index * sizeof(char), c); + } + finally + { + ProtectMemory(); + } + } + + internal unsafe IntPtr MarshalToBSTR() + { + int length = _decryptedLength; + IntPtr ptr = IntPtr.Zero; + IntPtr result = IntPtr.Zero; + byte* bufferPtr = null; + + UnprotectMemory(); + try + { + _buffer.AcquirePointer(ref bufferPtr); + int resultByteLength = (length + 1) * sizeof(char); + + ptr = Win32Native.SysAllocStringLen(null, length); + if (ptr == IntPtr.Zero) { + throw new OutOfMemoryException(); + } + + Buffer.MemoryCopy(bufferPtr, (byte*)ptr, resultByteLength, length * sizeof(char)); + + result = ptr; + } + finally + { + ProtectMemory(); + + // If we failed for any reason, free the new buffer + if (result == IntPtr.Zero && ptr != IntPtr.Zero) + { + Interop.NtDll.ZeroMemory(ptr, (UIntPtr)(length * sizeof(char))); + Win32Native.SysFreeString(ptr); + } + + if (bufferPtr != null) + { + _buffer.ReleasePointer(); + } + } + return result; + } + + internal unsafe IntPtr MarshalToStringCore(bool globalAlloc, bool unicode) + { + int length = _decryptedLength; + IntPtr ptr = IntPtr.Zero; + IntPtr result = IntPtr.Zero; + byte* bufferPtr = null; + + UnprotectMemory(); + try + { + _buffer.AcquirePointer(ref bufferPtr); + if (unicode) + { + int resultByteLength = (length + 1) * sizeof(char); + ptr = globalAlloc ? Marshal.AllocHGlobal(resultByteLength) : Marshal.AllocCoTaskMem(resultByteLength); + Buffer.MemoryCopy(bufferPtr, (byte*)ptr, resultByteLength, length * sizeof(char)); + *(length + (char*)ptr) = '\0'; + } + else + { + uint defaultChar = '?'; + int resultByteLength = 1 + Interop.mincore.WideCharToMultiByte( + Interop.mincore.CP_ACP, Interop.mincore.WC_NO_BEST_FIT_CHARS, (char*)bufferPtr, length, null, 0, (IntPtr)(&defaultChar), IntPtr.Zero); + ptr = globalAlloc ? Marshal.AllocHGlobal(resultByteLength) : Marshal.AllocCoTaskMem(resultByteLength); + Interop.mincore.WideCharToMultiByte( + Interop.mincore.CP_ACP, Interop.mincore.WC_NO_BEST_FIT_CHARS, (char*)bufferPtr, length, (byte*)ptr, resultByteLength - 1, (IntPtr)(&defaultChar), IntPtr.Zero); + *(resultByteLength - 1 + (byte*)ptr) = 0; + } + result = ptr; + } + finally + { + ProtectMemory(); + + // If we failed for any reason, free the new buffer + if (result == IntPtr.Zero && ptr != IntPtr.Zero) + { + Interop.NtDll.ZeroMemory(ptr, (UIntPtr)(length * sizeof(char))); + MarshalFree(ptr, globalAlloc); + } + + if (bufferPtr != null) + { + _buffer.ReleasePointer(); + } + } + return result; + } + + private void EnsureNotDisposed() + { + if (_buffer == null) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + // ----------------------------- + // ---- PAL layer ends here ---- + // ----------------------------- + + private const int BlockSize = (int)Interop.Crypt32.CRYPTPROTECTMEMORY_BLOCK_SIZE / sizeof(char); + private SafeBSTRHandle _buffer; + private bool _encrypted; + + private void AllocateBuffer(uint size) + { + _buffer = SafeBSTRHandle.Allocate(GetAlignedSize(size)); + } + + private static uint GetAlignedSize(uint size) => + size == 0 || size % BlockSize != 0 ? + BlockSize + ((size / BlockSize) * BlockSize) : + size; + + private void EnsureCapacity(int capacity) + { + if (capacity > MaxLength) + { + throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_Capacity); + } + + if (((uint)capacity * sizeof(char)) <= _buffer.ByteLength) + { + return; + } + + var oldBuffer = _buffer; + SafeBSTRHandle newBuffer = SafeBSTRHandle.Allocate(GetAlignedSize((uint)capacity)); + SafeBSTRHandle.Copy(oldBuffer, newBuffer, (uint)_decryptedLength * sizeof(char)); + _buffer = newBuffer; + oldBuffer.Dispose(); + } + + private void ProtectMemory() + { + Debug.Assert(!_buffer.IsInvalid, "Invalid buffer!"); + + if (_decryptedLength != 0 && + !_encrypted && + !Interop.Crypt32.CryptProtectMemory(_buffer, _buffer.Length * sizeof(char), Interop.Crypt32.CRYPTPROTECTMEMORY_SAME_PROCESS)) + { + throw new CryptographicException(Marshal.GetLastWin32Error()); + } + + _encrypted = true; + } + + private void UnprotectMemory() + { + Debug.Assert(!_buffer.IsInvalid, "Invalid buffer!"); + + if (_decryptedLength != 0 && + _encrypted && + !Interop.Crypt32.CryptUnprotectMemory(_buffer, _buffer.Length * sizeof(char), Interop.Crypt32.CRYPTPROTECTMEMORY_SAME_PROCESS)) + { + throw new CryptographicException(Marshal.GetLastWin32Error()); + } + + _encrypted = false; + } + } +} diff --git a/src/mscorlib/corefx/System/Security/SecureString.cs b/src/mscorlib/corefx/System/Security/SecureString.cs new file mode 100644 index 0000000000..9059f90e60 --- /dev/null +++ b/src/mscorlib/corefx/System/Security/SecureString.cs @@ -0,0 +1,189 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace System.Security +{ + public sealed partial class SecureString : IDisposable + { + private const int MaxLength = 65536; + private readonly object _methodLock = new object(); + private bool _readOnly; + private int _decryptedLength; + + public unsafe SecureString() + { + InitializeSecureString(null, 0); + } + + [CLSCompliant(false)] + public unsafe SecureString(char* value, int length) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum); + } + if (length > MaxLength) + { + throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_Length); + } + + InitializeSecureString(value, length); + } + + public int Length + { + get + { + lock (_methodLock) + { + EnsureNotDisposed(); + return _decryptedLength; + } + } + } + + public void AppendChar(char c) + { + lock (_methodLock) + { + EnsureNotDisposed(); + EnsureNotReadOnly(); + AppendCharCore(c); + } + } + + // clears the current contents. Only available if writable + public void Clear() + { + lock (_methodLock) + { + EnsureNotDisposed(); + EnsureNotReadOnly(); + ClearCore(); + } + } + + // Do a deep-copy of the SecureString + public SecureString Copy() + { + lock (_methodLock) + { + EnsureNotDisposed(); + return new SecureString(this); + } + } + + public void Dispose() + { + lock (_methodLock) + { + DisposeCore(); + } + } + + public void InsertAt(int index, char c) + { + lock (_methodLock) + { + if (index < 0 || index > _decryptedLength) + { + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_IndexString); + } + + EnsureNotDisposed(); + EnsureNotReadOnly(); + + InsertAtCore(index, c); + } + } + + public bool IsReadOnly() + { + lock (_methodLock) + { + EnsureNotDisposed(); + return _readOnly; + } + } + + public void MakeReadOnly() + { + lock (_methodLock) + { + EnsureNotDisposed(); + _readOnly = true; + } + } + + public void RemoveAt(int index) + { + lock (_methodLock) + { + EnsureNotDisposed(); + EnsureNotReadOnly(); + + if (index < 0 || index >= _decryptedLength) + { + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_IndexString); + } + + RemoveAtCore(index); + } + } + + public void SetAt(int index, char c) + { + lock (_methodLock) + { + if (index < 0 || index >= _decryptedLength) + { + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_IndexString); + } + Debug.Assert(index <= Int32.MaxValue / sizeof(char)); + + EnsureNotDisposed(); + EnsureNotReadOnly(); + + SetAtCore(index, c); + } + } + + private void EnsureNotReadOnly() + { + if (_readOnly) + { + throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); + } + } + + internal unsafe IntPtr MarshalToString(bool globalAlloc, bool unicode) + { + lock (_methodLock) + { + EnsureNotDisposed(); + return MarshalToStringCore(globalAlloc, unicode); + } + } + + private static void MarshalFree(IntPtr ptr, bool globalAlloc) + { + if (globalAlloc) + { + Marshal.FreeHGlobal(ptr); + } + else + { + Marshal.FreeCoTaskMem(ptr); + } + } + } +} diff --git a/src/mscorlib/corefx/System/Threading/ClrThreadPoolBoundHandle.cs b/src/mscorlib/corefx/System/Threading/ClrThreadPoolBoundHandle.cs new file mode 100644 index 0000000000..d0cc5afbae --- /dev/null +++ b/src/mscorlib/corefx/System/Threading/ClrThreadPoolBoundHandle.cs @@ -0,0 +1,319 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace System.Threading +{ + // + // Implementation of ThreadPoolBoundHandle that sits on top of the CLR's ThreadPool and Overlapped infrastructure + // + + /// + /// Represents an I/O handle that is bound to the system thread pool and enables low-level + /// components to receive notifications for asynchronous I/O operations. + /// + public sealed partial class ThreadPoolBoundHandle : IDisposable + { + private readonly SafeHandle _handle; + private bool _isDisposed; + + private ThreadPoolBoundHandle(SafeHandle handle) + { + _handle = handle; + } + + /// + /// Gets the bound operating system handle. + /// + /// + /// A object that holds the bound operating system handle. + /// + public SafeHandle Handle + { + get { return _handle; } + } + + /// + /// Returns a for the specific handle, + /// which is bound to the system thread pool. + /// + /// + /// A object that holds the operating system handle. The + /// handle must have been opened for overlapped I/O on the unmanaged side. + /// + /// + /// for , which + /// is bound to the system thread pool. + /// + /// + /// is . + /// + /// + /// has been disposed. + /// + /// -or- + /// + /// does not refer to a valid I/O handle. + /// + /// -or- + /// + /// refers to a handle that has not been opened + /// for overlapped I/O. + /// + /// -or- + /// + /// refers to a handle that has already been bound. + /// + /// + /// This method should be called once per handle. + /// + /// -or- + /// + /// does not take ownership of , + /// it remains the responsibility of the caller to call . + /// + public static ThreadPoolBoundHandle BindHandle(SafeHandle handle) + { + if (handle == null) + throw new ArgumentNullException(nameof(handle)); + + if (handle.IsClosed || handle.IsInvalid) + throw new ArgumentException(SR.Argument_InvalidHandle, nameof(handle)); + + try + { + // ThreadPool.BindHandle will always return true, otherwise, it throws. See the underlying FCall + // implementation in ThreadPoolNative::CorBindIoCompletionCallback to see the implementation. + bool succeeded = ThreadPool.BindHandle(handle); + Debug.Assert(succeeded); + } + catch (Exception ex) + { // BindHandle throws ApplicationException on full CLR and Exception on CoreCLR. + // We do not let either of these leak and convert them to ArgumentException to + // indicate that the specified handles are invalid. + + if (ex.HResult == System.HResults.E_HANDLE) // Bad handle + throw new ArgumentException(SR.Argument_InvalidHandle, nameof(handle)); + + if (ex.HResult == System.HResults.E_INVALIDARG) // Handle already bound or sync handle + throw new ArgumentException(SR.Argument_AlreadyBoundOrSyncHandle, nameof(handle)); + + throw; + } + + return new ThreadPoolBoundHandle(handle); + } + + /// + /// Returns an unmanaged pointer to a structure, specifying + /// a delegate that is invoked when the asynchronous I/O operation is complete, a user-provided + /// object providing context, and managed objects that serve as buffers. + /// + /// + /// An delegate that represents the callback method + /// invoked when the asynchronous I/O operation completes. + /// + /// + /// A user-provided object that distinguishes this from other + /// instances. Can be . + /// + /// + /// An object or array of objects representing the input or output buffer for the operation. Each + /// object represents a buffer, for example an array of bytes. Can be . + /// + /// + /// An unmanaged pointer to a structure. + /// + /// + /// + /// The unmanaged pointer returned by this method can be passed to the operating system in + /// overlapped I/O operations. The structure is fixed in + /// physical memory until is called. + /// + /// + /// The buffer or buffers specified in must be the same as those passed + /// to the unmanaged operating system function that performs the asynchronous I/O. + /// + /// + /// The buffers specified in are pinned for the duration of + /// the I/O operation. + /// + /// + /// + /// is . + /// + /// + /// This method was called after the was disposed. + /// + [CLSCompliant(false)] + public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object state, object pinData) + { + if (callback == null) + throw new ArgumentNullException(nameof(callback)); + + EnsureNotDisposed(); + + ThreadPoolBoundHandleOverlapped overlapped = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, preAllocated: null); + overlapped._boundHandle = this; + return overlapped._nativeOverlapped; + } + + /// + /// Returns an unmanaged pointer to a structure, using the callback, + /// state, and buffers associated with the specified object. + /// + /// + /// A object from which to create the NativeOverlapped pointer. + /// + /// + /// An unmanaged pointer to a structure. + /// + /// + /// + /// The unmanaged pointer returned by this method can be passed to the operating system in + /// overlapped I/O operations. The structure is fixed in + /// physical memory until is called. + /// + /// + /// + /// is . + /// + /// + /// is currently in use for another I/O operation. + /// + /// + /// This method was called after the was disposed, or + /// this method was called after was disposed. + /// + /// + [CLSCompliant(false)] + public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated) + { + if (preAllocated == null) + throw new ArgumentNullException(nameof(preAllocated)); + + EnsureNotDisposed(); + + preAllocated.AddRef(); + try + { + ThreadPoolBoundHandleOverlapped overlapped = preAllocated._overlapped; + + if (overlapped._boundHandle != null) + throw new ArgumentException(SR.Argument_PreAllocatedAlreadyAllocated, nameof(preAllocated)); + + overlapped._boundHandle = this; + + return overlapped._nativeOverlapped; + } + catch + { + preAllocated.Release(); + throw; + } + } + + /// + /// Frees the unmanaged memory associated with a structure + /// allocated by the method. + /// + /// + /// An unmanaged pointer to the structure to be freed. + /// + /// + /// + /// You must call the method exactly once + /// on every unmanaged pointer allocated using the + /// method. + /// If you do not call the method, you will + /// leak memory. If you call the method more + /// than once on the same unmanaged pointer, memory will be corrupted. + /// + /// + /// + /// is . + /// + /// + /// This method was called after the was disposed. + /// + [CLSCompliant(false)] + public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped) + { + if (overlapped == null) + throw new ArgumentNullException(nameof(overlapped)); + + // Note: we explicitly allow FreeNativeOverlapped calls after the ThreadPoolBoundHandle has been Disposed. + + ThreadPoolBoundHandleOverlapped wrapper = GetOverlappedWrapper(overlapped, this); + + if (wrapper._boundHandle != this) + throw new ArgumentException(SR.Argument_NativeOverlappedWrongBoundHandle, nameof(overlapped)); + + if (wrapper._preAllocated != null) + wrapper._preAllocated.Release(); + else + Overlapped.Free(overlapped); + } + + /// + /// Returns the user-provided object specified when the instance was + /// allocated using the . + /// + /// + /// An unmanaged pointer to the structure from which to return the + /// asscociated user-provided object. + /// + /// + /// A user-provided object that distinguishes this + /// from other instances, otherwise, if one was + /// not specified when the instance was allocated using . + /// + /// + /// is . + /// + [CLSCompliant(false)] + public unsafe static object GetNativeOverlappedState(NativeOverlapped* overlapped) + { + if (overlapped == null) + throw new ArgumentNullException(nameof(overlapped)); + + ThreadPoolBoundHandleOverlapped wrapper = GetOverlappedWrapper(overlapped, null); + Debug.Assert(wrapper._boundHandle != null); + return wrapper._userState; + } + + private static unsafe ThreadPoolBoundHandleOverlapped GetOverlappedWrapper(NativeOverlapped* overlapped, ThreadPoolBoundHandle expectedBoundHandle) + { + ThreadPoolBoundHandleOverlapped wrapper; + try + { + wrapper = (ThreadPoolBoundHandleOverlapped)Overlapped.Unpack(overlapped); + } + catch (NullReferenceException ex) + { + throw new ArgumentException(SR.Argument_NativeOverlappedAlreadyFree, nameof(overlapped), ex); + } + + return wrapper; + } + + public void Dispose() + { + // .NET Native's version of ThreadPoolBoundHandle that wraps the Win32 ThreadPool holds onto + // native resources so it needs to be disposable. To match the contract, we are also disposable. + // We also implement a disposable state to mimic behavior between this implementation and + // .NET Native's version (code written against us, will also work against .NET Native's version). + _isDisposed = true; + } + + + private void EnsureNotDisposed() + { + if (_isDisposed) + throw new ObjectDisposedException(GetType().ToString()); + } + } +} diff --git a/src/mscorlib/corefx/System/Threading/ClrThreadPoolBoundHandleOverlapped.cs b/src/mscorlib/corefx/System/Threading/ClrThreadPoolBoundHandleOverlapped.cs new file mode 100644 index 0000000000..1aea2a294b --- /dev/null +++ b/src/mscorlib/corefx/System/Threading/ClrThreadPoolBoundHandleOverlapped.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Threading +{ + /// + /// Overlapped subclass adding data needed by ThreadPoolBoundHandle. + /// + internal sealed class ThreadPoolBoundHandleOverlapped : Overlapped + { + private static readonly unsafe IOCompletionCallback s_completionCallback = CompletionCallback; + + private readonly IOCompletionCallback _userCallback; + internal readonly object _userState; + internal PreAllocatedOverlapped _preAllocated; + internal unsafe NativeOverlapped* _nativeOverlapped; + internal ThreadPoolBoundHandle _boundHandle; + internal bool _completed; + + public unsafe ThreadPoolBoundHandleOverlapped(IOCompletionCallback callback, object state, object pinData, PreAllocatedOverlapped preAllocated) + { + _userCallback = callback; + _userState = state; + _preAllocated = preAllocated; + + _nativeOverlapped = Pack(s_completionCallback, pinData); + _nativeOverlapped->OffsetLow = 0; // CLR reuses NativeOverlapped instances and does not reset these + _nativeOverlapped->OffsetHigh = 0; + } + + private unsafe static void CompletionCallback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped) + { + ThreadPoolBoundHandleOverlapped overlapped = (ThreadPoolBoundHandleOverlapped)Overlapped.Unpack(nativeOverlapped); + + // + // The Win32 thread pool implementation of ThreadPoolBoundHandle does not permit reuse of NativeOverlapped + // pointers without freeing them and allocating new a new one. We need to ensure that code using the CLR + // ThreadPool implementation follows those rules. + // + if (overlapped._completed) + throw new InvalidOperationException(SR.InvalidOperation_NativeOverlappedReused); + + overlapped._completed = true; + + if (overlapped._boundHandle == null) + throw new InvalidOperationException(SR.Argument_NativeOverlappedAlreadyFree); + + overlapped._userCallback(errorCode, numBytes, nativeOverlapped); + } + } +} diff --git a/src/mscorlib/corefx/System/Threading/ClrThreadPoolPreAllocatedOverlapped.cs b/src/mscorlib/corefx/System/Threading/ClrThreadPoolPreAllocatedOverlapped.cs new file mode 100644 index 0000000000..a42e0c7983 --- /dev/null +++ b/src/mscorlib/corefx/System/Threading/ClrThreadPoolPreAllocatedOverlapped.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Threading +{ + /// + /// Represents pre-allocated state for native overlapped I/O operations. + /// + /// + public sealed class PreAllocatedOverlapped : IDisposable, IDeferredDisposable + { + internal readonly ThreadPoolBoundHandleOverlapped _overlapped; + private DeferredDisposableLifetime _lifetime; + + /// + /// Initializes a new instance of the class, specifying + /// a delegate that is invoked when each asynchronous I/O operation is complete, a user-provided + /// object providing context, and managed objects that serve as buffers. + /// + /// + /// An delegate that represents the callback method + /// invoked when each asynchronous I/O operation completes. + /// + /// + /// A user-provided object that distinguishes instance produced from this + /// object from other instances. Can be . + /// + /// + /// An object or array of objects representing the input or output buffer for the operations. Each + /// object represents a buffer, for example an array of bytes. Can be . + /// + /// + /// The new instance can be passed to + /// , to produce + /// a instance that can be passed to the operating system in overlapped + /// I/O operations. A single instance can only be used for + /// a single native I/O operation at a time. However, the state stored in the + /// instance can be reused for subsequent native operations. + /// + /// The buffers specified in are pinned until is called. + /// + /// + /// + /// is . + /// + /// + /// This method was called after the was disposed. + /// + [CLSCompliant(false)] + public unsafe PreAllocatedOverlapped(IOCompletionCallback callback, object state, object pinData) + { + if (callback == null) + throw new ArgumentNullException(nameof(callback)); + + _overlapped = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, this); + } + + internal bool AddRef() + { + return _lifetime.AddRef(this); + } + + internal void Release() + { + _lifetime.Release(this); + } + + /// + /// Frees the resources associated with this instance. + /// + public unsafe void Dispose() + { + _lifetime.Dispose(this); + GC.SuppressFinalize(this); + } + + ~PreAllocatedOverlapped() + { + // + // During shutdown, don't automatically clean up, because this instance may still be + // reachable/usable by other code. + // + if (!Environment.HasShutdownStarted) + Dispose(); + } + + unsafe void IDeferredDisposable.OnFinalRelease(bool disposed) + { + if (_overlapped != null) + { + if (disposed) + { + Overlapped.Free(_overlapped._nativeOverlapped); + } + else + { + _overlapped._boundHandle = null; + _overlapped._completed = false; + *_overlapped._nativeOverlapped = default(NativeOverlapped); + } + } + } + } +} diff --git a/src/mscorlib/corefx/System/Threading/DeferredDisposableLifetime.cs b/src/mscorlib/corefx/System/Threading/DeferredDisposableLifetime.cs new file mode 100644 index 0000000000..89380fee60 --- /dev/null +++ b/src/mscorlib/corefx/System/Threading/DeferredDisposableLifetime.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace System.Threading +{ + /// + /// Provides callbacks to objects whose lifetime is managed by . + /// + internal interface IDeferredDisposable + { + /// + /// Called when the object's refcount reaches zero. + /// + /// + /// Indicates whether the object has been disposed. + /// + /// + /// If the refount reaches zero before the object is disposed, this method will be called with + /// set to false. If the object is then disposed, this method will be + /// called again, with set to true. If the refcount reaches zero + /// after the object has already been disposed, this will be called a single time, with + /// set to true. + /// + void OnFinalRelease(bool disposed); + } + + /// + /// Manages the lifetime of an object which implements IDisposable, but which must defer the actual + /// cleanup of state until all existing uses of the object are complete. + /// + /// The type of object whose lifetime will be managed. + /// + /// This type maintains a reference count, and tracks whether the object has been disposed. When + /// Callbacks are made to when the refcount + /// reaches zero. Objects that need to defer cleanup until they have been disposed *and* they have + /// no more references can do so in when + /// 'disposed' is true. + /// + internal struct DeferredDisposableLifetime where T : class, IDeferredDisposable + { + // + // _count is positive until Dispose is called, after which it's (-1 - refcount). + // + private int _count; + + public bool AddRef(T obj) + { + while (true) + { + int oldCount = Volatile.Read(ref _count); + + // Have we been disposed? + if (oldCount < 0) + throw new ObjectDisposedException(typeof(T).ToString()); + + int newCount = checked(oldCount + 1); + + if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount) + return true; + } + } + + public void Release(T obj) + { + while (true) + { + int oldCount = Volatile.Read(ref _count); + if (oldCount > 0) + { + // We haven't been disposed. Decrement _count. + int newCount = oldCount - 1; + if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount) + { + if (newCount == 0) + obj.OnFinalRelease(disposed: false); + return; + } + } + else + { + Debug.Assert(oldCount != 0 && oldCount != -1); + + // We've been disposed. Increment _count. + int newCount = oldCount + 1; + if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount) + { + if (newCount == -1) + obj.OnFinalRelease(disposed: true); + return; + } + } + } + } + + public void Dispose(T obj) + { + while (true) + { + int oldCount = Volatile.Read(ref _count); + if (oldCount < 0) + return; // already disposed + + int newCount = -1 - oldCount; + if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount) + { + if (newCount == -1) + obj.OnFinalRelease(disposed: true); + return; + } + } + } + } +} -- cgit v1.2.3