From 35c4932ee877677a8903f3f30018e94b6adaf98d Mon Sep 17 00:00:00 2001 From: tijanavg Date: Mon, 21 Feb 2022 16:59:35 +0100 Subject: [PATCH] Added new classes with behavior from MetadataManager & MetadataSource --- .../CountryCodeToRegionCodeMap.java | 2 +- .../internal/GeoEntityUtility.java | 56 +++++++++ .../DefaultMetadataDependenciesProvider.java | 92 ++++++++++++++ .../init/ClassPathResourceMetadataLoader.java | 32 +++++ .../metadata/init/MetadataParser.java | 108 ++++++++++++++++ .../BlockingMetadataBootstrappingGuard.java | 78 ++++++++++++ .../source/CompositeMetadataContainer.java | 69 +++++++++++ .../source/FormattingMetadataSource.java | 37 ++++++ .../source/FormattingMetadataSourceImpl.java | 57 +++++++++ .../source/MapBackedMetadataContainer.java | 74 +++++++++++ .../source/MetadataBootstrappingGuard.java | 35 ++++++ .../metadata/source/MetadataContainer.java | 32 +++++ .../metadata/source/MetadataSource.java | 21 ++++ .../metadata/source/MetadataSourceImpl.java | 72 +++++++++++ .../source/MultiFileModeFileNameProvider.java | 42 +++++++ .../NonGeographicalEntityMetadataSource.java | 47 +++++++ .../source/PhoneMetadataFileNameProvider.java | 36 ++++++ .../metadata/source/RegionMetadataSource.java | 40 ++++++ .../source/RegionMetadataSourceImpl.java | 62 ++++++++++ .../SingleFileModeFileNameProvider.java | 35 ++++++ .../internal/GeoEntityUtilityTest.java | 53 ++++++++ .../metadata/PhoneMetadataCollectionUtil.java | 21 ++++ .../metadata/init/MetadataParserTest.java | 98 +++++++++++++++ ...lockingMetadataBootstrappingGuardTest.java | 117 ++++++++++++++++++ .../CompositeMetadataContainerTest.java | 78 ++++++++++++ .../MapBackedMetadataContainerTest.java | 74 +++++++++++ .../MultiFileModeFileNameProviderTest.java | 51 ++++++++ .../SingleFileModeFileNameProviderTest.java | 37 ++++++ java/pom.xml | 6 + .../BuildMetadataProtoFromXml.java | 6 +- 30 files changed, 1564 insertions(+), 4 deletions(-) create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/internal/GeoEntityUtility.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/DefaultMetadataDependenciesProvider.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/ClassPathResourceMetadataLoader.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/MetadataParser.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuard.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainer.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSource.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSourceImpl.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainer.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataBootstrappingGuard.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataContainer.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSource.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSourceImpl.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProvider.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/NonGeographicalEntityMetadataSource.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/PhoneMetadataFileNameProvider.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSource.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSourceImpl.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProvider.java create mode 100644 java/libphonenumber/test/com/google/i18n/phonenumbers/internal/GeoEntityUtilityTest.java create mode 100644 java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/PhoneMetadataCollectionUtil.java create mode 100644 java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/init/MetadataParserTest.java create mode 100644 java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuardTest.java create mode 100644 java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainerTest.java create mode 100644 java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainerTest.java create mode 100644 java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProviderTest.java create mode 100644 java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProviderTest.java diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java index 159f940db..06571d15f 100644 --- a/java/libphonenumber/src/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java @@ -30,7 +30,7 @@ public class CountryCodeToRegionCodeMap { // country/region represented by that country code. In the case of multiple // countries sharing a calling code, such as the NANPA countries, the one // indicated with "isMainCountryForCode" in the metadata should be first. - static Map> getCountryCodeToRegionCodeMap() { + public static Map> getCountryCodeToRegionCodeMap() { // The capacity is set to 286 as there are 215 different entries, // and this offers a load factor of roughly 0.75. Map> countryCodeToRegionCodeMap = diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/internal/GeoEntityUtility.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/internal/GeoEntityUtility.java new file mode 100644 index 000000000..ef0cf67fa --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/internal/GeoEntityUtility.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.internal; + +import com.google.i18n.phonenumbers.CountryCodeToRegionCodeMap; +import java.util.List; + +/** + * Utility class for checking whether identifiers region code and country calling code belong + * to geographical entities. For more information about geo vs. non-geo entities see {@link + * com.google.i18n.phonenumbers.metadata.source.RegionMetadataSource} and {@link + * com.google.i18n.phonenumbers.metadata.source.NonGeographicalEntityMetadataSource} + */ +public final class GeoEntityUtility { + + /** Region code with a special meaning, used to mark non-geographical entities */ + public static final String REGION_CODE_FOR_NON_GEO_ENTITIES = "001"; + + /** Determines whether {@code regionCode} belongs to a geographical entity. */ + public static boolean isGeoEntity(String regionCode) { + return !regionCode.equals(REGION_CODE_FOR_NON_GEO_ENTITIES); + } + + /** + * Determines whether {@code countryCallingCode} belongs to a geographical entity. + * + *

A single country calling code could map to several different regions. It is considered that + * {@code countryCallingCode} belongs to a geo entity if all of these regions are geo entities + * + *

Note that this method will not throw an exception even when the underlying mapping for the + * {@code countryCallingCode} does not exist, instead it will return {@code false} + */ + public static boolean isGeoEntity(int countryCallingCode) { + List regionCodesForCountryCallingCode = + CountryCodeToRegionCodeMap.getCountryCodeToRegionCodeMap().get(countryCallingCode); + + return regionCodesForCountryCallingCode != null + && !regionCodesForCountryCallingCode.contains(REGION_CODE_FOR_NON_GEO_ENTITIES); + } + + private GeoEntityUtility() {} +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/DefaultMetadataDependenciesProvider.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/DefaultMetadataDependenciesProvider.java new file mode 100644 index 000000000..b3b88f202 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/DefaultMetadataDependenciesProvider.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata; + +import com.google.i18n.phonenumbers.MetadataLoader; +import com.google.i18n.phonenumbers.metadata.init.ClassPathResourceMetadataLoader; +import com.google.i18n.phonenumbers.metadata.init.MetadataParser; +import com.google.i18n.phonenumbers.metadata.source.FormattingMetadataSource; +import com.google.i18n.phonenumbers.metadata.source.FormattingMetadataSourceImpl; +import com.google.i18n.phonenumbers.metadata.source.MetadataSource; +import com.google.i18n.phonenumbers.metadata.source.MetadataSourceImpl; +import com.google.i18n.phonenumbers.metadata.source.RegionMetadataSource; +import com.google.i18n.phonenumbers.metadata.source.RegionMetadataSourceImpl; +import com.google.i18n.phonenumbers.metadata.source.SingleFileModeFileNameProvider; + +/** + * Provides metadata init & source dependencies when metadata is stored in multi-file mode and + * loaded as a classpath resource. + */ +public final class DefaultMetadataDependenciesProvider { + + private static final DefaultMetadataDependenciesProvider INSTANCE = new DefaultMetadataDependenciesProvider(); + + public static DefaultMetadataDependenciesProvider getInstance() { + return INSTANCE; + } + + private DefaultMetadataDependenciesProvider() {} + + private final MetadataParser metadataParser = MetadataParser.newStrictParser(); + private final MetadataLoader metadataLoader = new ClassPathResourceMetadataLoader(); + private final MetadataSource phoneNumberMetadataSource = + new MetadataSourceImpl( + new SingleFileModeFileNameProvider( + "/com/google/i18n/phonenumbers/buildtools/PhoneNumberMetadataProto"), + metadataLoader, + metadataParser); + private final RegionMetadataSource shortNumberMetadataSource = + new RegionMetadataSourceImpl( + new SingleFileModeFileNameProvider( + "/com/google/i18n/phonenumbers/buildtools/ShortNumberMetadataProto"), + metadataLoader, + metadataParser); + private final FormattingMetadataSource alternateFormatsMetadataSource = + new FormattingMetadataSourceImpl( + new SingleFileModeFileNameProvider( + "/com/google/i18n/phonenumbers/buildtools/PhoneNumberAlternateFormatsProto"), + metadataLoader, + metadataParser); + + public MetadataParser getMetadataParser() { + return metadataParser; + } + + public MetadataLoader getMetadataLoader() { + return metadataLoader; + } + + public MetadataSource getPhoneNumberMetadataSource() { + return phoneNumberMetadataSource; + } + + public RegionMetadataSource getShortNumberMetadataSource() { + return shortNumberMetadataSource; + } + + public FormattingMetadataSource getAlternateFormatsMetadataSource() { + return alternateFormatsMetadataSource; + } + + public String getCarrierDataDirectory() { + return "/com/google/i18n/phonenumbers/buildtools/carrier_data/"; + } + + public String getGeocodingDataDirectory() { + return "/com/google/i18n/phonenumbers/buildtools/geocoding_data/"; + } +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/ClassPathResourceMetadataLoader.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/ClassPathResourceMetadataLoader.java new file mode 100644 index 000000000..a9ed49d55 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/ClassPathResourceMetadataLoader.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.init; + +import com.google.i18n.phonenumbers.MetadataLoader; +import java.io.InputStream; + +/** + * A {@link MetadataLoader} implementation that reads phone number metadata files as classpath + * resources. + */ +public final class ClassPathResourceMetadataLoader implements MetadataLoader { + + @Override + public InputStream loadMetadata(String metadataFileName) { + return ClassPathResourceMetadataLoader.class.getResourceAsStream(metadataFileName); + } +} \ No newline at end of file diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/MetadataParser.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/MetadataParser.java new file mode 100644 index 000000000..e923dfadf --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/MetadataParser.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.init; + +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Exposes single method for parsing {@link InputStream} content into {@link Collection} of {@link + * PhoneMetadata} + */ +public final class MetadataParser { + + private static final Logger logger = Logger.getLogger(MetadataParser.class.getName()); + + /** + * Creates new instance in lenient mode, see {@link MetadataParser#parse(InputStream)} for more + * info. + */ + public static MetadataParser newLenientParser() { + return new MetadataParser(false); + } + + /** + * Creates new instance in strict mode, see {@link MetadataParser#parse(InputStream)} for more + * info + */ + public static MetadataParser newStrictParser() { + return new MetadataParser(true); + } + + private final boolean strictMode; + + private MetadataParser(boolean strictMode) { + this.strictMode = strictMode; + } + + /** + * Parses given {@link InputStream} into a {@link Collection} of {@link PhoneMetadata}. + * + * @throws IllegalArgumentException if {@code source} is {@code null} and strict mode is on + * @return parsed {@link PhoneMetadata}, or empty {@link Collection} if {@code source} is {@code + * null} and lenient mode is on + */ + public Collection parse(InputStream source) { + if (source == null) { + return handleNullSource(); + } + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(source); + PhoneMetadataCollection phoneMetadataCollection = new PhoneMetadataCollection(); + phoneMetadataCollection.readExternal(ois); + List phoneMetadata = phoneMetadataCollection.getMetadataList(); + // Sanity check; this should not happen if provided InputStream is valid + if (phoneMetadata.isEmpty()) { + throw new IllegalStateException("Empty metadata"); + } + return phoneMetadataCollection.getMetadataList(); + } catch (IOException e) { + throw new IllegalStateException("Unable to parse metadata file", e); + } finally { + if (ois != null) { + // This will close all underlying streams as well, including source. + close(ois); + } else { + close(source); + } + } + } + + private List handleNullSource() { + if (strictMode) { + throw new IllegalArgumentException("Source cannot be null"); + } + return Collections.emptyList(); + } + + private void close(InputStream inputStream) { + try { + inputStream.close(); + } catch (IOException e) { + logger.log(Level.WARNING, "Error closing input stream (ignored)", e); + } + } +} \ No newline at end of file diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuard.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuard.java new file mode 100644 index 000000000..a3ff68248 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuard.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import com.google.i18n.phonenumbers.MetadataLoader; +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; +import com.google.i18n.phonenumbers.metadata.init.MetadataParser; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * A blocking implementation of {@link MetadataBootstrappingGuard}. Can be used for both single-file + * (bulk) and multi-file metadata + * + * @param needs to extend {@link MetadataContainer} + */ +final class BlockingMetadataBootstrappingGuard + implements MetadataBootstrappingGuard { + + private final MetadataLoader metadataLoader; + private final MetadataParser metadataParser; + private final T metadataContainer; + private final Set loadedFiles; + + BlockingMetadataBootstrappingGuard( + MetadataLoader metadataLoader, MetadataParser metadataParser, T metadataContainer) { + this.metadataLoader = metadataLoader; + this.metadataParser = metadataParser; + this.metadataContainer = metadataContainer; + this.loadedFiles = new HashSet<>(); + } + + @Override + public T getOrBootstrap(String phoneMetadataFile) { + if (!loadedFiles.contains(phoneMetadataFile)) { + bootstrapMetadata(phoneMetadataFile); + } + return metadataContainer; + } + + private synchronized void bootstrapMetadata(String phoneMetadataFile) { + // Additional check is needed because multiple threads could pass the first check when calling + // getOrBootstrap() at the same time for unloaded metadata file + if (loadedFiles.contains(phoneMetadataFile)) { + return; + } + Collection phoneMetadata = read(phoneMetadataFile); + for (PhoneMetadata metadata : phoneMetadata) { + metadataContainer.accept(metadata); + } + loadedFiles.add(phoneMetadataFile); + } + + private Collection read(String phoneMetadataFile) { + try { + InputStream metadataStream = metadataLoader.loadMetadata(phoneMetadataFile); + return metadataParser.parse(metadataStream); + } catch (IllegalArgumentException | IllegalStateException e) { + throw new IllegalStateException("Failed to read file " + phoneMetadataFile, e); + } + } +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainer.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainer.java new file mode 100644 index 000000000..727574957 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainer.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; +import com.google.i18n.phonenumbers.internal.GeoEntityUtility; + +/** + * Implementation of {@link MetadataContainer} which is a composition of different {@link + * MapBackedMetadataContainer}s. It adds items to a single simpler container at a time depending on + * the content of {@link PhoneMetadata}. + */ +final class CompositeMetadataContainer implements MetadataContainer { + + private final MapBackedMetadataContainer metadataByCountryCode = + MapBackedMetadataContainer.byCountryCallingCode(); + private final MapBackedMetadataContainer metadataByRegionCode = + MapBackedMetadataContainer.byRegionCode(); + + /** + * Intended to be called for geographical regions only. For non-geographical entities, use {@link + * CompositeMetadataContainer#getMetadataBy(int)} + */ + PhoneMetadata getMetadataBy(String regionCode) { + return metadataByRegionCode.getMetadataBy(regionCode); + } + + /** + * Intended to be called for non-geographical entities only, such as 800 (country code assigned to + * the Universal International Freephone Service). For geographical regions, use {@link + * CompositeMetadataContainer#getMetadataBy(String)} + */ + PhoneMetadata getMetadataBy(int countryCallingCode) { + return metadataByCountryCode.getMetadataBy(countryCallingCode); + } + + /** + * If the metadata belongs to a specific geographical region (it has a region code other than + * {@link GeoEntityUtility#REGION_CODE_FOR_NON_GEO_ENTITIES}), it will be added to a {@link + * MapBackedMetadataContainer} which stores metadata by region code. Otherwise, it will be added + * to a {@link MapBackedMetadataContainer} which stores metadata by country calling code. This + * means that {@link CompositeMetadataContainer#getMetadataBy(int)} will not work for country + * calling codes such as 41 (country calling code for Switzerland), only for country calling codes + * such as 800 (country code assigned to the Universal International Freephone Service) + */ + @Override + public void accept(PhoneMetadata phoneMetadata) { + String regionCode = metadataByRegionCode.getKeyProvider().getKeyOf(phoneMetadata); + if (GeoEntityUtility.isGeoEntity(regionCode)) { + metadataByRegionCode.accept(phoneMetadata); + } else { + metadataByCountryCode.accept(phoneMetadata); + } + } +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSource.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSource.java new file mode 100644 index 000000000..f4f332cb2 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSource.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; + +/** A source of formatting phone metadata. */ +public interface FormattingMetadataSource { + + /** + * Returns formatting phone metadata for provided country calling code. + * + *

This method is similar to the one in {@link + * NonGeographicalEntityMetadataSource#getMetadataForNonGeographicalRegion(int)}, except that it + * will not fail for geographical regions, it can be used for both geo- and non-geo entities. + * + *

In case the provided {@code countryCallingCode} maps to several different regions, only one + * would contain formatting metadata. + * + * @return the phone metadata for provided {@code countryCallingCode}, or null if there is none. + */ + PhoneMetadata getFormattingMetadataForCountryCallingCode(int countryCallingCode); +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSourceImpl.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSourceImpl.java new file mode 100644 index 000000000..d6a819099 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSourceImpl.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import com.google.i18n.phonenumbers.MetadataLoader; +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; +import com.google.i18n.phonenumbers.metadata.init.MetadataParser; + +/** + * Implementation of {@link FormattingMetadataSource} guarded by {@link MetadataBootstrappingGuard} + * + *

By default, a {@link BlockingMetadataBootstrappingGuard} will be used, but any custom + * implementation can be injected. + */ +public final class FormattingMetadataSourceImpl implements FormattingMetadataSource { + + private final PhoneMetadataFileNameProvider phoneMetadataFileNameProvider; + private final MetadataBootstrappingGuard> bootstrappingGuard; + + public FormattingMetadataSourceImpl( + PhoneMetadataFileNameProvider phoneMetadataFileNameProvider, + MetadataBootstrappingGuard> bootstrappingGuard) { + this.phoneMetadataFileNameProvider = phoneMetadataFileNameProvider; + this.bootstrappingGuard = bootstrappingGuard; + } + + public FormattingMetadataSourceImpl( + PhoneMetadataFileNameProvider phoneMetadataFileNameProvider, + MetadataLoader metadataLoader, + MetadataParser metadataParser) { + this( + phoneMetadataFileNameProvider, + new BlockingMetadataBootstrappingGuard<>( + metadataLoader, metadataParser, MapBackedMetadataContainer.byCountryCallingCode())); + } + + @Override + public PhoneMetadata getFormattingMetadataForCountryCallingCode(int countryCallingCode) { + return bootstrappingGuard + .getOrBootstrap(phoneMetadataFileNameProvider.getFor(countryCallingCode)) + .getMetadataBy(countryCallingCode); + } +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainer.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainer.java new file mode 100644 index 000000000..639280d87 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainer.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A {@link MetadataContainer} implementation backed by a {@link ConcurrentHashMap} with generic + * keys. + */ +final class MapBackedMetadataContainer implements MetadataContainer { + + static MapBackedMetadataContainer byRegionCode() { + return new MapBackedMetadataContainer<>( + new KeyProvider() { + @Override + public String getKeyOf(PhoneMetadata phoneMetadata) { + return phoneMetadata.getId(); + } + }); + } + + static MapBackedMetadataContainer byCountryCallingCode() { + return new MapBackedMetadataContainer<>( + new KeyProvider() { + @Override + public Integer getKeyOf(PhoneMetadata phoneMetadata) { + return phoneMetadata.getCountryCode(); + } + }); + } + + private final ConcurrentMap metadataMap; + + private final KeyProvider keyProvider; + + private MapBackedMetadataContainer(KeyProvider keyProvider) { + this.metadataMap = new ConcurrentHashMap<>(); + this.keyProvider = keyProvider; + } + + PhoneMetadata getMetadataBy(T key) { + return key != null ? metadataMap.get(key) : null; + } + + KeyProvider getKeyProvider() { + return keyProvider; + } + + @Override + public void accept(PhoneMetadata phoneMetadata) { + metadataMap.put(keyProvider.getKeyOf(phoneMetadata), phoneMetadata); + } + + interface KeyProvider { + T getKeyOf(PhoneMetadata phoneMetadata); + } +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataBootstrappingGuard.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataBootstrappingGuard.java new file mode 100644 index 000000000..9380c5954 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataBootstrappingGuard.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +/** + * Guard that ensures that metadata bootstrapping process (loading and parsing) is triggered only + * once per metadata file. + * + * @param needs to extend {@link MetadataContainer} + */ +public interface MetadataBootstrappingGuard { + + /** + * If metadata from the provided file has not yet been read, invokes loading and parsing from the + * provided file and adds the result to guarded {@link MetadataContainer}. + * + * @param phoneMetadataFile to read from + * @return guarded {@link MetadataContainer} + */ + T getOrBootstrap(String phoneMetadataFile); +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataContainer.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataContainer.java new file mode 100644 index 000000000..3f6b21ed0 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataContainer.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; + +/** + * A container for {@link PhoneMetadata} + */ +interface MetadataContainer { + + /** + * Adds {@link PhoneMetadata} to the container. It depends on the implementation of the interface + * what this means, for example {@link MapBackedMetadataContainer} simply adds the provided + * metadata into the backing map. Implementing classes should ensure thread-safety. + */ + void accept(PhoneMetadata phoneMetadata); +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSource.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSource.java new file mode 100644 index 000000000..d353ce969 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSource.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +/** A source of phone metadata split by different regions. */ +public interface MetadataSource extends RegionMetadataSource, NonGeographicalEntityMetadataSource { +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSourceImpl.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSourceImpl.java new file mode 100644 index 000000000..c3d1c7360 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSourceImpl.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import com.google.i18n.phonenumbers.MetadataLoader; +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; +import com.google.i18n.phonenumbers.internal.GeoEntityUtility; +import com.google.i18n.phonenumbers.metadata.init.MetadataParser; + +/** + * Implementation of {@link MetadataSource} guarded by {@link MetadataBootstrappingGuard}. + * + *

By default, a {@link BlockingMetadataBootstrappingGuard} will be used, but any custom + * implementation can be injected. + */ +public final class MetadataSourceImpl implements MetadataSource { + + private final PhoneMetadataFileNameProvider phoneMetadataFileNameProvider; + private final MetadataBootstrappingGuard bootstrappingGuard; + + public MetadataSourceImpl( + PhoneMetadataFileNameProvider phoneMetadataFileNameProvider, + MetadataBootstrappingGuard bootstrappingGuard) { + this.phoneMetadataFileNameProvider = phoneMetadataFileNameProvider; + this.bootstrappingGuard = bootstrappingGuard; + } + + public MetadataSourceImpl( + PhoneMetadataFileNameProvider phoneMetadataFileNameProvider, + MetadataLoader metadataLoader, + MetadataParser metadataParser) { + this( + phoneMetadataFileNameProvider, + new BlockingMetadataBootstrappingGuard<>( + metadataLoader, metadataParser, new CompositeMetadataContainer())); + } + + @Override + public PhoneMetadata getMetadataForNonGeographicalRegion(int countryCallingCode) { + if (GeoEntityUtility.isGeoEntity(countryCallingCode)) { + throw new IllegalArgumentException( + countryCallingCode + " calling code belongs to a geo entity"); + } + return bootstrappingGuard + .getOrBootstrap(phoneMetadataFileNameProvider.getFor(countryCallingCode)) + .getMetadataBy(countryCallingCode); + } + + @Override + public PhoneMetadata getMetadataForRegion(String regionCode) { + if (!GeoEntityUtility.isGeoEntity(regionCode)) { + throw new IllegalArgumentException(regionCode + " region code is a non-geo entity"); + } + return bootstrappingGuard + .getOrBootstrap(phoneMetadataFileNameProvider.getFor(regionCode)) + .getMetadataBy(regionCode); + } +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProvider.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProvider.java new file mode 100644 index 000000000..0d9adb5ee --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProvider.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import java.util.regex.Pattern; + +/** + * {@link PhoneMetadataFileNameProvider} implementation which appends key as a suffix to the + * predefined metadata file name base. + */ +public final class MultiFileModeFileNameProvider implements PhoneMetadataFileNameProvider { + + private final String phoneMetadataFileNamePrefix; + private static final Pattern ALPHANUMERIC = Pattern.compile("^[\\p{L}\\p{N}]+$"); + + public MultiFileModeFileNameProvider(String phoneMetadataFileNameBase) { + this.phoneMetadataFileNamePrefix = phoneMetadataFileNameBase + "_"; + } + + @Override + public String getFor(Object key) { + String keyAsString = key.toString(); + if (!ALPHANUMERIC.matcher(keyAsString).matches()) { + throw new IllegalArgumentException("Invalid key: " + keyAsString); + } + return phoneMetadataFileNamePrefix + key; + } +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/NonGeographicalEntityMetadataSource.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/NonGeographicalEntityMetadataSource.java new file mode 100644 index 000000000..70db06df0 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/NonGeographicalEntityMetadataSource.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; + +/** + * A source of phone metadata for non-geographical entities. + * + *

Non-geographical entities are phone number ranges that have a country calling code, but either + * do not belong to an actual country (some international services), or belong to a region which has + * a different country calling code from the country it is part of. Examples of such ranges are + * those starting with: + * + *

    + *
  • 800 - country code assigned to the Universal International Freephone Service + *
  • 808 - country code assigned to the International Shared Cost Service + *
  • 870 - country code assigned to the Pitcairn Islands + *
  • ... + *
+ */ +public interface NonGeographicalEntityMetadataSource { + + /** + * Gets phone metadata for a non-geographical entity. + * + * @param countryCallingCode the country calling code. + * @return the phone metadata for that entity, or null if there is none. + * @throws IllegalArgumentException if provided {@code countryCallingCode} does not belong to a + * non-geographical entity + */ + PhoneMetadata getMetadataForNonGeographicalRegion(int countryCallingCode); +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/PhoneMetadataFileNameProvider.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/PhoneMetadataFileNameProvider.java new file mode 100644 index 000000000..c3d16887f --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/PhoneMetadataFileNameProvider.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +/** + * Abstraction responsible for inferring the metadata file name. + * + *

Two implementations are available: + * + *

    + *
  • {@link SingleFileModeFileNameProvider} for single-file metadata. + *
  • {@link MultiFileModeFileNameProvider} for multi-file metadata. + *
+ */ +public interface PhoneMetadataFileNameProvider { + + /** + * Returns phone metadata file path for the given key. Assumes that key.toString() is + * well-defined. + */ + String getFor(Object key); +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSource.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSource.java new file mode 100644 index 000000000..3cf15c206 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSource.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; +import com.google.i18n.phonenumbers.internal.GeoEntityUtility; + +/** + * A source of phone metadata split by geographical regions. + */ +public interface RegionMetadataSource { + + /** + * Returns phone metadata for provided geographical region. + * + *

The {@code regionCode} must be different from {@link + * GeoEntityUtility#REGION_CODE_FOR_NON_GEO_ENTITIES}, which has a special meaning and is used to + * mark non-geographical regions (see {@link NonGeographicalEntityMetadataSource} for more + * information). + * + * @return the phone metadata for provided {@code regionCode}, or null if there is none. + * @throws IllegalArgumentException if provided {@code regionCode} is {@link + * GeoEntityUtility#REGION_CODE_FOR_NON_GEO_ENTITIES} + */ + PhoneMetadata getMetadataForRegion(String regionCode); +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSourceImpl.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSourceImpl.java new file mode 100644 index 000000000..0078dd945 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSourceImpl.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import com.google.i18n.phonenumbers.MetadataLoader; +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; +import com.google.i18n.phonenumbers.internal.GeoEntityUtility; +import com.google.i18n.phonenumbers.metadata.init.MetadataParser; + +/** + * Implementation of {@link RegionMetadataSource} guarded by {@link MetadataBootstrappingGuard} + * + *

By default, a {@link BlockingMetadataBootstrappingGuard} will be used, but any custom + * implementation can be injected. + */ +public final class RegionMetadataSourceImpl implements RegionMetadataSource { + + private final PhoneMetadataFileNameProvider phoneMetadataFileNameProvider; + private final MetadataBootstrappingGuard> + bootstrappingGuard; + + public RegionMetadataSourceImpl( + PhoneMetadataFileNameProvider phoneMetadataFileNameProvider, + MetadataBootstrappingGuard> bootstrappingGuard) { + this.phoneMetadataFileNameProvider = phoneMetadataFileNameProvider; + this.bootstrappingGuard = bootstrappingGuard; + } + + public RegionMetadataSourceImpl( + PhoneMetadataFileNameProvider phoneMetadataFileNameProvider, + MetadataLoader metadataLoader, + MetadataParser metadataParser) { + this( + phoneMetadataFileNameProvider, + new BlockingMetadataBootstrappingGuard<>( + metadataLoader, metadataParser, MapBackedMetadataContainer.byRegionCode())); + } + + @Override + public PhoneMetadata getMetadataForRegion(String regionCode) { + if (!GeoEntityUtility.isGeoEntity(regionCode)) { + throw new IllegalArgumentException(regionCode + " region code is a non-geo entity"); + } + return bootstrappingGuard + .getOrBootstrap(phoneMetadataFileNameProvider.getFor(regionCode)) + .getMetadataBy(regionCode); + } +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProvider.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProvider.java new file mode 100644 index 000000000..1d3d1eb81 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +/** + * {@link PhoneMetadataFileNameProvider} implementation that returns the same metadata file name for + * each key + */ +public final class SingleFileModeFileNameProvider implements PhoneMetadataFileNameProvider { + + private final String phoneMetadataFileName; + + public SingleFileModeFileNameProvider(String phoneMetadataFileName) { + this.phoneMetadataFileName = phoneMetadataFileName; + } + + @Override + public String getFor(Object key) { + return phoneMetadataFileName; + } +} diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/internal/GeoEntityUtilityTest.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/internal/GeoEntityUtilityTest.java new file mode 100644 index 000000000..150164c89 --- /dev/null +++ b/java/libphonenumber/test/com/google/i18n/phonenumbers/internal/GeoEntityUtilityTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.internal; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class GeoEntityUtilityTest { + + @Test + public void isGeoEntity_shouldReturnTrueForCountryRegionCode() { + assertTrue(GeoEntityUtility.isGeoEntity("DE")); + } + + @Test + public void isGeoEntity_shouldReturnFalseForWorldRegionCode() { + assertFalse(GeoEntityUtility.isGeoEntity("001")); + } + + @Test + public void isGeoEntity_shouldReturnTrueForCountryCallingCode() { + assertTrue(GeoEntityUtility.isGeoEntity(41)); + } + + @Test + public void isGeoEntity_shouldReturnFalseForInternationalSharedCostServiceCallingCode() { + assertFalse(GeoEntityUtility.isGeoEntity(808)); + } + + @Test + public void isGeoEntity_shouldReturnFalseForNonExistingCountryCallingCode() { + assertFalse(GeoEntityUtility.isGeoEntity(111111111)); + } +} diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/PhoneMetadataCollectionUtil.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/PhoneMetadataCollectionUtil.java new file mode 100644 index 000000000..57fab1914 --- /dev/null +++ b/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/PhoneMetadataCollectionUtil.java @@ -0,0 +1,21 @@ +package com.google.i18n.phonenumbers.metadata; + +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectOutputStream; + +public class PhoneMetadataCollectionUtil { + + public static InputStream toInputStream(PhoneMetadataCollection metadata) throws IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); + metadata.writeExternal(objectOutputStream); + objectOutputStream.flush(); + InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); + objectOutputStream.close(); + return inputStream; + } +} diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/init/MetadataParserTest.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/init/MetadataParserTest.java new file mode 100644 index 000000000..2e649b74d --- /dev/null +++ b/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/init/MetadataParserTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.init; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection; +import com.google.i18n.phonenumbers.metadata.PhoneMetadataCollectionUtil; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import org.junit.Test; +import org.junit.function.ThrowingRunnable; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class MetadataParserTest { + + private static final MetadataParser metadataParser = MetadataParser.newStrictParser(); + + @Test + public void parse_shouldThrowExceptionForNullInput() { + assertThrows( + IllegalArgumentException.class, + new ThrowingRunnable() { + @Override + public void run() { + metadataParser.parse(null); + } + }); + } + + @Test + public void parse_shouldThrowExceptionForEmptyInput() { + final InputStream emptyInput = new ByteArrayInputStream(new byte[0]); + + assertThrows( + IllegalStateException.class, + new ThrowingRunnable() { + @Override + public void run() { + metadataParser.parse(emptyInput); + } + }); + } + + @Test + public void parse_shouldThrowExceptionForInvalidInput() { + final InputStream invalidInput = new ByteArrayInputStream("Some random input".getBytes(UTF_8)); + + assertThrows( + IllegalStateException.class, + new ThrowingRunnable() { + @Override + public void run() { + metadataParser.parse(invalidInput); + } + }); + } + + @Test + public void parse_shouldParseValidInput() throws IOException { + InputStream input = PhoneMetadataCollectionUtil.toInputStream( + PhoneMetadataCollection.newBuilder() + .addMetadata(PhoneMetadata.newBuilder().setId("id").build())); + + Collection actual = metadataParser.parse(input); + + assertEquals(1, actual.size()); + } + + @Test + public void parse_shouldReturnEmptyCollectionForNullInput() { + Collection actual = MetadataParser.newLenientParser().parse(null); + + assertTrue(actual.isEmpty()); + } +} \ No newline at end of file diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuardTest.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuardTest.java new file mode 100644 index 000000000..56820840d --- /dev/null +++ b/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuardTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.i18n.phonenumbers.MetadataLoader; +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection; +import com.google.i18n.phonenumbers.metadata.PhoneMetadataCollectionUtil; +import com.google.i18n.phonenumbers.metadata.init.MetadataParser; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.function.ThrowingRunnable; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(JUnit4.class) +public class BlockingMetadataBootstrappingGuardTest { + + private static final String PHONE_METADATA_FILE = "some metadata file"; + private static final PhoneMetadataCollection PHONE_METADATA = + PhoneMetadataCollection.newBuilder() + .addMetadata(PhoneMetadata.newBuilder().setId("id").build()); + + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + @Mock private MetadataLoader metadataLoader; + @Mock private MetadataContainer metadataContainer; + + private BlockingMetadataBootstrappingGuard bootstrappingGuard; + + @Before + public void setUp() throws IOException { + when(metadataLoader.loadMetadata(PHONE_METADATA_FILE)) + .thenReturn(PhoneMetadataCollectionUtil.toInputStream(PHONE_METADATA)); + bootstrappingGuard = + new BlockingMetadataBootstrappingGuard<>( + metadataLoader, MetadataParser.newStrictParser(), metadataContainer); + } + + @Test + public void getOrBootstrap_shouldInvokeBootstrappingOnlyOnce() { + bootstrappingGuard.getOrBootstrap(PHONE_METADATA_FILE); + bootstrappingGuard.getOrBootstrap(PHONE_METADATA_FILE); + + verify(metadataLoader, times(1)).loadMetadata(PHONE_METADATA_FILE); + verify(metadataContainer, only()).accept(any(PhoneMetadata.class)); + } + + @Test + public void getOrBootstrap_shouldIncludeFileNameInExceptionOnFailure() { + when(metadataLoader.loadMetadata(PHONE_METADATA_FILE)).thenReturn(null); + + ThrowingRunnable throwingRunnable = + new ThrowingRunnable() { + @Override + public void run() { + bootstrappingGuard.getOrBootstrap(PHONE_METADATA_FILE); + } + }; + + IllegalStateException exception = assertThrows(IllegalStateException.class, throwingRunnable); + Assert.assertTrue(exception.getMessage().contains(PHONE_METADATA_FILE)); + } + + @Test + public void getOrBootstrap_shouldInvokeBootstrappingOnlyOnceWhenThreadsCallItAtTheSameTime() + throws InterruptedException { + ExecutorService executorService = Executors.newFixedThreadPool(2); + + List runnables = new ArrayList<>(); + runnables.add(new BootstrappingRunnable()); + runnables.add(new BootstrappingRunnable()); + executorService.invokeAll(runnables); + + verify(metadataLoader, times(1)).loadMetadata(PHONE_METADATA_FILE); + verify(metadataContainer, only()).accept(any(PhoneMetadata.class)); + } + + private class BootstrappingRunnable implements Callable { + + @Override + public MetadataContainer call() { + return bootstrappingGuard.getOrBootstrap(PHONE_METADATA_FILE); + } + } +} diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainerTest.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainerTest.java new file mode 100644 index 000000000..138303ba1 --- /dev/null +++ b/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainerTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; +import com.google.i18n.phonenumbers.internal.GeoEntityUtility; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CompositeMetadataContainerTest { + + private static final String REGION_CODE = "US"; + private static final Integer COUNTRY_CODE = 1; + private static final PhoneMetadata PHONE_METADATA_WITH_REGION_CODE = + PhoneMetadata.newBuilder().setId(REGION_CODE).setCountryCode(COUNTRY_CODE); + private static final PhoneMetadata PHONE_METADATA_WITH_COUNTRY_CODE = + PhoneMetadata.newBuilder() + .setId(GeoEntityUtility.REGION_CODE_FOR_NON_GEO_ENTITIES) + .setCountryCode(COUNTRY_CODE); + + private CompositeMetadataContainer metadataContainer; + + @Before + public void setUp() { + metadataContainer = new CompositeMetadataContainer(); + } + + @Test + public void getMetadataBy_shouldReturnNullForNonExistingRegionCode() { + assertNull(metadataContainer.getMetadataBy(REGION_CODE)); + } + + @Test + public void getMetadataBy_shouldReturnMetadataForExistingRegionCode() { + metadataContainer.accept(PHONE_METADATA_WITH_REGION_CODE); + + assertSame(PHONE_METADATA_WITH_REGION_CODE, metadataContainer.getMetadataBy(REGION_CODE)); + } + + @Test + public void getMetadataBy_shouldReturnNullForNonExistingCountryCode() { + assertNull(metadataContainer.getMetadataBy(COUNTRY_CODE)); + } + + @Test + public void getMetadataBy_shouldReturnMetadataForExistingCountryCode() { + metadataContainer.accept(PHONE_METADATA_WITH_COUNTRY_CODE); + + assertSame(PHONE_METADATA_WITH_COUNTRY_CODE, metadataContainer.getMetadataBy(COUNTRY_CODE)); + } + + @Test + public void getMetadataBy_shouldReturnNullForExistingCountryCodeOfGeoRegion() { + metadataContainer.accept(PHONE_METADATA_WITH_REGION_CODE); + + assertNull(metadataContainer.getMetadataBy(COUNTRY_CODE)); + } +} diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainerTest.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainerTest.java new file mode 100644 index 000000000..c1d372654 --- /dev/null +++ b/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainerTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class MapBackedMetadataContainerTest { + + private static final String REGION_CODE = "US"; + private static final Integer COUNTRY_CODE = 41; + private static final PhoneMetadata PHONE_METADATA = + PhoneMetadata.newBuilder().setId(REGION_CODE).setCountryCode(COUNTRY_CODE); + + @Test + public void getMetadataBy_shouldReturnNullForNullRegionCode() { + assertNull(MapBackedMetadataContainer.byRegionCode().getMetadataBy(null)); + } + + @Test + public void getMetadataBy_shouldReturnNullForNonExistingRegionCode() { + assertNull(MapBackedMetadataContainer.byRegionCode().getMetadataBy(REGION_CODE)); + } + + @Test + public void getMetadataBy_shouldReturnMetadataForExistingRegionCode() { + MapBackedMetadataContainer metadataContainer = + MapBackedMetadataContainer.byRegionCode(); + + metadataContainer.accept(PHONE_METADATA); + + assertSame(PHONE_METADATA, metadataContainer.getMetadataBy(REGION_CODE)); + } + + @Test + public void getMetadataBy_shouldReturnNullForNullCountryCode() { + assertNull(MapBackedMetadataContainer.byCountryCallingCode().getMetadataBy(null)); + } + + @Test + public void getMetadataBy_shouldReturnNullForNonExistingCountryCode() { + assertNull(MapBackedMetadataContainer.byCountryCallingCode().getMetadataBy(COUNTRY_CODE)); + } + + @Test + public void getMetadataBy_shouldReturnMetadataForExistingCountryCode() { + MapBackedMetadataContainer metadataContainer = + MapBackedMetadataContainer.byCountryCallingCode(); + + metadataContainer.accept(PHONE_METADATA); + + assertSame(PHONE_METADATA, metadataContainer.getMetadataBy(COUNTRY_CODE)); + } +} diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProviderTest.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProviderTest.java new file mode 100644 index 000000000..9f0277ab8 --- /dev/null +++ b/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProviderTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.function.ThrowingRunnable; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class MultiFileModeFileNameProviderTest { + + private final PhoneMetadataFileNameProvider metadataFileNameProvider = + new MultiFileModeFileNameProvider("some/file"); + + @Test + public void getFor_shouldAppendKeyToTheBase() { + String metadataFileName = metadataFileNameProvider.getFor("key1"); + + assertEquals("some/file_key1", metadataFileName); + } + + @Test + public void getFor_shouldThrowExceptionForNonAlphanumericKey() { + assertThrows( + IllegalArgumentException.class, + new ThrowingRunnable() { + @Override + public void run() { + metadataFileNameProvider.getFor("\tkey1\n"); + } + }); + } +} diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProviderTest.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProviderTest.java new file mode 100644 index 000000000..f326e32a1 --- /dev/null +++ b/java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProviderTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.i18n.phonenumbers.metadata.source; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class SingleFileModeFileNameProviderTest { + + private final PhoneMetadataFileNameProvider metadataFileNameProvider = + new SingleFileModeFileNameProvider("some/file"); + + @Test + public void getFor_shouldReturnTheFileNameBase() { + String metadataFileName = metadataFileNameProvider.getFor("key1"); + + assertEquals("some/file", metadataFileName); + } +} diff --git a/java/pom.xml b/java/pom.xml index b252e8675..eb7784954 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -235,6 +235,12 @@ 4.13.1 test + + org.mockito + mockito-core + 4.3.1 + test + diff --git a/tools/java/java-build/src/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java b/tools/java/java-build/src/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java index f7db4c82a..8e4fc3b59 100644 --- a/tools/java/java-build/src/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java +++ b/tools/java/java-build/src/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java @@ -254,7 +254,7 @@ public class BuildMetadataProtoFromXml extends Command { writer.addToImports("java.util.List"); writer.addToImports("java.util.Map"); - writer.addToBody(" static Map> getCountryCodeToRegionCodeMap() {\n"); + writer.addToBody(" public static Map> getCountryCodeToRegionCodeMap() {\n"); writer.formatToBody(CAPACITY_COMMENT, capacity, countryCodeToRegionCodeMap.size()); writer.addToBody(" Map> countryCodeToRegionCodeMap =\n"); writer.addToBody(" new HashMap>(" + capacity + ");\n"); @@ -286,7 +286,7 @@ public class BuildMetadataProtoFromXml extends Command { writer.addToImports("java.util.HashSet"); writer.addToImports("java.util.Set"); - writer.addToBody(" static Set getRegionCodeSet() {\n"); + writer.addToBody(" public static Set getRegionCodeSet() {\n"); writer.formatToBody(CAPACITY_COMMENT, capacity, regionCodeList.size()); writer.addToBody(" Set regionCodeSet = new HashSet(" + capacity + ");\n"); writer.addToBody("\n"); @@ -307,7 +307,7 @@ public class BuildMetadataProtoFromXml extends Command { writer.addToImports("java.util.HashSet"); writer.addToImports("java.util.Set"); - writer.addToBody(" static Set getCountryCodeSet() {\n"); + writer.addToBody(" public static Set getCountryCodeSet() {\n"); writer.formatToBody(CAPACITY_COMMENT, capacity, countryCodeSet.size()); writer.addToBody(" Set countryCodeSet = new HashSet(" + capacity + ");\n"); writer.addToBody("\n");