| @ -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. | |||
| * | |||
| * <p>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 | |||
| * | |||
| * <p>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<String> regionCodesForCountryCallingCode = | |||
| CountryCodeToRegionCodeMap.getCountryCodeToRegionCodeMap().get(countryCallingCode); | |||
| return regionCodesForCountryCallingCode != null | |||
| && !regionCodesForCountryCallingCode.contains(REGION_CODE_FOR_NON_GEO_ENTITIES); | |||
| } | |||
| private GeoEntityUtility() {} | |||
| } | |||
| @ -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/"; | |||
| } | |||
| } | |||
| @ -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); | |||
| } | |||
| } | |||
| @ -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<PhoneMetadata> 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> 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<PhoneMetadata> 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); | |||
| } | |||
| } | |||
| } | |||
| @ -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 <T> needs to extend {@link MetadataContainer} | |||
| */ | |||
| final class BlockingMetadataBootstrappingGuard<T extends MetadataContainer> | |||
| implements MetadataBootstrappingGuard<T> { | |||
| private final MetadataLoader metadataLoader; | |||
| private final MetadataParser metadataParser; | |||
| private final T metadataContainer; | |||
| private final Set<String> 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> phoneMetadata = read(phoneMetadataFile); | |||
| for (PhoneMetadata metadata : phoneMetadata) { | |||
| metadataContainer.accept(metadata); | |||
| } | |||
| loadedFiles.add(phoneMetadataFile); | |||
| } | |||
| private Collection<PhoneMetadata> 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); | |||
| } | |||
| } | |||
| } | |||
| @ -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<Integer> metadataByCountryCode = | |||
| MapBackedMetadataContainer.byCountryCallingCode(); | |||
| private final MapBackedMetadataContainer<String> 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); | |||
| } | |||
| } | |||
| } | |||
| @ -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. | |||
| * | |||
| * <p>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. | |||
| * | |||
| * <p>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); | |||
| } | |||
| @ -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} | |||
| * | |||
| * <p>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<MapBackedMetadataContainer<Integer>> bootstrappingGuard; | |||
| public FormattingMetadataSourceImpl( | |||
| PhoneMetadataFileNameProvider phoneMetadataFileNameProvider, | |||
| MetadataBootstrappingGuard<MapBackedMetadataContainer<Integer>> 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); | |||
| } | |||
| } | |||
| @ -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<T> implements MetadataContainer { | |||
| static MapBackedMetadataContainer<String> byRegionCode() { | |||
| return new MapBackedMetadataContainer<>( | |||
| new KeyProvider<String>() { | |||
| @Override | |||
| public String getKeyOf(PhoneMetadata phoneMetadata) { | |||
| return phoneMetadata.getId(); | |||
| } | |||
| }); | |||
| } | |||
| static MapBackedMetadataContainer<Integer> byCountryCallingCode() { | |||
| return new MapBackedMetadataContainer<>( | |||
| new KeyProvider<Integer>() { | |||
| @Override | |||
| public Integer getKeyOf(PhoneMetadata phoneMetadata) { | |||
| return phoneMetadata.getCountryCode(); | |||
| } | |||
| }); | |||
| } | |||
| private final ConcurrentMap<T, PhoneMetadata> metadataMap; | |||
| private final KeyProvider<T> keyProvider; | |||
| private MapBackedMetadataContainer(KeyProvider<T> keyProvider) { | |||
| this.metadataMap = new ConcurrentHashMap<>(); | |||
| this.keyProvider = keyProvider; | |||
| } | |||
| PhoneMetadata getMetadataBy(T key) { | |||
| return key != null ? metadataMap.get(key) : null; | |||
| } | |||
| KeyProvider<T> getKeyProvider() { | |||
| return keyProvider; | |||
| } | |||
| @Override | |||
| public void accept(PhoneMetadata phoneMetadata) { | |||
| metadataMap.put(keyProvider.getKeyOf(phoneMetadata), phoneMetadata); | |||
| } | |||
| interface KeyProvider<T> { | |||
| T getKeyOf(PhoneMetadata phoneMetadata); | |||
| } | |||
| } | |||
| @ -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 <T> needs to extend {@link MetadataContainer} | |||
| */ | |||
| public interface MetadataBootstrappingGuard<T extends MetadataContainer> { | |||
| /** | |||
| * 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); | |||
| } | |||
| @ -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); | |||
| } | |||
| @ -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 { | |||
| } | |||
| @ -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}. | |||
| * | |||
| * <p>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<CompositeMetadataContainer> bootstrappingGuard; | |||
| public MetadataSourceImpl( | |||
| PhoneMetadataFileNameProvider phoneMetadataFileNameProvider, | |||
| MetadataBootstrappingGuard<CompositeMetadataContainer> 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); | |||
| } | |||
| } | |||
| @ -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; | |||
| } | |||
| } | |||
| @ -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. | |||
| * | |||
| * <p>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: | |||
| * | |||
| * <ul> | |||
| * <li>800 - country code assigned to the Universal International Freephone Service | |||
| * <li>808 - country code assigned to the International Shared Cost Service | |||
| * <li>870 - country code assigned to the Pitcairn Islands | |||
| * <li>... | |||
| * </ul> | |||
| */ | |||
| 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); | |||
| } | |||
| @ -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. | |||
| * | |||
| * <p>Two implementations are available: | |||
| * | |||
| * <ul> | |||
| * <li>{@link SingleFileModeFileNameProvider} for single-file metadata. | |||
| * <li>{@link MultiFileModeFileNameProvider} for multi-file metadata. | |||
| * </ul> | |||
| */ | |||
| public interface PhoneMetadataFileNameProvider { | |||
| /** | |||
| * Returns phone metadata file path for the given key. Assumes that key.toString() is | |||
| * well-defined. | |||
| */ | |||
| String getFor(Object key); | |||
| } | |||
| @ -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. | |||
| * | |||
| * <p>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); | |||
| } | |||
| @ -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} | |||
| * | |||
| * <p>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<MapBackedMetadataContainer<String>> | |||
| bootstrappingGuard; | |||
| public RegionMetadataSourceImpl( | |||
| PhoneMetadataFileNameProvider phoneMetadataFileNameProvider, | |||
| MetadataBootstrappingGuard<MapBackedMetadataContainer<String>> 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); | |||
| } | |||
| } | |||
| @ -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; | |||
| } | |||
| } | |||
| @ -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)); | |||
| } | |||
| } | |||
| @ -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; | |||
| } | |||
| } | |||
| @ -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<PhoneMetadata> actual = metadataParser.parse(input); | |||
| assertEquals(1, actual.size()); | |||
| } | |||
| @Test | |||
| public void parse_shouldReturnEmptyCollectionForNullInput() { | |||
| Collection<PhoneMetadata> actual = MetadataParser.newLenientParser().parse(null); | |||
| assertTrue(actual.isEmpty()); | |||
| } | |||
| } | |||
| @ -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<MetadataContainer> 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<BootstrappingRunnable> 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<MetadataContainer> { | |||
| @Override | |||
| public MetadataContainer call() { | |||
| return bootstrappingGuard.getOrBootstrap(PHONE_METADATA_FILE); | |||
| } | |||
| } | |||
| } | |||
| @ -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)); | |||
| } | |||
| } | |||
| @ -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<String> 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<Integer> metadataContainer = | |||
| MapBackedMetadataContainer.byCountryCallingCode(); | |||
| metadataContainer.accept(PHONE_METADATA); | |||
| assertSame(PHONE_METADATA, metadataContainer.getMetadataBy(COUNTRY_CODE)); | |||
| } | |||
| } | |||
| @ -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"); | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| @ -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); | |||
| } | |||
| } | |||