diff --git a/.gitignore b/.gitignore index 1b3939a1..cf35c463 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ build/NuGet.exe build/nuget.exe lib/ output/ +bin/ obj/ test-results/ .idea/ @@ -15,4 +16,13 @@ source/packages/ source/NuGetBuild/ # Visual Studio 2015 cache/options directory -.vs/ \ No newline at end of file +.vs/ + +# DNX +*project.lock.json +*project.fragment.lock.json +artifacts/ + +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index 93ae1d6c..b6a94a5c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -91,8 +91,8 @@ MONO_GAC_PREFIX="$MONO_PREFIX:/usr" export LD_LIBRARY_PATH PKG_CONFIG_PATH MONO_GAC_PREFIX -xbuild /t:Compile /property:Configuration=Release build/icu-dotnet.proj -xbuild /t:TestOnly /property:Configuration=Release build/icu-dotnet.proj +msbuild /t:Compile /property:Configuration=Release build/icu-dotnet.proj +msbuild /t:TestOnly /property:Configuration=Release build/icu-dotnet.proj ''' } diff --git a/build/NuGet.targets b/build/NuGet.targets index 2d6bdcfa..1b321569 100644 --- a/build/NuGet.targets +++ b/build/NuGet.targets @@ -61,6 +61,9 @@ Condition="Exists('$(SolutionPath)')"/> + + - $(MSBuildProjectDirectory)\.. - $(teamcity_build_checkoutDir) - /var/lib/TeamCity/agent + true + true + + $(MSBuildProjectDirectory)\.. + $(teamcity_build_checkoutDir) + /var/lib/TeamCity/agent icu.net.sln $(RootDir)/source + $(RootDir)/output ICU50 Deprecated;ByHand; - $(excludedCategories)KnownMonoIssue; - ReleaseMono - Release + $(excludedCategories)KnownMonoIssue; + + ReleaseMono + Release + + true + + + + + $(Configuration.Replace('Mono','')) + dotnet + $(SolutionDir)/icu.net.netstandard.sln @@ -17,10 +31,10 @@ + Condition=" '$(IsOnTeamCity)'=='true' And '$(IsOnWindows)'=='true'"/> + Condition=" '$(IsOnTeamCity)'=='true' And '$(IsOnWindows)'!='true'"/> @@ -48,37 +62,50 @@ - + + + + + - - - + + + + + + + + - - - + - + - + - + - + + OutputXmlFile="$(OutputDir)/$(Configuration)/TestResults.xml"/> + + + + + $(SolutionDir)/icu.net.netstandard.testrunner + + - + \ No newline at end of file diff --git a/source/icu.net.netstandard.sln b/source/icu.net.netstandard.sln new file mode 100644 index 00000000..d351c4be --- /dev/null +++ b/source/icu.net.netstandard.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.4 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "icu.net.netstandard", "icu.net.netstandard\icu.net.netstandard.csproj", "{AA4C50FD-F411-484F-A17B-0A69B8784A10}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "icu.net.netstandard.tests", "icu.net.netstandard.tests\icu.net.netstandard.tests.csproj", "{9D724A81-8913-45DA-B798-4710DA90BC91}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "icu.net.netstandard.testrunner", "icu.net.netstandard.testrunner\icu.net.netstandard.testrunner.csproj", "{B8D007FE-5102-461C-A10B-11CD9DF57FB4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AA4C50FD-F411-484F-A17B-0A69B8784A10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA4C50FD-F411-484F-A17B-0A69B8784A10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA4C50FD-F411-484F-A17B-0A69B8784A10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA4C50FD-F411-484F-A17B-0A69B8784A10}.Release|Any CPU.Build.0 = Release|Any CPU + {9D724A81-8913-45DA-B798-4710DA90BC91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D724A81-8913-45DA-B798-4710DA90BC91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D724A81-8913-45DA-B798-4710DA90BC91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D724A81-8913-45DA-B798-4710DA90BC91}.Release|Any CPU.Build.0 = Release|Any CPU + {B8D007FE-5102-461C-A10B-11CD9DF57FB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8D007FE-5102-461C-A10B-11CD9DF57FB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8D007FE-5102-461C-A10B-11CD9DF57FB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8D007FE-5102-461C-A10B-11CD9DF57FB4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/source/icu.net.netstandard.testrunner/Program.cs b/source/icu.net.netstandard.testrunner/Program.cs new file mode 100644 index 00000000..c3de8a4f --- /dev/null +++ b/source/icu.net.netstandard.testrunner/Program.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using Icu.Tests; +using NUnitLite; +using System.Reflection; + +namespace icu.net.netstandard.testrunner +{ + class Program + { + public static int Main(string[] args) + { + var assembly = typeof(BreakIteratorTests).GetTypeInfo().Assembly; + return new AutoRun(assembly).Execute(args); + } + } +} \ No newline at end of file diff --git a/source/icu.net.netstandard.testrunner/icu.net.netstandard.testrunner.csproj b/source/icu.net.netstandard.testrunner/icu.net.netstandard.testrunner.csproj new file mode 100644 index 00000000..24fe712e --- /dev/null +++ b/source/icu.net.netstandard.testrunner/icu.net.netstandard.testrunner.csproj @@ -0,0 +1,18 @@ + + + + Exe + netcoreapp1.1 + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/icu.net.netstandard.tests/icu.net.netstandard.tests.csproj b/source/icu.net.netstandard.tests/icu.net.netstandard.tests.csproj new file mode 100644 index 00000000..a37306b6 --- /dev/null +++ b/source/icu.net.netstandard.tests/icu.net.netstandard.tests.csproj @@ -0,0 +1,30 @@ + + + + ..\icu.net.tests + + netcoreapp1.1 + + + + + + + + + + + + + + + + + + %(RecursiveDir)%(Filename)%(Extension) + + + + + + diff --git a/source/icu.net.netstandard/NuGetAssets/icu.net.nuspec b/source/icu.net.netstandard/NuGetAssets/icu.net.nuspec new file mode 100644 index 00000000..d60aa7f3 --- /dev/null +++ b/source/icu.net.netstandard/NuGetAssets/icu.net.nuspec @@ -0,0 +1,30 @@ + + + + $id$ + $version$ + SIL International + https://github.com/sillsdev/icu-dotnet/blob/master/LICENSE + https://github.com/sillsdev/icu-dotnet + false + icu.net is a C# Wrapper around ICU4C + +This version of icu.net works with (more or less) any version of ICU4C. + +NOTE: this package contains the managed wrapper part of icu.net. You'll also have to have the unmanaged binaries of ICU installed. On Linux it is recommended to install the official ICU package that comes with the system. On Windows you can install one of the binary nuget packages, e.g. Icu4c.Win. + en-US + + + + + + + + + + + + + + + diff --git a/source/icu.net.netstandard/SortKey.cs b/source/icu.net.netstandard/SortKey.cs new file mode 100644 index 00000000..d62970e7 --- /dev/null +++ b/source/icu.net.netstandard/SortKey.cs @@ -0,0 +1,140 @@ +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using System; +using System.Globalization; + +namespace Icu +{ + /// + /// Replacement for System.Globalization.SortKey, which does not exist in + /// .NET Standard 1.5. Will be brought back in .NET Standard 2.0. + /// See https://github.com/dotnet/corefx/issues/10065 for more information. + /// + public class SortKey + { + private readonly string localeName; + private readonly CompareOptions options; + private readonly byte[] m_KeyData; + private readonly string m_String; + + internal SortKey(string localeName, string str, CompareOptions options, byte[] keyData) + { + var copy = new byte[keyData.Length]; + keyData.CopyTo(copy, 0); + + this.m_KeyData = copy; + this.localeName = localeName; + this.options = options; + this.m_String = str; + } + + /// + /// Gets the byte array representing the current System.Globalization.SortKey object. + /// + public virtual byte[] KeyData + { + get + { + var copy = new byte[m_KeyData.Length]; + m_KeyData.CopyTo(copy, 0); + + return copy; + } + } + + /// + /// Gets the original string used to create the current System.Globalization.SortKey + /// object. + /// + public virtual string OriginalString { get { return m_String; } } + + /// + /// Compares two sort keys. + /// + /// The first sort key to compare. + /// The second sort key to compare. + /// A signed integer that indicates the relationship between sortkey1 and sortkey2. + /// Value + /// Condition Less than zero: sortkey1 is less than sortkey2. + /// Zero : sortkey1 is equal to sortkey2. + /// Greater than zero : sortkey1 is greater than sortkey2. + /// + public static int Compare(SortKey sortkey1, SortKey sortkey2) + { + if (sortkey1 == null || sortkey2 == null) + { + throw new ArgumentNullException("A value is required to compare both values"); + } + + var keyData1 = sortkey1.KeyData; + var keyData2 = sortkey2.KeyData; + + if (keyData1.Length == 0) + { + return keyData2.Length == 0 ? 0 : -1; + } + + if (keyData2.Length == 0) + { + return keyData1.Length == 0 ? 0 : 1; + } + + var length = Math.Min(keyData1.Length, keyData2.Length); + + for (int i = 0; i < length; i++) + { + var value = keyData1[i]; + var value2 = keyData2[i]; + + if (value > value2) + { + return 1; + } + + if (value < value2) + { + return -1; + } + } + + return 0; + } + + /// + /// Determines whether the specified object is equal to the current + /// System.Globalization.SortKey object. + /// + /// The object to compare with the current + /// System.Globalization.SortKey object. + /// true if the value parameter is equal to the current + /// System.Globalization.SortKey object; otherwise, false. + public override bool Equals(object value) + { + var obj = value as SortKey; + + if (obj == null) + return false; + + return Compare(this, obj) == 0; + } + + /// + /// Serves as a hash function for the current System.Globalization.SortKey object + /// that is suitable for hashing algorithms and data structures such as a hash table. + /// + /// A hash code for the current System.Globalization.SortKey object. + public override int GetHashCode() + { + return CompareInfo.GetCompareInfo(localeName).GetHashCode(m_String, options); + } + + /// + /// Returns a string that represents the current System.Globalization.SortKey object. + /// + /// A string that represents the current System.Globalization.SortKey object. + public override string ToString() + { + return string.Format("SortKey - {0}, {1}, {3}", localeName, options, OriginalString); + } + } +} diff --git a/source/icu.net.netstandard/icu.net.netstandard.csproj b/source/icu.net.netstandard/icu.net.netstandard.csproj new file mode 100644 index 00000000..49683876 --- /dev/null +++ b/source/icu.net.netstandard/icu.net.netstandard.csproj @@ -0,0 +1,100 @@ + + + + net40;net451;netstandard1.6 + netstandard + ..\icu.net + $(MSBuildThisFileDirectory)obj\ + $(BaseIntermediateOutputPath)$(Configuration)\ + ..\..\output\$(Configuration)\$(MSBuildProjectName) + Icu + icu.net + prompt + 4 + + false + false + false + false + false + false + + + + + $(DefineConstants);FEATURE_ICLONEABLE + + + + + true + $(IcuSourceDirectory)\icu.net.snk + + + + + + + + + + + + + + all + + + all + + + + + + %(RecursiveDir)%(Filename)%(Extension) + + + + + + + + + + $(NuGetPackageRoot)pepitapackage\1.21.6 + + + + + + true + + + + + $(SolutionDir)NuGetBuild/$(PlatformAlias) + $(ProjectDir)NuGetAssets + $(NuGetBuildFolder)/lib/net40/icu.net.dll + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/icu.net.tests/CharacterTests.cs b/source/icu.net.tests/CharacterTests.cs index f0de7478..a01757f9 100644 --- a/source/icu.net.tests/CharacterTests.cs +++ b/source/icu.net.tests/CharacterTests.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2013 SIL International +// Copyright (c) 2013 SIL International // This software is licensed under the MIT license (http://opensource.org/licenses/MIT) using System; using NUnit.Framework; +using System.Globalization; namespace Icu.Tests { @@ -126,10 +127,11 @@ public void IsSpace_String() } [Test] - [SetUICulture("en-US")] [Category("Full ICU")] public void GetPrettyICUCharName() { + SetUICulture("en-US"); + Assert.That(Character.GetPrettyICUCharName("a"), Is.EqualTo("Latin Small Letter A")); Assert.That(Character.GetPrettyICUCharName("ab"), Is.Null); Assert.That(Character.GetPrettyICUCharName(string.Empty), Is.Null); @@ -143,5 +145,15 @@ public void GetCharName() Assert.That(Character.GetCharName(65), Is.EqualTo("LATIN CAPITAL LETTER A")); Assert.That(Character.GetCharName(-1), Is.Null); } + + private void SetUICulture(string culture) + { + var cultureInfo = new CultureInfo(culture); +#if NET40 + System.Threading.Thread.CurrentThread.CurrentUICulture = cultureInfo; +#else + CultureInfo.CurrentUICulture = cultureInfo; +#endif + } } } diff --git a/source/icu.net.tests/Collation/RuleBasedCollatorTests.cs b/source/icu.net.tests/Collation/RuleBasedCollatorTests.cs index 5e1b523c..9d78a67e 100644 --- a/source/icu.net.tests/Collation/RuleBasedCollatorTests.cs +++ b/source/icu.net.tests/Collation/RuleBasedCollatorTests.cs @@ -68,7 +68,7 @@ public void Construct_SyntaxErrorInRules_Throws() { // Previously "<<<<" was assumed to be syntax. Now that is Quaternary so we use a longer string string badRules = "& C < č <<<<<<<< Č < ć <<< Ć"; - Assert.That(() => new RuleBasedCollator(badRules), Throws.TypeOf()); + Assert.That(() => new RuleBasedCollator(badRules), Throws.TypeOf()); } [Test] @@ -81,11 +81,11 @@ public void Clone() } } - [TestCase("", null, "a", Result = -1)] - [TestCase("", "a", null, Result = 1)] - [TestCase("", null, null, Result = 0)] - [TestCase("", "ČUKIĆ SLOBODAN", "CUKIĆ SVETOZAR", Result = -1)] - [TestCase(SerbianRules, "ČUKIĆ SLOBODAN", "CUKIĆ SVETOZAR", Result = 1)] + [TestCase("", null, "a", ExpectedResult = -1)] + [TestCase("", "a", null, ExpectedResult = 1)] + [TestCase("", null, null, ExpectedResult = 0)] + [TestCase("", "ČUKIĆ SLOBODAN", "CUKIĆ SVETOZAR", ExpectedResult = -1)] + [TestCase(SerbianRules, "ČUKIĆ SLOBODAN", "CUKIĆ SVETOZAR", ExpectedResult = 1)] public int Compare(string rules, string string1, string string2) { using (var ucaCollator = new RuleBasedCollator(rules)) @@ -136,22 +136,22 @@ public void SetCollatorStrengthToIdentical() } } - [TestCase(CollationStrength.Tertiary, AlternateHandling.Shifted, "di Silva", "diSilva", Result = + [TestCase(CollationStrength.Tertiary, AlternateHandling.Shifted, "di Silva", "diSilva", ExpectedResult = 0)] - [TestCase(CollationStrength.Tertiary, AlternateHandling.Shifted, "diSilva", "Di Silva", Result = + [TestCase(CollationStrength.Tertiary, AlternateHandling.Shifted, "diSilva", "Di Silva", ExpectedResult = -1)] - [TestCase(CollationStrength.Tertiary, AlternateHandling.Shifted, "U.S.A.", "USA", Result = 0)] + [TestCase(CollationStrength.Tertiary, AlternateHandling.Shifted, "U.S.A.", "USA", ExpectedResult = 0)] [TestCase(CollationStrength.Quaternary, AlternateHandling.Shifted, "di Silva", "diSilva", - Result = -1)] + ExpectedResult = -1)] [TestCase(CollationStrength.Quaternary, AlternateHandling.Shifted, "diSilva", "Di Silva", - Result = -1)] - [TestCase(CollationStrength.Quaternary, AlternateHandling.Shifted, "U.S.A.", "USA", Result = + ExpectedResult = -1)] + [TestCase(CollationStrength.Quaternary, AlternateHandling.Shifted, "U.S.A.", "USA", ExpectedResult = -1)] [TestCase(CollationStrength.Tertiary, AlternateHandling.NonIgnorable, "di Silva", "Di Silva", - Result = -1)] + ExpectedResult = -1)] [TestCase(CollationStrength.Tertiary, AlternateHandling.NonIgnorable, "Di Silva", "diSilva", - Result = -1)] - [TestCase(CollationStrength.Tertiary, AlternateHandling.NonIgnorable, "U.S.A.", "USA", Result = + ExpectedResult = -1)] + [TestCase(CollationStrength.Tertiary, AlternateHandling.NonIgnorable, "U.S.A.", "USA", ExpectedResult = -1)] public int AlternateHandlingSetting(CollationStrength collationStrength, AlternateHandling alternateHandling, string string1, string string2) @@ -179,15 +179,15 @@ public int AlternateHandlingSetting(CollationStrength collationStrength, } } - [TestCase(CaseFirst.LowerFirst, "china", "China", Result = -1)] - [TestCase(CaseFirst.LowerFirst, "China", "denmark", Result = -1)] - [TestCase(CaseFirst.LowerFirst, "denmark", "Denmark", Result = -1)] - [TestCase(CaseFirst.Off, "china", "China", Result = -1)] - [TestCase(CaseFirst.Off, "China", "denmark", Result = -1)] - [TestCase(CaseFirst.Off, "denmark", "Denmark", Result = -1)] - [TestCase(CaseFirst.UpperFirst, "China", "china", Result = -1)] - [TestCase(CaseFirst.UpperFirst, "china", "Denmark", Result = -1)] - [TestCase(CaseFirst.UpperFirst, "Denmark", "denmark", Result = -1)] + [TestCase(CaseFirst.LowerFirst, "china", "China", ExpectedResult = -1)] + [TestCase(CaseFirst.LowerFirst, "China", "denmark", ExpectedResult = -1)] + [TestCase(CaseFirst.LowerFirst, "denmark", "Denmark", ExpectedResult = -1)] + [TestCase(CaseFirst.Off, "china", "China", ExpectedResult = -1)] + [TestCase(CaseFirst.Off, "China", "denmark", ExpectedResult = -1)] + [TestCase(CaseFirst.Off, "denmark", "Denmark", ExpectedResult = -1)] + [TestCase(CaseFirst.UpperFirst, "China", "china", ExpectedResult = -1)] + [TestCase(CaseFirst.UpperFirst, "china", "Denmark", ExpectedResult = -1)] + [TestCase(CaseFirst.UpperFirst, "Denmark", "denmark", ExpectedResult = -1)] public int CaseFirstSetting(CaseFirst caseFirst, string string1, string string2) { /* The Case_First attribute is used to control whether uppercase letters @@ -212,10 +212,10 @@ public int CaseFirstSetting(CaseFirst caseFirst, string string1, string string2) } } - [TestCase(CaseLevel.Off, "role", "Role", Result = 0)] - [TestCase(CaseLevel.Off, "role", "rôle", Result = 0)] - [TestCase(CaseLevel.On, "role", "rôle", Result = 0)] - [TestCase(CaseLevel.On, "rôle", "Role", Result = -1)] + [TestCase(CaseLevel.Off, "role", "Role", ExpectedResult = 0)] + [TestCase(CaseLevel.Off, "role", "rôle", ExpectedResult = 0)] + [TestCase(CaseLevel.On, "role", "rôle", ExpectedResult = 0)] + [TestCase(CaseLevel.On, "rôle", "Role", ExpectedResult = -1)] public int CaseLevelSetting(CaseLevel caseLevel, string string1, string string2) { /*The Case_Level attribute is used when ignoring accents but not case. In @@ -233,12 +233,12 @@ public int CaseLevelSetting(CaseLevel caseLevel, string string1, string string2) } } - [TestCase(FrenchCollation.Off, "cote", "coté", Result = -1)] - [TestCase(FrenchCollation.Off, "coté", "côte", Result = -1)] - [TestCase(FrenchCollation.Off, "côte", "côté", Result = -1)] - [TestCase(FrenchCollation.On, "cote", "côte", Result = -1)] - [TestCase(FrenchCollation.On, "côte", "coté", Result = -1)] - [TestCase(FrenchCollation.On, "coté", "côté", Result = -1)] + [TestCase(FrenchCollation.Off, "cote", "coté", ExpectedResult = -1)] + [TestCase(FrenchCollation.Off, "coté", "côte", ExpectedResult = -1)] + [TestCase(FrenchCollation.Off, "côte", "côté", ExpectedResult = -1)] + [TestCase(FrenchCollation.On, "cote", "côte", ExpectedResult = -1)] + [TestCase(FrenchCollation.On, "côte", "coté", ExpectedResult = -1)] + [TestCase(FrenchCollation.On, "coté", "côté", ExpectedResult = -1)] public int FrenchCollationSetting(FrenchCollation frenchCollation, string string1, string string2) { @@ -271,12 +271,12 @@ private static Collator CreateJaCollator() } } - [TestCase(CollationStrength.Tertiary, "きゅう", "キュウ", Result = 0)] - [TestCase(CollationStrength.Tertiary, "キュウ", "きゆう", Result = -1)] - [TestCase(CollationStrength.Tertiary, "きゆう", "キユウ", Result = 0)] - [TestCase(CollationStrength.Quaternary, "きゅう", "キュウ", Result = -1)] - [TestCase(CollationStrength.Quaternary, "キュウ", "きゆう", Result = -1)] - [TestCase(CollationStrength.Quaternary, "きゆう", "キユウ", Result = -1)] + [TestCase(CollationStrength.Tertiary, "きゅう", "キュウ", ExpectedResult = 0)] + [TestCase(CollationStrength.Tertiary, "キュウ", "きゆう", ExpectedResult = -1)] + [TestCase(CollationStrength.Tertiary, "きゆう", "キユウ", ExpectedResult = 0)] + [TestCase(CollationStrength.Quaternary, "きゅう", "キュウ", ExpectedResult = -1)] + [TestCase(CollationStrength.Quaternary, "キュウ", "きゆう", ExpectedResult = -1)] + [TestCase(CollationStrength.Quaternary, "きゆう", "キユウ", ExpectedResult = -1)] public int HiraganaQuarternarySetting(CollationStrength collationStrength, string string1, string string2) { @@ -298,12 +298,12 @@ public int HiraganaQuarternarySetting(CollationStrength collationStrength, strin } } - [TestCase(NormalizationMode.Off, "ä", "a\u0308", Result = 0)] - [TestCase(NormalizationMode.Off, "a\u0308", "ä\u0323", Result = -1)] - [TestCase(NormalizationMode.Off, "ä\u0323", "ạ\u0308", Result = -1)] - [TestCase(NormalizationMode.On, "ä", "a\u0308", Result = 0)] - [TestCase(NormalizationMode.On, "a\u0308", "ä\u0323", Result = -1)] - [TestCase(NormalizationMode.On, "ä\u0323", "ạ\u0308", Result = 0)] + [TestCase(NormalizationMode.Off, "ä", "a\u0308", ExpectedResult = 0)] + [TestCase(NormalizationMode.Off, "a\u0308", "ä\u0323", ExpectedResult = -1)] + [TestCase(NormalizationMode.Off, "ä\u0323", "ạ\u0308", ExpectedResult = -1)] + [TestCase(NormalizationMode.On, "ä", "a\u0308", ExpectedResult = 0)] + [TestCase(NormalizationMode.On, "a\u0308", "ä\u0323", ExpectedResult = -1)] + [TestCase(NormalizationMode.On, "ä\u0323", "ạ\u0308", ExpectedResult = 0)] public int NormalizationModeSetting(NormalizationMode normalizationMode, string string1, string string2) { @@ -330,13 +330,13 @@ public int NormalizationModeSetting(NormalizationMode normalizationMode, string } // 1 < 10 < 2 < 20 - [TestCase(NumericCollation.Off, "1", "10", Result = -1)] - [TestCase(NumericCollation.Off, "10", "2", Result = -1)] - [TestCase(NumericCollation.Off, "2", "20", Result = -1)] + [TestCase(NumericCollation.Off, "1", "10", ExpectedResult = -1)] + [TestCase(NumericCollation.Off, "10", "2", ExpectedResult = -1)] + [TestCase(NumericCollation.Off, "2", "20", ExpectedResult = -1)] // 1 < 2 < 10 < 20 - [TestCase(NumericCollation.On, "1", "10", Result = -1)] - [TestCase(NumericCollation.On, "10", "2", Result = 1)] - [TestCase(NumericCollation.On, "2", "20", Result = -1)] + [TestCase(NumericCollation.On, "1", "10", ExpectedResult = -1)] + [TestCase(NumericCollation.On, "10", "2", ExpectedResult = 1)] + [TestCase(NumericCollation.On, "2", "20", ExpectedResult = -1)] public int NumericCollationSetting(NumericCollation numericCollation, string string1, string string2) { @@ -348,15 +348,15 @@ public int NumericCollationSetting(NumericCollation numericCollation, string str } } - [TestCase(CollationStrength.Primary, "role", "Role", Result = 0)] - [TestCase(CollationStrength.Primary, "Role", "rôle", Result = 0)] - [TestCase(CollationStrength.Secondary, "role", "Role", Result = 0)] - [TestCase(CollationStrength.Secondary, "Role", "rôle", Result = -1)] - [TestCase(CollationStrength.Tertiary, "role", "Role", Result = -1)] - [TestCase(CollationStrength.Tertiary, "Role", "rôle", Result = -1)] - [TestCase(CollationStrength.Quaternary, "ab", "a c", Result = -1)] - [TestCase(CollationStrength.Quaternary, "a c", "a-c", Result = -1)] - [TestCase(CollationStrength.Quaternary, "a-c", "ac", Result = -1)] + [TestCase(CollationStrength.Primary, "role", "Role", ExpectedResult = 0)] + [TestCase(CollationStrength.Primary, "Role", "rôle", ExpectedResult = 0)] + [TestCase(CollationStrength.Secondary, "role", "Role", ExpectedResult = 0)] + [TestCase(CollationStrength.Secondary, "Role", "rôle", ExpectedResult = -1)] + [TestCase(CollationStrength.Tertiary, "role", "Role", ExpectedResult = -1)] + [TestCase(CollationStrength.Tertiary, "Role", "rôle", ExpectedResult = -1)] + [TestCase(CollationStrength.Quaternary, "ab", "a c", ExpectedResult = -1)] + [TestCase(CollationStrength.Quaternary, "a c", "a-c", ExpectedResult = -1)] + [TestCase(CollationStrength.Quaternary, "a-c", "ac", ExpectedResult = -1)] public int StrengthSetting(CollationStrength collationStrength, string string1, string string2) { @@ -831,7 +831,7 @@ public void GetAvailableLocales_ReturnsList() [Category("KnownMonoIssue")] public void ConvertToIcuRules_SurrogateCharacterLowBound_Throws() { - Assert.Throws( + Assert.Throws( // Invalid unicode character escape sequence: () => new RuleBasedCollator(IcuStart + "\ud800") ); @@ -845,7 +845,7 @@ public void ConvertToIcuRules_SurrogateCharacterLowBound_Throws() [Category("KnownMonoIssue")] public void ConvertToIcuRules_SurrogateCharacterHighBound_Throws() { - Assert.Throws( + Assert.Throws( // Invalid unicode character escape sequence: () => new RuleBasedCollator(IcuStart + "\udfff") ); @@ -859,7 +859,7 @@ public void ConvertToIcuRules_SurrogateCharacterHighBound_Throws() [Category("KnownMonoIssue")] public void ConvertToIcuRules_SurrogateCharactersOutOfOrder_Throws() { - Assert.Throws( + Assert.Throws( // Invalid unicode character escape sequence: () => new RuleBasedCollator(IcuStart + "a << \udc00\ud800") ); @@ -905,7 +905,8 @@ public void GetSortRules_English() var collationRules = Collator.GetCollationRules(locale, UColRuleOption.UCOL_FULL_RULES); Assert.IsEmpty(tailoredRules); - Assert.IsNotNullOrEmpty(collationRules); + Assert.IsNotNull(collationRules); + Assert.IsNotEmpty(collationRules); } } } diff --git a/source/icu.net.tests/Collation/SortKeyTests.cs b/source/icu.net.tests/Collation/SortKeyTests.cs index 3a2f0b6c..af68cb48 100644 --- a/source/icu.net.tests/Collation/SortKeyTests.cs +++ b/source/icu.net.tests/Collation/SortKeyTests.cs @@ -15,34 +15,30 @@ public class SortKeyTests public const int Same = 0; [Test] - [ExpectedException(typeof(ArgumentNullException))] public void SortKey_nullKeyData_Throws() { - Collator.CreateSortKey("hello", null); + Assert.Throws(() => Collator.CreateSortKey("hello", null)); } [Test] - [ExpectedException(typeof(ArgumentOutOfRangeException))] public void SortKey_KeyDataLengthTooLarge_Throws() { byte[] keyData = new byte[] { 0xae, 0x1,0x20,0x1}; - Collator.CreateSortKey("hello", keyData, keyData.Length+1); + Assert.Throws(() => Collator.CreateSortKey("hello", keyData, keyData.Length + 1)); } [Test] - [ExpectedException(typeof(ArgumentOutOfRangeException))] public void SortKey_KeyDataLengthNegative_Throws() { byte[] keyData = new byte[] { 0xae, 0x1, 0x20,0x1 }; - Collator.CreateSortKey("hello", keyData, -1); + Assert.Throws(() => Collator.CreateSortKey("hello", keyData, -1)); } [Test] - [ExpectedException(typeof(ArgumentNullException))] public void SortKey_nullOriginalString_Throws() { byte[] keyData = new byte[] { 0xae, 0x1, 0x20,0x1 }; - Collator.CreateSortKey(null, keyData); + Assert.Throws(() => Collator.CreateSortKey(null, keyData)); } [Test] @@ -73,28 +69,25 @@ public void Compare_keyDataByteChanges_NotAffected() } [Test] - [ExpectedException(typeof(ArgumentNullException))] public void Compare_bothnull_throws() { - SortKey.Compare(null, null); + Assert.Throws(() => SortKey.Compare(null, null)); } [Test] - [ExpectedException(typeof(ArgumentNullException))] public void Compare_firstnull_throws() { byte[] keyData = new byte[] { 0xae, 0x1, 0x20, 0x1 }; SortKey sortKey = Collator.CreateSortKey("heo", keyData); - SortKey.Compare(null, sortKey); + Assert.Throws(() => SortKey.Compare(null, sortKey)); } [Test] - [ExpectedException(typeof(ArgumentNullException))] public void Compare_secondnull_throws() { byte[] keyData = new byte[] { 0xae, 0x1, 0x20, 0x1 }; SortKey sortKey = Collator.CreateSortKey("heo", keyData); - SortKey.Compare(sortKey, null); + Assert.Throws(() => SortKey.Compare(sortKey, null)); } [Test] diff --git a/source/icu.net.tests/LocaleTests.cs b/source/icu.net.tests/LocaleTests.cs index 0d9580e6..84448ddd 100644 --- a/source/icu.net.tests/LocaleTests.cs +++ b/source/icu.net.tests/LocaleTests.cs @@ -1,20 +1,36 @@ -// Copyright (c) 2013 SIL International +// Copyright (c) 2013 SIL International // This software is licensed under the MIT license (http://opensource.org/licenses/MIT) using System; using NUnit.Framework; using Icu; +using System.Globalization; namespace Icu.Tests { [TestFixture] - [SetCulture("es-ES")] - [SetUICulture("en-US")] public class LocaleTests { + private readonly CultureInfo DefaultCulture = CultureInfo.CurrentCulture; + private readonly CultureInfo DefaultUICulture = CultureInfo.CurrentUICulture; + + [SetUp] + public void Setup() + { + SetCulture("es-ES"); + SetUICulture("en-US"); + } + + [TearDown] + public void TearDown() + { + SetCulture(DefaultCulture.Name); + SetUICulture(DefaultUICulture.Name); + } + [Test] - [SetUICulture("de-DE")] public void ConstructDefault() { + SetUICulture("de-DE"); Locale locale = new Locale(); Assert.That(locale.Id, Is.EqualTo("de_DE")); } @@ -203,10 +219,11 @@ public void DisplayLanguage() } [Test] - [SetUICulture("de-DE")] [Category("Full ICU")] public void DisplayLanguage_DifferentDefaultLocale() { + SetUICulture("de-DE"); + Locale locale = new Locale("en-US"); Assert.That(locale.DisplayLanguage, Is.EqualTo("Englisch")); } @@ -256,5 +273,25 @@ public void ImplicitCast() Locale locale = "en-US"; Assert.That(locale.Id, Is.EqualTo("en_US")); } + + private void SetUICulture(string culture) + { + var cultureInfo = new CultureInfo(culture); +#if NET40 + System.Threading.Thread.CurrentThread.CurrentUICulture = cultureInfo; +#else + CultureInfo.CurrentUICulture = cultureInfo; +#endif + } + + private void SetCulture(string culture) + { + var cultureInfo = new CultureInfo(culture); +#if NET40 + System.Threading.Thread.CurrentThread.CurrentCulture = cultureInfo; +#else + CultureInfo.CurrentCulture = cultureInfo; +#endif + } } } diff --git a/source/icu.net.tests/NativeMethodsTests.cs b/source/icu.net.tests/NativeMethodsTests.cs index 86cecf76..520d99d7 100644 --- a/source/icu.net.tests/NativeMethodsTests.cs +++ b/source/icu.net.tests/NativeMethodsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2016 SIL International +// Copyright (c) 2016 SIL International // This software is licensed under the MIT license (http://opensource.org/licenses/MIT) using System; using System.Diagnostics; @@ -8,9 +8,13 @@ namespace Icu.Tests { - [TestFixture] +#if NETCOREAPP1_1 + [Ignore("System.Diagnostics.Process is not supported in .NETStandard 1.6.")] +#else [Platform(Exclude = "Linux", Reason = "These tests require ICU4C installed from NuGet packages which isn't available on Linux")] +#endif + [TestFixture] public class NativeMethodsTests { private string _tmpDir; @@ -34,7 +38,14 @@ private static string GetArchSubdir(string prefix = "") } private static string OutputDirectory => Path.GetDirectoryName( - new Uri(typeof(NativeMethodsTests).Assembly.CodeBase).LocalPath); + new Uri( +#if NET40 + typeof(NativeMethodsTests).Assembly.CodeBase +#else + typeof(NativeMethodsTests).GetTypeInfo().Assembly.CodeBase +#endif + ) + .LocalPath); private static string IcuDirectory => Path.Combine(OutputDirectory, "lib", GetArchSubdir("win-")); diff --git a/source/icu.net.tests/NormalizerTests.cs b/source/icu.net.tests/NormalizerTests.cs index ddfda1eb..adfe601d 100644 --- a/source/icu.net.tests/NormalizerTests.cs +++ b/source/icu.net.tests/NormalizerTests.cs @@ -10,20 +10,20 @@ namespace Icu.Tests [TestFixture] public class NormalizerTests { - [TestCase("XA\u0308bc", Normalizer.UNormalizationMode.UNORM_NFC, Result = "X\u00C4bc")] - [TestCase("X\u00C4bc", Normalizer.UNormalizationMode.UNORM_NFD, Result = "XA\u0308bc")] - [TestCase("tést", Normalizer.UNormalizationMode.UNORM_NFD, Result = "te\u0301st")] - [TestCase("te\u0301st", Normalizer.UNormalizationMode.UNORM_NFC, Result = "tést")] - [TestCase("te\u0301st", Normalizer.UNormalizationMode.UNORM_NFD, Result = "te\u0301st")] + [TestCase("XA\u0308bc", Normalizer.UNormalizationMode.UNORM_NFC, ExpectedResult = "X\u00C4bc")] + [TestCase("X\u00C4bc", Normalizer.UNormalizationMode.UNORM_NFD, ExpectedResult = "XA\u0308bc")] + [TestCase("tést", Normalizer.UNormalizationMode.UNORM_NFD, ExpectedResult = "te\u0301st")] + [TestCase("te\u0301st", Normalizer.UNormalizationMode.UNORM_NFC, ExpectedResult = "tést")] + [TestCase("te\u0301st", Normalizer.UNormalizationMode.UNORM_NFD, ExpectedResult = "te\u0301st")] public string Normalize(string src, Normalizer.UNormalizationMode mode) { return Normalizer.Normalize(src, mode); } - [TestCase("X\u00C4bc", Normalizer.UNormalizationMode.UNORM_NFC, Result = true)] - [TestCase("XA\u0308bc", Normalizer.UNormalizationMode.UNORM_NFC, Result = false)] - [TestCase("X\u00C4bc", Normalizer.UNormalizationMode.UNORM_NFD, Result = false)] - [TestCase("XA\u0308bc", Normalizer.UNormalizationMode.UNORM_NFD, Result = true)] + [TestCase("X\u00C4bc", Normalizer.UNormalizationMode.UNORM_NFC, ExpectedResult = true)] + [TestCase("XA\u0308bc", Normalizer.UNormalizationMode.UNORM_NFC, ExpectedResult = false)] + [TestCase("X\u00C4bc", Normalizer.UNormalizationMode.UNORM_NFD, ExpectedResult = false)] + [TestCase("XA\u0308bc", Normalizer.UNormalizationMode.UNORM_NFD, ExpectedResult = true)] public bool IsNormalized(string src, Normalizer.UNormalizationMode expectNormalizationMode) { return Normalizer.IsNormalized(src, expectNormalizationMode); diff --git a/source/icu.net.tests/SetUpFixture.cs b/source/icu.net.tests/SetUpFixture.cs index f187b08d..0db5d595 100644 --- a/source/icu.net.tests/SetUpFixture.cs +++ b/source/icu.net.tests/SetUpFixture.cs @@ -8,13 +8,21 @@ namespace Icu.Tests [SetUpFixture] public class SetUpFixture { +#if NUNIT2 [SetUp] +#else + [OneTimeSetUp] +#endif public void RunBeforeAnyTests() { Wrapper.Init(); } +#if NUNIT2 [TearDown] +#else + [OneTimeTearDown] +#endif public void RunAfterAnyTests() { Wrapper.Cleanup(); diff --git a/source/icu.net.tests/UnicodeSetTests.cs b/source/icu.net.tests/UnicodeSetTests.cs index 0e967a41..9a4f1142 100644 --- a/source/icu.net.tests/UnicodeSetTests.cs +++ b/source/icu.net.tests/UnicodeSetTests.cs @@ -1,4 +1,6 @@ -using System; +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -49,7 +51,7 @@ public void SinglePatternToChar() IEnumerable unicodeSet = UnicodeSet.ToCharacters(pattern); IEnumerable expected = "A".Split(' '); Assert.That(unicodeSet, Is.EqualTo(expected)); - + } [Test] diff --git a/source/icu.net.tests/VersionInfoTests.cs b/source/icu.net.tests/VersionInfoTests.cs index 63b4c4f4..ac3b3cb6 100644 --- a/source/icu.net.tests/VersionInfoTests.cs +++ b/source/icu.net.tests/VersionInfoTests.cs @@ -1,4 +1,6 @@ -using NUnit.Framework; +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using NUnit.Framework; namespace Icu.Tests { diff --git a/source/icu.net.tests/icu.net.tests.csproj b/source/icu.net.tests/icu.net.tests.csproj index 1a5672e9..2fc8779d 100644 --- a/source/icu.net.tests/icu.net.tests.csproj +++ b/source/icu.net.tests/icu.net.tests.csproj @@ -110,7 +110,7 @@ - $(DefineConstants);ICU_VER_$(icu_ver) + $(DefineConstants);NET40;NUNIT2 ..\..\output\$(Configuration) diff --git a/source/icu.net/Boundary.cs b/source/icu.net/Boundary.cs index 4ab90bc3..85155ec4 100644 --- a/source/icu.net/Boundary.cs +++ b/source/icu.net/Boundary.cs @@ -1,4 +1,6 @@ -using System; +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using System; namespace Icu { diff --git a/source/icu.net/BreakIterators/RuleBasedBreakIterator.cs b/source/icu.net/BreakIterators/RuleBasedBreakIterator.cs index 68992e2f..5b132154 100644 --- a/source/icu.net/BreakIterators/RuleBasedBreakIterator.cs +++ b/source/icu.net/BreakIterators/RuleBasedBreakIterator.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2013-2017 SIL International +// Copyright (c) 2013-2017 SIL International // This software is licensed under the MIT license (http://opensource.org/licenses/MIT) using System; using System.Collections.Generic; @@ -16,6 +16,9 @@ namespace Icu public class RuleBasedBreakIterator : BreakIterator { private readonly UBreakIteratorType _iteratorType; + /// + /// Sets the rules this break iterator uses + /// protected string Rules; private readonly Locale _locale = DefaultLocale; @@ -84,6 +87,9 @@ private RuleBasedBreakIterator(RuleBasedBreakIterator bi) throw new Exception($"BreakIterator.ubrk_safeClone() failed with code {errorCode}"); } + /// + /// Clones this RuleBasedBreakIterator + /// public override BreakIterator Clone() { return new RuleBasedBreakIterator(this); diff --git a/source/icu.net/Character.cs b/source/icu.net/Character.cs index 98c88890..81108c9b 100644 --- a/source/icu.net/Character.cs +++ b/source/icu.net/Character.cs @@ -724,8 +724,8 @@ public static string GetPrettyICUCharName(string chr) string name; if (CharName(chr[0], UCharNameChoice.UNICODE_CHAR_NAME, out name) > 0) { - name = name.ToLower(); - return CultureInfo.CurrentUICulture.TextInfo.ToTitleCase(name); + var lowercase = CultureInfo.CurrentUICulture.TextInfo.ToLower(name); + return UnicodeString.ToTitle(lowercase, new Locale()); } } return null; diff --git a/source/icu.net/Collation/Collator.cs b/source/icu.net/Collation/Collator.cs index 471cb674..b1554572 100644 --- a/source/icu.net/Collation/Collator.cs +++ b/source/icu.net/Collation/Collator.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2013 SIL International +// Copyright (c) 2013 SIL International // This software is licensed under the MIT license (http://opensource.org/licenses/MIT) using System; using System.Collections.Generic; @@ -14,7 +14,10 @@ namespace Icu.Collation /// You use this class to build searching and sorting routines for natural /// language text. /// - public abstract class Collator : IComparer, ICloneable, IDisposable + public abstract class Collator : IComparer, IDisposable +#if FEATURE_ICLONEABLE + , ICloneable +#endif { /// /// Gets or sets the minimum strength that will be used in comparison @@ -28,7 +31,7 @@ public abstract class Collator : IComparer, ICloneable, IDisposable public abstract NormalizationMode NormalizationMode{ get; set; } /// - /// Gets or sets the FrenchCollation. Attribute for direction of + /// Gets or sets the FrenchCollation. Attribute for directionCollof /// secondary weights - used in Canadian French. /// public abstract FrenchCollation FrenchCollation{ get; set; } @@ -151,7 +154,7 @@ public static Collator Create(CultureInfo cultureInfo, Fallback fallback) { throw new ArgumentNullException(); } - return Create(cultureInfo.IetfLanguageTag, fallback); + return Create(cultureInfo.Name, fallback); } /// @@ -189,13 +192,20 @@ static public SortKey CreateSortKey(string originalString, byte[] keyData, int k throw new ArgumentOutOfRangeException("keyDataLength"); } - SortKey sortKey = CultureInfo.InvariantCulture.CompareInfo.GetSortKey(string.Empty); + CompareOptions options = CompareOptions.None; + +#if NETSTANDARD1_6 + SortKey sortKey = new SortKey(CultureInfo.InvariantCulture.Name, originalString, options, keyData); +#else + SortKey sortKey = CultureInfo.InvariantCulture.CompareInfo.GetSortKey(string.Empty, options); SetInternalOriginalStringField(sortKey, originalString); SetInternalKeyDataField(sortKey, keyData, keyDataLength); +#endif return sortKey; } +#if !NETSTANDARD1_6 private static void SetInternalKeyDataField(SortKey sortKey, byte[] keyData, int keyDataLength) { byte[] keyDataCopy = new byte[keyDataLength]; @@ -233,19 +243,9 @@ private static void SetInternalFieldForPublicProperty( { Type type = instance.GetType(); - FieldInfo fieldInfo; - if (IsRunningOnMono()) - { - fieldInfo = type.GetField(monoInternalFieldName, - BindingFlags.Instance - | BindingFlags.NonPublic); - } - else //Is Running On .Net - { - fieldInfo = type.GetField(netInternalFieldName, - BindingFlags.Instance - | BindingFlags.NonPublic); - } + string fieldName = IsRunningOnMono() ? monoInternalFieldName : netInternalFieldName; + + FieldInfo fieldInfo = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); Debug.Assert(fieldInfo != null, "Unsupported runtime", @@ -263,6 +263,7 @@ private static bool IsRunningOnMono() { return Type.GetType("Mono.Runtime") != null; } +#endif /// /// Simple class to allow passing collation error info back to the caller of CheckRules. diff --git a/source/icu.net/Collation/RuleBasedCollator.cs b/source/icu.net/Collation/RuleBasedCollator.cs index 153cf529..a70f8479 100644 --- a/source/icu.net/Collation/RuleBasedCollator.cs +++ b/source/icu.net/Collation/RuleBasedCollator.cs @@ -1,10 +1,15 @@ -// Copyright (c) 2013 SIL International +// Copyright (c) 2013 SIL International // This software is licensed under the MIT license (http://opensource.org/licenses/MIT) using System; using System.Collections.Generic; -using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; + +#if NETSTANDARD1_6 +using Icu; +#else using System.Globalization; +using System.Runtime.ConstrainedExecution; +#endif namespace Icu.Collation { @@ -26,7 +31,9 @@ public SafeRuleBasedCollatorHandle() : /// true if the handle is released successfully; otherwise, in the event of a catastrophic failure, false. /// In this case, it generates a ReleaseHandleFailed Managed Debugging Assistant. /// +#if !NETSTANDARD1_6 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] +#endif protected override bool ReleaseHandle() { if (handle != IntPtr.Zero) @@ -301,7 +308,7 @@ public static IList GetAvailableCollationLocales() } finally { - en.Close(); + en.Dispose(); } return locales; } @@ -318,7 +325,9 @@ public SafeEnumeratorHandle() /// ///true if the handle is released successfully; otherwise, in the event of a catastrophic failure, false. In this case, it generates a ReleaseHandleFailed Managed Debugging Assistant. /// +#if !NETSTANDARD1_6 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] +#endif protected override bool ReleaseHandle() { NativeMethods.uenum_close(handle); diff --git a/source/icu.net/ErrorCode.cs b/source/icu.net/ErrorCode.cs index 8e7f95ef..53b9dfac 100644 --- a/source/icu.net/ErrorCode.cs +++ b/source/icu.net/ErrorCode.cs @@ -1,8 +1,6 @@ // Copyright (c) 2013 SIL International // This software is licensed under the MIT license (http://opensource.org/licenses/MIT) using System; -using System.ComponentModel; -using System.Data; using System.IO; namespace Icu @@ -428,15 +426,15 @@ private static void ThrowIfError(ErrorCode e, string extraInfo, bool throwOnWarn case ErrorCode.ILLEGAL_ARGUMENT_ERROR: throw new ArgumentException(extraInfo); case ErrorCode.MISSING_RESOURCE_ERROR: - throw new ApplicationException("The requested resource cannot be found " + extraInfo); + throw new MissingResourceException("The requested resource cannot be found " + extraInfo); case ErrorCode.INVALID_FORMAT_ERROR: - throw new ApplicationException("Data format is not what is expected " + extraInfo); + throw new ArgumentException("Data format is not what is expected " + extraInfo); case ErrorCode.FILE_ACCESS_ERROR: throw new FileNotFoundException("The requested file cannot be found " + extraInfo); case ErrorCode.INTERNAL_PROGRAM_ERROR: throw new InvalidOperationException("Indicates a bug in the library code " + extraInfo); case ErrorCode.MESSAGE_PARSE_ERROR: - throw new ApplicationException("Unable to parse a message (message format) " + extraInfo); + throw new ArgumentException("Unable to parse a message (message format) " + extraInfo); case ErrorCode.MEMORY_ALLOCATION_ERROR: throw new OutOfMemoryException(extraInfo); case ErrorCode.INDEX_OUTOFBOUNDS_ERROR: @@ -454,7 +452,7 @@ private static void ThrowIfError(ErrorCode e, string extraInfo, bool throwOnWarn case ErrorCode.INVALID_TABLE_FILE: throw new FileNotFoundException("Conversion table file not found " + extraInfo); case ErrorCode.BUFFER_OVERFLOW_ERROR: - throw new InternalBufferOverflowException("A result would not fit in the supplied buffer " + extraInfo); + throw new OverflowException("A result would not fit in the supplied buffer " + extraInfo); case ErrorCode.UNSUPPORTED_ERROR: throw new InvalidOperationException("Requested operation not supported in current context " + extraInfo); case ErrorCode.RESOURCE_TYPE_MISMATCH: @@ -472,79 +470,79 @@ private static void ThrowIfError(ErrorCode e, string extraInfo, bool throwOnWarn case ErrorCode.STATE_TOO_OLD_ERROR: throw new NotSupportedException("ICU cannot construct a service from this state, as it is no longer supported " + extraInfo); case ErrorCode.TOO_MANY_ALIASES_ERROR: - throw new ApplicationException("There are too many aliases in the path to the requested resource.\nIt is very possible that a circular alias definition has occured " + extraInfo); + throw new ArgumentException("There are too many aliases in the path to the requested resource.\nIt is very possible that a circular alias definition has occured " + extraInfo); case ErrorCode.ENUM_OUT_OF_SYNC_ERROR: throw new InvalidOperationException("Enumeration out of sync with underlying collection " + extraInfo); case ErrorCode.INVARIANT_CONVERSION_ERROR: - throw new ApplicationException("Unable to convert a UChar* string to char* with the invariant converter " + extraInfo); + throw new ArgumentException("Unable to convert a UChar* string to char* with the invariant converter " + extraInfo); case ErrorCode.INVALID_STATE_ERROR: throw new InvalidOperationException("Requested operation can not be completed with ICU in its current state " + extraInfo); case ErrorCode.COLLATOR_VERSION_MISMATCH: throw new InvalidOperationException("Collator version is not compatible with the base version " + extraInfo); case ErrorCode.USELESS_COLLATOR_ERROR: - throw new ApplicationException("Collator is options only and no base is specified " + extraInfo); + throw new ArgumentException("Collator is options only and no base is specified " + extraInfo); case ErrorCode.NO_WRITE_PERMISSION: - throw new ApplicationException("Attempt to modify read-only or constant data. " + extraInfo); + throw new InvalidOperationException("Attempt to modify read-only or constant data. " + extraInfo); case ErrorCode.BAD_VARIABLE_DEFINITION: - throw new ApplicationException("Transliterator Parse Error: Missing '$' or duplicate variable name " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: Missing '$' or duplicate variable name " + extraInfo); case ErrorCode.MALFORMED_RULE: - throw new ApplicationException("Transliterator Parse Error: Elements of a rule are misplaced " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: Elements of a rule are misplaced " + extraInfo); case ErrorCode.MALFORMED_SET: - throw new ApplicationException("Transliterator Parse Error: A UnicodeSet pattern is invalid " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A UnicodeSet pattern is invalid " + extraInfo); case ErrorCode.MALFORMED_UNICODE_ESCAPE: - throw new ApplicationException("Transliterator Parse Error: A Unicode escape pattern is invalid " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A Unicode escape pattern is invalid " + extraInfo); case ErrorCode.MALFORMED_VARIABLE_DEFINITION: - throw new ApplicationException("Transliterator Parse Error: A variable definition is invalid " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A variable definition is invalid " + extraInfo); case ErrorCode.MALFORMED_VARIABLE_REFERENCE: - throw new ApplicationException("Transliterator Parse Error: A variable reference is invalid " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A variable reference is invalid " + extraInfo); case ErrorCode.MISPLACED_ANCHOR_START: - throw new ApplicationException("Transliterator Parse Error: A start anchor appears at an illegal position " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A start anchor appears at an illegal position " + extraInfo); case ErrorCode.MISPLACED_CURSOR_OFFSET: - throw new ApplicationException("Transliterator Parse Error: A cursor offset occurs at an illegal position " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A cursor offset occurs at an illegal position " + extraInfo); case ErrorCode.MISPLACED_QUANTIFIER: - throw new ApplicationException("Transliterator Parse Error: A quantifier appears after a segment close delimiter " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A quantifier appears after a segment close delimiter " + extraInfo); case ErrorCode.MISSING_OPERATOR: - throw new ApplicationException("Transliterator Parse Error: A rule contains no operator " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A rule contains no operator " + extraInfo); case ErrorCode.MULTIPLE_ANTE_CONTEXTS: - throw new ApplicationException("Transliterator Parse Error: More than one ante context " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: More than one ante context " + extraInfo); case ErrorCode.MULTIPLE_CURSORS: - throw new ApplicationException("Transliterator Parse Error: More than one cursor " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: More than one cursor " + extraInfo); case ErrorCode.MULTIPLE_POST_CONTEXTS: - throw new ApplicationException("Transliterator Parse Error: More than one post context " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: More than one post context " + extraInfo); case ErrorCode.TRAILING_BACKSLASH: - throw new ApplicationException("Transliterator Parse Error: A dangling backslash " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A dangling backslash " + extraInfo); case ErrorCode.UNDEFINED_SEGMENT_REFERENCE: - throw new ApplicationException("Transliterator Parse Error: A segment reference does not correspond to a defined segment " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A segment reference does not correspond to a defined segment " + extraInfo); case ErrorCode.UNDEFINED_VARIABLE: - throw new ApplicationException("Transliterator Parse Error: A variable reference does not correspond to a defined variable " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A variable reference does not correspond to a defined variable " + extraInfo); case ErrorCode.UNQUOTED_SPECIAL: - throw new ApplicationException("Transliterator Parse Error: A special character was not quoted or escaped " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A special character was not quoted or escaped " + extraInfo); case ErrorCode.UNTERMINATED_QUOTE: - throw new ApplicationException("Transliterator Parse Error: A closing single quote is missing " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A closing single quote is missing " + extraInfo); case ErrorCode.RULE_MASK_ERROR: - throw new ApplicationException("Transliterator Parse Error: A rule is hidden by an earlier more general rule " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A rule is hidden by an earlier more general rule " + extraInfo); case ErrorCode.MISPLACED_COMPOUND_FILTER: - throw new ApplicationException("Transliterator Parse Error: A compound filter is in an invalid location " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A compound filter is in an invalid location " + extraInfo); case ErrorCode.MULTIPLE_COMPOUND_FILTERS: - throw new ApplicationException("Transliterator Parse Error: More than one compound filter " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: More than one compound filter " + extraInfo); case ErrorCode.INVALID_RBT_SYNTAX: - throw new ApplicationException("Transliterator Parse Error: A '::id' rule was passed to the RuleBasedTransliterator parser " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A '::id' rule was passed to the RuleBasedTransliterator parser " + extraInfo); case ErrorCode.MALFORMED_PRAGMA: - throw new ApplicationException("Transliterator Parse Error: A 'use' pragma is invlalid " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A 'use' pragma is invlalid " + extraInfo); case ErrorCode.UNCLOSED_SEGMENT: - throw new ApplicationException("Transliterator Parse Error: A closing ')' is missing " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A closing ')' is missing " + extraInfo); case ErrorCode.VARIABLE_RANGE_EXHAUSTED: - throw new ApplicationException("Transliterator Parse Error: Too many stand-ins generated for the given variable range " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: Too many stand-ins generated for the given variable range " + extraInfo); case ErrorCode.VARIABLE_RANGE_OVERLAP: - throw new ApplicationException("Transliterator Parse Error: The variable range overlaps characters used in rules " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: The variable range overlaps characters used in rules " + extraInfo); case ErrorCode.ILLEGAL_CHARACTER: - throw new ApplicationException("Transliterator Parse Error: A special character is outside its allowed context " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A special character is outside its allowed context " + extraInfo); case ErrorCode.INTERNAL_TRANSLITERATOR_ERROR: - throw new ApplicationException("Transliterator Parse Error: Internal transliterator system error " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: Internal transliterator system error " + extraInfo); case ErrorCode.INVALID_ID: - throw new ApplicationException("Transliterator Parse Error: A '::id' rule specifies an unknown transliterator " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A '::id' rule specifies an unknown transliterator " + extraInfo); case ErrorCode.INVALID_FUNCTION: - throw new ApplicationException("Transliterator Parse Error: A '&fn()' rule specifies an unknown transliterator " + extraInfo); + throw new TransliteratorParseException("Transliterator Parse Error: A '&fn()' rule specifies an unknown transliterator " + extraInfo); case ErrorCode.UNEXPECTED_TOKEN: throw new SyntaxErrorException("Format Parse Error: Unexpected token in format pattern " + extraInfo); case ErrorCode.MULTIPLE_DECIMAL_SEPARATORS: @@ -574,97 +572,97 @@ private static void ThrowIfError(ErrorCode e, string extraInfo, bool throwOnWarn case ErrorCode.DEFAULT_KEYWORD_MISSING: throw new SyntaxErrorException("Format Parse Error: Missing DEFAULT rule in plural rules. " + extraInfo); case ErrorCode.BRK_INTERNAL_ERROR: - throw new ApplicationException("Break Error: An internal error (bug) was detected. " + extraInfo); + throw new BreakException("Break Error: An internal error (bug) was detected. " + extraInfo); case ErrorCode.BRK_HEX_DIGITS_EXPECTED: - throw new ApplicationException("Break Error: Hex digits expected as part of a escaped char in a rule. " + extraInfo); + throw new BreakException("Break Error: Hex digits expected as part of a escaped char in a rule. " + extraInfo); case ErrorCode.BRK_SEMICOLON_EXPECTED: - throw new ApplicationException("Break Error: Missing ';' at the end of a RBBI rule. " + extraInfo); + throw new BreakException("Break Error: Missing ';' at the end of a RBBI rule. " + extraInfo); case ErrorCode.BRK_RULE_SYNTAX: - throw new ApplicationException("Break Error: Syntax error in RBBI rule. " + extraInfo); + throw new BreakException("Break Error: Syntax error in RBBI rule. " + extraInfo); case ErrorCode.BRK_UNCLOSED_SET: - throw new ApplicationException("Break Error: UnicodeSet witing an RBBI rule missing a closing ']'. " + extraInfo); + throw new BreakException("Break Error: UnicodeSet witing an RBBI rule missing a closing ']'. " + extraInfo); case ErrorCode.BRK_ASSIGN_ERROR: - throw new ApplicationException("Break Error: Syntax error in RBBI rule assignment statement. " + extraInfo); + throw new BreakException("Break Error: Syntax error in RBBI rule assignment statement. " + extraInfo); case ErrorCode.BRK_VARIABLE_REDFINITION: - throw new ApplicationException("Break Error: RBBI rule $Variable redefined. " + extraInfo); + throw new BreakException("Break Error: RBBI rule $Variable redefined. " + extraInfo); case ErrorCode.BRK_MISMATCHED_PAREN: - throw new ApplicationException("Break Error: Mis-matched parentheses in an RBBI rule. " + extraInfo); + throw new BreakException("Break Error: Mis-matched parentheses in an RBBI rule. " + extraInfo); case ErrorCode.BRK_NEW_LINE_IN_QUOTED_STRING: - throw new ApplicationException("Break Error: Missing closing quote in an RBBI rule. " + extraInfo); + throw new BreakException("Break Error: Missing closing quote in an RBBI rule. " + extraInfo); case ErrorCode.BRK_UNDEFINED_VARIABLE: - throw new ApplicationException("Break Error: Use of an undefined $Variable in an RBBI rule. " + extraInfo); + throw new BreakException("Break Error: Use of an undefined $Variable in an RBBI rule. " + extraInfo); case ErrorCode.BRK_INIT_ERROR: - throw new ApplicationException("Break Error: Initialization failure. Probable missing ICU Data. " + extraInfo); + throw new BreakException("Break Error: Initialization failure. Probable missing ICU Data. " + extraInfo); case ErrorCode.BRK_RULE_EMPTY_SET: - throw new ApplicationException("Break Error: Rule contains an empty Unicode Set. " + extraInfo); + throw new BreakException("Break Error: Rule contains an empty Unicode Set. " + extraInfo); case ErrorCode.BRK_UNRECOGNIZED_OPTION: - throw new ApplicationException("Break Error: !!option in RBBI rules not recognized. " + extraInfo); + throw new BreakException("Break Error: !!option in RBBI rules not recognized. " + extraInfo); case ErrorCode.BRK_MALFORMED_RULE_TAG: - throw new ApplicationException("Break Error: The {nnn} tag on a rule is mal formed " + extraInfo); + throw new BreakException("Break Error: The {nnn} tag on a rule is mal formed " + extraInfo); case ErrorCode.BRK_ERROR_LIMIT: - throw new ApplicationException("Break Error: This must always be the last value to indicate the limit for Break Iterator failures " + extraInfo); + throw new BreakException("Break Error: This must always be the last value to indicate the limit for Break Iterator failures " + extraInfo); case ErrorCode.REGEX_INTERNAL_ERROR: - throw new ApplicationException("RegEx Error: An internal error (bug) was detected. " + extraInfo); + throw new RegexException("RegEx Error: An internal error (bug) was detected. " + extraInfo); case ErrorCode.REGEX_RULE_SYNTAX: - throw new ApplicationException("RegEx Error: Syntax error in regexp pattern. " + extraInfo); + throw new RegexException("RegEx Error: Syntax error in regexp pattern. " + extraInfo); case ErrorCode.REGEX_INVALID_STATE: - throw new ApplicationException("RegEx Error: RegexMatcher in invalid state for requested operation " + extraInfo); + throw new RegexException("RegEx Error: RegexMatcher in invalid state for requested operation " + extraInfo); case ErrorCode.REGEX_BAD_ESCAPE_SEQUENCE: - throw new ApplicationException("RegEx Error: Unrecognized backslash escape sequence in pattern " + extraInfo); + throw new RegexException("RegEx Error: Unrecognized backslash escape sequence in pattern " + extraInfo); case ErrorCode.REGEX_PROPERTY_SYNTAX: - throw new ApplicationException("RegEx Error: Incorrect Unicode property " + extraInfo); + throw new RegexException("RegEx Error: Incorrect Unicode property " + extraInfo); case ErrorCode.REGEX_UNIMPLEMENTED: - throw new ApplicationException("RegEx Error: Use of regexp feature that is not yet implemented. " + extraInfo); + throw new RegexException("RegEx Error: Use of regexp feature that is not yet implemented. " + extraInfo); case ErrorCode.REGEX_MISMATCHED_PAREN: - throw new ApplicationException("RegEx Error: Incorrectly nested parentheses in regexp pattern. " + extraInfo); + throw new RegexException("RegEx Error: Incorrectly nested parentheses in regexp pattern. " + extraInfo); case ErrorCode.REGEX_NUMBER_TOO_BIG: - throw new ApplicationException("RegEx Error: Decimal number is too large. " + extraInfo); + throw new RegexException("RegEx Error: Decimal number is too large. " + extraInfo); case ErrorCode.REGEX_BAD_INTERVAL: - throw new ApplicationException("RegEx Error: Error in {min,max} interval " + extraInfo); + throw new RegexException("RegEx Error: Error in {min,max} interval " + extraInfo); case ErrorCode.REGEX_MAX_LT_MIN: - throw new ApplicationException("RegEx Error: In {min,max}, max is less than min. " + extraInfo); + throw new RegexException("RegEx Error: In {min,max}, max is less than min. " + extraInfo); case ErrorCode.REGEX_INVALID_BACK_REF: - throw new ApplicationException("RegEx Error: Back-reference to a non-existent capture group. " + extraInfo); + throw new RegexException("RegEx Error: Back-reference to a non-existent capture group. " + extraInfo); case ErrorCode.REGEX_INVALID_FLAG: - throw new ApplicationException("RegEx Error: Invalid value for match mode flags. " + extraInfo); + throw new RegexException("RegEx Error: Invalid value for match mode flags. " + extraInfo); case ErrorCode.REGEX_LOOK_BEHIND_LIMIT: - throw new ApplicationException("RegEx Error: Look-Behind pattern matches must have a bounded maximum length. " + extraInfo); + throw new RegexException("RegEx Error: Look-Behind pattern matches must have a bounded maximum length. " + extraInfo); case ErrorCode.REGEX_SET_CONTAINS_STRING: - throw new ApplicationException("RegEx Error: Regexps cannot have UnicodeSets containing strings. " + extraInfo); + throw new RegexException("RegEx Error: Regexps cannot have UnicodeSets containing strings. " + extraInfo); case ErrorCode.REGEX_OCTAL_TOO_BIG: - throw new ApplicationException("Regex Error: Octal character constants must be <= 0377. " + extraInfo); + throw new RegexException("Regex Error: Octal character constants must be <= 0377. " + extraInfo); case ErrorCode.REGEX_MISSING_CLOSE_BRACKET: - throw new ApplicationException("Regex Error: Missing closing bracket on a bracket expression. " + extraInfo); + throw new RegexException("Regex Error: Missing closing bracket on a bracket expression. " + extraInfo); case ErrorCode.REGEX_INVALID_RANGE: - throw new ApplicationException("Regex Error: In a character range [x-y], x is greater than y. " + extraInfo); + throw new RegexException("Regex Error: In a character range [x-y], x is greater than y. " + extraInfo); case ErrorCode.REGEX_STACK_OVERFLOW: - throw new ApplicationException("Regex Error: Regular expression backtrack stack overflow. " + extraInfo); + throw new RegexException("Regex Error: Regular expression backtrack stack overflow. " + extraInfo); case ErrorCode.REGEX_TIME_OUT: - throw new ApplicationException("Regex Error: Maximum allowed match time exceeded. " + extraInfo); + throw new RegexException("Regex Error: Maximum allowed match time exceeded. " + extraInfo); case ErrorCode.REGEX_STOPPED_BY_CALLER: - throw new ApplicationException("Regex Error: Matching operation aborted by user callback fn. " + extraInfo); + throw new RegexException("Regex Error: Matching operation aborted by user callback fn. " + extraInfo); case ErrorCode.REGEX_ERROR_LIMIT: - throw new ApplicationException("RegEx Error: " + extraInfo); + throw new RegexException("RegEx Error: " + extraInfo); case ErrorCode.IDNA_PROHIBITED_ERROR: - throw new ApplicationException("IDNA Error: Prohibited " + extraInfo); + throw new IDNAException("IDNA Error: Prohibited " + extraInfo); case ErrorCode.IDNA_UNASSIGNED_ERROR: - throw new ApplicationException("IDNA Error: Unassigned " + extraInfo); + throw new IDNAException("IDNA Error: Unassigned " + extraInfo); case ErrorCode.IDNA_CHECK_BIDI_ERROR: - throw new ApplicationException("IDNA Error: Check Bidi " + extraInfo); + throw new IDNAException("IDNA Error: Check Bidi " + extraInfo); case ErrorCode.IDNA_STD3_ASCII_RULES_ERROR: - throw new ApplicationException("IDNA Error: Std3 Ascii Rules Error " + extraInfo); + throw new IDNAException("IDNA Error: Std3 Ascii Rules Error " + extraInfo); case ErrorCode.IDNA_ACE_PREFIX_ERROR: - throw new ApplicationException("IDNA Error: Ace Prefix Error " + extraInfo); + throw new IDNAException("IDNA Error: Ace Prefix Error " + extraInfo); case ErrorCode.IDNA_VERIFICATION_ERROR: - throw new ApplicationException("IDNA Error: Verification Error " + extraInfo); + throw new IDNAException("IDNA Error: Verification Error " + extraInfo); case ErrorCode.IDNA_LABEL_TOO_LONG_ERROR: - throw new ApplicationException("IDNA Error: Label too long " + extraInfo); + throw new IDNAException("IDNA Error: Label too long " + extraInfo); case ErrorCode.IDNA_ZERO_LENGTH_LABEL_ERROR: - throw new ApplicationException("IDNA Error: Zero length label " + extraInfo); + throw new IDNAException("IDNA Error: Zero length label " + extraInfo); case ErrorCode.IDNA_DOMAIN_NAME_TOO_LONG_ERROR: - throw new ApplicationException("IDNA Error: Domain name too long " + extraInfo); + throw new IDNAException("IDNA Error: Domain name too long " + extraInfo); default: - throw new InvalidEnumArgumentException("Missing implementation for ErrorCode " + e); + throw new NotSupportedException("Missing implementation for ErrorCode " + e); } } } diff --git a/source/icu.net/Exceptions/BreakException.cs b/source/icu.net/Exceptions/BreakException.cs new file mode 100644 index 00000000..c891e650 --- /dev/null +++ b/source/icu.net/Exceptions/BreakException.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using System; + +namespace Icu +{ + /// + /// Exceptions related to BreakIterator functionality. + /// + public class BreakException : Exception + { + /// + /// Creates exception with the provided message. + /// + /// The message that describes the error. + public BreakException(string message) : base(message) + { } + } +} diff --git a/source/icu.net/Exceptions/IDNAException.cs b/source/icu.net/Exceptions/IDNAException.cs new file mode 100644 index 00000000..8622e922 --- /dev/null +++ b/source/icu.net/Exceptions/IDNAException.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using System; + +namespace Icu +{ + /// + /// Exception thrown when an IDNA ErrorCode is thrown. + /// + public class IDNAException : Exception + { + /// + /// Create an IDNA Exception with the following message. + /// + /// Message to pass to exception + public IDNAException(string message) : base(message) + { } + } +} diff --git a/source/icu.net/Exceptions/MissingResourceException.cs b/source/icu.net/Exceptions/MissingResourceException.cs new file mode 100644 index 00000000..562c3d43 --- /dev/null +++ b/source/icu.net/Exceptions/MissingResourceException.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using System; + +namespace Icu +{ + /// + /// Exception when icu4c cannot find a resource. + /// + public class MissingResourceException : Exception + { + /// + /// Creates exception with the provided message. + /// + /// The message that describes the error. + public MissingResourceException(string message) : base(message) + { } + } +} diff --git a/source/icu.net/Exceptions/RegexException.cs b/source/icu.net/Exceptions/RegexException.cs new file mode 100644 index 00000000..73198647 --- /dev/null +++ b/source/icu.net/Exceptions/RegexException.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using System; + +namespace Icu +{ + /// + /// Exceptions indicating Regexp failures + /// + public class RegexException : Exception + { + /// + /// Creates exception with the provided message. + /// + /// The message that describes the error. + public RegexException(string message) : base(message) + { } + } +} diff --git a/source/icu.net/Exceptions/SyntaxErrorException.cs b/source/icu.net/Exceptions/SyntaxErrorException.cs new file mode 100644 index 00000000..84e202fc --- /dev/null +++ b/source/icu.net/Exceptions/SyntaxErrorException.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using System; + +namespace Icu +{ + /// + /// Exception for syntax errors in a format pattern (ie. Number, exponent patterns.) + /// + public class SyntaxErrorException : Exception + { + /// + /// Creates exception with the provided message. + /// + /// The message that describes the error. + public SyntaxErrorException(string message) : base(message) + { } + } +} diff --git a/source/icu.net/Exceptions/TransliteratorParseException.cs b/source/icu.net/Exceptions/TransliteratorParseException.cs new file mode 100644 index 00000000..064774a1 --- /dev/null +++ b/source/icu.net/Exceptions/TransliteratorParseException.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using System; + +namespace Icu +{ + /// + /// Exceptions for Transliterator errors. + /// + public class TransliteratorParseException : Exception + { + /// + /// Creates exception with the provided message. + /// + /// The message that describes the error. + public TransliteratorParseException(string message) : base(message) + { } + } +} diff --git a/source/icu.net/Exceptions/WarningException.cs b/source/icu.net/Exceptions/WarningException.cs new file mode 100644 index 00000000..6bc64e61 --- /dev/null +++ b/source/icu.net/Exceptions/WarningException.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using System; + +namespace Icu +{ + /// + /// Exceptions indicating that there was a warning returned from icu. + /// + public class WarningException : Exception + { + /// + /// Creates exception with the provided message. + /// + /// The message that describes the error. + public WarningException(string message) : base(message) + { } + } +} diff --git a/source/icu.net/IcuVersionInfo.cs b/source/icu.net/IcuVersionInfo.cs new file mode 100644 index 00000000..88b97514 --- /dev/null +++ b/source/icu.net/IcuVersionInfo.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using System; +using System.IO; + +namespace Icu +{ + /// + /// Returns the result of trying to resolve the icu binary paths. + /// + internal class IcuVersionInfo + { + public IcuVersionInfo() + { + Success = false; + } + + public IcuVersionInfo(DirectoryInfo icuPath, int icuVersion) + { + if (icuVersion <= 0) + throw new ArgumentOutOfRangeException(nameof(icuVersion), "IcuVersion should be greater than 0"); + + IcuPath = icuPath; + IcuVersion = icuVersion; + Success = true; + } + + public bool Success { get; } + + public readonly DirectoryInfo IcuPath; + public readonly int IcuVersion; + } +} \ No newline at end of file diff --git a/source/icu.net/Locale.cs b/source/icu.net/Locale.cs index 824141ce..66937fcf 100644 --- a/source/icu.net/Locale.cs +++ b/source/icu.net/Locale.cs @@ -11,7 +11,10 @@ namespace Icu /// /// A Locale object represents a specific geographical, political, or cultural region. /// - public class Locale: ICloneable + public class Locale +#if FEATURE_ICLONEABLE + : ICloneable +#endif { /// /// Construct a default locale object, a Locale for the default locale ID diff --git a/source/icu.net/NativeMethods.NetCore.cs b/source/icu.net/NativeMethods.NetCore.cs new file mode 100644 index 00000000..9d37bf64 --- /dev/null +++ b/source/icu.net/NativeMethods.NetCore.cs @@ -0,0 +1,315 @@ +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +#if !NET40 +using Microsoft.Extensions.DependencyModel; +#endif + +namespace Icu +{ + /// + /// Helper class to try and get path to icu native binaries when running on + /// Windows or .NET Core. + /// + internal static class NativeMethodsHelper + { + private const string Icu4c = nameof(Icu4c); + private const string IcuRegexLinux = @"libicu\w+.so\.(?[0-9\.]{2,})"; + private const string IcuRegexWindows = @"icu\w+(?[0-9\.]{2,})\.dll"; + + private static readonly Regex IcuBinaryRegex = new Regex($"{IcuRegexWindows}|{IcuRegexLinux}$", RegexOptions.Compiled); + private static readonly string IcuSearchPattern = Platform.OperatingSystem == OperatingSystemType.Windows ? "icu*.dll" : "libicu*.so.*"; + private static readonly string NugetPackageDirectory = GetDefaultPackageDirectory(Platform.OperatingSystem); + + private static IcuVersionInfo IcuVersion; + + /// + /// Tries to get path and version to Icu when running on .NET Core or Windows. + /// + /// The path and version of icu binaries if found. Check + /// to see if the values were set. + public static IcuVersionInfo GetIcuVersionInfoForNetCoreOrWindows() + { + // We've already tried to set the path to native assets. Don't try again. + if (IcuVersion != null) + { + return IcuVersion; + } + + // Set the default to IcuVersion with an empty path and no version set. + IcuVersion = new IcuVersionInfo(); + + if (TryGetPathFromAssemblyDirectory()) + { + return IcuVersion; + } + + // That's odd.. I guess we use normal search paths from %PATH% then. + // One possibility is that it is not a dev machine and the application + // is a published app... but then it should have returned true in + // TryGetPathFromAssemblyDirectory. + if (string.IsNullOrEmpty(NugetPackageDirectory)) + { + Trace.TraceWarning($"{nameof(NugetPackageDirectory)} is empty and application was unable to set path from current assembly directory."); + return IcuVersion; + } + +#if !NET40 + var context = DependencyContext.Default; + // If this is false, something went wrong. These files should have + // either been found above or we should have been able to locate the + // asset paths (for .NET Core and NuGet v3+ projects). + if (!TryGetNativeAssetPaths(context, out string[] nativeAssetPaths)) + { + Trace.WriteLine("Could not locate icu native assets from DependencyModel."); + return IcuVersion; + } + + var icuLib = context.CompileLibraries + .Where(x => x.Name.StartsWith(Icu4c, StringComparison.OrdinalIgnoreCase)) + .FirstOrDefault(); + + if (icuLib == default(CompilationLibrary)) + { + Trace.TraceWarning("Could not find Icu4c compilation library. Possible that the library writer did not include the Icu4c.Win.Full.Lib or Icu4c.Win.Full.Lib NuGet package."); + return IcuVersion; + } + + if (!TryResolvePackagePath(icuLib, NugetPackageDirectory, out string packagePath)) + { + Trace.WriteLine("Could not resolve nuget package directory...."); + return IcuVersion; + } + + TrySetIcuPathFromDirectory(new DirectoryInfo(packagePath), nativeAssetPaths); +#endif + + return IcuVersion; + } + + /// + /// Tries to set the native library using the + /// as the root directory. The following scenarios could happen: + /// 1. {directoryOfAssembly}/icu*.dll + /// Occurs when the project is published using the .NET Core CLI + /// against the full .NET framework. + /// 2. {directoryOfAssembly}/lib/{arch}/icu*.dll + /// Occurs when the project is using NuGet v2, the traditional + /// .NET Framework csproj or the new VS2017 projects. + /// 3. {directoryOfAssembly}/runtimes/{runtimeId}/native/icu*.dll + /// Occurs when the project is published using the .NET Core CLI. + /// If one of the scenarios match, sets the . + /// + /// True if it was able to find the icu binaries within + /// , false otherwise. + /// + private static bool TryGetPathFromAssemblyDirectory() + { + var assemblyDirectory = new DirectoryInfo(NativeMethods.DirectoryOfThisAssembly); + int version; + + // 1. Check in {assemblyDirectory}/ + if (TryGetIcuVersionNumber(assemblyDirectory, out version)) + { + IcuVersion = new IcuVersionInfo(assemblyDirectory, version); + return true; + } + + // 2. Check in {assemblyDirectory}/lib/*{architecture}*/ + var libDirectory = Path.Combine(assemblyDirectory.FullName, "lib"); + var candidateDirectories = Directory.EnumerateDirectories(libDirectory, $"*{Platform.ProcessArchitecture}*") + .Select(x => new DirectoryInfo(x)); + + foreach (var directory in candidateDirectories) + { + if (TryGetIcuVersionNumber(directory, out version)) + { + IcuVersion = new IcuVersionInfo(directory, version); + return true; + } + } + + string[] nativeAssetPaths = null; +#if !NET40 + // 3. Check in {directoryOfAssembly}/runtimes/{runtimeId}/native/ + if (!TryGetNativeAssetPaths(DependencyContext.Default, out nativeAssetPaths)) + { + Trace.WriteLine("Could not locate icu native assets from DependencyModel."); + return false; + } +#endif + // If we found the icu*.dll files under {directoryOfAssembly}/runtimes/{rid}/native/, + // they should ALL be there... or else something went wrong in publishing the app or + // restoring the files, or packaging the NuGet package. + return TrySetIcuPathFromDirectory(assemblyDirectory, nativeAssetPaths); + } + + /// + /// Iterates through the directory for files with the path icu*.dll and + /// tries to fetch the icu version number from them. + /// + /// Returns the version number if the search was successful and + /// null, otherwise. + private static bool TryGetIcuVersionNumber(DirectoryInfo directory, out int icuVersion) + { + icuVersion = int.MinValue; + + if (directory == null || !directory.Exists) + return false; + + var version = directory.GetFiles(IcuSearchPattern) + ?.Select(x => + { + var match = IcuBinaryRegex.Match(x.Name); + return match.Success + ? int.Parse(match.Groups["version"].Value) + : new int?(); + }) + .OrderByDescending(x => x) + .FirstOrDefault(); + + if (version.HasValue) + icuVersion = version.Value; + + return version.HasValue; + } + + /// + /// Given a root path and a set of native asset paths, tries to see if + /// the files exist and if they do, sets . + /// + /// Root path to append asset paths to. + /// Set of native asset paths to check. + /// true if it was able to find the directory for all the asset + /// paths given; false otherwise. + private static bool TrySetIcuPathFromDirectory(DirectoryInfo baseDirectory, string[] nativeAssetPaths) + { + if (nativeAssetPaths == null || nativeAssetPaths.Length == 0) + return false; + + Trace.WriteLine("Assets: " + Environment.NewLine + string.Join(Environment.NewLine + "\t-", nativeAssetPaths)); + + var assetPaths = nativeAssetPaths + .Select(asset => new FileInfo(Path.Combine(baseDirectory.FullName, asset))); + + var doAllAssetsExistInDirectory = assetPaths.All(x => x.Exists); + + if (doAllAssetsExistInDirectory) + { + var directories = assetPaths.Select(file => file.Directory).ToArray(); + + if (directories.Length > 1) + Trace.TraceWarning($"There are multiple directories for these runtime assets: {string.Join(Path.PathSeparator.ToString(), directories.Select(x => x.FullName))}. There should only be one... Using first directory."); + + var icuDirectory = directories.First(); + + if (TryGetIcuVersionNumber(icuDirectory, out int version)) + { + IcuVersion = new IcuVersionInfo(icuDirectory, version); + } + } + + return doAllAssetsExistInDirectory; + } + +#if !NET40 + /// + /// Tries to get the icu native binaries by searching the Runtime + /// ID graph to find the first set of paths that have those binaries. + /// + /// Unique relative paths to the native assets; empty if none + /// could be found. + private static bool TryGetNativeAssetPaths(DependencyContext context, out string[] nativeAssetPaths) + { + var assetPaths = Enumerable.Empty(); + + if (context == null) + { + nativeAssetPaths = assetPaths.ToArray(); + return false; + } + + var defaultNativeAssets = context.GetDefaultNativeAssets().ToArray(); + + // This goes through the runtime graph and tries to find icu native + // asset paths matching that runtime. + foreach (var runtime in context.RuntimeGraph) + { + var nativeAssets = context.GetRuntimeNativeAssets(runtime.Runtime); + assetPaths = nativeAssets.Except(defaultNativeAssets).Where(assetPath => IcuBinaryRegex.IsMatch(assetPath)); + + if (assetPaths.Any()) + break; + } + + nativeAssetPaths = assetPaths.ToArray(); + + return nativeAssetPaths.Length > 0; + } + + /// + /// Given a CompilationLibrary and a base path, tries to construct the + /// nuget package location and returns true if it exists. + /// + /// Taken from: https://github.com/dotnet/core-setup/blob/master/src/Microsoft.Extensions.DependencyModel/Resolution/ResolverUtils.cs#L12 + /// + /// Compilation library to try to get the rooted + /// path from. + /// Rooted base path to try and get library from. + /// The path for the library if it exists; + /// null otherwise. + /// + private static bool TryResolvePackagePath(CompilationLibrary library, string basePath, out string packagePath) + { + var path = library.Path; + if (string.IsNullOrEmpty(path)) + { + path = Path.Combine(library.Name, library.Version); + } + + packagePath = Path.Combine(basePath, path); + + return Directory.Exists(packagePath); + } +#endif + + /// + /// Tries to fetch the default package directory for NuGet packages. + /// Taken from: + /// https://github.com/dotnet/core-setup/blob/master/src/Microsoft.Extensions.DependencyModel/Resolution/PackageCompilationAssemblyResolver.cs#L41-L64 + /// + /// OS Platform to fetch default package + /// directory for. + /// The path to the default package directory; null if none + /// could be set. + private static string GetDefaultPackageDirectory(OperatingSystemType osPlatform) + { + var packageDirectory = Environment.GetEnvironmentVariable("NUGET_PACKAGES"); + + if (!string.IsNullOrEmpty(packageDirectory)) + { + return packageDirectory; + } + + string basePath; + if (osPlatform == OperatingSystemType.Windows) + { + basePath = Environment.GetEnvironmentVariable("USERPROFILE"); + } + else + { + basePath = Environment.GetEnvironmentVariable("HOME"); + } + if (string.IsNullOrEmpty(basePath)) + { + return null; + } + return Path.Combine(basePath, ".nuget", "packages"); + } + } +} diff --git a/source/icu.net/NativeMethods.cs b/source/icu.net/NativeMethods.cs index 696b1338..5779133e 100644 --- a/source/icu.net/NativeMethods.cs +++ b/source/icu.net/NativeMethods.cs @@ -1,9 +1,11 @@ // Copyright (c) 2013-2017 SIL International // This software is licensed under the MIT license (http://opensource.org/licenses/MIT) using System; +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using System.Runtime.InteropServices; using Icu.Collation; @@ -11,6 +13,8 @@ namespace Icu { internal static class NativeMethods { + private static readonly object _lock = new object(); + private const int MinIcuVersionDefault = 44; private const int MaxIcuVersionDefault = 60; private static int minIcuVersion = MinIcuVersionDefault; @@ -29,8 +33,12 @@ public static void SetMinMaxIcuVersions(int minVersion = MinIcuVersionDefault, throw new ArgumentOutOfRangeException("maxVersion", $"supported ICU versions are between {MinIcuVersionDefault} and {MaxIcuVersionDefault}"); } - minIcuVersion = Math.Min(minVersion, maxVersion); - maxIcuVersion = Math.Max(minVersion, maxVersion); + + lock (_lock) + { + minIcuVersion = Math.Min(minVersion, maxVersion); + maxIcuVersion = Math.Max(minVersion, maxVersion); + } } private static MethodsContainer Methods; @@ -38,6 +46,7 @@ public static void SetMinMaxIcuVersions(int minVersion = MinIcuVersionDefault, static NativeMethods() { Methods = new MethodsContainer(); + ResetIcuVersionInfo(); } #region Dynamic method loading @@ -47,20 +56,20 @@ static NativeMethods() private const int RTLD_NOW = 2; [DllImport("libdl.so", SetLastError = true)] - private static extern IntPtr dlopen([MarshalAs(UnmanagedType.LPTStr)] string file, int mode); + private static extern IntPtr dlopen(string file, int mode); [DllImport("libdl.so", SetLastError = true)] private static extern int dlclose(IntPtr handle); [DllImport("libdl.so", SetLastError = true)] - private static extern IntPtr dlsym(IntPtr handle, [MarshalAs(UnmanagedType.LPTStr)] string name); + private static extern IntPtr dlsym(IntPtr handle, string name); #endregion #region Native methods for Windows [DllImport("kernel32.dll", SetLastError = true)] - private static extern IntPtr LoadLibrary(string dllToLoad); + private static extern IntPtr LoadLibraryEx(string dllToLoad, IntPtr hReservedNull, LoadLibraryFlags dwFlags); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool FreeLibrary(IntPtr hModule); @@ -68,8 +77,23 @@ static NativeMethods() [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool SetDllDirectory(string dllDirectory); + [Flags] + private enum LoadLibraryFlags : uint + { + NONE = 0x00000000, + DONT_RESOLVE_DLL_REFERENCES = 0x00000001, + LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010, + LOAD_LIBRARY_AS_DATAFILE = 0x00000002, + LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040, + LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020, + LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200, + LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000, + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100, + LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800, + LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400, + LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008 + } + #endregion private static int IcuVersion; @@ -77,7 +101,7 @@ static NativeMethods() private static IntPtr _IcuCommonLibHandle; private static IntPtr _IcuI18NLibHandle; - private static bool IsWindows => Environment.OSVersion.Platform != PlatformID.Unix; + private static bool IsWindows => Platform.OperatingSystem == OperatingSystemType.Windows; private static IntPtr IcuCommonLibHandle { @@ -99,24 +123,33 @@ private static IntPtr IcuI18NLibHandle } } - private static string DirectoryOfThisAssembly + internal static string DirectoryOfThisAssembly { get { - var uri = new Uri(typeof(NativeMethods).Assembly.CodeBase); + //NOTE: .GetTypeInfo() is not supported until .NET 4.5 onwards. +#if NET40 + Assembly currentAssembly = typeof(NativeMethods).Assembly; +#else + Assembly currentAssembly = typeof(NativeMethods).GetTypeInfo().Assembly; +#endif + var managedPath = currentAssembly.CodeBase ?? currentAssembly.Location; + var uri = new Uri(managedPath); + return Path.GetDirectoryName(uri.LocalPath); } } - private static bool IsRunning64Bit => Environment.Is64BitProcess; + private static bool IsRunning64Bit => Platform.ProcessArchitecture == Platform.x64; private static bool IsInitialized { get; set; } private static void AddDirectoryToSearchPath(string directory) { - if (IsWindows) - SetDllDirectory(directory); - else + // Only perform this for Linux because we are using LoadLibraryEx + // to ensure that a library's dependencies is loaded starting from + // where that library is located. + if (!IsWindows) { var ldLibPath = Environment.GetEnvironmentVariable("LD_LIBRARY_PATH"); Environment.SetEnvironmentVariable("LD_LIBRARY_PATH", @@ -146,6 +179,7 @@ private static bool CheckDirectoryForIcuBinaries(string directory, string librar icuVersion, directory); IcuVersion = icuVersion; _IcuPath = directory; + AddDirectoryToSearchPath(directory); return true; } @@ -190,41 +224,63 @@ private static IntPtr LoadIcuLibrary(string libraryName) { Trace.WriteLineIf(!IsInitialized, "WARNING: ICU is not initialized."); - if (IcuVersion <= 0) - LocateIcuLibrary(libraryName); - - var handle = GetIcuLibHandle(libraryName, IcuVersion > 0 ? IcuVersion : maxIcuVersion); - if (handle == IntPtr.Zero) + lock(_lock) { - throw new FileLoadException($"Can't load ICU library (version {IcuVersion})", - libraryName); + if (IcuVersion <= 0) + LocateIcuLibrary(libraryName); + + var handle = GetIcuLibHandle(libraryName, IcuVersion > 0 ? IcuVersion : maxIcuVersion); + if (handle == IntPtr.Zero) + { + throw new FileLoadException($"Can't load ICU library (version {IcuVersion})", + libraryName); + } + return handle; } - return handle; } private static IntPtr GetIcuLibHandle(string basename, int icuVersion) { if (icuVersion < minIcuVersion) return IntPtr.Zero; + IntPtr handle; string libPath; + int lastError = 0; + if (IsWindows) { var libName = $"{basename}{icuVersion}.dll"; - libPath = string.IsNullOrEmpty(_IcuPath) ? libName : Path.Combine(_IcuPath, libName); - handle = LoadLibrary(libPath); + var isIcuPathSpecified = !string.IsNullOrEmpty(_IcuPath); + libPath = isIcuPathSpecified ? Path.Combine(_IcuPath, libName) : libName; + + var loadLibraryFlags = LoadLibraryFlags.NONE; + + if (isIcuPathSpecified) + loadLibraryFlags |= LoadLibraryFlags.LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LoadLibraryFlags.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS; + + handle = LoadLibraryEx(libPath, IntPtr.Zero, loadLibraryFlags); + lastError = Marshal.GetLastWin32Error(); + + if (handle == IntPtr.Zero && lastError != 0) + { + string errorMessage = new Win32Exception(lastError).Message; + Trace.WriteLine(string.Format("Unable to load [{0}]. Error: {1}", libPath, errorMessage)); + } } else { var libName = $"lib{basename}.so.{icuVersion}"; libPath = string.IsNullOrEmpty(_IcuPath) ? libName : Path.Combine(_IcuPath, libName); + handle = dlopen(libPath, RTLD_NOW); + lastError = Marshal.GetLastWin32Error(); } if (handle == IntPtr.Zero) { Trace.TraceWarning("{0} of {1} failed with error {2}", - IsWindows ? "LoadLibrary" : "dlopen", - libPath, Marshal.GetLastWin32Error()); + IsWindows ? "LoadLibraryEx" : "dlopen", + libPath, lastError); return GetIcuLibHandle(basename, icuVersion - 1); } @@ -234,26 +290,45 @@ private static IntPtr GetIcuLibHandle(string basename, int icuVersion) public static void Cleanup() { - u_cleanup(); - if (IsWindows) + lock (_lock) { - if (_IcuCommonLibHandle != IntPtr.Zero) - FreeLibrary(_IcuCommonLibHandle); - if (_IcuI18NLibHandle != IntPtr.Zero) - FreeLibrary(_IcuI18NLibHandle); - } - else - { - if (_IcuCommonLibHandle != IntPtr.Zero) - dlclose(_IcuCommonLibHandle); - if (_IcuI18NLibHandle != IntPtr.Zero) - dlclose(_IcuI18NLibHandle); + u_cleanup(); + if (IsWindows) + { + if (_IcuCommonLibHandle != IntPtr.Zero) + FreeLibrary(_IcuCommonLibHandle); + if (_IcuI18NLibHandle != IntPtr.Zero) + FreeLibrary(_IcuI18NLibHandle); + } + else + { + if (_IcuCommonLibHandle != IntPtr.Zero) + dlclose(_IcuCommonLibHandle); + if (_IcuI18NLibHandle != IntPtr.Zero) + dlclose(_IcuI18NLibHandle); + } + _IcuCommonLibHandle = IntPtr.Zero; + _IcuI18NLibHandle = IntPtr.Zero; + + Methods = new MethodsContainer(); + ResetIcuVersionInfo(); } - _IcuCommonLibHandle = IntPtr.Zero; - _IcuI18NLibHandle = IntPtr.Zero; + } + + private static void ResetIcuVersionInfo() + { IcuVersion = 0; _IcuPath = null; - Methods = new MethodsContainer(); + +#if !NET40 + var icuInfo = NativeMethodsHelper.GetIcuVersionInfoForNetCoreOrWindows(); + + if (icuInfo.Success) + { + _IcuPath = icuInfo.IcuPath.FullName; + IcuVersion = icuInfo.IcuVersion; + } +#endif } // This method is thread-safe and idempotent @@ -265,8 +340,13 @@ private static T GetMethod(IntPtr handle, string methodName, bool missingInMi dlsym(handle, versionedMethodName); if (methodPointer != IntPtr.Zero) { + // NOTE: Starting in .NET 4.5.1, Marshal.GetDelegateForFunctionPointer(IntPtr, Type) is obsolete. +#if NET40 return Marshal.GetDelegateForFunctionPointer( methodPointer, typeof(T)) as T; +#else + return Marshal.GetDelegateForFunctionPointer(methodPointer); +#endif } if (missingInMinimal) { @@ -1810,7 +1890,7 @@ public static IntPtr uregex_open( } /// - /// Attempts to match the input string against the pattern. + /// Attempts to match the input string against the pattern. /// public static bool uregex_matches( IntPtr regexp, diff --git a/source/icu.net/Platform.cs b/source/icu.net/Platform.cs new file mode 100644 index 00000000..917d61b0 --- /dev/null +++ b/source/icu.net/Platform.cs @@ -0,0 +1,73 @@ +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using System; +using System.Runtime.InteropServices; + +namespace Icu +{ + /// + /// Operating systems. + /// Taken from: + /// https://github.com/libgit2/libgit2sharp/blob/master/LibGit2Sharp/Core/Platform.cs + /// + internal enum OperatingSystemType + { + Windows, + Unix, + MacOSX + } + + /// + /// Class that fetches the current process bitness and architecture. + /// Adapted from: + /// https://github.com/libgit2/libgit2sharp/blob/master/LibGit2Sharp/Core/Platform.cs + /// + internal static class Platform + { + public const string x64 = nameof(x64); + public const string x86 = nameof(x86); + + public static string ProcessArchitecture + { + get { + +#if NETSTANDARD1_6 + // Workaround described here since the API does not exist: + // https://github.com/dotnet/corefx/issues/999#issuecomment-75907756 + return IntPtr.Size == 4 ? x86 : x64; +#else + return Environment.Is64BitProcess ? x64 : x86; +#endif + } + } + + public static OperatingSystemType OperatingSystem + { + get + { +#if NET40 + // See http://www.mono-project.com/docs/faq/technical/#how-to-detect-the-execution-platform + switch ((int)Environment.OSVersion.Platform) + { + case 4: + case 128: + return OperatingSystemType.Unix; + case 6: + return OperatingSystemType.MacOSX; + default: + return OperatingSystemType.Windows; + } +#else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return OperatingSystemType.Windows; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return OperatingSystemType.Unix; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return OperatingSystemType.MacOSX; + else + throw new NotSupportedException("Cannot get OperatingSystemType from: " + RuntimeInformation.OSDescription); +#endif + } + } + } +} diff --git a/source/icu.net/Properties/AssemblyInfo.cs b/source/icu.net/Properties/AssemblyInfo.cs index 569d99fe..cd9312bc 100644 --- a/source/icu.net/Properties/AssemblyInfo.cs +++ b/source/icu.net/Properties/AssemblyInfo.cs @@ -1,6 +1,5 @@ -// Copyright (c) 2007-2017 SIL International +// Copyright (c) 2007-2017 SIL International // This software is licensed under the MIT license (http://opensource.org/licenses/MIT) - using System; using System.Reflection; using System.Runtime.InteropServices; @@ -17,6 +16,7 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] +// Setting assembly to CLS compliant so that it can be used on any other .NET languages. [assembly: CLSCompliant(true)] // Setting ComVisible to false makes the types in this assembly not visible diff --git a/source/icu.net/UnicodeSet.cs b/source/icu.net/UnicodeSet.cs index ebaf7e42..e1ead498 100644 --- a/source/icu.net/UnicodeSet.cs +++ b/source/icu.net/UnicodeSet.cs @@ -1,4 +1,6 @@ -using System; +// Copyright (c) 2013 SIL International +// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -89,7 +91,7 @@ public static IEnumerable ToCharacters(string pattern) // Add a character range to the set for (int j = startChar; j <= endChar; j++) { - output.Add(((char) j).ToString(CultureInfo.InvariantCulture)); + output.Add(string.Format(CultureInfo.InvariantCulture, "{0}", (char)j)); } } else diff --git a/source/icu.net/icu.net.csproj b/source/icu.net/icu.net.csproj index 4a9860b6..0df50ce8 100644 --- a/source/icu.net/icu.net.csproj +++ b/source/icu.net/icu.net.csproj @@ -115,11 +115,15 @@ + net40 + ..\..\lib\icu ..\..\output\$(Configuration) - $(DefineConstants);ICU_VER_$(icu_ver) - true + $(DefineConstants);FEATURE_ICLONEABLE;NET40;NUNIT2 - + + + true icu.net.snk @@ -140,7 +144,15 @@ + + + + + + + + @@ -181,15 +193,18 @@ - + + $(SolutionDir)NuGetBuild/$(PlatformAlias) + $(ProjectDir)NuGetAssets + - + @@ -197,10 +212,10 @@ - - - - + + + + @@ -213,5 +228,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + \ No newline at end of file diff --git a/source/icu.net/packages.config b/source/icu.net/packages.config index 6019d61e..c2c272a8 100644 --- a/source/icu.net/packages.config +++ b/source/icu.net/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file