diff --git a/csharp/PhoneNumbers.Test/TestAreaCodeMap.cs b/csharp/PhoneNumbers.Test/TestAreaCodeMap.cs index 21c9b34c0..0d8dd31bf 100644 --- a/csharp/PhoneNumbers.Test/TestAreaCodeMap.cs +++ b/csharp/PhoneNumbers.Test/TestAreaCodeMap.cs @@ -87,7 +87,7 @@ private static SortedDictionary CreateFlyweightStorageMapCandidate( public void TestGetSmallerMapStorageChoosesDefaultImpl() { var mapStorage = - new AreaCodeMap().GetSmallerMapStorage(CreateDefaultStorageMapCandidate()); + new AreaCodeMap().GetSmallerMapStorageInternal(CreateDefaultStorageMapCandidate()); Assert.False(mapStorage.GetType() == typeof(FlyweightMapStorage)); } @@ -95,7 +95,7 @@ public void TestGetSmallerMapStorageChoosesDefaultImpl() public void TestGetSmallerMapStorageChoosesFlyweightImpl() { var mapStorage = - new AreaCodeMap().GetSmallerMapStorage(CreateFlyweightStorageMapCandidate()); + new AreaCodeMap().GetSmallerMapStorageInternal(CreateFlyweightStorageMapCandidate()); Assert.True(mapStorage.GetType() == typeof(FlyweightMapStorage)); } diff --git a/csharp/PhoneNumbers.Test/TestBuildMetadataFromXml.cs b/csharp/PhoneNumbers.Test/TestBuildMetadataFromXml.cs index 61e6016c9..765936f24 100644 --- a/csharp/PhoneNumbers.Test/TestBuildMetadataFromXml.cs +++ b/csharp/PhoneNumbers.Test/TestBuildMetadataFromXml.cs @@ -38,9 +38,9 @@ public void TestValidateRERemovesWhiteSpaces() { var input = " hello world "; // Should remove all the white spaces contained in the provided string. - Assert.Equal("helloworld", BuildMetadataFromXml.ValidateRE(input, true)); + Assert.Equal("helloworld", BuildMetadataFromXml.ValidateRegex(input, true)); // Make sure it only happens when the last parameter is set to true. - Assert.Equal(" hello world ", BuildMetadataFromXml.ValidateRE(input, false)); + Assert.Equal(" hello world ", BuildMetadataFromXml.ValidateRegex(input, false)); } [Fact] @@ -51,7 +51,7 @@ public void TestValidateREThrowsException() // parameter (remove white spaces). try { - BuildMetadataFromXml.ValidateRE(invalidPattern, false); + BuildMetadataFromXml.ValidateRegex(invalidPattern, false); Assert.True(false); } catch (ArgumentException) @@ -60,7 +60,7 @@ public void TestValidateREThrowsException() } try { - BuildMetadataFromXml.ValidateRE(invalidPattern, true); + BuildMetadataFromXml.ValidateRegex(invalidPattern, true); Assert.True(false); } catch (ArgumentException) @@ -74,7 +74,7 @@ public void TestValidateRE() { var validPattern = "[a-zA-Z]d{1,9}"; // The provided pattern should be left unchanged. - Assert.Equal(validPattern, BuildMetadataFromXml.ValidateRE(validPattern, false)); + Assert.Equal(validPattern, BuildMetadataFromXml.ValidateRegex(validPattern, false)); } // Tests NationalPrefix. @@ -83,7 +83,7 @@ public void TestGetNationalPrefix() { var xmlInput = ""; var territoryElement = ParseXmlString(xmlInput); - Assert.Equal("00", BuildMetadataFromXml.GetNationalPrefix(territoryElement)); + Assert.Equal("00", BuildMetadataFromXml.GetNationalPrefixInternal(territoryElement)); } // Tests LoadTerritoryTagMetadata(). @@ -154,7 +154,7 @@ public void TestLoadInternationalFormat() var metadata = new PhoneMetadata.Builder(); var nationalFormat = ""; - Assert.True(BuildMetadataFromXml.LoadInternationalFormat(metadata, numberFormatElement, + Assert.True(BuildMetadataFromXml.LoadInternationalFormatInternal(metadata, numberFormatElement, nationalFormat)); Assert.Equal(intlFormat, metadata.IntlNumberFormatList[0].Format); } @@ -168,7 +168,7 @@ public void TestLoadInternationalFormatWithBothNationalAndIntlFormatsDefined() var metadata = new PhoneMetadata.Builder(); var nationalFormat = "$1"; - Assert.True(BuildMetadataFromXml.LoadInternationalFormat(metadata, numberFormatElement, + Assert.True(BuildMetadataFromXml.LoadInternationalFormatInternal(metadata, numberFormatElement, nationalFormat)); Assert.Equal(intlFormat, metadata.IntlNumberFormatList[0].Format); } @@ -183,7 +183,7 @@ public void TestLoadInternationalFormatExpectsOnlyOnePattern() // Should throw an exception as multiple intlFormats are provided. try { - BuildMetadataFromXml.LoadInternationalFormat(metadata, numberFormatElement, ""); + BuildMetadataFromXml.LoadInternationalFormatInternal(metadata, numberFormatElement, ""); Assert.True(false); } catch (Exception) @@ -200,7 +200,7 @@ public void TestLoadInternationalFormatUsesNationalFormatByDefault() var metadata = new PhoneMetadata.Builder(); var nationalFormat = "$1 $2 $3"; - Assert.False(BuildMetadataFromXml.LoadInternationalFormat(metadata, numberFormatElement, + Assert.False(BuildMetadataFromXml.LoadInternationalFormatInternal(metadata, numberFormatElement, nationalFormat)); Assert.Equal(nationalFormat, metadata.IntlNumberFormatList[0].Format); } @@ -217,7 +217,7 @@ public void TestLoadNationalFormat() var numberFormat = new NumberFormat.Builder(); Assert.Equal(nationalFormat, - BuildMetadataFromXml.LoadNationalFormat(metadata, numberFormatElement, + BuildMetadataFromXml.LoadNationalFormatInternal(metadata, numberFormatElement, numberFormat)); } @@ -231,7 +231,7 @@ public void TestLoadNationalFormatRequiresFormat() try { - BuildMetadataFromXml.LoadNationalFormat(metadata, numberFormatElement, numberFormat); + BuildMetadataFromXml.LoadNationalFormatInternal(metadata, numberFormatElement, numberFormat); Assert.True(false); } catch (Exception) @@ -250,7 +250,7 @@ public void TestLoadNationalFormatExpectsExactlyOneFormat() try { - BuildMetadataFromXml.LoadNationalFormat(metadata, numberFormatElement, numberFormat); + BuildMetadataFromXml.LoadNationalFormatInternal(metadata, numberFormatElement, numberFormat); Assert.True(false); } catch (Exception) @@ -274,7 +274,7 @@ public void TestLoadAvailableFormats() ""; var element = ParseXmlString(xmlInput); var metadata = new PhoneMetadata.Builder(); - BuildMetadataFromXml.LoadAvailableFormats( + BuildMetadataFromXml.LoadAvailableFormatsInternal( metadata, element, "0", "", false /* NP not optional */); Assert.Equal("(${1})", metadata.NumberFormatList[0].NationalPrefixFormattingRule); Assert.Equal("0 $CC (${1})", metadata.NumberFormatList[0].DomesticCarrierCodeFormattingRule); @@ -294,7 +294,7 @@ public void TestLoadAvailableFormatsPropagatesCarrierCodeFormattingRule() ""; var element = ParseXmlString(xmlInput); var metadata = new PhoneMetadata.Builder(); - BuildMetadataFromXml.LoadAvailableFormats( + BuildMetadataFromXml.LoadAvailableFormatsInternal( metadata, element, "0", "", false /* NP not optional */); Assert.Equal("(${1})", metadata.NumberFormatList[0].NationalPrefixFormattingRule); Assert.Equal("0 $CC (${1})", metadata.NumberFormatList[0].DomesticCarrierCodeFormattingRule); @@ -312,7 +312,7 @@ public void TestLoadAvailableFormatsSetsProvidedNationalPrefixFormattingRule() ""; var element = ParseXmlString(xmlInput); var metadata = new PhoneMetadata.Builder(); - BuildMetadataFromXml.LoadAvailableFormats( + BuildMetadataFromXml.LoadAvailableFormatsInternal( metadata, element, "0", "($1)", false /* NP not optional */); Assert.Equal("($1)", metadata.NumberFormatList[0].NationalPrefixFormattingRule); } @@ -328,7 +328,7 @@ public void TestLoadAvailableFormatsClearsIntlFormat() ""; var element = ParseXmlString(xmlInput); var metadata = new PhoneMetadata.Builder(); - BuildMetadataFromXml.LoadAvailableFormats( + BuildMetadataFromXml.LoadAvailableFormatsInternal( metadata, element, "0", "($1)", false /* NP not optional */); Assert.Equal(0, metadata.IntlNumberFormatCount); } @@ -345,7 +345,7 @@ public void TestLoadAvailableFormatsHandlesMultipleNumberFormats() ""; var element = ParseXmlString(xmlInput); var metadata = new PhoneMetadata.Builder(); - BuildMetadataFromXml.LoadAvailableFormats( + BuildMetadataFromXml.LoadAvailableFormatsInternal( metadata, element, "0", "($1)", false /* NP not optional */); Assert.Equal("$1 $2 $3", metadata.NumberFormatList[0].Format); Assert.Equal("$1-$2", metadata.NumberFormatList[1].Format); @@ -374,7 +374,7 @@ public void TestGetNationalPrefixFormattingRuleFromElement() var xmlInput = ""; var element = ParseXmlString(xmlInput); Assert.Equal("0${1}", - BuildMetadataFromXml.GetNationalPrefixFormattingRuleFromElement(element, "0")); + BuildMetadataFromXml.GetNationalPrefixFormattingRuleFromElementInternal(element, "0")); } // Tests getDomesticCarrierCodeFormattingRuleFromElement(). @@ -395,7 +395,7 @@ public void TestProcessPhoneNumberDescElementWithInvalidInput() { var territoryElement = ParseXmlString(""); - var phoneNumberDesc = BuildMetadataFromXml.ProcessPhoneNumberDescElement( + var phoneNumberDesc = BuildMetadataFromXml.ProcessPhoneNumberDescElementInternal( null, territoryElement, "invalidType"); Assert.False(phoneNumberDesc.HasNationalNumberPattern); } @@ -411,7 +411,7 @@ public void TestProcessPhoneNumberDescElementOverridesGeneralDesc() ""; var territoryElement = ParseXmlString(xmlInput); - var phoneNumberDesc = BuildMetadataFromXml.ProcessPhoneNumberDescElement( + var phoneNumberDesc = BuildMetadataFromXml.ProcessPhoneNumberDescElementInternal( generalDesc, territoryElement, "fixedLine"); Assert.Equal("\\d{6}", phoneNumberDesc.NationalNumberPattern); } @@ -425,7 +425,7 @@ public void TestProcessPhoneNumberDescOutputsExampleNumberByDefault() ""; var territoryElement = ParseXmlString(xmlInput); - var phoneNumberDesc = BuildMetadataFromXml.ProcessPhoneNumberDescElement( + var phoneNumberDesc = BuildMetadataFromXml.ProcessPhoneNumberDescElementInternal( null, territoryElement, "fixedLine"); Assert.Equal("01 01 01 01", phoneNumberDesc.ExampleNumber); } @@ -439,7 +439,7 @@ public void TestProcessPhoneNumberDescRemovesWhiteSpacesInPatterns() ""; var countryElement = ParseXmlString(xmlInput); - var phoneNumberDesc = BuildMetadataFromXml.ProcessPhoneNumberDescElement( + var phoneNumberDesc = BuildMetadataFromXml.ProcessPhoneNumberDescElementInternal( null, countryElement, "fixedLine"); Assert.Equal("\\d{6}", phoneNumberDesc.NationalNumberPattern); } diff --git a/csharp/PhoneNumbers.Test/TestMetadataFilter.cs b/csharp/PhoneNumbers.Test/TestMetadataFilter.cs index e9934bcfc..f7977baf5 100644 --- a/csharp/PhoneNumbers.Test/TestMetadataFilter.cs +++ b/csharp/PhoneNumbers.Test/TestMetadataFilter.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using Xunit; @@ -33,35 +34,44 @@ public class MetadataFilterTest private const string INTERNATIONAL_PREFIX = "0[01]"; private const string PREFERRED_INTERNATIONAL_PREFIX = "00"; private const string NATIONAL_NUMBER_PATTERN = "\\d{8}"; + private const string EXAMPLE_NUMBER = "10123456"; private static readonly int[] PossibleLengths = {8}; private static readonly int[] PossibleLengthsLocalOnly = {5, 6}; - private const string EXAMPLE_NUMBER = "10123456"; + + private static readonly ImmutableSortedSet ExcludableChildFieldsSet = + MetadataFilter.ExcludableChildFields.ToImmutableSortedSet(); + + private static readonly ImmutableSortedSet ExampleNumberSet = + new[] {"exampleNumber"}.ToImmutableSortedSet(); + + private static readonly ImmutableSortedSet NationalNumberPatternSet = + new[] {"nationalNumberPattern"}.ToImmutableSortedSet(); // If this behavior changes then consider whether the change in the blacklist is intended, or you // should change the special build configuration. Also look into any change in the size of the // build. [Fact] - public void TestForLiteBuild() + public void ForLiteBuild() { - var blacklist = new Dictionary> - { - { "fixedLine", new SortedSet { "exampleNumber" } }, - { "mobile", new SortedSet { "exampleNumber" } }, - { "tollFree", new SortedSet { "exampleNumber" } }, - { "premiumRate", new SortedSet { "exampleNumber" } }, - { "sharedCost", new SortedSet { "exampleNumber" } }, - { "personalNumber", new SortedSet { "exampleNumber" } }, - { "voip", new SortedSet { "exampleNumber" } }, - { "pager", new SortedSet { "exampleNumber" } }, - { "uan", new SortedSet { "exampleNumber" } }, - { "emergency", new SortedSet { "exampleNumber" } }, - { "voicemail", new SortedSet { "exampleNumber" } }, - { "shortCode", new SortedSet { "exampleNumber" } }, - { "standardRate", new SortedSet { "exampleNumber" } }, - { "carrierSpecific", new SortedSet { "exampleNumber" } }, - { "smsServices", new SortedSet { "exampleNumber" } }, - { "noInternationalDialling", new SortedSet { "exampleNumber" } } - }; + var blacklist = new Dictionary>() + { + { "fixedLine", ExampleNumberSet }, + { "mobile", ExampleNumberSet }, + { "tollFree", ExampleNumberSet }, + { "premiumRate", ExampleNumberSet }, + { "sharedCost", ExampleNumberSet }, + { "personalNumber", ExampleNumberSet }, + { "voip", ExampleNumberSet }, + { "pager", ExampleNumberSet }, + { "uan", ExampleNumberSet }, + { "emergency", ExampleNumberSet }, + { "voicemail", ExampleNumberSet }, + { "shortCode", ExampleNumberSet }, + { "standardRate", ExampleNumberSet }, + { "carrierSpecific", ExampleNumberSet }, + { "smsServices", ExampleNumberSet }, + { "noInternationalDialling", ExampleNumberSet } + }.ToImmutableDictionary(); Assert.Equal(MetadataFilter.ForLiteBuild(), new MetadataFilter(blacklist)); } @@ -70,62 +80,62 @@ public void TestForLiteBuild() // should change the special build configuration. Also look into any change in the size of the // build. [Fact] - public void TestForSpecialBuild() + public void ForSpecialBuild() { - var blacklist = new Dictionary> - { - { "fixedLine", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "tollFree", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "premiumRate", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "sharedCost", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "personalNumber", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "voip", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "pager", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "uan", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "emergency", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "voicemail", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "shortCode", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "standardRate", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "carrierSpecific", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "smsServices", new SortedSet(MetadataFilter.ExcludableChildFields) }, + var blacklist = new Dictionary> + { + { "fixedLine", ExcludableChildFieldsSet }, + { "tollFree", ExcludableChildFieldsSet }, + { "premiumRate", ExcludableChildFieldsSet }, + { "sharedCost", ExcludableChildFieldsSet }, + { "personalNumber", ExcludableChildFieldsSet }, + { "voip", ExcludableChildFieldsSet }, + { "pager", ExcludableChildFieldsSet }, + { "uan", ExcludableChildFieldsSet }, + { "emergency", ExcludableChildFieldsSet }, + { "voicemail", ExcludableChildFieldsSet }, + { "shortCode", ExcludableChildFieldsSet }, + { "standardRate", ExcludableChildFieldsSet }, + { "carrierSpecific", ExcludableChildFieldsSet }, + { "smsServices", ExcludableChildFieldsSet }, { "noInternationalDialling", - new SortedSet(MetadataFilter.ExcludableChildFields) + ExcludableChildFieldsSet }, - { "preferredInternationalPrefix", new SortedSet() }, - { "nationalPrefix", new SortedSet() }, - { "preferredExtnPrefix", new SortedSet() }, - { "nationalPrefixTransformRule", new SortedSet() }, - { "sameMobileAndFixedLinePattern", new SortedSet() }, - { "mainCountryForCode", new SortedSet() }, - { "leadingZeroPossible", new SortedSet() }, - { "mobileNumberPortableRegion", new SortedSet() } - }; + { "preferredInternationalPrefix", ImmutableSortedSet.Empty }, + { "nationalPrefix", ImmutableSortedSet.Empty }, + { "preferredExtnPrefix", ImmutableSortedSet.Empty }, + { "nationalPrefixTransformRule", ImmutableSortedSet.Empty }, + { "sameMobileAndFixedLinePattern", ImmutableSortedSet.Empty }, + { "mainCountryForCode", ImmutableSortedSet.Empty }, + { "leadingZeroPossible", ImmutableSortedSet.Empty }, + { "mobileNumberPortableRegion", ImmutableSortedSet.Empty } + }.ToImmutableDictionary(); Assert.Equal(MetadataFilter.ForSpecialBuild(), new MetadataFilter(blacklist)); } [Fact] - public void TestEmptyFilter() + public void EmptyFilter() { Assert.Equal(MetadataFilter.EmptyFilter(), - new MetadataFilter(new Dictionary>())); + new MetadataFilter(ImmutableDictionary>.Empty)); } [Fact] - public void testParseFieldMapFromString_parentAsGroup() + public void ParseFieldMapFromString_parentAsGroup() { - var fieldMap = new Dictionary> + var fieldMap = new Dictionary> { { "fixedLine", - new SortedSet(new List + new [] { "nationalNumberPattern", "possibleLength", "possibleLengthLocalOnly", "exampleNumber" - }) + }.ToImmutableSortedSet() } }; @@ -133,65 +143,65 @@ public void testParseFieldMapFromString_parentAsGroup() } [Fact] - public void testParseFieldMapFromString_childAsGroup() + public void ParseFieldMapFromString_childAsGroup() { - var fieldMap = new Dictionary> - { - { "fixedLine", new SortedSet { "exampleNumber" } }, - { "mobile", new SortedSet { "exampleNumber" } }, - { "tollFree", new SortedSet { "exampleNumber" } }, - { "premiumRate", new SortedSet { "exampleNumber" } }, - { "sharedCost", new SortedSet { "exampleNumber" } }, - { "personalNumber", new SortedSet { "exampleNumber" } }, - { "voip", new SortedSet { "exampleNumber" } }, - { "pager", new SortedSet { "exampleNumber" } }, - { "uan", new SortedSet { "exampleNumber" } }, - { "emergency", new SortedSet { "exampleNumber" } }, - { "voicemail", new SortedSet { "exampleNumber" } }, - { "shortCode", new SortedSet { "exampleNumber" } }, - { "standardRate", new SortedSet { "exampleNumber" } }, - { "carrierSpecific", new SortedSet { "exampleNumber" } }, - { "smsServices", new SortedSet { "exampleNumber" } }, - { "noInternationalDialling", new SortedSet { "exampleNumber" } } - }; + var fieldMap = new Dictionary> + { + { "fixedLine", ExampleNumberSet }, + { "mobile", ExampleNumberSet }, + { "tollFree", ExampleNumberSet }, + { "premiumRate", ExampleNumberSet }, + { "sharedCost", ExampleNumberSet }, + { "personalNumber", ExampleNumberSet }, + { "voip", ExampleNumberSet }, + { "pager", ExampleNumberSet }, + { "uan", ExampleNumberSet }, + { "emergency", ExampleNumberSet }, + { "voicemail", ExampleNumberSet }, + { "shortCode", ExampleNumberSet }, + { "standardRate", ExampleNumberSet }, + { "carrierSpecific", ExampleNumberSet }, + { "smsServices", ExampleNumberSet }, + { "noInternationalDialling", ExampleNumberSet } + }.ToImmutableDictionary(); Assert.Equal(MetadataFilter.ParseFieldMapFromString("exampleNumber"), fieldMap); } [Fact] - public void testParseFieldMapFromString_childlessFieldAsGroup() + public void ParseFieldMapFromString_childlessFieldAsGroup() { - var fieldMap = new Dictionary> + var fieldMap = new Dictionary> { - { "nationalPrefix", new SortedSet() } + { "nationalPrefix", ImmutableSortedSet.Empty } }; Assert.Equal(MetadataFilter.ParseFieldMapFromString("nationalPrefix"), fieldMap); } [Fact] - public void testParseFieldMapFromString_parentWithOneChildAsGroup() + public void ParseFieldMapFromString_parentWithOneChildAsGroup() { - var fieldMap = new Dictionary> + var fieldMap = new Dictionary> { - { "fixedLine", new SortedSet { "exampleNumber" } } + { "fixedLine", new[] { "exampleNumber" }.ToImmutableSortedSet() } }; Assert.Equal(MetadataFilter.ParseFieldMapFromString("fixedLine(exampleNumber)"), fieldMap); } [Fact] - public void testParseFieldMapFromString_parentWithTwoChildrenAsGroup() + public void ParseFieldMapFromString_parentWithTwoChildrenAsGroup() { - var fieldMap = new Dictionary> + var fieldMap = new Dictionary> { { "fixedLine", - new SortedSet(new List + new List { "exampleNumber", "possibleLength" - }) + }.ToImmutableSortedSet() } }; @@ -201,59 +211,54 @@ public void testParseFieldMapFromString_parentWithTwoChildrenAsGroup() } [Fact] - public void testParseFieldMapFromString_mixOfGroups() + public void ParseFieldMapFromString_mixOfGroups() { - var fieldMap = new Dictionary> - { - { - "uan", - new SortedSet(new List - { - "possibleLength", - "exampleNumber", - "possibleLengthLocalOnly", - "nationalNumberPattern" - }) - }, - { - "pager", - new SortedSet(new List - { - "exampleNumber", - "nationalNumberPattern" - }) - }, - { - "fixedLine", - new SortedSet(new List - { - "nationalNumberPattern", - "possibleLength", - "possibleLengthLocalOnly", - "exampleNumber" - }) - }, - { "nationalPrefix", new SortedSet() }, - { "mobile", new SortedSet { "nationalNumberPattern" } }, - { "tollFree", new SortedSet { "nationalNumberPattern" } }, - { "premiumRate", new SortedSet { "nationalNumberPattern" } }, - { "sharedCost", new SortedSet { "nationalNumberPattern" } }, - { "personalNumber", new SortedSet { "nationalNumberPattern" } }, - { "voip", new SortedSet { "nationalNumberPattern" } }, - { "emergency", new SortedSet { "nationalNumberPattern" } }, - { "voicemail", new SortedSet { "nationalNumberPattern" } }, - { "shortCode", new SortedSet { "nationalNumberPattern" } }, - { "standardRate", new SortedSet { "nationalNumberPattern" } }, - { "carrierSpecific", new SortedSet { "nationalNumberPattern" } }, - { "smsServices", new SortedSet { "nationalNumberPattern" } }, + var fieldMap = new Dictionary> { - "noInternationalDialling", - new SortedSet(new List - { - "nationalNumberPattern" - }) + { + "uan", + new [] + { + "possibleLength", + "exampleNumber", + "possibleLengthLocalOnly", + "nationalNumberPattern" + }.ToImmutableSortedSet() + }, + { + "pager", + new [] + { + "exampleNumber", + "nationalNumberPattern" + }.ToImmutableSortedSet() + }, + { + "fixedLine", + new [] + { + "nationalNumberPattern", + "possibleLength", + "possibleLengthLocalOnly", + "exampleNumber" + }.ToImmutableSortedSet() + }, + {"nationalPrefix", ImmutableSortedSet.Empty}, + {"mobile", NationalNumberPatternSet}, + {"tollFree", NationalNumberPatternSet}, + {"premiumRate", NationalNumberPatternSet}, + {"sharedCost", NationalNumberPatternSet}, + {"personalNumber", NationalNumberPatternSet}, + {"voip", NationalNumberPatternSet}, + {"emergency", NationalNumberPatternSet}, + {"voicemail", NationalNumberPatternSet}, + {"shortCode", NationalNumberPatternSet}, + {"standardRate", NationalNumberPatternSet}, + {"carrierSpecific", NationalNumberPatternSet}, + {"smsServices", NationalNumberPatternSet}, + {"noInternationalDialling", NationalNumberPatternSet} } - }; + .ToImmutableDictionary(); Assert.Equal(MetadataFilter.ParseFieldMapFromString( "uan(possibleLength,exampleNumber,possibleLengthLocalOnly)" @@ -269,7 +274,7 @@ public void testParseFieldMapFromString_mixOfGroups() // do their semantics; therefore we allow currently longer expressions, and we allow explicit // listing of children, even if these are currently all the children [Fact] [Fact] - public void TestParseFieldMapFromString_EquivalentExpressions() + public void ParseFieldMapFromString_EquivalentExpressions() { // Listing all excludable parent fields is equivalent to listing all excludable child fields. Assert.Equal( @@ -388,7 +393,7 @@ public void TestParseFieldMapFromString_EquivalentExpressions() } [Fact] - public void testParseFieldMapFromString_RuntimeExceptionCases() + public void ParseFieldMapFromString_RuntimeExceptionCases() { // Null input. Assert.Throws(() => MetadataFilter.ParseFieldMapFromString(null)); @@ -616,116 +621,109 @@ public void testParseFieldMapFromString_RuntimeExceptionCases() } [Fact] - public void testComputeComplement_allAndNothing() + public void ComputeComplement_allAndNothing() { - var map1 = new Dictionary> - { - { "fixedLine", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "mobile", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "tollFree", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "premiumRate", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "sharedCost", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "personalNumber", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "voip", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "pager", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "uan", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "emergency", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "voicemail", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "shortCode", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "standardRate", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "carrierSpecific", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "smsServices", new SortedSet(MetadataFilter.ExcludableChildFields) }, + var map1 = new Dictionary> + { + { "fixedLine", ExcludableChildFieldsSet }, + { "mobile", ExcludableChildFieldsSet }, + { "tollFree", ExcludableChildFieldsSet }, + { "premiumRate", ExcludableChildFieldsSet }, + { "sharedCost", ExcludableChildFieldsSet }, + { "personalNumber", ExcludableChildFieldsSet }, + { "voip", ExcludableChildFieldsSet }, + { "pager", ExcludableChildFieldsSet }, + { "uan", ExcludableChildFieldsSet }, + { "emergency", ExcludableChildFieldsSet }, + { "voicemail", ExcludableChildFieldsSet }, + { "shortCode", ExcludableChildFieldsSet }, + { "standardRate", ExcludableChildFieldsSet }, + { "carrierSpecific", ExcludableChildFieldsSet }, + { "smsServices", ExcludableChildFieldsSet }, { "noInternationalDialling", - new SortedSet(MetadataFilter.ExcludableChildFields) + ExcludableChildFieldsSet }, - { "preferredInternationalPrefix", new SortedSet() }, - { "nationalPrefix", new SortedSet() }, - { "preferredExtnPrefix", new SortedSet() }, - { "nationalPrefixTransformRule", new SortedSet() }, - { "sameMobileAndFixedLinePattern", new SortedSet() }, - { "mainCountryForCode", new SortedSet() }, - { "leadingZeroPossible", new SortedSet() }, - { "mobileNumberPortableRegion", new SortedSet() } + { "preferredInternationalPrefix", ImmutableSortedSet.Empty }, + { "nationalPrefix", ImmutableSortedSet.Empty }, + { "preferredExtnPrefix", ImmutableSortedSet.Empty }, + { "nationalPrefixTransformRule", ImmutableSortedSet.Empty }, + { "sameMobileAndFixedLinePattern", ImmutableSortedSet.Empty }, + { "mainCountryForCode", ImmutableSortedSet.Empty }, + { "leadingZeroPossible", ImmutableSortedSet.Empty }, + { "mobileNumberPortableRegion", ImmutableSortedSet.Empty } }; - var map2 = new Dictionary>(); + var map2 = new Dictionary>(); Assert.Equal(MetadataFilter.ComputeComplement(map1), map2); Assert.Equal(MetadataFilter.ComputeComplement(map2), map1); } [Fact] - public void testComputeComplement_inBetween() + public void ComputeComplement_inBetween() { - var map1 = new Dictionary> - { - { "fixedLine", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "mobile", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "tollFree", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "premiumRate", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "emergency", new SortedSet { "nationalNumberPattern" } }, - { "voicemail", new SortedSet(new List { "possibleLength", "exampleNumber" }) }, - { "shortCode", new SortedSet { "exampleNumber" } }, - { "standardRate", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "carrierSpecific", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "smsServices", new SortedSet { "nationalNumberPattern" } }, - { - "noInternationalDialling", - new SortedSet(MetadataFilter.ExcludableChildFields) - }, - { "nationalPrefixTransformRule", new SortedSet() }, - { "sameMobileAndFixedLinePattern", new SortedSet() }, - { "mainCountryForCode", new SortedSet() }, - { "leadingZeroPossible", new SortedSet() }, - { "mobileNumberPortableRegion", new SortedSet() } + var map1 = new Dictionary> + { + { "fixedLine", ExcludableChildFieldsSet }, + { "mobile", ExcludableChildFieldsSet }, + { "tollFree", ExcludableChildFieldsSet }, + { "premiumRate", ExcludableChildFieldsSet }, + { "emergency", NationalNumberPatternSet }, + { "voicemail", new [] { "possibleLength", "exampleNumber" }.ToImmutableSortedSet() }, + { "shortCode", ExampleNumberSet }, + { "standardRate", ExcludableChildFieldsSet }, + { "carrierSpecific", ExcludableChildFieldsSet }, + { "smsServices", NationalNumberPatternSet }, + {"noInternationalDialling", ExcludableChildFieldsSet}, + { "nationalPrefixTransformRule", ImmutableSortedSet.Empty }, + { "sameMobileAndFixedLinePattern", ImmutableSortedSet.Empty }, + { "mainCountryForCode", ImmutableSortedSet.Empty }, + { "leadingZeroPossible", ImmutableSortedSet.Empty }, + { "mobileNumberPortableRegion", ImmutableSortedSet.Empty } }; - var map2 = new Dictionary> + var map2 = new Dictionary> { - { "sharedCost", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "personalNumber", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "voip", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "pager", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "uan", new SortedSet(MetadataFilter.ExcludableChildFields) }, + {"sharedCost", ExcludableChildFieldsSet}, + {"personalNumber", ExcludableChildFieldsSet}, + {"voip", ExcludableChildFieldsSet}, + {"pager", ExcludableChildFieldsSet}, + {"uan", ExcludableChildFieldsSet}, { - "emergency", - new SortedSet(new List - { - "possibleLength", - "possibleLengthLocalOnly", - "exampleNumber" - }) + "emergency", new[] + { + "possibleLength", + "possibleLengthLocalOnly", + "exampleNumber" + }.ToImmutableSortedSet() }, { - "smsServices", - new SortedSet(new List - { - "possibleLength", - "possibleLengthLocalOnly", - "exampleNumber" - }) + "smsServices", new[] + { + "possibleLength", + "possibleLengthLocalOnly", + "exampleNumber" + }.ToImmutableSortedSet() }, { - "voicemail", - new SortedSet(new List - { - "nationalNumberPattern", - "possibleLengthLocalOnly" - }) + "voicemail", new[] + { + "nationalNumberPattern", + "possibleLengthLocalOnly" + }.ToImmutableSortedSet() }, { - "shortCode", - new SortedSet(new List - { - "nationalNumberPattern", - "possibleLength", - "possibleLengthLocalOnly" - }) + "shortCode", new[] + { + "nationalNumberPattern", + "possibleLength", + "possibleLengthLocalOnly" + }.ToImmutableSortedSet() }, - { "preferredInternationalPrefix", new SortedSet() }, - { "nationalPrefix", new SortedSet() }, - { "preferredExtnPrefix", new SortedSet() } + {"preferredInternationalPrefix", ImmutableSortedSet.Empty}, + {"nationalPrefix", ImmutableSortedSet.Empty}, + {"preferredExtnPrefix", ImmutableSortedSet.Empty} }; Assert.Equal(MetadataFilter.ComputeComplement(map1), map2); @@ -733,37 +731,37 @@ public void testComputeComplement_inBetween() } [Fact] - public void TestShouldDrop() + public void ShouldDrop() { - var blacklist = new Dictionary> + var blacklist = new Dictionary> { - { "fixedLine", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "mobile", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "tollFree", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "premiumRate", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "emergency", new SortedSet { "nationalNumberPattern" } }, + {"fixedLine", ExcludableChildFieldsSet}, + {"mobile", ExcludableChildFieldsSet}, + {"tollFree", ExcludableChildFieldsSet}, + {"premiumRate", ExcludableChildFieldsSet}, + {"emergency", NationalNumberPatternSet}, { "voicemail", - new SortedSet(new List - { - "possibleLength", - "exampleNumber" - }) + new[] + { + "possibleLength", + "exampleNumber" + }.ToImmutableSortedSet() }, - { "shortCode", new SortedSet { "exampleNumber" } }, - { "standardRate", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "carrierSpecific", new SortedSet(MetadataFilter.ExcludableChildFields) }, - { "smsServices", new SortedSet(MetadataFilter.ExcludableChildFields) }, + {"shortCode", ExampleNumberSet}, + {"standardRate", ExcludableChildFieldsSet}, + {"carrierSpecific", ExcludableChildFieldsSet}, + {"smsServices", ExcludableChildFieldsSet}, { "noInternationalDialling", - new SortedSet(MetadataFilter.ExcludableChildFields) + ExcludableChildFieldsSet }, - { "nationalPrefixTransformRule", new SortedSet() }, - { "sameMobileAndFixedLinePattern", new SortedSet() }, - { "mainCountryForCode", new SortedSet() }, - { "leadingZeroPossible", new SortedSet() }, - { "mobileNumberPortableRegion", new SortedSet() } - }; + {"nationalPrefixTransformRule", ImmutableSortedSet.Empty}, + {"sameMobileAndFixedLinePattern", ImmutableSortedSet.Empty}, + {"mainCountryForCode", ImmutableSortedSet.Empty}, + {"leadingZeroPossible", ImmutableSortedSet.Empty}, + {"mobileNumberPortableRegion", ImmutableSortedSet.Empty} + }.ToImmutableDictionary(); var filter = new MetadataFilter(blacklist); Assert.True(filter.ShouldDrop("fixedLine", "exampleNumber")); @@ -792,14 +790,14 @@ public void TestShouldDrop() MetadataFilter.ParseFieldMapFromString("uan"))).ShouldDrop("fixedLine", "exampleNumber")); // Integration tests with an empty blacklist. - Assert.False(new MetadataFilter(new Dictionary>()) + Assert.False(new MetadataFilter(ImmutableDictionary>.Empty) .ShouldDrop("fixedLine", "exampleNumber")); } // Test that a fake PhoneMetadata filtered for liteBuild ends up clearing exactly the expected // fields. The lite build is used to clear example_number fields from all PhoneNumberDescs [Fact] [Fact] - public void TestFilterMetadata_LiteBuild() + public void FilterMetadata_LiteBuild() { var metadata = FakeArmeniaPhoneMetadata(); @@ -831,7 +829,7 @@ public void TestFilterMetadata_LiteBuild() // fields. The special build is used to clear PhoneNumberDescs other than general_desc and mobile, // and non-PhoneNumberDesc PhoneMetadata fields that aren't needed for parsing [Fact] [Fact] - public void TestFilterMetadata_SpecialBuild() + public void FilterMetadata_SpecialBuild() { var metadata = FakeArmeniaPhoneMetadata(); @@ -877,7 +875,7 @@ public void TestFilterMetadata_SpecialBuild() // Test that filtering a fake PhoneMetadata with the empty MetadataFilter results in no change [Fact] [Fact] - public void TestFilterMetadata_EmptyFilter() + public void FilterMetadata_EmptyFilter() { var metadata = FakeArmeniaPhoneMetadata(); @@ -906,7 +904,7 @@ public void TestFilterMetadata_EmptyFilter() } [Fact] - public void TestIntegrityOfFieldSets() + public void IntegrityOfFieldSets() { var union = new List() .Union(MetadataFilter.ExcludableParentFields) diff --git a/csharp/PhoneNumbers.Test/TestPhoneNumberMatcher.cs b/csharp/PhoneNumbers.Test/TestPhoneNumberMatcher.cs index 6808e0b84..7fbe15e9f 100644 --- a/csharp/PhoneNumbers.Test/TestPhoneNumberMatcher.cs +++ b/csharp/PhoneNumbers.Test/TestPhoneNumberMatcher.cs @@ -243,18 +243,18 @@ public void TestMatchWithSurroundingZipcodes() [Fact] public void TestIsLatinLetter() { - Assert.True(PhoneNumberMatcher.IsLatinLetter('c')); - Assert.True(PhoneNumberMatcher.IsLatinLetter('C')); - Assert.True(PhoneNumberMatcher.IsLatinLetter('\u00C9')); - Assert.True(PhoneNumberMatcher.IsLatinLetter('\u0301')); // Combining acute accent + Assert.True(PhoneNumberMatcher.IsLatinLetterInternal('c')); + Assert.True(PhoneNumberMatcher.IsLatinLetterInternal('C')); + Assert.True(PhoneNumberMatcher.IsLatinLetterInternal('\u00C9')); + Assert.True(PhoneNumberMatcher.IsLatinLetterInternal('\u0301')); // Combining acute accent // Punctuation, digits and white-space are not considered "latin letters". - Assert.False(PhoneNumberMatcher.IsLatinLetter(':')); - Assert.False(PhoneNumberMatcher.IsLatinLetter('5')); - Assert.False(PhoneNumberMatcher.IsLatinLetter('-')); - Assert.False(PhoneNumberMatcher.IsLatinLetter('.')); - Assert.False(PhoneNumberMatcher.IsLatinLetter(' ')); - Assert.False(PhoneNumberMatcher.IsLatinLetter('\u6211')); // Chinese character - Assert.False(PhoneNumberMatcher.IsLatinLetter('\u306E')); // Hiragana letter no + Assert.False(PhoneNumberMatcher.IsLatinLetterInternal(':')); + Assert.False(PhoneNumberMatcher.IsLatinLetterInternal('5')); + Assert.False(PhoneNumberMatcher.IsLatinLetterInternal('-')); + Assert.False(PhoneNumberMatcher.IsLatinLetterInternal('.')); + Assert.False(PhoneNumberMatcher.IsLatinLetterInternal(' ')); + Assert.False(PhoneNumberMatcher.IsLatinLetterInternal('\u6211')); // Chinese character + Assert.False(PhoneNumberMatcher.IsLatinLetterInternal('\u306E')); // Hiragana letter no } [Fact] diff --git a/csharp/PhoneNumbers.Test/TestPhoneNumberToCarrierMapper.cs b/csharp/PhoneNumbers.Test/TestPhoneNumberToCarrierMapper.cs new file mode 100644 index 000000000..1b07332a4 --- /dev/null +++ b/csharp/PhoneNumbers.Test/TestPhoneNumberToCarrierMapper.cs @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2013 The Libphonenumber Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using PhoneNumbers.Carrier; +using Xunit; + +namespace PhoneNumbers.Test +{ + /** + * Unit tests for PhoneNumberToCarrierMapper.java + * + * @author Cecilia Roes + */ + public class PhoneNumberToCarrierMapperTest + { + private readonly PhoneNumberToCarrierMapper carrierMapper = + new PhoneNumberToCarrierMapper(TEST_MAPPING_DATA_DIRECTORY); + + private const string TEST_MAPPING_DATA_DIRECTORY = "/com/google/i18n/phonenumbers/carrier/testing_data/"; + + // Set up some test numbers to re-use. + private static readonly PhoneNumber AoMobile1 = + new PhoneNumber.Builder().SetCountryCode(244).SetNationalNumber(917654321L).Build(); + + private static readonly PhoneNumber AoMobile2 = + new PhoneNumber.Builder().SetCountryCode(244).SetNationalNumber(927654321L).Build(); + + private static readonly PhoneNumber AoFixed1 = + new PhoneNumber.Builder().SetCountryCode(244).SetNationalNumber(22254321L).Build(); + + private static readonly PhoneNumber AoFixed2 = + new PhoneNumber.Builder().SetCountryCode(244).SetNationalNumber(26254321L).Build(); + + private static readonly PhoneNumber AoInvalidNumber = + new PhoneNumber.Builder().SetCountryCode(244).SetNationalNumber(101234L).Build(); + + private static readonly PhoneNumber UkMobile1 = + new PhoneNumber.Builder().SetCountryCode(44).SetNationalNumber(7387654321L).Build(); + + private static readonly PhoneNumber UkMobile2 = + new PhoneNumber.Builder().SetCountryCode(44).SetNationalNumber(7487654321L).Build(); + + private static readonly PhoneNumber UkFixed1 = + new PhoneNumber.Builder().SetCountryCode(44).SetNationalNumber(1123456789L).Build(); + + private static readonly PhoneNumber UkFixed2 = + new PhoneNumber.Builder().SetCountryCode(44).SetNationalNumber(2987654321L).Build(); + + private static readonly PhoneNumber UkInvalidNumber = + new PhoneNumber.Builder().SetCountryCode(44).SetNationalNumber(7301234L).Build(); + + private static readonly PhoneNumber UkPager = + new PhoneNumber.Builder().SetCountryCode(44).SetNationalNumber(7601234567L).Build(); + + private static readonly PhoneNumber USFixedOrMobile = + new PhoneNumber.Builder().SetCountryCode(1).SetNationalNumber(6502123456L).Build(); + + private static readonly PhoneNumber NumberWithInvalidCountryCode = + new PhoneNumber.Builder().SetCountryCode(999).SetNationalNumber(2423651234L).Build(); + + private static readonly PhoneNumber InternationalTollFree = + new PhoneNumber.Builder().SetCountryCode(800).SetNationalNumber(12345678L).Build(); + + [Fact(Skip="NotImplemented")] + public void TestGetNameForMobilePortableRegion() + { + Assert.Equal("British carrier", + carrierMapper.GetNameForNumber(UkMobile1, Locale.English)); + Assert.Equal("Brittisk operat\u00F6r", + carrierMapper.GetNameForNumber(UkMobile1, new Locale("sv", "SE"))); + Assert.Equal("British carrier", + carrierMapper.GetNameForNumber(UkMobile1, Locale.French)); + // Returns an empty string because the UK implements mobile number portability. + Assert.Equal("", carrierMapper.GetSafeDisplayName(UkMobile1, Locale.English)); + } + + [Fact(Skip="NotImplemented")] + public void TestGetNameForNonMobilePortableRegion() + { + Assert.Equal("Angolan carrier", + carrierMapper.GetNameForNumber(AoMobile1, Locale.English)); + Assert.Equal("Angolan carrier", + carrierMapper.GetSafeDisplayName(AoMobile1, Locale.English)); + } + + [Fact(Skip="NotImplemented")] + public void TestGetNameForFixedLineNumber() + { + Assert.Equal("", carrierMapper.GetNameForNumber(AoFixed1, Locale.English)); + Assert.Equal("", carrierMapper.GetNameForNumber(UkFixed1, Locale.English)); + // If the carrier information is present in the files and the method that assumes a valid + // number is used, a carrier is returned. + Assert.Equal("Angolan fixed line carrier", + carrierMapper.GetNameForValidNumber(AoFixed2, Locale.English)); + Assert.Equal("", carrierMapper.GetNameForValidNumber(UkFixed2, Locale.English)); + } + + [Fact(Skip="NotImplemented")] + public void TestGetNameForFixedOrMobileNumber() + { + Assert.Equal("US carrier", carrierMapper.GetNameForNumber(USFixedOrMobile, + Locale.English)); + } + + [Fact(Skip="NotImplemented")] + public void TestGetNameForPagerNumber() + { + Assert.Equal("British pager", carrierMapper.GetNameForNumber(UkPager, Locale.English)); + } + + [Fact(Skip="NotImplemented")] + public void TestGetNameForNumberWithNoDataFile() + { + Assert.Equal("", carrierMapper.GetNameForNumber(NumberWithInvalidCountryCode, + Locale.English)); + Assert.Equal("", carrierMapper.GetNameForNumber(InternationalTollFree, + Locale.English)); + Assert.Equal("", carrierMapper.GetNameForValidNumber(NumberWithInvalidCountryCode, + Locale.English)); + Assert.Equal("", carrierMapper.GetNameForValidNumber(InternationalTollFree, + Locale.English)); + } + + [Fact(Skip="NotImplemented")] + public void TestGetNameForNumberWithMissingPrefix() + { + Assert.Equal("", carrierMapper.GetNameForNumber(UkMobile2, Locale.English)); + Assert.Equal("", carrierMapper.GetNameForNumber(AoMobile2, Locale.English)); + } + + [Fact(Skip="NotImplemented")] + public void TestGetNameForInvalidNumber() + { + Assert.Equal("", carrierMapper.GetNameForNumber(UkInvalidNumber, Locale.English)); + Assert.Equal("", carrierMapper.GetNameForNumber(AoInvalidNumber, Locale.English)); + } + } +} \ No newline at end of file diff --git a/csharp/PhoneNumbers.Test/TestPhoneNumberUtil.cs b/csharp/PhoneNumbers.Test/TestPhoneNumberUtil.cs index e67e6c6a5..7d4b606d0 100644 --- a/csharp/PhoneNumbers.Test/TestPhoneNumberUtil.cs +++ b/csharp/PhoneNumbers.Test/TestPhoneNumberUtil.cs @@ -1407,32 +1407,32 @@ public void TestTruncateTooLongNumber() [Fact] public void TestIsViablePhoneNumber() { - Assert.False(PhoneNumberUtil.IsViablePhoneNumber("1")); + Assert.False(PhoneNumberUtil.IsViablePhoneNumberInternal("1")); // Only one or two digits before strange non-possible punctuation. - Assert.False(PhoneNumberUtil.IsViablePhoneNumber("1+1+1")); - Assert.False(PhoneNumberUtil.IsViablePhoneNumber("80+0")); + Assert.False(PhoneNumberUtil.IsViablePhoneNumberInternal("1+1+1")); + Assert.False(PhoneNumberUtil.IsViablePhoneNumberInternal("80+0")); // Two digits is viable. - Assert.True(PhoneNumberUtil.IsViablePhoneNumber("00")); - Assert.True(PhoneNumberUtil.IsViablePhoneNumber("111")); + Assert.True(PhoneNumberUtil.IsViablePhoneNumberInternal("00")); + Assert.True(PhoneNumberUtil.IsViablePhoneNumberInternal("111")); // Alpha numbers. - Assert.True(PhoneNumberUtil.IsViablePhoneNumber("0800-4-pizza")); - Assert.True(PhoneNumberUtil.IsViablePhoneNumber("0800-4-PIZZA")); + Assert.True(PhoneNumberUtil.IsViablePhoneNumberInternal("0800-4-pizza")); + Assert.True(PhoneNumberUtil.IsViablePhoneNumberInternal("0800-4-PIZZA")); // We need at least three digits before any alpha characters. - Assert.False(PhoneNumberUtil.IsViablePhoneNumber("08-PIZZA")); - Assert.False(PhoneNumberUtil.IsViablePhoneNumber("8-PIZZA")); - Assert.False(PhoneNumberUtil.IsViablePhoneNumber("12. March")); + Assert.False(PhoneNumberUtil.IsViablePhoneNumberInternal("08-PIZZA")); + Assert.False(PhoneNumberUtil.IsViablePhoneNumberInternal("8-PIZZA")); + Assert.False(PhoneNumberUtil.IsViablePhoneNumberInternal("12. March")); } [Fact] public void TestIsViablePhoneNumberNonAscii() { // Only one or two digits before possible punctuation followed by more digits. - Assert.True(PhoneNumberUtil.IsViablePhoneNumber("1\u300034")); - Assert.False(PhoneNumberUtil.IsViablePhoneNumber("1\u30003+4")); + Assert.True(PhoneNumberUtil.IsViablePhoneNumberInternal("1\u300034")); + Assert.False(PhoneNumberUtil.IsViablePhoneNumberInternal("1\u30003+4")); // Unicode variants of possible starting character and other allowed punctuation/digits. - Assert.True(PhoneNumberUtil.IsViablePhoneNumber("\uFF081\uFF09\u30003456789")); + Assert.True(PhoneNumberUtil.IsViablePhoneNumberInternal("\uFF081\uFF09\u30003456789")); // Testing a leading + is okay. - Assert.True(PhoneNumberUtil.IsViablePhoneNumber("+1\uFF09\u30003456789")); + Assert.True(PhoneNumberUtil.IsViablePhoneNumberInternal("+1\uFF09\u30003456789")); } [Fact] @@ -1470,28 +1470,28 @@ public void TestMaybeStripNationalPrefix() .BuildPartial(); var numberToStrip = new StringBuilder("34356778"); var strippedNumber = "356778"; - Assert.True(phoneUtil.MaybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata, null)); + Assert.True(phoneUtil.MaybeStripNationalPrefixAndCarrierCodeInternal(numberToStrip, metadata, null)); Assert.Equal(strippedNumber, numberToStrip.ToString()); // Retry stripping - now the number should not start with the national prefix, so no more // stripping should occur. - Assert.False(phoneUtil.MaybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata, null)); + Assert.False(phoneUtil.MaybeStripNationalPrefixAndCarrierCodeInternal(numberToStrip, metadata, null)); Assert.Equal(strippedNumber, numberToStrip.ToString()); // Some countries have no national prefix. Repeat test with none specified. metadata = Update(metadata).SetNationalPrefixForParsing("").BuildPartial(); - Assert.False(phoneUtil.MaybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata, null)); + Assert.False(phoneUtil.MaybeStripNationalPrefixAndCarrierCodeInternal(numberToStrip, metadata, null)); Assert.Equal(strippedNumber, numberToStrip.ToString()); // If the resultant number doesn't match the national rule, it shouldn't be stripped. metadata = Update(metadata).SetNationalPrefixForParsing("3").BuildPartial(); numberToStrip = new StringBuilder("3123"); strippedNumber = "3123"; - Assert.False(phoneUtil.MaybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata, null)); + Assert.False(phoneUtil.MaybeStripNationalPrefixAndCarrierCodeInternal(numberToStrip, metadata, null)); Assert.Equal(strippedNumber, numberToStrip.ToString()); // Test extracting carrier selection code. metadata = Update(metadata).SetNationalPrefixForParsing("0(81)?").BuildPartial(); numberToStrip = new StringBuilder("08122123456"); strippedNumber = "22123456"; var carrierCode = new StringBuilder(); - Assert.True(phoneUtil.MaybeStripNationalPrefixAndCarrierCode( + Assert.True(phoneUtil.MaybeStripNationalPrefixAndCarrierCodeInternal( numberToStrip, metadata, carrierCode)); Assert.Equal("81", carrierCode.ToString()); Assert.Equal(strippedNumber, numberToStrip.ToString()); @@ -1501,7 +1501,7 @@ public void TestMaybeStripNationalPrefix() .SetNationalPrefixForParsing("0(\\d{2})").BuildPartial(); numberToStrip = new StringBuilder("031123"); var transformedNumber = "5315123"; - Assert.True(phoneUtil.MaybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata, null)); + Assert.True(phoneUtil.MaybeStripNationalPrefixAndCarrierCodeInternal(numberToStrip, metadata, null)); Assert.Equal(transformedNumber, numberToStrip.ToString()); } @@ -1513,37 +1513,37 @@ public void TestMaybeStripInternationalPrefix() // Note the dash is removed as part of the normalization. var strippedNumber = new StringBuilder("45677003898003"); Assert.Equal(PhoneNumber.Types.CountryCodeSource.FROM_NUMBER_WITH_IDD, - phoneUtil.MaybeStripInternationalPrefixAndNormalize(numberToStrip, + phoneUtil.MaybeStripInternationalPrefixAndNormalizeInternal(numberToStrip, internationalPrefix)); Assert.Equal(strippedNumber.ToString(), numberToStrip.ToString()); // Now the number no longer starts with an IDD prefix, so it should now report // FROM_DEFAULT_COUNTRY. Assert.Equal(PhoneNumber.Types.CountryCodeSource.FROM_DEFAULT_COUNTRY, - phoneUtil.MaybeStripInternationalPrefixAndNormalize(numberToStrip, + phoneUtil.MaybeStripInternationalPrefixAndNormalizeInternal(numberToStrip, internationalPrefix)); numberToStrip = new StringBuilder("00945677003898003"); Assert.Equal(PhoneNumber.Types.CountryCodeSource.FROM_NUMBER_WITH_IDD, - phoneUtil.MaybeStripInternationalPrefixAndNormalize(numberToStrip, + phoneUtil.MaybeStripInternationalPrefixAndNormalizeInternal(numberToStrip, internationalPrefix)); Assert.Equal(strippedNumber.ToString(), numberToStrip.ToString()); // Test it works when the international prefix is broken up by spaces. numberToStrip = new StringBuilder("00 9 45677003898003"); Assert.Equal(PhoneNumber.Types.CountryCodeSource.FROM_NUMBER_WITH_IDD, - phoneUtil.MaybeStripInternationalPrefixAndNormalize(numberToStrip, + phoneUtil.MaybeStripInternationalPrefixAndNormalizeInternal(numberToStrip, internationalPrefix)); Assert.Equal(strippedNumber.ToString(), numberToStrip.ToString()); // Now the number no longer starts with an IDD prefix, so it should now report // FROM_DEFAULT_COUNTRY. Assert.Equal(PhoneNumber.Types.CountryCodeSource.FROM_DEFAULT_COUNTRY, - phoneUtil.MaybeStripInternationalPrefixAndNormalize(numberToStrip, + phoneUtil.MaybeStripInternationalPrefixAndNormalizeInternal(numberToStrip, internationalPrefix)); // Test the + symbol is also recognised and stripped. numberToStrip = new StringBuilder("+45677003898003"); strippedNumber = new StringBuilder("45677003898003"); Assert.Equal(PhoneNumber.Types.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN, - phoneUtil.MaybeStripInternationalPrefixAndNormalize(numberToStrip, + phoneUtil.MaybeStripInternationalPrefixAndNormalizeInternal(numberToStrip, internationalPrefix)); Assert.Equal(strippedNumber.ToString(), numberToStrip.ToString()); @@ -1552,13 +1552,13 @@ public void TestMaybeStripInternationalPrefix() numberToStrip = new StringBuilder("0090112-3123"); strippedNumber = new StringBuilder("00901123123"); Assert.Equal(PhoneNumber.Types.CountryCodeSource.FROM_DEFAULT_COUNTRY, - phoneUtil.MaybeStripInternationalPrefixAndNormalize(numberToStrip, + phoneUtil.MaybeStripInternationalPrefixAndNormalizeInternal(numberToStrip, internationalPrefix)); Assert.Equal(strippedNumber.ToString(), numberToStrip.ToString()); // Here the 0 is separated by a space from the IDD. numberToStrip = new StringBuilder("009 0-112-3123"); Assert.Equal(PhoneNumber.Types.CountryCodeSource.FROM_DEFAULT_COUNTRY, - phoneUtil.MaybeStripInternationalPrefixAndNormalize(numberToStrip, + phoneUtil.MaybeStripInternationalPrefixAndNormalizeInternal(numberToStrip, internationalPrefix)); } @@ -1575,7 +1575,7 @@ public void TestMaybeExtractCountryCode() var countryCallingCode = 1; var numberToFill = new StringBuilder(); Assert.Equal(countryCallingCode, - phoneUtil.MaybeExtractCountryCode(phoneNumber, metadata, numberToFill, true, number)); + phoneUtil.MaybeExtractCountryCodeInternal(phoneNumber, metadata, numberToFill, true, number)); Assert.Equal(PhoneNumber.Types.CountryCodeSource.FROM_NUMBER_WITH_IDD, number.CountryCodeSource); // Should strip and normalize national significant number. Assert.Equal(strippedNumber, @@ -1592,7 +1592,7 @@ public void TestMaybeExtractCountryCode() var countryCallingCode = 64; var numberToFill = new StringBuilder(); Assert.Equal(countryCallingCode, - phoneUtil.MaybeExtractCountryCode(phoneNumber, metadata, numberToFill, true, number)); + phoneUtil.MaybeExtractCountryCodeInternal(phoneNumber, metadata, numberToFill, true, number)); Assert.Equal(PhoneNumber.Types.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN, number.CountryCodeSource); } catch (NumberParseException) @@ -1606,7 +1606,7 @@ public void TestMaybeExtractCountryCode() var countryCallingCode = 800; var numberToFill = new StringBuilder(); Assert.Equal(countryCallingCode, - phoneUtil.MaybeExtractCountryCode(phoneNumber, metadata, numberToFill, true, number)); + phoneUtil.MaybeExtractCountryCodeInternal(phoneNumber, metadata, numberToFill, true, number)); Assert.Equal(PhoneNumber.Types.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN, number.CountryCodeSource); } catch (NumberParseException) @@ -1619,7 +1619,7 @@ public void TestMaybeExtractCountryCode() var phoneNumber = "2345-6789"; var numberToFill = new StringBuilder(); Assert.Equal(0, - phoneUtil.MaybeExtractCountryCode(phoneNumber, metadata, numberToFill, true, number)); + phoneUtil.MaybeExtractCountryCodeInternal(phoneNumber, metadata, numberToFill, true, number)); Assert.Equal(PhoneNumber.Types.CountryCodeSource.FROM_DEFAULT_COUNTRY, number.CountryCodeSource); } catch (NumberParseException) @@ -1631,7 +1631,7 @@ public void TestMaybeExtractCountryCode() { var phoneNumber = "0119991123456789"; var numberToFill = new StringBuilder(); - phoneUtil.MaybeExtractCountryCode(phoneNumber, metadata, numberToFill, true, number); + phoneUtil.MaybeExtractCountryCodeInternal(phoneNumber, metadata, numberToFill, true, number); Assert.True(false); } catch (NumberParseException e) @@ -1647,7 +1647,7 @@ public void TestMaybeExtractCountryCode() var countryCallingCode = 1; var numberToFill = new StringBuilder(); Assert.Equal(countryCallingCode, - phoneUtil.MaybeExtractCountryCode(phoneNumber, metadata, numberToFill, true, + phoneUtil.MaybeExtractCountryCodeInternal(phoneNumber, metadata, numberToFill, true, number)); Assert.Equal(PhoneNumber.Types.CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN, number.CountryCodeSource); @@ -1663,7 +1663,7 @@ public void TestMaybeExtractCountryCode() var countryCallingCode = 1; var numberToFill = new StringBuilder(); Assert.Equal(countryCallingCode, - phoneUtil.MaybeExtractCountryCode(phoneNumber, metadata, numberToFill, false, + phoneUtil.MaybeExtractCountryCodeInternal(phoneNumber, metadata, numberToFill, false, number)); Assert.False(number.HasCountryCodeSource, "Should not contain CountryCodeSource."); } @@ -1677,7 +1677,7 @@ public void TestMaybeExtractCountryCode() var phoneNumber = "(1 610) 619 446"; var numberToFill = new StringBuilder(); Assert.Equal(0, - phoneUtil.MaybeExtractCountryCode(phoneNumber, metadata, numberToFill, false, + phoneUtil.MaybeExtractCountryCodeInternal(phoneNumber, metadata, numberToFill, false, number)); Assert.False(number.HasCountryCodeSource, "Should not contain CountryCodeSource."); } @@ -1691,7 +1691,7 @@ public void TestMaybeExtractCountryCode() var phoneNumber = "(1 610) 619"; var numberToFill = new StringBuilder(); Assert.Equal(0, - phoneUtil.MaybeExtractCountryCode(phoneNumber, metadata, numberToFill, true, + phoneUtil.MaybeExtractCountryCodeInternal(phoneNumber, metadata, numberToFill, true, number)); Assert.Equal(PhoneNumber.Types.CountryCodeSource.FROM_DEFAULT_COUNTRY, number.CountryCodeSource); } diff --git a/csharp/PhoneNumbers.Test/TestPhonePrefixMap.cs b/csharp/PhoneNumbers.Test/TestPhonePrefixMap.cs new file mode 100644 index 000000000..12c39f552 --- /dev/null +++ b/csharp/PhoneNumbers.Test/TestPhonePrefixMap.cs @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2011 The Libphonenumber Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.Generic; +using System.Collections.Immutable; +using PhoneNumbers.Carrier; +using Xunit; +// ReSharper disable xUnit1004 + +namespace PhoneNumbers.Test +{ +/** + * Unittests for PhonePrefixMap.java + * + * @author Shaopeng Jia + */ + public class TestPhonePrefixMap + { + private readonly PhonePrefixMap phonePrefixMapForUS = new PhonePrefixMap(); + private readonly PhonePrefixMap phonePrefixMapForIT = new PhonePrefixMap(); + private readonly PhoneNumber.Builder number = new PhoneNumber.Builder(); + + public TestPhonePrefixMap() + { + var sortedMapForUS = new Dictionary() + { + {1212, "New York"}, + { + 1480, "Arizona" + }, + {1650, "California"}, + { + 1907, "Alaska" + }, + {1201664, "Westwood, NJ"}, + {1480893, "Phoenix, AZ"}, + {1501372, "Little Rock, AR"}, + {1626308, "Alhambra, CA"}, + {1650345, "San Mateo, CA"}, + {1867993, "Dawson, YT"}, + {1972480, "Richardson, TX"} + }.ToImmutableDictionary(); + + phonePrefixMapForUS.ReadPhonePrefixMap(sortedMapForUS); + + var sortedMapForIT = new Dictionary + { + {3902, "Milan"}, + {3906, "Rome"}, + {39010, "Genoa"}, + {390131, "Alessandria"}, + {390321, "Novara"}, + {390975, "Potenza"} + }.ToImmutableDictionary(); + + phonePrefixMapForIT.ReadPhonePrefixMap(sortedMapForIT); + } + + private static ImmutableDictionary CreateDefaultStorageMapCandidate() + // Make the phone prefixes bigger to store them using integer. + => new Dictionary + { + {121212345, "New York"}, + {148034434, "Arizona" + } + }.ToImmutableDictionary(); + + private static ImmutableDictionary CreateFlyweightStorageMapCandidate() + => new Dictionary + { + {1212, "New York"}, + {1213, "New York"}, + {1214, "New York"}, + {1480, "Arizona"} + }.ToImmutableDictionary(); + + [Fact(Skip="NotImplemented")] + public void GetSmallerMapStorageChoosesDefaultImpl() + { + var mapStorage = + new PhonePrefixMap().GetSmallerMapStorage(CreateDefaultStorageMapCandidate()); + Assert.False(mapStorage is FlyweightMapStorage); + } + + [Fact(Skip="NotImplemented")] + public void GetSmallerMapStorageChoosesFlyweightImpl() + { + var mapStorage = + new PhonePrefixMap().GetSmallerMapStorage(CreateFlyweightStorageMapCandidate()); + Assert.True(mapStorage is FlyweightMapStorage); + } + + [Fact(Skip="NotImplemented")] + public void LookupInvalidNumber_US() + { + // central office code cannot start with 1. + number.SetCountryCode(1).SetNationalNumber(2121234567L); + Assert.Equal("New York", phonePrefixMapForUS.Lookup(number.Build())); + } + + [Fact(Skip="NotImplemented")] + public void LookupNumber_NJ() + { + number.SetCountryCode(1).SetNationalNumber(2016641234L); + Assert.Equal("Westwood, NJ", phonePrefixMapForUS.Lookup(number.Build())); + } + + [Fact(Skip="NotImplemented")] + public void LookupNumber_NY() + { + number.SetCountryCode(1).SetNationalNumber(2126641234L); + Assert.Equal("New York", phonePrefixMapForUS.Lookup(number.Build())); + } + + [Fact(Skip="NotImplemented")] + public void LookupNumber_CA_1() + { + number.SetCountryCode(1).SetNationalNumber(6503451234L); + Assert.Equal("San Mateo, CA", phonePrefixMapForUS.Lookup(number.Build())); + } + + [Fact(Skip="NotImplemented")] + public void LookupNumber_CA_2() + { + number.SetCountryCode(1).SetNationalNumber(6502531234L); + Assert.Equal("California", phonePrefixMapForUS.Lookup(number.Build())); + } + + [Fact(Skip="NotImplemented")] + public void LookupNumberFound_TX() + { + number.SetCountryCode(1).SetNationalNumber(9724801234L); + Assert.Equal("Richardson, TX", phonePrefixMapForUS.Lookup(number.Build())); + } + + [Fact(Skip="NotImplemented")] + public void LookupNumberNotFound_TX() + { + number.SetCountryCode(1).SetNationalNumber(9724811234L); + Assert.Null(phonePrefixMapForUS.Lookup(number.Build())); + } + + [Fact(Skip="NotImplemented")] + public void LookupNumber_CH() + { + number.SetCountryCode(41).SetNationalNumber(446681300L); + Assert.Null(phonePrefixMapForUS.Lookup(number.Build())); + } + + [Fact(Skip="NotImplemented")] + public void LookupNumber_IT() + { + number.SetCountryCode(39).SetNationalNumber(212345678L).SetItalianLeadingZero(true); + Assert.Equal("Milan", phonePrefixMapForIT.Lookup(number.Build())); + + number.SetNationalNumber(612345678L); + Assert.Equal("Rome", phonePrefixMapForIT.Lookup(number.Build())); + + number.SetNationalNumber(3211234L); + Assert.Equal("Novara", phonePrefixMapForIT.Lookup(number.Build())); + + // A mobile number + number.SetNationalNumber(321123456L).SetItalianLeadingZero(false); + Assert.Null(phonePrefixMapForIT.Lookup(number.Build())); + + // An invalid number (too short) + number.SetNationalNumber(321123L).SetItalianLeadingZero(true); + Assert.Equal("Novara", phonePrefixMapForIT.Lookup(number.Build())); + } + + /** + * Creates a new phone prefix map serializing the provided phone prefix map to a stream and then + * reading this stream. The resulting phone prefix map is expected to be strictly equal to the + * provided one from which it was generated. + */ + private static PhonePrefixMap CreateNewPhonePrefixMap( + PhonePrefixMap phonePrefixMap) + { + //var byteArrayOutputStream = new ByteArrayOutputStream(); + //var objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); + //phonePrefixMap.WriteExternal(objectOutputStream); + //objectOutputStream.flush(); + + var newPhonePrefixMap = new PhonePrefixMap(); + //newPhonePrefixMap.ReadExternal( + // new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()))); + return newPhonePrefixMap; + } + + [Fact(Skip="NotImplemented")] + public void ReadWriteExternalWithDefaultStrategy() + { + var localPhonePrefixMap = new PhonePrefixMap(); + localPhonePrefixMap.ReadPhonePrefixMap(CreateDefaultStorageMapCandidate()); + Assert.False(localPhonePrefixMap.GetPhonePrefixMapStorage() is FlyweightMapStorage); + + var newPhonePrefixMap = CreateNewPhonePrefixMap(localPhonePrefixMap); + Assert.Equal(localPhonePrefixMap.ToString(), newPhonePrefixMap.ToString()); + } + + [Fact(Skip="NotImplemented")] + public void ReadWriteExternalWithFlyweightStrategy() + { + var localPhonePrefixMap = new PhonePrefixMap(); + localPhonePrefixMap.ReadPhonePrefixMap(CreateFlyweightStorageMapCandidate()); + Assert.True(localPhonePrefixMap.GetPhonePrefixMapStorage() is FlyweightMapStorage); + + var newPhonePrefixMap = CreateNewPhonePrefixMap(localPhonePrefixMap); + Assert.Equal(localPhonePrefixMap.ToString(), newPhonePrefixMap.ToString()); + } + } +} \ No newline at end of file diff --git a/csharp/PhoneNumbers.Test/TestPrefixFileReader.cs b/csharp/PhoneNumbers.Test/TestPrefixFileReader.cs new file mode 100644 index 000000000..0d769f129 --- /dev/null +++ b/csharp/PhoneNumbers.Test/TestPrefixFileReader.cs @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2013 The Libphonenumber Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using PhoneNumbers.Carrier; +using Xunit; + +namespace PhoneNumbers.Test +{ + /** + * Unit tests for PrefixFileReader.java + * + * @author Cecilia Roes + */ + public class PrefixFileReaderTest + { + private readonly PrefixFileReader reader = new PrefixFileReader(TEST_MAPPING_DATA_DIRECTORY); + private const string TEST_MAPPING_DATA_DIRECTORY = "/com/google/i18n/phonenumbers/geocoding/testing_data/"; + + private static readonly PhoneNumber KONumber = + new PhoneNumber.Builder().SetCountryCode(82).SetNationalNumber(22123456L).Build(); + private static readonly PhoneNumber USNumber1 = + new PhoneNumber.Builder().SetCountryCode(1).SetNationalNumber(6502530000L).Build(); + private static readonly PhoneNumber USNumber2 = + new PhoneNumber.Builder().SetCountryCode(1).SetNationalNumber(2128120000L).Build(); + private static readonly PhoneNumber USNumber3 = + new PhoneNumber.Builder().SetCountryCode(1).SetNationalNumber(6174240000L).Build(); + private static readonly PhoneNumber SENumber = + new PhoneNumber.Builder().SetCountryCode(46).SetNationalNumber(81234567L).Build(); + + [Fact(Skip="NotImplemented")] + public void TestGetDescriptionForNumberWithMapping() { + Assert.Equal("Kalifornien", + reader.GetDescriptionForNumber(USNumber1, "de", "", "CH")); + Assert.Equal("CA", + reader.GetDescriptionForNumber(USNumber1, "en", "", "AU")); + Assert.Equal("\uC11C\uC6B8", + reader.GetDescriptionForNumber(KONumber, "ko", "", "")); + Assert.Equal("Seoul", + reader.GetDescriptionForNumber(KONumber, "en", "", "")); + } + + [Fact(Skip="NotImplemented")] + public void TestGetDescriptionForNumberWithMissingMapping() { + Assert.Equal("", reader.GetDescriptionForNumber(USNumber3, "en", "", "")); + } + + [Fact(Skip="NotImplemented")] + public void TestGetDescriptionUsingFallbackLanguage() { + // Mapping file exists but the number isn't present, causing it to fallback. + Assert.Equal("New York, NY", + reader.GetDescriptionForNumber(USNumber2, "de", "", "CH")); + // No mapping file exists, causing it to fallback. + Assert.Equal("New York, NY", + reader.GetDescriptionForNumber(USNumber2, "sv", "", "")); + } + + [Fact(Skip="NotImplemented")] + public void TestGetDescriptionForNonFallbackLanguage() { + Assert.Equal("", reader.GetDescriptionForNumber(USNumber2, "ko", "", "")); + } + + [Fact(Skip="NotImplemented")] + public void TestGetDescriptionForNumberWithoutMappingFile() { + Assert.Equal("", reader.GetDescriptionForNumber(SENumber, "sv", "", "")); + Assert.Equal("", reader.GetDescriptionForNumber(SENumber, "en", "", "")); + } + } +} \ No newline at end of file diff --git a/csharp/PhoneNumbers.Test/TestRegexCache.cs b/csharp/PhoneNumbers.Test/TestRegexCache.cs index 4f2370f9e..b49dd7274 100644 --- a/csharp/PhoneNumbers.Test/TestRegexCache.cs +++ b/csharp/PhoneNumbers.Test/TestRegexCache.cs @@ -36,20 +36,20 @@ public void TestRegexInsertion() const string regex3 = "[1-3][58]"; regexCache.GetPatternForRegex(regex1); - Assert.True(regexCache.ContainsRegex(regex1)); + Assert.True(regexCache.ContainsRegexInternal(regex1)); regexCache.GetPatternForRegex(regex2); - Assert.True(regexCache.ContainsRegex(regex2)); - Assert.True(regexCache.ContainsRegex(regex1)); + Assert.True(regexCache.ContainsRegexInternal(regex2)); + Assert.True(regexCache.ContainsRegexInternal(regex1)); regexCache.GetPatternForRegex(regex1); - Assert.True(regexCache.ContainsRegex(regex1)); + Assert.True(regexCache.ContainsRegexInternal(regex1)); regexCache.GetPatternForRegex(regex3); - Assert.True(regexCache.ContainsRegex(regex3)); + Assert.True(regexCache.ContainsRegexInternal(regex3)); - Assert.False(regexCache.ContainsRegex(regex2)); - Assert.True(regexCache.ContainsRegex(regex1)); + Assert.False(regexCache.ContainsRegexInternal(regex2)); + Assert.True(regexCache.ContainsRegexInternal(regex1)); } } } \ No newline at end of file diff --git a/csharp/PhoneNumbers.sln.DotSettings b/csharp/PhoneNumbers.sln.DotSettings index 9a3657d95..ace9f0059 100644 --- a/csharp/PhoneNumbers.sln.DotSettings +++ b/csharp/PhoneNumbers.sln.DotSettings @@ -16,11 +16,13 @@ GB IT JP + KO MX NA NANPA NZ RE + SE SG US <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> @@ -44,6 +46,7 @@ True True True + True True True True diff --git a/csharp/PhoneNumbers/AreaCodeMap.cs b/csharp/PhoneNumbers/AreaCodeMap.cs index 3db31a775..5f4206058 100644 --- a/csharp/PhoneNumbers/AreaCodeMap.cs +++ b/csharp/PhoneNumbers/AreaCodeMap.cs @@ -14,7 +14,9 @@ * limitations under the License. */ +using System; using System.Collections.Generic; +using System.Linq; namespace PhoneNumbers { @@ -27,11 +29,11 @@ public class AreaCodeMap { private readonly PhoneNumberUtil phoneUtil = PhoneNumberUtil.GetInstance(); - private AreaCodeMapStorageStrategy areaCodeMapStorage; + private PhonePrefixMapStorageStrategy phonePrefixMapStorage; - public AreaCodeMapStorageStrategy GetAreaCodeMapStorage() + public PhonePrefixMapStorageStrategy GetAreaCodeMapStorage() { - return areaCodeMapStorage; + return phonePrefixMapStorage; } /** @@ -47,32 +49,35 @@ public AreaCodeMapStorageStrategy GetAreaCodeMapStorage() /// /// /// - private static int GetSizeOfAreaCodeMapStorage(AreaCodeMapStorageStrategy mapStorage, + private static int GetSizeOfAreaCodeMapStorage(PhonePrefixMapStorageStrategy mapStorage, SortedDictionary areaCodeMap) { mapStorage.ReadFromSortedMap(areaCodeMap); return mapStorage.GetStorageSize(); } - private static AreaCodeMapStorageStrategy CreateDefaultMapStorage() + private static PhonePrefixMapStorageStrategy CreateDefaultMapStorage() { return new DefaultMapStorage(); } - private static AreaCodeMapStorageStrategy CreateFlyweightMapStorage() + private static PhonePrefixMapStorageStrategy CreateFlyweightMapStorage() { return new FlyweightMapStorage(); } /// - /// Gets the smaller area code map storage strategy according to the provided area code map. It - /// actually uses (outputs the data to a stream) both strategies and retains the best one which - /// make this method quite expensive. - /// + /// Gets the smaller area code map storage strategy according to the provided area code map. + /// It actually uses (outputs the data to a stream) both strategies and retains the best one + /// which make this method quite expensive. /// /// /// - public AreaCodeMapStorageStrategy GetSmallerMapStorage(SortedDictionary areaCodeMap) + [Obsolete("This method was public to be @VisibleForTesting, it will be moved to internal in a future release.")] + public PhonePrefixMapStorageStrategy GetSmallerMapStorage(SortedDictionary areaCodeMap) + => GetSmallerMapStorageInternal(areaCodeMap); + + internal PhonePrefixMapStorageStrategy GetSmallerMapStorageInternal(SortedDictionary areaCodeMap) { var flyweightMapStorage = CreateFlyweightMapStorage(); var sizeOfFlyweightMapStorage = GetSizeOfAreaCodeMapStorage(flyweightMapStorage, areaCodeMap); @@ -93,7 +98,7 @@ public AreaCodeMapStorageStrategy GetSmallerMapStorage(SortedDictionary public void ReadAreaCodeMap(SortedDictionary sortedAreaCodeMap) { - areaCodeMapStorage = GetSmallerMapStorage(sortedAreaCodeMap); + phonePrefixMapStorage = GetSmallerMapStorageInternal(sortedAreaCodeMap); } /// @@ -106,7 +111,7 @@ public void ReadAreaCodeMap(SortedDictionary sortedAreaCodeMap) /// The description of the geographical area. public string Lookup(PhoneNumber number) { - var numOfEntries = areaCodeMapStorage.GetNumOfEntries(); + var numOfEntries = phonePrefixMapStorage.GetNumOfEntries(); if (numOfEntries == 0) { return null; @@ -114,11 +119,11 @@ public string Lookup(PhoneNumber number) var phonePrefix = long.Parse(number.CountryCode + phoneUtil.GetNationalSignificantNumber(number)); var currentIndex = numOfEntries - 1; - var currentSetOfLengths = areaCodeMapStorage.GetPossibleLengths(); + var currentSetOfLengths = phonePrefixMapStorage.GetPossibleLengths(); var length = currentSetOfLengths.Count; while (length > 0) { - var possibleLength = currentSetOfLengths[length - 1]; + var possibleLength = currentSetOfLengths.ElementAt(length - 1); var phonePrefixStr = phonePrefix.ToString(); if (phonePrefixStr.Length > possibleLength) { @@ -129,12 +134,12 @@ public string Lookup(PhoneNumber number) { return null; } - var currentPrefix = areaCodeMapStorage.GetPrefix(currentIndex); + var currentPrefix = phonePrefixMapStorage.GetPrefix(currentIndex); if (phonePrefix == currentPrefix) { - return areaCodeMapStorage.GetDescription(currentIndex); + return phonePrefixMapStorage.GetDescription(currentIndex); } - while (length > 0 && currentSetOfLengths[length - 1] >= possibleLength) + while (length > 0 && currentSetOfLengths.ElementAt(length - 1) >= possibleLength) length--; } return null; @@ -156,7 +161,7 @@ private int BinarySearch(int start, int end, long value) while (start <= end) { current = (start + end) / 2; - var currentValue = areaCodeMapStorage.GetPrefix(current); + var currentValue = phonePrefixMapStorage.GetPrefix(current); if (currentValue == value) { return current; @@ -180,7 +185,7 @@ private int BinarySearch(int start, int end, long value) /// public override string ToString() { - return areaCodeMapStorage.ToString(); + return phonePrefixMapStorage.ToString(); } } } \ No newline at end of file diff --git a/csharp/PhoneNumbers/BuildMetadataFromXml.cs b/csharp/PhoneNumbers/BuildMetadataFromXml.cs index 42b0ea069..a1dcdae65 100644 --- a/csharp/PhoneNumbers/BuildMetadataFromXml.cs +++ b/csharp/PhoneNumbers/BuildMetadataFromXml.cs @@ -76,12 +76,13 @@ public class BuildMetadataFromXml private const string VOIP = "voip"; // Build the PhoneMetadataCollection from the input XML file. + [Obsolete("This method was public to be @VisibleForTesting, it will be moved to internal in a future release.")] public static PhoneMetadataCollection BuildPhoneMetadataCollection(string name, bool liteBuild, bool specialBuild, bool isShortNumberMetadata, bool isAlternateFormatsMetadata) - => BuildPhoneMetadata(name, null, liteBuild, specialBuild, isShortNumberMetadata, isAlternateFormatsMetadata, nameSuffix: false); + => BuildPhoneMetadataCollection(name, null, liteBuild, specialBuild, isShortNumberMetadata, isAlternateFormatsMetadata, nameSuffix: false); - internal static PhoneMetadataCollection BuildPhoneMetadata(string name, Assembly asm = null, - bool liteBuild = false, bool specialBuild = false, bool isShortNumberMetadata = false, bool isAlternateFormatsMetadata = false, - bool nameSuffix = true) + internal static PhoneMetadataCollection BuildPhoneMetadataCollection(string name, Assembly asm = null, + bool liteBuild = false, bool specialBuild = false, bool isShortNumberMetadata = false, + bool isAlternateFormatsMetadata = false, bool nameSuffix = true) { XDocument document; #if NETSTANDARD1_3 || PORTABLE @@ -104,13 +105,11 @@ internal static PhoneMetadataCollection BuildPhoneMetadata(string name, Assembly var metadataCollection = new PhoneMetadataCollection.Builder(); var metadataFilter = GetMetadataFilter(liteBuild, specialBuild); - foreach (var territory in document.Root.Element("territories").Elements()) + foreach (var metadata in document.Root.Element("territories") + .Elements() + .Select(territory => LoadCountryMetadataInternal(territory.GetAttribute("id"), territory, + isShortNumberMetadata, isAlternateFormatsMetadata))) { - // For the main metadata file this should always be set, but for other supplementary data - // files the country calling code may be all that is needed. - var regionCode = territory.GetAttribute("id"); - var metadata = LoadCountryMetadata(regionCode, territory, - isShortNumberMetadata, isAlternateFormatsMetadata); metadataFilter.FilterMetadata(metadata); metadataCollection.AddMetadata(metadata); } @@ -151,18 +150,17 @@ public static Dictionary> BuildCountryCodeToRegionCodeMap( private static readonly HashSet ValidPatterns = new HashSet(); - public static string ValidateRE(string regex) - { - return ValidateRE(regex, false); - } + [Obsolete("This method was public to be @VisibleForTesting, it will be moved to internal in a future release.")] + public static string ValidateRE(string regex, bool removeWhitespace = false) + => ValidateRegex(regex, removeWhitespace); - public static string ValidateRE(string regex, bool removeWhitespace) + public static string ValidateRegex(string regex, bool removeWhitespace = false) { // Removes all the whitespace and newline from the regexp. Not using pattern compile options to // make it work across programming languages. if (removeWhitespace) { - for (int i = 0; i < regex.Length; i++) + for (var i = 0; i < regex.Length; i++) if (char.IsWhiteSpace(regex[i])) { var sb = new StringBuilder(regex, 0, i, regex.Length); @@ -193,7 +191,10 @@ public static string ValidateRE(string regex, bool removeWhitespace) /** * Returns the national prefix of the provided country element. */ - public static string GetNationalPrefix(XElement element) + [Obsolete("This method was public to be @VisibleForTesting, it will be moved to internal in a future release.")] + public static string GetNationalPrefix(XElement element) => GetNationalPrefixInternal(element); + + internal static string GetNationalPrefixInternal(XElement element) { return element.GetAttribute(NATIONAL_PREFIX); } @@ -206,19 +207,19 @@ public static PhoneMetadata.Builder LoadTerritoryTagMetadata(string regionCode, if (element.HasAttribute(COUNTRY_CODE)) metadata.SetCountryCode(int.Parse(element.GetAttribute(COUNTRY_CODE))); if (element.HasAttribute(LEADING_DIGITS)) - metadata.SetLeadingDigits(ValidateRE(element.GetAttribute(LEADING_DIGITS))); + metadata.SetLeadingDigits(ValidateRegex(element.GetAttribute(LEADING_DIGITS))); if (element.HasAttribute(INTERNATIONAL_PREFIX)) - metadata.SetInternationalPrefix(ValidateRE(element.GetAttribute(INTERNATIONAL_PREFIX))); + metadata.SetInternationalPrefix(ValidateRegex(element.GetAttribute(INTERNATIONAL_PREFIX))); if (element.HasAttribute(PREFERRED_INTERNATIONAL_PREFIX)) metadata.SetPreferredInternationalPrefix( element.GetAttribute(PREFERRED_INTERNATIONAL_PREFIX)); if (element.HasAttribute(NATIONAL_PREFIX_FOR_PARSING)) { metadata.SetNationalPrefixForParsing( - ValidateRE(element.GetAttribute(NATIONAL_PREFIX_FOR_PARSING), true)); + ValidateRegex(element.GetAttribute(NATIONAL_PREFIX_FOR_PARSING), true)); if (element.HasAttribute(NATIONAL_PREFIX_TRANSFORM_RULE)) metadata.SetNationalPrefixTransformRule( - ValidateRE(element.GetAttribute(NATIONAL_PREFIX_TRANSFORM_RULE))); + ValidateRegex(element.GetAttribute(NATIONAL_PREFIX_TRANSFORM_RULE))); } if (!string.IsNullOrEmpty(nationalPrefix)) { @@ -242,7 +243,12 @@ public static PhoneMetadata.Builder LoadTerritoryTagMetadata(string regionCode, * @throws RuntimeException if multiple intlFormats have been encountered. * @return whether an international number format is defined. */ - public static bool LoadInternationalFormat(PhoneMetadata.Builder metadata, + [Obsolete("This method was public to be @VisibleForTesting, it will be moved to internal in a future release.")] + public static bool LoadInternationalFormat(PhoneMetadata.Builder metadata, XElement numberFormatElement, + string nationalFormat) + => LoadInternationalFormatInternal(metadata, numberFormatElement, nationalFormat); + + public static bool LoadInternationalFormatInternal(PhoneMetadata.Builder metadata, XElement numberFormatElement, string nationalFormat) { @@ -278,11 +284,16 @@ public static bool LoadInternationalFormat(PhoneMetadata.Builder metadata, * @throws RuntimeException if multiple or no formats have been encountered. * @return the national format string. */ + [Obsolete("This method was public to be @VisibleForTesting, it will be moved to internal in a future release.")] public static string LoadNationalFormat(PhoneMetadata.Builder metadata, XElement numberFormatElement, NumberFormat.Builder format) + => LoadNationalFormatInternal(metadata, numberFormatElement, format); + + internal static string LoadNationalFormatInternal(PhoneMetadata.Builder metadata, XElement numberFormatElement, + NumberFormat.Builder format) { SetLeadingDigitsPatterns(numberFormatElement, format); - format.SetPattern(ValidateRE(numberFormatElement.GetAttribute(PATTERN))); + format.SetPattern(ValidateRegex(numberFormatElement.GetAttribute(PATTERN))); var formatPattern = numberFormatElement.Elements(FORMAT).ToList(); if (formatPattern.Count != 1) @@ -299,20 +310,26 @@ public static string LoadNationalFormat(PhoneMetadata.Builder metadata, XElement * nationalPrefixFormattingRule and nationalPrefixOptionalWhenFormatting values are provided from * the parent (territory) element. */ - public static void LoadAvailableFormats(PhoneMetadata.Builder metadata, - XElement element, string nationalPrefix, - string nationalPrefixFormattingRule, - bool nationalPrefixOptionalWhenFormatting) + + + [Obsolete("This method was public to be @VisibleForTesting, it will be moved to internal in a future release.")] + public static void LoadAvailableFormats(PhoneMetadata.Builder metadata, XElement element, string nationalPrefix, + string nationalPrefixFormattingRule, bool nationalPrefixOptionalWhenFormatting) + => LoadAvailableFormatsInternal(metadata, element, nationalPrefix, nationalPrefixFormattingRule, + nationalPrefixOptionalWhenFormatting); + + internal static void LoadAvailableFormatsInternal(PhoneMetadata.Builder metadata, XElement element, string nationalPrefix, + string nationalPrefixFormattingRule, bool nationalPrefixOptionalWhenFormatting) { var carrierCodeFormattingRule = ""; if (element.HasAttribute(CARRIER_CODE_FORMATTING_RULE)) - carrierCodeFormattingRule = ValidateRE( + carrierCodeFormattingRule = ValidateRegex( GetDomesticCarrierCodeFormattingRuleFromElement(element, nationalPrefix)); var availableFormats = element.Element("availableFormats"); var hasExplicitIntlFormatDefined = false; - if (availableFormats != null && availableFormats.HasElements) + if (availableFormats?.HasElements == true) { foreach (var numberFormatElement in availableFormats.Elements()) { @@ -321,7 +338,7 @@ public static void LoadAvailableFormats(PhoneMetadata.Builder metadata, if (numberFormatElement.HasAttribute(NATIONAL_PREFIX_FORMATTING_RULE)) { format.SetNationalPrefixFormattingRule( - GetNationalPrefixFormattingRuleFromElement(numberFormatElement, nationalPrefix)); + GetNationalPrefixFormattingRuleFromElementInternal(numberFormatElement, nationalPrefix)); } else if (!nationalPrefixFormattingRule.Equals("")) { @@ -338,7 +355,7 @@ public static void LoadAvailableFormats(PhoneMetadata.Builder metadata, format.SetNationalPrefixOptionalWhenFormatting(nationalPrefixOptionalWhenFormatting); } if (numberFormatElement.HasAttribute("carrierCodeFormattingRule")) - format.SetDomesticCarrierCodeFormattingRule(ValidateRE( + format.SetDomesticCarrierCodeFormattingRule(ValidateRegex( GetDomesticCarrierCodeFormattingRuleFromElement( numberFormatElement, nationalPrefix))); else if (!carrierCodeFormattingRule.Equals("")) @@ -346,10 +363,10 @@ public static void LoadAvailableFormats(PhoneMetadata.Builder metadata, // Extract the pattern for the national format. var nationalFormat = - LoadNationalFormat(metadata, numberFormatElement, format); + LoadNationalFormatInternal(metadata, numberFormatElement, format); metadata.AddNumberFormat(format); - if (LoadInternationalFormat(metadata, numberFormatElement, nationalFormat)) + if (LoadInternationalFormatInternal(metadata, numberFormatElement, nationalFormat)) hasExplicitIntlFormatDefined = true; } // Only a small number of regions need to specify the intlFormats in the xml. For the majority @@ -364,10 +381,10 @@ public static void LoadAvailableFormats(PhoneMetadata.Builder metadata, public static void SetLeadingDigitsPatterns(XElement numberFormatElement, NumberFormat.Builder format) { foreach (var e in numberFormatElement.Elements(LEADING_DIGITS)) - format.AddLeadingDigitsPattern(ValidateRE(e.Value, true)); + format.AddLeadingDigitsPattern(ValidateRegex(e.Value, true)); } - public static string GetNationalPrefixFormattingRuleFromElement(XElement element, + internal static string GetNationalPrefixFormattingRuleFromElementInternal(XElement element, string nationalPrefix) { var nationalPrefixFormattingRule = element.GetAttribute(NATIONAL_PREFIX_FORMATTING_RULE); @@ -426,8 +443,13 @@ private static bool ArePossibleLengthsEqual(SortedSet possibleLengths, * file with information about that type * @return complete description of that phone number type */ + [Obsolete("This method was public to be @VisibleForTesting, it will be moved to internal in a future release.")] public static PhoneNumberDesc.Builder ProcessPhoneNumberDescElement(PhoneNumberDesc parentDesc, XElement countryElement, string numberType) + => ProcessPhoneNumberDescElementInternal(parentDesc, countryElement, numberType); + + internal static PhoneNumberDesc.Builder ProcessPhoneNumberDescElementInternal(PhoneNumberDesc parentDesc, + XElement countryElement, string numberType) { var phoneNumberDescList = countryElement.Elements(numberType).ToList(); var numberDesc = new PhoneNumberDesc.Builder(); @@ -451,7 +473,7 @@ public static PhoneNumberDesc.Builder ProcessPhoneNumberDescElement(PhoneNumberD var validPattern = element.Element(NATIONAL_NUMBER_PATTERN); if (validPattern != null) - numberDesc.SetNationalNumberPattern(ValidateRE(validPattern.Value, true)); + numberDesc.SetNationalNumberPattern(ValidateRegex(validPattern.Value, true)); var exampleNumber = element.Element(EXAMPLE_NUMBER); if (exampleNumber != null) @@ -463,7 +485,7 @@ public static PhoneNumberDesc.Builder ProcessPhoneNumberDescElement(PhoneNumberD private static void SetRelevantDescPatterns(PhoneMetadata.Builder metadata, XElement element, bool isShortNumberMetadata) { - var generalDescBuilder = ProcessPhoneNumberDescElement(null, element, + var generalDescBuilder = ProcessPhoneNumberDescElementInternal(null, element, GENERAL_DESC); // Calculate the possible lengths for the general description. This will be based on the // possible lengths of the child elements. @@ -476,35 +498,35 @@ private static void SetRelevantDescPatterns(PhoneMetadata.Builder metadata, XEle if (!isShortNumberMetadata) { // Set fields used by regular length phone numbers. - metadata.SetFixedLine(ProcessPhoneNumberDescElement(generalDesc, element, FIXED_LINE)); - metadata.SetMobile(ProcessPhoneNumberDescElement(generalDesc, element, MOBILE)); - metadata.SetSharedCost(ProcessPhoneNumberDescElement(generalDesc, element, SHARED_COST)); - metadata.SetVoip(ProcessPhoneNumberDescElement(generalDesc, element, VOIP)); - metadata.SetPersonalNumber(ProcessPhoneNumberDescElement(generalDesc, element, + metadata.SetFixedLine(ProcessPhoneNumberDescElementInternal(generalDesc, element, FIXED_LINE)); + metadata.SetMobile(ProcessPhoneNumberDescElementInternal(generalDesc, element, MOBILE)); + metadata.SetSharedCost(ProcessPhoneNumberDescElementInternal(generalDesc, element, SHARED_COST)); + metadata.SetVoip(ProcessPhoneNumberDescElementInternal(generalDesc, element, VOIP)); + metadata.SetPersonalNumber(ProcessPhoneNumberDescElementInternal(generalDesc, element, PERSONAL_NUMBER)); - metadata.SetPager(ProcessPhoneNumberDescElement(generalDesc, element, PAGER)); - metadata.SetUan(ProcessPhoneNumberDescElement(generalDesc, element, UAN)); - metadata.SetVoicemail(ProcessPhoneNumberDescElement(generalDesc, element, VOICEMAIL)); - metadata.SetNoInternationalDialling(ProcessPhoneNumberDescElement(generalDesc, element, + metadata.SetPager(ProcessPhoneNumberDescElementInternal(generalDesc, element, PAGER)); + metadata.SetUan(ProcessPhoneNumberDescElementInternal(generalDesc, element, UAN)); + metadata.SetVoicemail(ProcessPhoneNumberDescElementInternal(generalDesc, element, VOICEMAIL)); + metadata.SetNoInternationalDialling(ProcessPhoneNumberDescElementInternal(generalDesc, element, NO_INTERNATIONAL_DIALLING)); var mobileAndFixedAreSame = metadata.Mobile.NationalNumberPattern .Equals(metadata.FixedLine.NationalNumberPattern); if (metadata.SameMobileAndFixedLinePattern != mobileAndFixedAreSame) metadata.SetSameMobileAndFixedLinePattern(mobileAndFixedAreSame); - metadata.SetTollFree(ProcessPhoneNumberDescElement(generalDesc, element, TOLL_FREE)); - metadata.SetPremiumRate(ProcessPhoneNumberDescElement(generalDesc, element, PREMIUM_RATE)); + metadata.SetTollFree(ProcessPhoneNumberDescElementInternal(generalDesc, element, TOLL_FREE)); + metadata.SetPremiumRate(ProcessPhoneNumberDescElementInternal(generalDesc, element, PREMIUM_RATE)); } else { // Set fields used by short numbers. - metadata.SetStandardRate(ProcessPhoneNumberDescElement(generalDesc, element, STANDARD_RATE)); - metadata.SetShortCode(ProcessPhoneNumberDescElement(generalDesc, element, SHORT_CODE)); - metadata.SetCarrierSpecific(ProcessPhoneNumberDescElement(generalDesc, element, + metadata.SetStandardRate(ProcessPhoneNumberDescElementInternal(generalDesc, element, STANDARD_RATE)); + metadata.SetShortCode(ProcessPhoneNumberDescElementInternal(generalDesc, element, SHORT_CODE)); + metadata.SetCarrierSpecific(ProcessPhoneNumberDescElementInternal(generalDesc, element, CARRIER_SPECIFIC)); - metadata.SetEmergency(ProcessPhoneNumberDescElement(generalDesc, element, EMERGENCY)); - metadata.SetTollFree(ProcessPhoneNumberDescElement(generalDesc, element, TOLL_FREE)); - metadata.SetPremiumRate(ProcessPhoneNumberDescElement(generalDesc, element, PREMIUM_RATE)); - metadata.SetSmsServices(ProcessPhoneNumberDescElement(generalDesc, element, SMS_SERVICES)); + metadata.SetEmergency(ProcessPhoneNumberDescElementInternal(generalDesc, element, EMERGENCY)); + metadata.SetTollFree(ProcessPhoneNumberDescElementInternal(generalDesc, element, TOLL_FREE)); + metadata.SetPremiumRate(ProcessPhoneNumberDescElementInternal(generalDesc, element, PREMIUM_RATE)); + metadata.SetSmsServices(ProcessPhoneNumberDescElementInternal(generalDesc, element, SMS_SERVICES)); } } @@ -614,7 +636,7 @@ private static void SetPossibleLengthsGeneralDesc(PhoneNumberDesc.Builder genera } if (!isShortNumberMetadata) { - var allDescData = data.Descendants(POSSIBLE_LENGTHS).Where(e => e.Parent.Name != NO_INTERNATIONAL_DIALLING); + var allDescData = data.Descendants(POSSIBLE_LENGTHS).Where(e => e.Parent?.Name != NO_INTERNATIONAL_DIALLING); PopulatePossibleLengthSets(allDescData, lengths, localOnlyLengths); } else @@ -649,7 +671,7 @@ private static void SetPossibleLengths(SortedSet lengths, // lengths in the general desc (for metadata size reasons). if (parentDesc == null || !ArePossibleLengthsEqual(lengths, parentDesc)) foreach (var length in lengths) - if (parentDesc == null || parentDesc.PossibleLengthList.Contains(length)) + if (parentDesc?.PossibleLengthList.Contains(length) != false) desc.PossibleLengthList.Add(length); else throw new Exception( @@ -663,8 +685,7 @@ private static void SetPossibleLengths(SortedSet lengths, // saw this) before adding it to the collection of possible local-only lengths. foreach (var length in localOnlyLengths) if (!lengths.Contains(length)) - if (parentDesc == null || parentDesc.PossibleLengthLocalOnlyList.Contains(length) - || parentDesc.PossibleLengthList.Contains(length)) + if (parentDesc?.PossibleLengthLocalOnlyList.Contains(length) != false || parentDesc.PossibleLengthList.Contains(length)) desc.PossibleLengthLocalOnlyList.Add(length); else throw new Exception( @@ -684,40 +705,44 @@ private static string ReplaceFirst(string input, string value, string replacemen return input; } - // @VisibleForTesting - public static void LoadGeneralDesc(PhoneMetadata.Builder metadata, XElement element) + internal static void LoadGeneralDesc(PhoneMetadata.Builder metadata, XElement element) { - var generalDescBuilder = ProcessPhoneNumberDescElement(null, element, GENERAL_DESC); + var generalDescBuilder = ProcessPhoneNumberDescElementInternal(null, element, GENERAL_DESC); SetPossibleLengthsGeneralDesc(generalDescBuilder, metadata.Id, element, false); var generalDesc = generalDescBuilder.Build(); - metadata.SetFixedLine(ProcessPhoneNumberDescElement(generalDesc, element, FIXED_LINE)); - metadata.SetMobile(ProcessPhoneNumberDescElement(generalDesc, element, MOBILE)); - metadata.SetTollFree(ProcessPhoneNumberDescElement(generalDesc, element, TOLL_FREE)); - metadata.SetPremiumRate(ProcessPhoneNumberDescElement(generalDesc, element, PREMIUM_RATE)); - metadata.SetSharedCost(ProcessPhoneNumberDescElement(generalDesc, element, SHARED_COST)); - metadata.SetVoip(ProcessPhoneNumberDescElement(generalDesc, element, VOIP)); - metadata.SetPersonalNumber(ProcessPhoneNumberDescElement(generalDesc, element, PERSONAL_NUMBER)); - metadata.SetPager(ProcessPhoneNumberDescElement(generalDesc, element, PAGER)); - metadata.SetUan(ProcessPhoneNumberDescElement(generalDesc, element, UAN)); - metadata.SetVoicemail(ProcessPhoneNumberDescElement(generalDesc, element, VOICEMAIL)); - metadata.SetEmergency(ProcessPhoneNumberDescElement(generalDesc, element, EMERGENCY)); + metadata.SetFixedLine(ProcessPhoneNumberDescElementInternal(generalDesc, element, FIXED_LINE)); + metadata.SetMobile(ProcessPhoneNumberDescElementInternal(generalDesc, element, MOBILE)); + metadata.SetTollFree(ProcessPhoneNumberDescElementInternal(generalDesc, element, TOLL_FREE)); + metadata.SetPremiumRate(ProcessPhoneNumberDescElementInternal(generalDesc, element, PREMIUM_RATE)); + metadata.SetSharedCost(ProcessPhoneNumberDescElementInternal(generalDesc, element, SHARED_COST)); + metadata.SetVoip(ProcessPhoneNumberDescElementInternal(generalDesc, element, VOIP)); + metadata.SetPersonalNumber(ProcessPhoneNumberDescElementInternal(generalDesc, element, PERSONAL_NUMBER)); + metadata.SetPager(ProcessPhoneNumberDescElementInternal(generalDesc, element, PAGER)); + metadata.SetUan(ProcessPhoneNumberDescElementInternal(generalDesc, element, UAN)); + metadata.SetVoicemail(ProcessPhoneNumberDescElementInternal(generalDesc, element, VOICEMAIL)); + metadata.SetEmergency(ProcessPhoneNumberDescElementInternal(generalDesc, element, EMERGENCY)); metadata.SetNoInternationalDialling( - ProcessPhoneNumberDescElement(generalDesc, element, NO_INTERNATIONAL_DIALLING)); + ProcessPhoneNumberDescElementInternal(generalDesc, element, NO_INTERNATIONAL_DIALLING)); metadata.SetSameMobileAndFixedLinePattern( metadata.Mobile.NationalNumberPattern.Equals( metadata.FixedLine.NationalNumberPattern)); } - public static PhoneMetadata.Builder LoadCountryMetadata(string regionCode, + [Obsolete("This method was public to be @VisibleForTesting, it will be moved to private in a future release.")] + public static PhoneMetadata.Builder LoadCountryMetadata(string regionCode, XElement element, + bool isShortNumberMetadata, bool isAlternateFormatsMetadata) + => LoadCountryMetadataInternal(regionCode, element, isShortNumberMetadata, isAlternateFormatsMetadata); + + private static PhoneMetadata.Builder LoadCountryMetadataInternal(string regionCode, XElement element, bool isShortNumberMetadata, bool isAlternateFormatsMetadata) { - var nationalPrefix = GetNationalPrefix(element); + var nationalPrefix = GetNationalPrefixInternal(element); var metadata = LoadTerritoryTagMetadata(regionCode, element, nationalPrefix); - var nationalPrefixFormattingRule = GetNationalPrefixFormattingRuleFromElement(element, nationalPrefix); - LoadAvailableFormats(metadata, element, nationalPrefix, + var nationalPrefixFormattingRule = GetNationalPrefixFormattingRuleFromElementInternal(element, nationalPrefix); + LoadAvailableFormatsInternal(metadata, element, nationalPrefix, nationalPrefixFormattingRule, element.HasAttribute(NATIONAL_PREFIX_OPTIONAL_WHEN_FORMATTING)); LoadGeneralDesc(metadata, element); @@ -728,7 +753,7 @@ public static PhoneMetadata.Builder LoadCountryMetadata(string regionCode, public static Dictionary> GetCountryCodeToRegionCodeMap(string filePrefix) { - var collection = BuildPhoneMetadata(filePrefix); // todo lite/special build + var collection = BuildPhoneMetadataCollection(filePrefix); // todo lite/special build return BuildCountryCodeToRegionCodeMap(collection); } @@ -739,7 +764,6 @@ public static Dictionary> GetCountryCodeToRegionCodeMap(string * @param liteBuild The liteBuild flag value as given by the command-line * @param specialBuild The specialBuild flag value as given by the command-line */ - // @VisibleForTesting internal static MetadataFilter GetMetadataFilter(bool liteBuild, bool specialBuild) { if (specialBuild) diff --git a/csharp/PhoneNumbers/Carrier/PhoneNumberToCarrierMapper.cs b/csharp/PhoneNumbers/Carrier/PhoneNumberToCarrierMapper.cs new file mode 100644 index 000000000..00b3aa2b8 --- /dev/null +++ b/csharp/PhoneNumbers/Carrier/PhoneNumberToCarrierMapper.cs @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2013 The Libphonenumber Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.CompilerServices; + +namespace PhoneNumbers.Carrier +{ + /** + * A phone prefix mapper which provides carrier information related to a phone number. + * + * @author Cecilia Roes + */ + public class PhoneNumberToCarrierMapper + { + private static PhoneNumberToCarrierMapper instance; + + private const string MAPPING_DATA_DIRECTORY = "/com/google/i18n/phtonenumbers/carrier/data/"; + + private readonly PrefixFileReader prefixFileReader; + + private readonly PhoneNumberUtil phoneUtil = PhoneNumberUtil.GetInstance(); + + private readonly object obj = new object(); + + internal PhoneNumberToCarrierMapper(string phonePrefixDataDirectory) + { + lock (obj) + { + prefixFileReader = new PrefixFileReader(phonePrefixDataDirectory); + } + } + + /** + * Gets a {@link PhoneNumberToCarrierMapper} instance to carry out international carrier lookup. + * + *

The {@link PhoneNumberToCarrierMapper} is implemented as a singleton. Therefore, calling + * this method multiple times will only result in one instance being created.

+ * + * @return a {@link PhoneNumberToCarrierMapper} instance + */ + public static PhoneNumberToCarrierMapper GetInstance() + { + instance ??= new PhoneNumberToCarrierMapper(MAPPING_DATA_DIRECTORY); + return instance; + } + + /** + * Returns a carrier name for the given phone number, in the language provided. The carrier name + * is the one the number was originally allocated to, however if the country supports mobile + * number portability the number might not belong to the returned carrier anymore. If no mapping + * is found an empty string is returned. + * + *

This method assumes the validity of the number passed in has already been checked, and that + * the number is suitable for carrier lookup. We consider mobile and pager numbers possible + * candidates for carrier lookup.

+ * + * @param number a valid phone number for which we want to get a carrier name + * @param languageCode the language code in which the name should be written + * @return a carrier name for the given phone number + */ + public string GetNameForValidNumber(PhoneNumber number, Locale languageCode) + => prefixFileReader.GetDescriptionForNumber(number, languageCode.Language, string.Empty, + languageCode.Language); + + /** + * Gets the name of the carrier for the given phone number, in the language provided. As per + * {@link #getNameForValidNumber(PhoneNumber, Locale)} but explicitly checks the validity of + * the number passed in. + * + * @param number the phone number for which we want to get a carrier name + * @param languageCode the language code in which the name should be written + * @return a carrier name for the given phone number, or empty string if the number passed in is + * invalid + */ + public string GetNameForNumber(PhoneNumber number, Locale languageCode) + => IsMobile(phoneUtil.GetNumberType(number)) ? GetNameForValidNumber(number, languageCode) : ""; + + /** + * Gets the name of the carrier for the given phone number only when it is 'safe' to display to + * users. A carrier name is considered safe if the number is valid and for a region that doesn't + * support + * mobile number portability. + * + * @param number the phone number for which we want to get a carrier name + * @param languageCode the language code in which the name should be written + * @return a carrier name that is safe to display to users, or the empty string + */ + public string GetSafeDisplayName(PhoneNumber number, Locale languageCode) + => phoneUtil.IsMobileNumberPortableRegion(phoneUtil.GetRegionCodeForNumber(number)) + ? "" + : GetNameForNumber(number, languageCode); + + /** + * Checks if the supplied number type supports carrier lookup. + */ + private static bool IsMobile(PhoneNumberType numberType) + => numberType == PhoneNumberType.MOBILE + || numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE + || numberType == PhoneNumberType.PAGER; + } +} \ No newline at end of file diff --git a/csharp/PhoneNumbers/Carrier/PhonePrefixMap.cs b/csharp/PhoneNumbers/Carrier/PhonePrefixMap.cs new file mode 100644 index 000000000..fe097d23b --- /dev/null +++ b/csharp/PhoneNumbers/Carrier/PhonePrefixMap.cs @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2011 The Libphonenumber Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; + +namespace PhoneNumbers.Carrier +{ + /** + * A utility that maps phone number prefixes to a description string, which may be, for example, + * the geographical area the prefix covers. + * + * @author Shaopeng Jia + */ + public class PhonePrefixMap + { + private readonly PhoneNumberUtil phoneUtil = PhoneNumberUtil.GetInstance(); + + private PhonePrefixMapStorageStrategy phonePrefixMapStorage; + + internal PhonePrefixMapStorageStrategy GetPhonePrefixMapStorage() + { + return phonePrefixMapStorage; + } + + /** + * Creates an empty {@link PhonePrefixMap}. The default constructor is necessary for implementing + * {@link Externalizable}. The empty map could later be populated by + * {@link #readPhonePrefixMap(java.util.SortedMap)} or {@link #readExternal(java.io.ObjectInput)}. + */ + public PhonePrefixMap() + { + } + + /** + * Gets the size of the provided phone prefix map storage. The map storage passed-in will be + * filled as a result. + */ + private static int GetSizeOfPhonePrefixMapStorage(PhonePrefixMapStorageStrategy mapStorage, +#if !NET35 + ImmutableDictionary phonePrefixMap) +#else + SortedDictionary phonePrefixMap) +#endif + { + return 0; + // mapStorage.ReadFromSortedMap(phonePrefixMap); + // var byteArrayOutputStream = new ByteArrayOutputStream(); + // var objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); + // mapStorage.WriteExternal(objectOutputStream); + // objectOutputStream.flush(); + // int sizeOfStorage = byteArrayOutputStream.size(); + // objectOutputStream.close(); + // return sizeOfStorage; + } + + private PhonePrefixMapStorageStrategy createDefaultMapStorage() + { + return new DefaultMapStorage(); + } + + private PhonePrefixMapStorageStrategy createFlyweightMapStorage() + { + return new FlyweightMapStorage(); + } + + /** + * Gets the smaller phone prefix map storage strategy according to the provided phone prefix map. + * It actually uses (outputs the data to a stream) both strategies and retains the best one which + * make this method quite expensive. + */ + internal PhonePrefixMapStorageStrategy GetSmallerMapStorage(ImmutableDictionary phonePrefixMap) + { + try + { + var flyweightMapStorage = createFlyweightMapStorage(); + var sizeOfFlyweightMapStorage = GetSizeOfPhonePrefixMapStorage(flyweightMapStorage, + phonePrefixMap); + + var defaultMapStorage = createDefaultMapStorage(); + var sizeOfDefaultMapStorage = GetSizeOfPhonePrefixMapStorage(defaultMapStorage, + phonePrefixMap); + + return sizeOfFlyweightMapStorage < sizeOfDefaultMapStorage + ? flyweightMapStorage + : defaultMapStorage; + } + catch (IOException) + { + return createFlyweightMapStorage(); + } + } + + /** + * Creates an {@link PhonePrefixMap} initialized with {@code sortedPhonePrefixMap}. Note that the + * underlying implementation of this method is expensive thus should not be called by + * time-critical applications. + * + * @param sortedPhonePrefixMap a map from phone number prefixes to descriptions of those prefixes + * sorted in ascending order of the phone number prefixes as integers. + */ + public void ReadPhonePrefixMap(ImmutableDictionary sortedPhonePrefixMap) + { + phonePrefixMapStorage = GetSmallerMapStorage(sortedPhonePrefixMap); + } + + /** + * Supports Java Serialization. + */ + public void ReadExternal(Stream objectInput) + { + // Read the phone prefix map storage strategy flag. + var useFlyweightMapStorage = objectInput.ReadByte() == 1; + if (useFlyweightMapStorage) + { + phonePrefixMapStorage = new FlyweightMapStorage(); + } + else + { + phonePrefixMapStorage = new DefaultMapStorage(); + } + + phonePrefixMapStorage.ReadExternal(objectInput); + } + + /** + * Supports Java Serialization. + */ + public void WriteExternal(Stream objectOutput) + { + objectOutput.WriteByte((byte) (phonePrefixMapStorage is FlyweightMapStorage ? 1 : 0)); + phonePrefixMapStorage.WriteExternal(objectOutput); + } + + /** + * Returns the description of the {@code number}. This method distinguishes the case of an invalid + * prefix and a prefix for which the name is not available in the current language. If the + * description is not available in the current language an empty string is returned. If no + * description was found for the provided number, null is returned. + * + * @param number the phone number to look up + * @return the description of the number + */ + string Lookup(long number) + { + var numOfEntries = phonePrefixMapStorage.GetNumOfEntries(); + if (numOfEntries == 0) + { + return null; + } + + var phonePrefix = number; + var currentIndex = numOfEntries - 1; + var currentSetOfLengths = phonePrefixMapStorage.GetPossibleLengths(); + while (currentSetOfLengths.Count > 0) + { + var possibleLength = currentSetOfLengths.Last(); + var phonePrefixStr = phonePrefix.ToString(); + if (phonePrefixStr.Length > possibleLength) + { + phonePrefix = long.Parse(phonePrefixStr.Substring(0, possibleLength)); + } + + currentIndex = BinarySearch(0, currentIndex, phonePrefix); + if (currentIndex < 0) + { + return null; + } + + var currentPrefix = phonePrefixMapStorage.GetPrefix(currentIndex); + if (phonePrefix == currentPrefix) + { + return phonePrefixMapStorage.GetDescription(currentIndex); + } + + currentSetOfLengths = currentSetOfLengths.Where(length => length < possibleLength).ToImmutableSortedSet(); + } + + return null; + } + + /** + * As per {@link #lookup(long)}, but receives the number as a PhoneNumber instead of a long. + * + * @param number the phone number to look up + * @return the description corresponding to the prefix that best matches this phone number + */ + public string Lookup(PhoneNumber number) + { + var phonePrefix = + long.Parse(number.CountryCode + phoneUtil.GetNationalSignificantNumber(number)); + return Lookup(phonePrefix); + } + + /** + * Does a binary search for {@code value} in the provided array from {@code start} to {@code end} + * (inclusive). Returns the position if {@code value} is found; otherwise, returns the + * position which has the largest value that is less than {@code value}. This means if + * {@code value} is the smallest, -1 will be returned. + */ + private int BinarySearch(int start, int end, long value) + { + var current = 0; + while (start <= end) + { + current = (start + end) >> 1; + var currentValue = phonePrefixMapStorage.GetPrefix(current); + if (currentValue == value) + { + return current; + } + else if (currentValue > value) + { + current--; + end = current; + } + else + { + start = current + 1; + } + } + + return current; + } + + /** + * Dumps the mappings contained in the phone prefix map. + */ + public override string ToString() + { + return phonePrefixMapStorage.ToString(); + } + } +} \ No newline at end of file diff --git a/csharp/PhoneNumbers/Carrier/PhonePrefixMapStorageStrategy.cs b/csharp/PhoneNumbers/Carrier/PhonePrefixMapStorageStrategy.cs new file mode 100644 index 000000000..949ac3eda --- /dev/null +++ b/csharp/PhoneNumbers/Carrier/PhonePrefixMapStorageStrategy.cs @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2011 The Libphonenumber Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +/** + * Abstracts the way phone prefix data is stored into memory and serialized to a stream. It is used + * by {@link PhonePrefixMap} to support the most space-efficient storage strategy according to the + * provided data. + * + * @author Philippe Liard + */ +abstract class PhonePrefixMapStorageStrategy { + protected int numOfEntries = 0; + protected readonly SortedSet possibleLengths = new SortedSet(); + + /** + * Gets the phone number prefix located at the provided {@code index}. + * + * @param index the index of the prefix that needs to be returned + * @return the phone number prefix at the provided index + */ + public abstract int GetPrefix(int index); + + /** + * Gets the description corresponding to the phone number prefix located at the provided {@code + * index}. If the description is not available in the current language an empty string is + * returned. + * + * @param index the index of the phone number prefix that needs to be returned + * @return the description corresponding to the phone number prefix at the provided index + */ + public abstract string GetDescription(int index); + + /** + * Sets the internal state of the underlying storage implementation from the provided {@code + * sortedPhonePrefixMap} that maps phone number prefixes to description strings. + * + * @param sortedPhonePrefixMap a sorted map that maps phone number prefixes including country + * calling code to description strings + */ + public abstract void ReadFromSortedMap(SortedDictionary sortedPhonePrefixMap); + + /** + * Sets the internal state of the underlying storage implementation reading the provided {@code + * objectInput}. + * + * @param objectInput the object input stream from which the phone prefix map is read + * @throws IOException if an error occurred reading the provided input stream + */ + public abstract void ReadExternal(Stream objectInput); + + /** + * Writes the internal state of the underlying storage implementation to the provided {@code + * objectOutput}. + * + * @param objectOutput the object output stream to which the phone prefix map is written + * @throws IOException if an error occurred writing to the provided output stream + */ + public abstract void WriteExternal(Stream objectOutput); + + /** + * @return the number of entries contained in the phone prefix map + */ + public int GetNumOfEntries() { + return numOfEntries; + } + + /** + * @return the set containing the possible lengths of prefixes + */ + public SortedSet GetPossibleLengths() { + return possibleLengths; + } + + public override string ToString() { + var output = new StringBuilder(); + + for (var i = 0; i < GetNumOfEntries(); i++) { + output.Append(GetPrefix(i)) + .Append("|") + .Append(GetDescription(i)) + .Append("\n"); + } + return output.ToString(); + } +} \ No newline at end of file diff --git a/csharp/PhoneNumbers/Carrier/PrefixFileReader.cs b/csharp/PhoneNumbers/Carrier/PrefixFileReader.cs new file mode 100644 index 000000000..79aca71be --- /dev/null +++ b/csharp/PhoneNumbers/Carrier/PrefixFileReader.cs @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2011 The Libphonenumber Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +namespace PhoneNumbers.Carrier +{ + /** + * A helper class doing file handling and lookup of phone number prefix mappings. + * + * @author Shaopeng Jia + */ + public class PrefixFileReader + { + + private readonly string phonePrefixDataDirectory; + + // The mappingFileProvider knows for which combination of countryCallingCode and language a phone + // prefix mapping file is available in the file system, so that a file can be loaded when needed. + private readonly MappingFileProvider mappingFileProvider = new MappingFileProvider(); + + // A mapping from countryCallingCode_lang to the corresponding phone prefix map that has been + // loaded. + private readonly Dictionary availablePhonePrefixMaps = + new Dictionary(); + + public PrefixFileReader(string phonePrefixDataDirectory) + { + this.phonePrefixDataDirectory = phonePrefixDataDirectory; + LoadMappingFileProvider(); + } + + private void LoadMappingFileProvider() + { + var source = + typeof(PhoneNumberUtil).Assembly.GetManifestResourceStream(phonePrefixDataDirectory + "config"); + try + { + mappingFileProvider.ReadExternal(source); + } + catch (IOException) + { + } + } + + private PhonePrefixMap GetPhonePrefixDescriptions( + int prefixMapKey, string language, string script, string region) + { + var fileName = mappingFileProvider.GetFileName(prefixMapKey, language, script, region); + if (fileName.Length == 0) + { + return null; + } + + if (!availablePhonePrefixMaps.ContainsKey(fileName)) + { + LoadPhonePrefixMapFromFile(fileName); + } + + return availablePhonePrefixMaps[fileName]; + } + + private void LoadPhonePrefixMapFromFile(string fileName) + { + var source = typeof(PhoneNumberUtil).Assembly.GetManifestResourceStream(phonePrefixDataDirectory + fileName); + try + { + var map = new PhonePrefixMap(); + map.ReadExternal(source); + availablePhonePrefixMaps.Add(fileName, map); + } + catch (IOException) + { + } + } + + private static void Close(Stream input) + { + if (input != null) + { + try + { + input.Close(); + } + catch (IOException) + { + } + } + } + + /** + * Returns a text description in the given language for the given phone number. + * + * @param number the phone number for which we want to get a text description + * @param language two or three-letter lowercase ISO language codes as defined by ISO 639. Note + * that where two different language codes exist (e.g. 'he' and 'iw' for Hebrew) we use the + * one that Java/Android canonicalized on ('iw' in this case). + * @param script four-letter titlecase (the first letter is uppercase and the rest of the letters + * are lowercase) ISO script code as defined in ISO 15924 + * @param region two-letter uppercase ISO country code as defined by ISO 3166-1 + * @return a text description in the given language for the given phone number, or an empty + * string if a description is not available + */ + public string GetDescriptionForNumber(PhoneNumber number, string language, string script, string region) + { + var countryCallingCode = number.CountryCode; + // As the NANPA data is split into multiple files covering 3-digit areas, use a phone number + // prefix of 4 digits for NANPA instead, e.g. 1650. + var phonePrefix = (countryCallingCode != 1) + ? countryCallingCode + : (1000 + (int) (number.NationalNumber / 10000000)); + var phonePrefixDescriptions = + GetPhonePrefixDescriptions(phonePrefix, language, script, region); + var description = phonePrefixDescriptions?.Lookup(number); + // When a location is not available in the requested language, fall back to English. + if (string.IsNullOrEmpty(description) && MayFallBackToEnglish(language)) + { + var defaultMap = GetPhonePrefixDescriptions(phonePrefix, "en", "", ""); + if (defaultMap == null) + { + return ""; + } + + description = defaultMap.Lookup(number); + } + + return description ?? ""; + } + + private static bool MayFallBackToEnglish(string lang) + { + // Don't fall back to English if the requested language is among the following: + // - Chinese + // - Japanese + // - Korean + return !lang.Equals("zh") && !lang.Equals("ja") && !lang.Equals("ko"); + } + } +} \ No newline at end of file diff --git a/csharp/PhoneNumbers/DefaultMapStorage.cs b/csharp/PhoneNumbers/DefaultMapStorage.cs index ea1bb600a..03f468136 100644 --- a/csharp/PhoneNumbers/DefaultMapStorage.cs +++ b/csharp/PhoneNumbers/DefaultMapStorage.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; namespace PhoneNumbers @@ -27,7 +28,7 @@ namespace PhoneNumbers * * @author Shaopeng Jia */ - public class DefaultMapStorage : AreaCodeMapStorageStrategy + public class DefaultMapStorage : PhonePrefixMapStorageStrategy { private int[] phoneNumberPrefixes; private string[] descriptions; @@ -63,8 +64,17 @@ public override void ReadFromSortedMap(SortedDictionary sortedAreaC possibleLengthsSet.Add(lengthOfPrefix); } PossibleLengths.Clear(); - PossibleLengths.AddRange(possibleLengthsSet); - PossibleLengths.Sort(); + PossibleLengths.UnionWith(possibleLengthsSet); + } + + public override void WriteExternal(Stream stream) + { + throw new NotImplementedException(); + } + + public override void ReadExternal(Stream inputStream) + { + throw new NotImplementedException(); } } } diff --git a/csharp/PhoneNumbers/FlyweightMapStorage.cs b/csharp/PhoneNumbers/FlyweightMapStorage.cs index a480640c1..5a8209a94 100644 --- a/csharp/PhoneNumbers/FlyweightMapStorage.cs +++ b/csharp/PhoneNumbers/FlyweightMapStorage.cs @@ -28,7 +28,7 @@ namespace PhoneNumbers * * @author Philippe Liard */ - public class FlyweightMapStorage : AreaCodeMapStorageStrategy + public class FlyweightMapStorage : PhonePrefixMapStorageStrategy { // Size of short and integer types in bytes. private static readonly int ShortNumBytes = sizeof(short); @@ -92,11 +92,20 @@ public override void ReadFromSortedMap(SortedDictionary areaCodeMap index++; } PossibleLengths.Clear(); - PossibleLengths.AddRange(possibleLengthsSet); - PossibleLengths.Sort(); + PossibleLengths.UnionWith(possibleLengthsSet); CreateDescriptionPool(descriptionsSet, areaCodeMap); } + public override void WriteExternal(Stream stream) + { + throw new NotImplementedException(); + } + + public override void ReadExternal(Stream inputStream) + { + throw new NotImplementedException(); + } + /** * Creates the description pool from the provided set of string descriptions and area code map. */ diff --git a/csharp/PhoneNumbers/MappingFileProvider.cs b/csharp/PhoneNumbers/MappingFileProvider.cs index 91826936c..44a0b8d97 100644 --- a/csharp/PhoneNumbers/MappingFileProvider.cs +++ b/csharp/PhoneNumbers/MappingFileProvider.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; @@ -180,5 +181,10 @@ private static void AppendSubsequentLocalePart(string subsequentLocalePart, Stri if (subsequentLocalePart.Length > 0) fullLocale.Append('_').Append(subsequentLocalePart); } + + public void ReadExternal(Stream source) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/csharp/PhoneNumbers/MetadataFilter.cs b/csharp/PhoneNumbers/MetadataFilter.cs index d6f79d34a..67559a3ac 100644 --- a/csharp/PhoneNumbers/MetadataFilter.cs +++ b/csharp/PhoneNumbers/MetadataFilter.cs @@ -17,6 +17,9 @@ using System; using System.Linq; using System.Collections.Generic; +#if !NET35 +using System.Collections.Immutable; +#endif namespace PhoneNumbers { @@ -40,7 +43,11 @@ public class MetadataFilter // Currently we support only one non-primitive type and the depth of the "family tree" is 2, // meaning a field may have only direct descendants, who may not have descendants of their own. If // this changes, the blacklist handling in this class should also change. +#if !NET35 + internal static readonly ImmutableSortedSet ExcludableParentFields = new [] +#else internal static readonly SortedSet ExcludableParentFields = new SortedSet +#endif { "fixedLine", "mobile", @@ -58,21 +65,37 @@ public class MetadataFilter "carrierSpecific", "smsServices", "noInternationalDialling" - }; + } +#if !NET35 + .ToImmutableSortedSet() +#endif + ; // Note: If this set changes, the descHasData implementation must change in PhoneNumberUtil. // The current implementation assumes that all PhoneNumberDesc fields are present here, since it // "clears" a PhoneNumberDesc field by simply clearing all of the fields under it. See the comment // above, about all 3 sets, for more about these fields. +#if !NET35 + internal static readonly ImmutableSortedSet ExcludableChildFields = new [] +#else internal static readonly SortedSet ExcludableChildFields = new SortedSet +#endif { "nationalNumberPattern", "possibleLength", "possibleLengthLocalOnly", "exampleNumber" - }; + } +#if !NET35 + .ToImmutableSortedSet() +#endif + ; +#if !NET35 + internal static readonly ImmutableSortedSet ExcludableChildlessFields = new [] +#else internal static readonly SortedSet ExcludableChildlessFields = new SortedSet +#endif { "preferredInternationalPrefix", "nationalPrefix", @@ -82,12 +105,22 @@ public class MetadataFilter "mainCountryForCode", "leadingZeroPossible", "mobileNumberPortableRegion" - }; + } +#if !NET35 + .ToImmutableSortedSet() +#endif + ; + +#if !NET35 + private readonly ImmutableDictionary> blacklist; + internal MetadataFilter(ImmutableDictionary> blacklist) +#else private readonly Dictionary> blacklist; - // @VisibleForTesting internal MetadataFilter(Dictionary> blacklist) +#endif + { this.blacklist = blacklist; } @@ -100,8 +133,12 @@ internal MetadataFilter(Dictionary> blacklist) internal static MetadataFilter ForSpecialBuild() => new MetadataFilter(ComputeComplement(ParseFieldMapFromString("mobile"))); // Empty blacklist, meaning we filter nothing. - internal static MetadataFilter EmptyFilter() => new MetadataFilter(new Dictionary>()); - + internal static MetadataFilter EmptyFilter() => new MetadataFilter( +#if !NET35 + ImmutableDictionary>.Empty); +#else + new Dictionary>(); +#endif public override bool Equals(object obj) => blacklist.Count == ((MetadataFilter) obj)?.blacklist?.Count && blacklist.All(kvp => @@ -187,7 +224,11 @@ internal void FilterMetadata(PhoneMetadata.Builder metadata) * the sets of excludable fields. We also throw Exception for empty strings since such * strings should be treated as a special case by the flag checking code and not passed here. */ +#if !NET35 + internal static ImmutableDictionary> ParseFieldMapFromString(string str) +#else internal static Dictionary> ParseFieldMapFromString(string str) +#endif { if (str == null) throw new Exception("Null string should not be passed to ParseFieldMapFromString"); @@ -271,20 +312,34 @@ internal static Dictionary> ParseFieldMapFromString(st } } - return fieldMap; + return fieldMap +#if !NET35 + .ToImmutableDictionary(item => item.Key, item => item.Value.ToImmutableSortedSet()); +#endif } // Does not check that legal tokens are used, assuming that fieldMap is constructed using // ParseFieldMapFromString(String) which does check. If fieldMap Contains illegal tokens or parent // fields with no children or other unexpected state, the behavior of this function is undefined. +#if !NET35 + internal static ImmutableDictionary> ComputeComplement( + IDictionary> fieldMap) + { + var complement = new Dictionary>(); +#else internal static Dictionary> ComputeComplement( IDictionary> fieldMap) { var complement = new Dictionary>(); +#endif foreach (var parent in ExcludableParentFields) if (!fieldMap.TryGetValue(parent, out var otherChildren)) { - complement.Add(parent, new SortedSet(ExcludableChildFields)); + complement.Add(parent, ExcludableChildFields +#if !NET35 + .ToImmutableSortedSet() +#endif + ); } else { @@ -292,17 +347,30 @@ internal static Dictionary> ComputeComplement( // parent as a key. if (otherChildren.Count != ExcludableChildFields.Count) { - var children = new SortedSet(); - foreach (var child in ExcludableChildFields) - if (!otherChildren.Contains(child)) - children.Add(child); + var children = ExcludableChildFields.Where(child => !otherChildren.Contains(child)) +#if !NET35 + .ToImmutableSortedSet() +#endif + ; complement.Add(parent, children); } } - foreach (var childlessField in ExcludableChildlessFields) - if (!fieldMap.ContainsKey(childlessField)) - complement.Add(childlessField, new SortedSet()); + + foreach (var childlessField in ExcludableChildlessFields.Where(childlessField => + !fieldMap.ContainsKey(childlessField))) + { + complement.Add(childlessField, +#if !NET35 + ImmutableSortedSet.Empty); + } + + return complement.ToImmutableDictionary(); +#else + new SortedSet(); + } + return complement; +#endif } internal bool ShouldDrop(string parent, string child) diff --git a/csharp/PhoneNumbers/MetadataManager.cs b/csharp/PhoneNumbers/MetadataManager.cs index 79a4d27e6..19f78c1d2 100644 --- a/csharp/PhoneNumbers/MetadataManager.cs +++ b/csharp/PhoneNumbers/MetadataManager.cs @@ -31,14 +31,14 @@ public static class MetadataManager private static class AlternateFormats { public static readonly Dictionary Map = - BuildMetadataFromXml.BuildPhoneMetadata("PhoneNumberAlternateFormats.xml", isAlternateFormatsMetadata: true).MetadataList.ToDictionary(m => m.CountryCode); + BuildMetadataFromXml.BuildPhoneMetadataCollection("PhoneNumberAlternateFormats.xml", isAlternateFormatsMetadata: true).MetadataList.ToDictionary(m => m.CountryCode); } private static class ShortNumber { // A mapping from a region code to the short number metadata for that region code. public static readonly Dictionary MetadataMap = - BuildMetadataFromXml.BuildPhoneMetadata("ShortNumberMetadata.xml", isShortNumberMetadata: true).MetadataList.ToDictionary(m => m.Id); + BuildMetadataFromXml.BuildPhoneMetadataCollection("ShortNumberMetadata.xml", isShortNumberMetadata: true).MetadataList.ToDictionary(m => m.Id); } public static PhoneMetadata GetAlternateFormatsForCountry(int countryCallingCode) diff --git a/csharp/PhoneNumbers/PhoneNumberMatcher.cs b/csharp/PhoneNumbers/PhoneNumberMatcher.cs index 294d6b235..f2a64fd32 100644 --- a/csharp/PhoneNumbers/PhoneNumberMatcher.cs +++ b/csharp/PhoneNumbers/PhoneNumberMatcher.cs @@ -43,11 +43,11 @@ public class PhoneNumberMatcher : IEnumerator { private static readonly Regex Pattern; - /// + /// /// Matches strings that look like publication pages. Example: ///
Computing Complete Answers to Queries in the Presence of Limited Access Patterns.
         /// Chen Li. VLDB J. 12(3): 211-227 (2003).
- /// + /// /// The string "211-227 (2003)" is not a telephone number. ///
private static readonly Regex PubPages = new Regex("\\d{1,5}-+\\d{1,5}\\s{0,4}\\(\\d{1,4}", InternalRegexOptions.Default); @@ -175,7 +175,7 @@ private static string Limit(int lower, int upper) /// Creates a new instance. See the factory methods in {@link PhoneNumberUtil} on how to obtain a /// new instance. ///
- /// + /// /// the phone number util to use /// the character sequence that we will search, null for no text /// the country to assume for phone numbers not written in international format @@ -247,7 +247,10 @@ private static string TrimAfterFirstMatch(Regex pattern, string candidate) /// combining marks should also return true since we assume they have been added to a preceding /// Latin character. ///
- public static bool IsLatinLetter(char letter) + [Obsolete("This method was public to be @VisibleForTesting, it will be moved to internal in a future release.")] + public static bool IsLatinLetter(char letter) => IsLatinLetterInternal(letter); + + internal static bool IsLatinLetterInternal(char letter) { // Combining marks are a subset of non-spacing-mark. if (!char.IsLetter(letter) && CharUnicodeInfo.GetUnicodeCategory(letter) != UnicodeCategory.NonSpacingMark) @@ -295,10 +298,10 @@ public static string TrimAfterUnwantedChars(string str) return found >= 0 ? str.Substring(0, found) : str; } - /// + /// /// Attempts to extract a match from a candidate character sequence. - /// - /// + /// + /// /// the candidate text that might contain a phone number /// the offset of candidate within /// the match found, null if none can be found @@ -410,7 +413,7 @@ private PhoneNumberMatch ParseAndVerify(string candidate, int offset) { var previousChar = text[offset - 1]; // We return null if it is a latin letter or an invalid punctuation symbol. - if (IsInvalidPunctuationSymbol(previousChar) || IsLatinLetter(previousChar)) + if (IsInvalidPunctuationSymbol(previousChar) || IsLatinLetterInternal(previousChar)) { return null; } @@ -419,7 +422,7 @@ private PhoneNumberMatch ParseAndVerify(string candidate, int offset) if (lastCharIndex < text.Length) { var nextChar = text[lastCharIndex]; - if (IsInvalidPunctuationSymbol(nextChar) || IsLatinLetter(nextChar)) + if (IsInvalidPunctuationSymbol(nextChar) || IsLatinLetterInternal(nextChar)) { return null; } @@ -556,7 +559,7 @@ private static IList GetNationalNumberGroups(PhoneNumberUtil util, Phone return rfc3966Format.Substring(startIndex, endIndex - startIndex).Split('-'); } - /// + /// /// Helper method to get the national-number part of a number, formatted without any national /// prefix, and return it as a set of digit blocks that should be formatted together according to /// the formatting pattern passed in. @@ -697,7 +700,7 @@ public static bool IsNationalPrefixPresentIfRequired(PhoneNumber number, PhoneNu var rawInput = new StringBuilder(rawInputCopy); // Check if we found a national prefix and/or carrier code at the start of the raw input, and // return the result. - return util.MaybeStripNationalPrefixAndCarrierCode(rawInput, metadata, null); + return util.MaybeStripNationalPrefixAndCarrierCodeInternal(rawInput, metadata, null); } return true; } diff --git a/csharp/PhoneNumbers/PhoneNumberOfflineGeocoder.cs b/csharp/PhoneNumbers/PhoneNumberOfflineGeocoder.cs index 0a938e69c..6981677d2 100644 --- a/csharp/PhoneNumbers/PhoneNumberOfflineGeocoder.cs +++ b/csharp/PhoneNumbers/PhoneNumberOfflineGeocoder.cs @@ -68,7 +68,7 @@ public string GetDisplayCountry(string language) private static string GetCountryName(string country, string language) { var names = LocaleData.Data[country]; - if (!names.TryGetValue(language, out string name)) + if (!names.TryGetValue(language, out var name)) return null; if (name.Length > 0 && name[0] == '*') return names[name.Substring(1)]; @@ -98,8 +98,7 @@ public class PhoneNumberOfflineGeocoder // loaded. private readonly Dictionary availablePhonePrefixMaps = new Dictionary(); - // @VisibleForTesting - public PhoneNumberOfflineGeocoder(string phonePrefixDataDirectory, Assembly asm = null) + internal PhoneNumberOfflineGeocoder(string phonePrefixDataDirectory, Assembly asm = null) { var files = new SortedDictionary>(); #if NETSTANDARD1_3 || PORTABLE @@ -152,11 +151,9 @@ private AreaCodeMap GetPhonePrefixDescriptions( private AreaCodeMap LoadAreaCodeMapFromFile(string fileName) { var resName = phonePrefixDataDirectory + fileName; - using (var fp = assembly.GetManifestResourceStream(resName)) - { - var areaCodeMap = AreaCodeParser.ParseAreaCodeMap(fp); - return availablePhonePrefixMaps[fileName] = areaCodeMap; - } + using var fp = assembly.GetManifestResourceStream(resName); + var areaCodeMap = AreaCodeParser.ParseAreaCodeMap(fp); + return availablePhonePrefixMaps[fileName] = areaCodeMap; } /** @@ -172,7 +169,7 @@ public static PhoneNumberOfflineGeocoder GetInstance() { lock (ThisLock) { - return instance ?? (instance = new PhoneNumberOfflineGeocoder(MAPPING_DATA_DIRECTORY)); + return instance ??= new PhoneNumberOfflineGeocoder(MAPPING_DATA_DIRECTORY); } } @@ -205,8 +202,7 @@ private string GetCountryNameForNumber(PhoneNumber number, Locale language) */ private static string GetRegionDisplayName(string regionCode, Locale language) { - return regionCode == null || regionCode.Equals("ZZ") || - regionCode.Equals(PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY) + return regionCode?.Equals("ZZ") != false || regionCode.Equals(PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY) ? "" : new Locale("", regionCode).GetDisplayCountry(language.Language); } @@ -224,12 +220,8 @@ private static string GetRegionDisplayName(string regionCode, Locale language) */ public string GetDescriptionForValidNumber(PhoneNumber number, Locale languageCode) { - var langStr = languageCode.Language; - var scriptStr = ""; // No script is specified - var regionStr = languageCode.Country; - var areaDescription = - GetAreaDescriptionForNumber(number, langStr, scriptStr, regionStr); + GetAreaDescriptionForNumber(number, languageCode.Language, string.Empty, languageCode.Country); return (areaDescription.Length > 0) ? areaDescription : GetCountryNameForNumber(number, languageCode); } @@ -260,16 +252,12 @@ public string GetDescriptionForValidNumber(PhoneNumber number, Locale languageCo { // If the user region matches the number's region, then we just show the lower-level // description, if one exists - if no description exists, we will show the region(country) name - // for the number. + // for the number. Otherwise, we just show the region(country) name for now. var regionCode = phoneUtil.GetRegionCodeForNumber(number); - if (userRegion.Equals(regionCode)) - { - return GetDescriptionForValidNumber(number, languageCode); - } - // Otherwise, we just show the region(country) name for now. - return GetRegionDisplayName(regionCode, languageCode); - // TODO: Concatenate the lower-level and country-name information in an appropriate - // way for each language. + return userRegion.Equals(regionCode) + ? GetDescriptionForValidNumber(number, languageCode) + : GetRegionDisplayName(regionCode, languageCode); + // TODO: Concatenate the lower-level and country-name information in an appropriate way for each language. } /** diff --git a/csharp/PhoneNumbers/PhoneNumberUtil.cs b/csharp/PhoneNumbers/PhoneNumberUtil.cs index b6a98b519..6d24c4344 100644 --- a/csharp/PhoneNumbers/PhoneNumberUtil.cs +++ b/csharp/PhoneNumbers/PhoneNumberUtil.cs @@ -20,9 +20,11 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; +[assembly: InternalsVisibleTo("PhoneNumbers.Test")] namespace PhoneNumbers { /// @@ -529,10 +531,9 @@ public bool Verify( } } - // Visible for testing internal PhoneNumberUtil(string baseFileLocation, Assembly asm = null, Dictionary> countryCallingCodeToRegionCodeMap = null) { - var phoneMetadata = BuildMetadataFromXml.BuildPhoneMetadata(baseFileLocation, asm); + var phoneMetadata = BuildMetadataFromXml.BuildPhoneMetadataCollection(baseFileLocation, asm); this.countryCallingCodeToRegionCodeMap = countryCallingCodeToRegionCodeMap ??= BuildMetadataFromXml.BuildCountryCodeToRegionCodeMap(phoneMetadata); foreach (var regionCodes in countryCallingCodeToRegionCodeMap) @@ -596,12 +597,11 @@ public static string ExtractPossibleNumber(string number) /// /// String to be checked for viability as a phone number. /// True if the number could be a phone number of some sort, otherwise false. - public static bool IsViablePhoneNumber(string number) - { - if (number.Length < MIN_LENGTH_FOR_NSN) - return false; - return ValidPhoneNumberPattern.IsMatchAll(number); - } + [Obsolete("This method was public to be @VisibleForTesting, it will be moved to internal in a future release.")] + public static bool IsViablePhoneNumber(string number) => IsViablePhoneNumberInternal(number); + + internal static bool IsViablePhoneNumberInternal(string number) + => number.Length >= MIN_LENGTH_FOR_NSN && ValidPhoneNumberPattern.IsMatchAll(number); /// /// Normalizes a string of characters representing a phone number. This performs the following @@ -881,8 +881,8 @@ private static string NormalizeHelper(string number, Dictionary norm private static StringBuilder NormalizeHelper(StringBuilder number, Dictionary normalizationReplacements, bool removeNonMatches) { - int pos = 0; - for (int i = 0; i < number.Length; i++) + var pos = 0; + for (var i = 0; i < number.Length; i++) { var character = number[i]; if (normalizationReplacements.TryGetValue(char.ToUpperInvariant(character), out char newDigit)) @@ -902,8 +902,13 @@ private static StringBuilder NormalizeHelper(StringBuilder number, Dictionary /// a PhoneNumberUtil instance + [Obsolete("This method will be moved to private in a future release.")] public static PhoneNumberUtil GetInstance(string baseFileLocation, Dictionary> countryCallingCodeToRegionCodeMap = null) + => GetInstanceInternal(baseFileLocation, countryCallingCodeToRegionCodeMap); + + private static PhoneNumberUtil GetInstanceInternal(string baseFileLocation, + Dictionary> countryCallingCodeToRegionCodeMap = null) { lock (ThisLock) return instance ??= new PhoneNumberUtil(baseFileLocation, null, countryCallingCodeToRegionCodeMap); @@ -1043,7 +1048,7 @@ public HashSet GetSupportedTypesForNonGeoEntity(int countryCall /// A instance. public static PhoneNumberUtil GetInstance() { - return instance ?? GetInstance(META_DATA_FILE_PREFIX); + return instance ?? GetInstanceInternal(META_DATA_FILE_PREFIX); } /// @@ -2301,7 +2306,7 @@ public bool IsNANPACountry(string regionCode) /// True if the number is a valid vanity number. public bool IsAlphaNumber(string number) { - if (!IsViablePhoneNumber(number)) + if (!IsViablePhoneNumberInternal(number)) { // Number is too short, or doesn't match the basic phone number pattern. return false; @@ -2625,7 +2630,12 @@ internal int ExtractCountryCode(StringBuilder fullNumber, StringBuilder national /// to be populated. Note the country_code is always populated, whereas country_code_source is /// only populated when keepCountryCodeSource is true. /// the country calling code extracted or 0 if none could be extracted + [Obsolete("This method was public to be @VisibleForTesting, it will be moved to internal in a future release.")] public int MaybeExtractCountryCode(string number, PhoneMetadata defaultRegionMetadata, + StringBuilder nationalNumber, bool keepRawInput, PhoneNumber.Builder phoneNumber) => + MaybeExtractCountryCodeInternal(number, defaultRegionMetadata, nationalNumber, keepRawInput, phoneNumber); + + internal int MaybeExtractCountryCodeInternal(string number, PhoneMetadata defaultRegionMetadata, StringBuilder nationalNumber, bool keepRawInput, PhoneNumber.Builder phoneNumber) { if (number.Length == 0) @@ -2639,7 +2649,7 @@ public int MaybeExtractCountryCode(string number, PhoneMetadata defaultRegionMet } var countryCodeSource = - MaybeStripInternationalPrefixAndNormalize(fullNumber, possibleCountryIddPrefix); + MaybeStripInternationalPrefixAndNormalizeInternal(fullNumber, possibleCountryIddPrefix); if (keepRawInput) { phoneNumber.SetCountryCodeSource(countryCodeSource); @@ -2702,7 +2712,7 @@ public int MaybeExtractCountryCode(string number, PhoneMetadata defaultRegionMet /// /// Strips the IDD from the start of the number if present. Helper function used by - /// . + /// . /// private static bool ParsePrefixAsIdd(PhoneRegex iddPattern, StringBuilder number) { @@ -2736,8 +2746,13 @@ private static bool ParsePrefixAsIdd(PhoneRegex iddPattern, StringBuilder number /// The corresponding CountryCodeSource if an international dialing prefix could be /// removed from the number, otherwise CountryCodeSource.FROM_DEFAULT_COUNTRY if the number did /// not seem to be in international format. - public PhoneNumber.Types.CountryCodeSource MaybeStripInternationalPrefixAndNormalize(StringBuilder number, - string possibleIddPrefix) + [Obsolete("This method was public to be @VisibleForTesting, it will be moved to internal in a future release.")] + public PhoneNumber.Types.CountryCodeSource MaybeStripInternationalPrefixAndNormalize( + StringBuilder number, string possibleIddPrefix) => + MaybeStripInternationalPrefixAndNormalizeInternal(number, possibleIddPrefix); + + internal PhoneNumber.Types.CountryCodeSource MaybeStripInternationalPrefixAndNormalizeInternal( + StringBuilder number, string possibleIddPrefix) { if (number.Length == 0) return PhoneNumber.Types.CountryCodeSource.FROM_DEFAULT_COUNTRY; @@ -2767,10 +2782,14 @@ public PhoneNumber.Types.CountryCodeSource MaybeStripInternationalPrefixAndNorma /// The metadata for the region that we think this number is from. /// A place to insert the carrier code if one is extracted. /// True if a national prefix or carrier code (or both) could be extracted. + [Obsolete("This method was public to be @VisibleForTesting, it will be moved to internal in a future release.")] public bool MaybeStripNationalPrefixAndCarrierCode(StringBuilder number, PhoneMetadata metadata, StringBuilder carrierCode) => MaybeStripNationalPrefixAndCarrierCode(number, null, metadata, carrierCode); - internal bool MaybeStripNationalPrefixAndCarrierCode(StringBuilder number, string numberString, PhoneMetadata metadata, StringBuilder carrierCode = null) + internal bool MaybeStripNationalPrefixAndCarrierCodeInternal(StringBuilder number, PhoneMetadata metadata, StringBuilder carrierCode) + => MaybeStripNationalPrefixAndCarrierCode(number, null, metadata, carrierCode); + + private bool MaybeStripNationalPrefixAndCarrierCode(StringBuilder number, string numberString, PhoneMetadata metadata, StringBuilder carrierCode = null) { var numberLength = numberString?.Length ?? number.Length; var possibleNationalPrefix = metadata.NationalPrefixForParsing; @@ -2831,12 +2850,12 @@ internal bool MaybeStripNationalPrefixAndCarrierCode(StringBuilder number, strin /// The non-normalized telephone number that we wish to strip the extension from. /// The same number as a string /// The phone extension. - static string MaybeStripExtension(StringBuilder number, string numberString) + private static string MaybeStripExtension(StringBuilder number, string numberString) { var m = ExtnPattern.Match(numberString); // If we find a potential extension, and the number preceding this is a viable number, we assume // it is an extension. - if (m.Success && IsViablePhoneNumber(numberString.Substring(0, m.Index))) + if (m.Success && IsViablePhoneNumberInternal(numberString.Substring(0, m.Index))) { // The numbers are captured into groups in the regular expression. for (int i = 1, length = m.Groups.Count; i < length; i++) @@ -3011,7 +3030,7 @@ private void ParseHelper(string numberToParse, string defaultRegion, bool keepRa BuildNationalNumberForParsing(numberToParse, nationalNumber); var nationalNumberString = nationalNumber.ToString(); - if (!IsViablePhoneNumber(nationalNumberString)) + if (!IsViablePhoneNumberInternal(nationalNumberString)) throw new NumberParseException(ErrorType.NOT_A_NUMBER, "The string supplied did not seem to be a phone number."); @@ -3043,7 +3062,7 @@ private void ParseHelper(string numberToParse, string defaultRegion, bool keepRa // TODO: This method should really just take in the string buffer that has already // been created, and just remove the prefix, rather than taking in a string and then // outputting a string buffer. - countryCode = MaybeExtractCountryCode(nationalNumberString, regionMetadata, + countryCode = MaybeExtractCountryCodeInternal(nationalNumberString, regionMetadata, normalizedNationalNumber, keepRawInput, phoneNumber); } catch (NumberParseException e) when (e.ErrorType == ErrorType.INVALID_COUNTRY_CODE) @@ -3053,7 +3072,7 @@ private void ParseHelper(string numberToParse, string defaultRegion, bool keepRa if (m > 0) { // Strip the plus-char, and try again. - countryCode = MaybeExtractCountryCode( + countryCode = MaybeExtractCountryCodeInternal( nationalNumberString.Substring(m), regionMetadata, normalizedNationalNumber, keepRawInput, phoneNumber); @@ -3396,5 +3415,15 @@ public bool CanBeInternationallyDialled(PhoneNumber number) var nationalSignificantNumber = GetNationalSignificantNumber(number); return !IsNumberMatchingDesc(nationalSignificantNumber, metadata.NoInternationalDialling); } + + /** + * Returns true if the supplied region supports mobile number portability. Returns false for + * invalid, unknown or regions that don't support mobile number portability. + * + * @param regionCode the region for which we want to know whether it supports mobile number + * portability or not + */ + public bool IsMobileNumberPortableRegion(string regionCode) + => GetMetadataForRegion(regionCode)?.MobileNumberPortableRegion ?? false; } } diff --git a/csharp/PhoneNumbers/PhoneNumbers.csproj b/csharp/PhoneNumbers/PhoneNumbers.csproj index 5fb62e204..8983c6d8f 100644 --- a/csharp/PhoneNumbers/PhoneNumbers.csproj +++ b/csharp/PhoneNumbers/PhoneNumbers.csproj @@ -7,7 +7,7 @@ libphonenumber-csharp $(APPVEYOR_BUILD_VERSION) Patrick Mézard;Tom Clegg;Jarrod Alexander;Google;libphonenumber contributors - net35;net40;net46;netstandard1.3;netstandard2.0;portable-net45+win8+wpa81+wp8 + netstandard2.0 8.0 PhoneNumbers libphonenumber-csharp @@ -44,7 +44,7 @@ - + diff --git a/csharp/PhoneNumbers/AreaCodeMapStorageStrategy.cs b/csharp/PhoneNumbers/PhonePrefixMapStorageStrategy.cs similarity index 86% rename from csharp/PhoneNumbers/AreaCodeMapStorageStrategy.cs rename to csharp/PhoneNumbers/PhonePrefixMapStorageStrategy.cs index 035acf25e..b9bc69b89 100644 --- a/csharp/PhoneNumbers/AreaCodeMapStorageStrategy.cs +++ b/csharp/PhoneNumbers/PhonePrefixMapStorageStrategy.cs @@ -15,6 +15,8 @@ */ using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; using System.Text; namespace PhoneNumbers @@ -25,10 +27,10 @@ namespace PhoneNumbers /// provided data. /// /// - public abstract class AreaCodeMapStorageStrategy + public abstract class PhonePrefixMapStorageStrategy { protected int NumOfEntries = 0; - protected readonly List PossibleLengths = new List(); + protected readonly SortedSet PossibleLengths = new SortedSet(); /// /// Gets the phone number prefix located at the provided index. @@ -69,9 +71,9 @@ public int GetNumOfEntries() /// The set containing the possible lengths of prefixes. /// /// The set containing the possible lengths of prefixes. - public List GetPossibleLengths() + public ImmutableSortedSet GetPossibleLengths() { - return PossibleLengths; + return PossibleLengths.ToImmutableSortedSet(); } public override string ToString() @@ -87,5 +89,7 @@ public override string ToString() return output.ToString(); } + public abstract void WriteExternal(Stream outputStream); + public abstract void ReadExternal(Stream inputStream); } } \ No newline at end of file diff --git a/csharp/PhoneNumbers/RegexCache.cs b/csharp/PhoneNumbers/RegexCache.cs index 8e5eda173..46f5a2cdb 100644 --- a/csharp/PhoneNumbers/RegexCache.cs +++ b/csharp/PhoneNumbers/RegexCache.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System; using System.Collections.Generic; namespace PhoneNumbers @@ -69,8 +70,10 @@ public PhoneRegex GetPatternForRegex(string regex) } } - // This method is used for testing. - public bool ContainsRegex(string regex) + [Obsolete("This method was public to be @VisibleForTesting, it will be moved to internal in a future release.")] + public bool ContainsRegex(string regex) => ContainsRegexInternal(regex); + + internal bool ContainsRegexInternal(string regex) { lock (regexLock) {