* Added new classes with behavior from MetadataManager & MetadataSource * Replaced MetadataManager usages with new classes * New PrefixDataIOHandler which packs data into a jar * Added mockito-core jar to lib folder * Added mockito-all jar to lib folder and updated tests to not use junit4 annotations * Fixed prefix generation entry points * Specific exception for missing metadata cases * Small change in checking missing metadata casespull/3882/head
| @ -1,233 +0,0 @@ | |||||
| /* | |||||
| * Copyright (C) 2012 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; | |||||
| 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.Collections; | |||||
| import java.util.HashMap; | |||||
| import java.util.List; | |||||
| import java.util.Map; | |||||
| import java.util.Set; | |||||
| import java.util.concurrent.ConcurrentHashMap; | |||||
| import java.util.concurrent.atomic.AtomicReference; | |||||
| import java.util.logging.Level; | |||||
| import java.util.logging.Logger; | |||||
| /** | |||||
| * Manager for loading metadata for alternate formats and short numbers. We also declare some | |||||
| * constants for phone number metadata loading, to more easily maintain all three types of metadata | |||||
| * together. | |||||
| * TODO: Consider managing phone number metadata loading here too. | |||||
| */ | |||||
| final class MetadataManager { | |||||
| static final String MULTI_FILE_PHONE_NUMBER_METADATA_FILE_PREFIX = | |||||
| "/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto"; | |||||
| static final String SINGLE_FILE_PHONE_NUMBER_METADATA_FILE_NAME = | |||||
| "/com/google/i18n/phonenumbers/data/SingleFilePhoneNumberMetadataProto"; | |||||
| private static final String ALTERNATE_FORMATS_FILE_PREFIX = | |||||
| "/com/google/i18n/phonenumbers/data/PhoneNumberAlternateFormatsProto"; | |||||
| private static final String SHORT_NUMBER_METADATA_FILE_PREFIX = | |||||
| "/com/google/i18n/phonenumbers/data/ShortNumberMetadataProto"; | |||||
| static final MetadataLoader DEFAULT_METADATA_LOADER = new MetadataLoader() { | |||||
| @Override | |||||
| public InputStream loadMetadata(String metadataFileName) { | |||||
| return MetadataManager.class.getResourceAsStream(metadataFileName); | |||||
| } | |||||
| }; | |||||
| private static final Logger logger = Logger.getLogger(MetadataManager.class.getName()); | |||||
| // A mapping from a country calling code to the alternate formats for that country calling code. | |||||
| private static final ConcurrentHashMap<Integer, PhoneMetadata> alternateFormatsMap = | |||||
| new ConcurrentHashMap<Integer, PhoneMetadata>(); | |||||
| // A mapping from a region code to the short number metadata for that region code. | |||||
| private static final ConcurrentHashMap<String, PhoneMetadata> shortNumberMetadataMap = | |||||
| new ConcurrentHashMap<String, PhoneMetadata>(); | |||||
| // The set of country calling codes for which there are alternate formats. For every country | |||||
| // calling code in this set there should be metadata linked into the resources. | |||||
| private static final Set<Integer> alternateFormatsCountryCodes = | |||||
| AlternateFormatsCountryCodeSet.getCountryCodeSet(); | |||||
| // The set of region codes for which there are short number metadata. For every region code in | |||||
| // this set there should be metadata linked into the resources. | |||||
| private static final Set<String> shortNumberMetadataRegionCodes = | |||||
| ShortNumbersRegionCodeSet.getRegionCodeSet(); | |||||
| private MetadataManager() {} | |||||
| static PhoneMetadata getAlternateFormatsForCountry(int countryCallingCode) { | |||||
| if (!alternateFormatsCountryCodes.contains(countryCallingCode)) { | |||||
| return null; | |||||
| } | |||||
| return getMetadataFromMultiFilePrefix(countryCallingCode, alternateFormatsMap, | |||||
| ALTERNATE_FORMATS_FILE_PREFIX, DEFAULT_METADATA_LOADER); | |||||
| } | |||||
| static PhoneMetadata getShortNumberMetadataForRegion(String regionCode) { | |||||
| if (!shortNumberMetadataRegionCodes.contains(regionCode)) { | |||||
| return null; | |||||
| } | |||||
| return getMetadataFromMultiFilePrefix(regionCode, shortNumberMetadataMap, | |||||
| SHORT_NUMBER_METADATA_FILE_PREFIX, DEFAULT_METADATA_LOADER); | |||||
| } | |||||
| static Set<String> getSupportedShortNumberRegions() { | |||||
| return Collections.unmodifiableSet(shortNumberMetadataRegionCodes); | |||||
| } | |||||
| /** | |||||
| * @param key the lookup key for the provided map, typically a region code or a country calling | |||||
| * code | |||||
| * @param map the map containing mappings of already loaded metadata from their {@code key}. If | |||||
| * this {@code key}'s metadata isn't already loaded, it will be added to this map after | |||||
| * loading | |||||
| * @param filePrefix the prefix of the file to load metadata from | |||||
| * @param metadataLoader the metadata loader used to inject alternative metadata sources | |||||
| */ | |||||
| static <T> PhoneMetadata getMetadataFromMultiFilePrefix(T key, | |||||
| ConcurrentHashMap<T, PhoneMetadata> map, String filePrefix, MetadataLoader metadataLoader) { | |||||
| PhoneMetadata metadata = map.get(key); | |||||
| if (metadata != null) { | |||||
| return metadata; | |||||
| } | |||||
| // We assume key.toString() is well-defined. | |||||
| String fileName = filePrefix + "_" + key; | |||||
| List<PhoneMetadata> metadataList = getMetadataFromSingleFileName(fileName, metadataLoader); | |||||
| if (metadataList.size() > 1) { | |||||
| logger.log(Level.WARNING, "more than one metadata in file " + fileName); | |||||
| } | |||||
| metadata = metadataList.get(0); | |||||
| PhoneMetadata oldValue = map.putIfAbsent(key, metadata); | |||||
| return (oldValue != null) ? oldValue : metadata; | |||||
| } | |||||
| // Loader and holder for the metadata maps loaded from a single file. | |||||
| static class SingleFileMetadataMaps { | |||||
| static SingleFileMetadataMaps load(String fileName, MetadataLoader metadataLoader) { | |||||
| List<PhoneMetadata> metadataList = getMetadataFromSingleFileName(fileName, metadataLoader); | |||||
| Map<String, PhoneMetadata> regionCodeToMetadata = new HashMap<String, PhoneMetadata>(); | |||||
| Map<Integer, PhoneMetadata> countryCallingCodeToMetadata = | |||||
| new HashMap<Integer, PhoneMetadata>(); | |||||
| for (PhoneMetadata metadata : metadataList) { | |||||
| String regionCode = metadata.getId(); | |||||
| if (PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY.equals(regionCode)) { | |||||
| // regionCode belongs to a non-geographical entity. | |||||
| countryCallingCodeToMetadata.put(metadata.getCountryCode(), metadata); | |||||
| } else { | |||||
| regionCodeToMetadata.put(regionCode, metadata); | |||||
| } | |||||
| } | |||||
| return new SingleFileMetadataMaps(regionCodeToMetadata, countryCallingCodeToMetadata); | |||||
| } | |||||
| // A map from a region code to the PhoneMetadata for that region. | |||||
| // For phone number metadata, the region code "001" is excluded, since that is used for the | |||||
| // non-geographical phone number entities. | |||||
| private final Map<String, PhoneMetadata> regionCodeToMetadata; | |||||
| // A map from a country calling code to the PhoneMetadata for that country calling code. | |||||
| // Examples of the country calling codes include 800 (International Toll Free Service) and 808 | |||||
| // (International Shared Cost Service). | |||||
| // For phone number metadata, only the non-geographical phone number entities' country calling | |||||
| // codes are present. | |||||
| private final Map<Integer, PhoneMetadata> countryCallingCodeToMetadata; | |||||
| private SingleFileMetadataMaps(Map<String, PhoneMetadata> regionCodeToMetadata, | |||||
| Map<Integer, PhoneMetadata> countryCallingCodeToMetadata) { | |||||
| this.regionCodeToMetadata = Collections.unmodifiableMap(regionCodeToMetadata); | |||||
| this.countryCallingCodeToMetadata = Collections.unmodifiableMap(countryCallingCodeToMetadata); | |||||
| } | |||||
| PhoneMetadata get(String regionCode) { | |||||
| return regionCodeToMetadata.get(regionCode); | |||||
| } | |||||
| PhoneMetadata get(int countryCallingCode) { | |||||
| return countryCallingCodeToMetadata.get(countryCallingCode); | |||||
| } | |||||
| } | |||||
| // Manages the atomic reference lifecycle of a SingleFileMetadataMaps encapsulation. | |||||
| static SingleFileMetadataMaps getSingleFileMetadataMaps( | |||||
| AtomicReference<SingleFileMetadataMaps> ref, String fileName, MetadataLoader metadataLoader) { | |||||
| SingleFileMetadataMaps maps = ref.get(); | |||||
| if (maps != null) { | |||||
| return maps; | |||||
| } | |||||
| maps = SingleFileMetadataMaps.load(fileName, metadataLoader); | |||||
| ref.compareAndSet(null, maps); | |||||
| return ref.get(); | |||||
| } | |||||
| private static List<PhoneMetadata> getMetadataFromSingleFileName(String fileName, | |||||
| MetadataLoader metadataLoader) { | |||||
| InputStream source = metadataLoader.loadMetadata(fileName); | |||||
| if (source == null) { | |||||
| // Sanity check; this would only happen if we packaged jars incorrectly. | |||||
| throw new IllegalStateException("missing metadata: " + fileName); | |||||
| } | |||||
| PhoneMetadataCollection metadataCollection = loadMetadataAndCloseInput(source); | |||||
| List<PhoneMetadata> metadataList = metadataCollection.getMetadataList(); | |||||
| if (metadataList.size() == 0) { | |||||
| // Sanity check; this should not happen since we build with non-empty metadata. | |||||
| throw new IllegalStateException("empty metadata: " + fileName); | |||||
| } | |||||
| return metadataList; | |||||
| } | |||||
| /** | |||||
| * Loads and returns the metadata from the given stream and closes the stream. | |||||
| * | |||||
| * @param source the non-null stream from which metadata is to be read | |||||
| * @return the loaded metadata | |||||
| */ | |||||
| private static PhoneMetadataCollection loadMetadataAndCloseInput(InputStream source) { | |||||
| ObjectInputStream ois = null; | |||||
| try { | |||||
| try { | |||||
| ois = new ObjectInputStream(source); | |||||
| } catch (IOException e) { | |||||
| throw new RuntimeException("cannot load/parse metadata", e); | |||||
| } | |||||
| PhoneMetadataCollection metadataCollection = new PhoneMetadataCollection(); | |||||
| try { | |||||
| metadataCollection.readExternal(ois); | |||||
| } catch (IOException e) { | |||||
| throw new RuntimeException("cannot load/parse metadata", e); | |||||
| } | |||||
| return metadataCollection; | |||||
| } finally { | |||||
| try { | |||||
| if (ois != null) { | |||||
| // This will close all underlying streams as well, including source. | |||||
| ois.close(); | |||||
| } else { | |||||
| source.close(); | |||||
| } | |||||
| } catch (IOException e) { | |||||
| logger.log(Level.WARNING, "error closing input stream (ignored)", e); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,9 @@ | |||||
| package com.google.i18n.phonenumbers; | |||||
| /** Exception class for cases when expected metadata cannot be found. */ | |||||
| public final class MissingMetadataException extends IllegalStateException { | |||||
| public MissingMetadataException(String message) { | |||||
| super(message); | |||||
| } | |||||
| } | |||||
| @ -1,86 +0,0 @@ | |||||
| /* | |||||
| * Copyright (C) 2015 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; | |||||
| import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; | |||||
| import java.util.List; | |||||
| import java.util.concurrent.ConcurrentHashMap; | |||||
| /** | |||||
| * Implementation of {@link MetadataSource} that reads from multiple resource files. | |||||
| */ | |||||
| final class MultiFileMetadataSourceImpl implements MetadataSource { | |||||
| // The prefix of the binary files containing phone number metadata for different regions. | |||||
| // This enables us to set up with different metadata, such as for testing. | |||||
| private final String phoneNumberMetadataFilePrefix; | |||||
| // The {@link MetadataLoader} used to inject alternative metadata sources. | |||||
| private final MetadataLoader metadataLoader; | |||||
| // A mapping from a region code to the phone number metadata for that region code. | |||||
| // Unlike the mappings for alternate formats and short number metadata, the phone number metadata | |||||
| // is loaded from a non-statically determined file prefix; therefore this map is bound to the | |||||
| // instance and not static. | |||||
| private final ConcurrentHashMap<String, PhoneMetadata> geographicalRegions = | |||||
| new ConcurrentHashMap<String, PhoneMetadata>(); | |||||
| // A mapping from a country calling code for a non-geographical entity to the phone number | |||||
| // metadata for that country calling code. Examples of the country calling codes include 800 | |||||
| // (International Toll Free Service) and 808 (International Shared Cost Service). | |||||
| // Unlike the mappings for alternate formats and short number metadata, the phone number metadata | |||||
| // is loaded from a non-statically determined file prefix; therefore this map is bound to the | |||||
| // instance and not static. | |||||
| private final ConcurrentHashMap<Integer, PhoneMetadata> nonGeographicalRegions = | |||||
| new ConcurrentHashMap<Integer, PhoneMetadata>(); | |||||
| // It is assumed that metadataLoader is not null. Checks should happen before passing it in here. | |||||
| // @VisibleForTesting | |||||
| MultiFileMetadataSourceImpl(String phoneNumberMetadataFilePrefix, MetadataLoader metadataLoader) { | |||||
| this.phoneNumberMetadataFilePrefix = phoneNumberMetadataFilePrefix; | |||||
| this.metadataLoader = metadataLoader; | |||||
| } | |||||
| // It is assumed that metadataLoader is not null. Checks should happen before passing it in here. | |||||
| MultiFileMetadataSourceImpl(MetadataLoader metadataLoader) { | |||||
| this(MetadataManager.MULTI_FILE_PHONE_NUMBER_METADATA_FILE_PREFIX, metadataLoader); | |||||
| } | |||||
| @Override | |||||
| public PhoneMetadata getMetadataForRegion(String regionCode) { | |||||
| return MetadataManager.getMetadataFromMultiFilePrefix(regionCode, geographicalRegions, | |||||
| phoneNumberMetadataFilePrefix, metadataLoader); | |||||
| } | |||||
| @Override | |||||
| public PhoneMetadata getMetadataForNonGeographicalRegion(int countryCallingCode) { | |||||
| if (!isNonGeographical(countryCallingCode)) { | |||||
| // The given country calling code was for a geographical region. | |||||
| return null; | |||||
| } | |||||
| return MetadataManager.getMetadataFromMultiFilePrefix(countryCallingCode, nonGeographicalRegions, | |||||
| phoneNumberMetadataFilePrefix, metadataLoader); | |||||
| } | |||||
| // A country calling code is non-geographical if it only maps to the non-geographical region code, | |||||
| // i.e. "001". | |||||
| private boolean isNonGeographical(int countryCallingCode) { | |||||
| List<String> regionCodes = | |||||
| CountryCodeToRegionCodeMap.getCountryCodeToRegionCodeMap().get(countryCallingCode); | |||||
| return (regionCodes.size() == 1 | |||||
| && PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY.equals(regionCodes.get(0))); | |||||
| } | |||||
| } | |||||
| @ -1,65 +0,0 @@ | |||||
| /* | |||||
| * Copyright (C) 2015 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; | |||||
| import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; | |||||
| import java.util.concurrent.atomic.AtomicReference; | |||||
| /** | |||||
| * Implementation of {@link MetadataSource} that reads from a single resource file. | |||||
| */ | |||||
| final class SingleFileMetadataSourceImpl implements MetadataSource { | |||||
| // The name of the binary file containing phone number metadata for different regions. | |||||
| // This enables us to set up with different metadata, such as for testing. | |||||
| private final String phoneNumberMetadataFileName; | |||||
| // The {@link MetadataLoader} used to inject alternative metadata sources. | |||||
| private final MetadataLoader metadataLoader; | |||||
| private final AtomicReference<MetadataManager.SingleFileMetadataMaps> phoneNumberMetadataRef = | |||||
| new AtomicReference<MetadataManager.SingleFileMetadataMaps>(); | |||||
| // It is assumed that metadataLoader is not null. Checks should happen before passing it in here. | |||||
| // @VisibleForTesting | |||||
| SingleFileMetadataSourceImpl(String phoneNumberMetadataFileName, MetadataLoader metadataLoader) { | |||||
| this.phoneNumberMetadataFileName = phoneNumberMetadataFileName; | |||||
| this.metadataLoader = metadataLoader; | |||||
| } | |||||
| // It is assumed that metadataLoader is not null. Checks should happen before passing it in here. | |||||
| SingleFileMetadataSourceImpl(MetadataLoader metadataLoader) { | |||||
| this(MetadataManager.SINGLE_FILE_PHONE_NUMBER_METADATA_FILE_NAME, metadataLoader); | |||||
| } | |||||
| @Override | |||||
| public PhoneMetadata getMetadataForRegion(String regionCode) { | |||||
| return MetadataManager.getSingleFileMetadataMaps(phoneNumberMetadataRef, | |||||
| phoneNumberMetadataFileName, metadataLoader).get(regionCode); | |||||
| } | |||||
| @Override | |||||
| public PhoneMetadata getMetadataForNonGeographicalRegion(int countryCallingCode) { | |||||
| // A country calling code is non-geographical if it only maps to the non-geographical region | |||||
| // code, i.e. "001". If this is not true of the given country calling code, then we will return | |||||
| // null here. If not for the atomic reference, such as if we were loading in multiple stages, we | |||||
| // would check that the passed in country calling code was indeed non-geographical to avoid | |||||
| // loading costs for a null result. Here though we do not check this since the entire data must | |||||
| // be loaded anyway if any of it is needed at some point in the life cycle of this class. | |||||
| return MetadataManager.getSingleFileMetadataMaps(phoneNumberMetadataRef, | |||||
| phoneNumberMetadataFileName, metadataLoader).get(countryCallingCode); | |||||
| } | |||||
| } | |||||
| @ -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,115 @@ | |||||
| /* | |||||
| * 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.MultiFileModeFileNameProvider; | |||||
| import com.google.i18n.phonenumbers.metadata.source.PhoneMetadataFileNameProvider; | |||||
| import com.google.i18n.phonenumbers.metadata.source.RegionMetadataSource; | |||||
| import com.google.i18n.phonenumbers.metadata.source.RegionMetadataSourceImpl; | |||||
| /** | |||||
| * Provides metadata init and 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.newLenientParser(); | |||||
| private final MetadataLoader metadataLoader = new ClassPathResourceMetadataLoader(); | |||||
| private final PhoneMetadataFileNameProvider phoneNumberMetadataFileNameProvider = | |||||
| new MultiFileModeFileNameProvider( | |||||
| "/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto"); | |||||
| private final MetadataSource phoneNumberMetadataSource = | |||||
| new MetadataSourceImpl( | |||||
| phoneNumberMetadataFileNameProvider, | |||||
| metadataLoader, | |||||
| metadataParser); | |||||
| private final PhoneMetadataFileNameProvider shortNumberMetadataFileNameProvider = | |||||
| new MultiFileModeFileNameProvider( | |||||
| "/com/google/i18n/phonenumbers/data/ShortNumberMetadataProto"); | |||||
| private final RegionMetadataSource shortNumberMetadataSource = | |||||
| new RegionMetadataSourceImpl( | |||||
| shortNumberMetadataFileNameProvider, | |||||
| metadataLoader, | |||||
| metadataParser); | |||||
| private final PhoneMetadataFileNameProvider alternateFormatsMetadataFileNameProvider = | |||||
| new MultiFileModeFileNameProvider( | |||||
| "/com/google/i18n/phonenumbers/data/PhoneNumberAlternateFormatsProto"); | |||||
| private final FormattingMetadataSource alternateFormatsMetadataSource = | |||||
| new FormattingMetadataSourceImpl( | |||||
| alternateFormatsMetadataFileNameProvider, | |||||
| metadataLoader, | |||||
| metadataParser); | |||||
| public MetadataParser getMetadataParser() { | |||||
| return metadataParser; | |||||
| } | |||||
| public MetadataLoader getMetadataLoader() { | |||||
| return metadataLoader; | |||||
| } | |||||
| public PhoneMetadataFileNameProvider getPhoneNumberMetadataFileNameProvider() { | |||||
| return phoneNumberMetadataFileNameProvider; | |||||
| } | |||||
| public MetadataSource getPhoneNumberMetadataSource() { | |||||
| return phoneNumberMetadataSource; | |||||
| } | |||||
| public PhoneMetadataFileNameProvider getShortNumberMetadataFileNameProvider() { | |||||
| return shortNumberMetadataFileNameProvider; | |||||
| } | |||||
| public RegionMetadataSource getShortNumberMetadataSource() { | |||||
| return shortNumberMetadataSource; | |||||
| } | |||||
| public PhoneMetadataFileNameProvider getAlternateFormatsMetadataFileNameProvider() { | |||||
| return alternateFormatsMetadataFileNameProvider; | |||||
| } | |||||
| 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,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.init; | |||||
| import com.google.i18n.phonenumbers.MetadataLoader; | |||||
| import java.io.InputStream; | |||||
| import java.util.logging.Level; | |||||
| import java.util.logging.Logger; | |||||
| /** | |||||
| * A {@link MetadataLoader} implementation that reads phone number metadata files as classpath | |||||
| * resources. | |||||
| */ | |||||
| public final class ClassPathResourceMetadataLoader implements MetadataLoader { | |||||
| private static final Logger logger = | |||||
| Logger.getLogger(ClassPathResourceMetadataLoader.class.getName()); | |||||
| @Override | |||||
| public InputStream loadMetadata(String metadataFileName) { | |||||
| InputStream inputStream = | |||||
| ClassPathResourceMetadataLoader.class.getResourceAsStream(metadataFileName); | |||||
| if (inputStream == null) { | |||||
| logger.log(Level.WARNING, String.format("File %s not found", metadataFileName)); | |||||
| } | |||||
| return inputStream; | |||||
| } | |||||
| } | |||||
| @ -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,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; | |||||
| } | |||||
| } | |||||
| @ -1,88 +0,0 @@ | |||||
| /* | |||||
| * Copyright (C) 2012 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; | |||||
| import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; | |||||
| import java.util.concurrent.ConcurrentHashMap; | |||||
| import junit.framework.TestCase; | |||||
| /** | |||||
| * Some basic tests to check that metadata can be correctly loaded. | |||||
| */ | |||||
| public class MetadataManagerTest extends TestCase { | |||||
| public void testAlternateFormatsLoadCorrectly() { | |||||
| // We should have some data for Germany. | |||||
| PhoneMetadata germanyMetadata = MetadataManager.getAlternateFormatsForCountry(49); | |||||
| assertNotNull(germanyMetadata); | |||||
| assertTrue(germanyMetadata.getNumberFormatCount() > 0); | |||||
| } | |||||
| public void testAlternateFormatsFailsGracefully() throws Exception { | |||||
| PhoneMetadata noAlternateFormats = MetadataManager.getAlternateFormatsForCountry(999); | |||||
| assertNull(noAlternateFormats); | |||||
| } | |||||
| public void testShortNumberMetadataLoadCorrectly() throws Exception { | |||||
| // We should have some data for France. | |||||
| PhoneMetadata franceMetadata = MetadataManager.getShortNumberMetadataForRegion("FR"); | |||||
| assertNotNull(franceMetadata); | |||||
| assertTrue(franceMetadata.hasShortCode()); | |||||
| } | |||||
| public void testShortNumberMetadataFailsGracefully() throws Exception { | |||||
| PhoneMetadata noShortNumberMetadata = MetadataManager.getShortNumberMetadataForRegion("XXX"); | |||||
| assertNull(noShortNumberMetadata); | |||||
| } | |||||
| public void testGetMetadataFromMultiFilePrefix_regionCode() { | |||||
| ConcurrentHashMap<String, PhoneMetadata> map = new ConcurrentHashMap<String, PhoneMetadata>(); | |||||
| PhoneMetadata metadata = MetadataManager.getMetadataFromMultiFilePrefix("CA", map, | |||||
| "/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting", | |||||
| MetadataManager.DEFAULT_METADATA_LOADER); | |||||
| assertEquals(metadata, map.get("CA")); | |||||
| } | |||||
| public void testGetMetadataFromMultiFilePrefix_countryCallingCode() { | |||||
| ConcurrentHashMap<Integer, PhoneMetadata> map = new ConcurrentHashMap<Integer, PhoneMetadata>(); | |||||
| PhoneMetadata metadata = MetadataManager.getMetadataFromMultiFilePrefix(800, map, | |||||
| "/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting", | |||||
| MetadataManager.DEFAULT_METADATA_LOADER); | |||||
| assertEquals(metadata, map.get(800)); | |||||
| } | |||||
| public void testGetMetadataFromMultiFilePrefix_missingMetadataFileThrowsRuntimeException() { | |||||
| // In normal usage we should never get a state where we are asking to load metadata that doesn't | |||||
| // exist. However if the library is packaged incorrectly in the jar, this could happen and the | |||||
| // best we can do is make sure the exception has the file name in it. | |||||
| try { | |||||
| MetadataManager.getMetadataFromMultiFilePrefix("XX", | |||||
| new ConcurrentHashMap<String, PhoneMetadata>(), "no/such/file", | |||||
| MetadataManager.DEFAULT_METADATA_LOADER); | |||||
| fail("expected exception"); | |||||
| } catch (RuntimeException e) { | |||||
| assertTrue("Unexpected error: " + e, e.getMessage().contains("no/such/file_XX")); | |||||
| } | |||||
| try { | |||||
| MetadataManager.getMetadataFromMultiFilePrefix(123, | |||||
| new ConcurrentHashMap<Integer, PhoneMetadata>(), "no/such/file", | |||||
| MetadataManager.DEFAULT_METADATA_LOADER); | |||||
| fail("expected exception"); | |||||
| } catch (RuntimeException e) { | |||||
| assertTrue("Unexpected error: " + e, e.getMessage().contains("no/such/file_123")); | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -1,62 +0,0 @@ | |||||
| /* | |||||
| * Copyright (C) 2015 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; | |||||
| import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; | |||||
| import junit.framework.TestCase; | |||||
| /** | |||||
| * Unit tests for MultiFileMetadataSourceImpl.java. | |||||
| */ | |||||
| public class MultiFileMetadataSourceImplTest extends TestCase { | |||||
| private static final MultiFileMetadataSourceImpl SOURCE = | |||||
| new MultiFileMetadataSourceImpl(MetadataManager.DEFAULT_METADATA_LOADER); | |||||
| private static final MultiFileMetadataSourceImpl MISSING_FILE_SOURCE = | |||||
| new MultiFileMetadataSourceImpl("no/such/file", MetadataManager.DEFAULT_METADATA_LOADER); | |||||
| public void testGeoPhoneNumberMetadataLoadCorrectly() { | |||||
| // We should have some data for the UAE. | |||||
| PhoneMetadata uaeMetadata = SOURCE.getMetadataForRegion("AE"); | |||||
| assertEquals(uaeMetadata.getCountryCode(), 971); | |||||
| assertTrue(uaeMetadata.hasGeneralDesc()); | |||||
| } | |||||
| public void testGeoPhoneNumberMetadataLoadFromMissingFileThrowsException() throws Exception { | |||||
| try { | |||||
| MISSING_FILE_SOURCE.getMetadataForRegion("AE"); | |||||
| fail("expected exception"); | |||||
| } catch (RuntimeException e) { | |||||
| assertTrue("Unexpected error: " + e, e.getMessage().contains("no/such/file")); | |||||
| } | |||||
| } | |||||
| public void testNonGeoPhoneNumberMetadataLoadCorrectly() { | |||||
| // We should have some data for international toll-free numbers. | |||||
| PhoneMetadata intlMetadata = SOURCE.getMetadataForNonGeographicalRegion(800); | |||||
| assertEquals(intlMetadata.getId(), "001"); | |||||
| assertTrue(intlMetadata.hasGeneralDesc()); | |||||
| } | |||||
| public void testNonGeoPhoneNumberMetadataLoadFromMissingFileThrowsException() throws Exception { | |||||
| try { | |||||
| MISSING_FILE_SOURCE.getMetadataForNonGeographicalRegion(800); | |||||
| fail("expected exception"); | |||||
| } catch (RuntimeException e) { | |||||
| assertTrue("Unexpected error: " + e, e.getMessage().contains("no/such/file")); | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -1,48 +0,0 @@ | |||||
| /* | |||||
| * Copyright (C) 2015 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; | |||||
| import junit.framework.TestCase; | |||||
| /** | |||||
| * Unit tests for SingleFileMetadataSourceImpl.java. | |||||
| * | |||||
| * <p> | |||||
| * We do not package single file metadata files, so it is only possible to test failures here. | |||||
| */ | |||||
| public class SingleFileMetadataSourceImplTest extends TestCase { | |||||
| private static final SingleFileMetadataSourceImpl MISSING_FILE_SOURCE = | |||||
| new SingleFileMetadataSourceImpl("no/such/file", MetadataManager.DEFAULT_METADATA_LOADER); | |||||
| public void testGeoPhoneNumberMetadataLoadFromMissingFileThrowsException() throws Exception { | |||||
| try { | |||||
| MISSING_FILE_SOURCE.getMetadataForRegion("AE"); | |||||
| fail("expected exception"); | |||||
| } catch (RuntimeException e) { | |||||
| assertTrue("Unexpected error: " + e, e.getMessage().contains("no/such/file")); | |||||
| } | |||||
| } | |||||
| public void testNonGeoPhoneNumberMetadataLoadFromMissingFileThrowsException() throws Exception { | |||||
| try { | |||||
| MISSING_FILE_SOURCE.getMetadataForNonGeographicalRegion(800); | |||||
| fail("expected exception"); | |||||
| } catch (RuntimeException e) { | |||||
| assertTrue("Unexpected error: " + e, e.getMessage().contains("no/such/file")); | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -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.internal; | |||||
| import junit.framework.TestCase; | |||||
| public class GeoEntityUtilityTest extends TestCase { | |||||
| public void test_isGeoEntity_shouldReturnTrueForCountryRegionCode() { | |||||
| assertTrue(GeoEntityUtility.isGeoEntity("DE")); | |||||
| } | |||||
| public void test_isGeoEntity_shouldReturnFalseForWorldRegionCode() { | |||||
| assertFalse(GeoEntityUtility.isGeoEntity("001")); | |||||
| } | |||||
| public void test_isGeoEntity_shouldReturnTrueForCountryCallingCode() { | |||||
| assertTrue(GeoEntityUtility.isGeoEntity(41)); | |||||
| } | |||||
| public void test_isGeoEntity_shouldReturnFalseForInternationalSharedCostServiceCallingCode() { | |||||
| assertFalse(GeoEntityUtility.isGeoEntity(808)); | |||||
| } | |||||
| public void test_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,88 @@ | |||||
| /* | |||||
| * 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.assertThrows; | |||||
| 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 junit.framework.TestCase; | |||||
| import org.junit.function.ThrowingRunnable; | |||||
| public final class MetadataParserTest extends TestCase { | |||||
| private static final MetadataParser metadataParser = MetadataParser.newStrictParser(); | |||||
| public void test_parse_shouldThrowExceptionForNullInput() { | |||||
| assertThrows( | |||||
| IllegalArgumentException.class, | |||||
| new ThrowingRunnable() { | |||||
| @Override | |||||
| public void run() { | |||||
| metadataParser.parse(null); | |||||
| } | |||||
| }); | |||||
| } | |||||
| public void test_parse_shouldThrowExceptionForEmptyInput() { | |||||
| final InputStream emptyInput = new ByteArrayInputStream(new byte[0]); | |||||
| assertThrows( | |||||
| IllegalStateException.class, | |||||
| new ThrowingRunnable() { | |||||
| @Override | |||||
| public void run() { | |||||
| metadataParser.parse(emptyInput); | |||||
| } | |||||
| }); | |||||
| } | |||||
| public void test_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); | |||||
| } | |||||
| }); | |||||
| } | |||||
| public void test_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()); | |||||
| } | |||||
| public void test_parse_shouldReturnEmptyCollectionForNullInput() { | |||||
| Collection<PhoneMetadata> actual = MetadataParser.newLenientParser().parse(null); | |||||
| assertTrue(actual.isEmpty()); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,102 @@ | |||||
| /* | |||||
| * 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.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 junit.framework.TestCase; | |||||
| import org.junit.Assert; | |||||
| import org.junit.function.ThrowingRunnable; | |||||
| import org.mockito.Mockito; | |||||
| public class BlockingMetadataBootstrappingGuardTest extends TestCase { | |||||
| private static final String PHONE_METADATA_FILE = "some metadata file"; | |||||
| private static final PhoneMetadataCollection PHONE_METADATA = | |||||
| PhoneMetadataCollection.newBuilder() | |||||
| .addMetadata(PhoneMetadata.newBuilder().setId("id").build()); | |||||
| private final MetadataLoader metadataLoader = Mockito.mock(MetadataLoader.class); | |||||
| private final MetadataContainer metadataContainer = Mockito.mock(MetadataContainer.class); | |||||
| private BlockingMetadataBootstrappingGuard<MetadataContainer> bootstrappingGuard; | |||||
| @Override | |||||
| public void setUp() throws IOException { | |||||
| when(metadataLoader.loadMetadata(PHONE_METADATA_FILE)) | |||||
| .thenReturn(PhoneMetadataCollectionUtil.toInputStream(PHONE_METADATA)); | |||||
| bootstrappingGuard = | |||||
| new BlockingMetadataBootstrappingGuard<>( | |||||
| metadataLoader, MetadataParser.newStrictParser(), metadataContainer); | |||||
| } | |||||
| public void test_getOrBootstrap_shouldInvokeBootstrappingOnlyOnce() { | |||||
| bootstrappingGuard.getOrBootstrap(PHONE_METADATA_FILE); | |||||
| bootstrappingGuard.getOrBootstrap(PHONE_METADATA_FILE); | |||||
| verify(metadataLoader, times(1)).loadMetadata(PHONE_METADATA_FILE); | |||||
| } | |||||
| public void test_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)); | |||||
| } | |||||
| public void test_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); | |||||
| } | |||||
| private class BootstrappingRunnable implements Callable<MetadataContainer> { | |||||
| @Override | |||||
| public MetadataContainer call() { | |||||
| return bootstrappingGuard.getOrBootstrap(PHONE_METADATA_FILE); | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,66 @@ | |||||
| /* | |||||
| * 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; | |||||
| import junit.framework.TestCase; | |||||
| public class CompositeMetadataContainerTest extends TestCase { | |||||
| 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; | |||||
| @Override | |||||
| public void setUp() { | |||||
| metadataContainer = new CompositeMetadataContainer(); | |||||
| } | |||||
| public void test_getMetadataBy_shouldReturnNullForNonExistingRegionCode() { | |||||
| assertNull(metadataContainer.getMetadataBy(REGION_CODE)); | |||||
| } | |||||
| public void test_getMetadataBy_shouldReturnMetadataForExistingRegionCode() { | |||||
| metadataContainer.accept(PHONE_METADATA_WITH_REGION_CODE); | |||||
| assertSame(PHONE_METADATA_WITH_REGION_CODE, metadataContainer.getMetadataBy(REGION_CODE)); | |||||
| } | |||||
| public void test_getMetadataBy_shouldReturnNullForNonExistingCountryCode() { | |||||
| assertNull(metadataContainer.getMetadataBy(COUNTRY_CODE)); | |||||
| } | |||||
| public void test_getMetadataBy_shouldReturnMetadataForExistingCountryCode() { | |||||
| metadataContainer.accept(PHONE_METADATA_WITH_COUNTRY_CODE); | |||||
| assertSame(PHONE_METADATA_WITH_COUNTRY_CODE, metadataContainer.getMetadataBy(COUNTRY_CODE)); | |||||
| } | |||||
| public void test_getMetadataBy_shouldReturnNullForExistingCountryCodeOfGeoRegion() { | |||||
| metadataContainer.accept(PHONE_METADATA_WITH_REGION_CODE); | |||||
| assertNull(metadataContainer.getMetadataBy(COUNTRY_CODE)); | |||||
| } | |||||
| } | |||||
| @ -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.Phonemetadata.PhoneMetadata; | |||||
| import junit.framework.TestCase; | |||||
| public class MapBackedMetadataContainerTest extends TestCase { | |||||
| 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); | |||||
| public void test_getMetadataBy_shouldReturnNullForNullRegionCode() { | |||||
| assertNull(MapBackedMetadataContainer.byRegionCode().getMetadataBy(null)); | |||||
| } | |||||
| public void test_getMetadataBy_shouldReturnNullForNonExistingRegionCode() { | |||||
| assertNull(MapBackedMetadataContainer.byRegionCode().getMetadataBy(REGION_CODE)); | |||||
| } | |||||
| public void test_getMetadataBy_shouldReturnMetadataForExistingRegionCode() { | |||||
| MapBackedMetadataContainer<String> metadataContainer = | |||||
| MapBackedMetadataContainer.byRegionCode(); | |||||
| metadataContainer.accept(PHONE_METADATA); | |||||
| assertSame(PHONE_METADATA, metadataContainer.getMetadataBy(REGION_CODE)); | |||||
| } | |||||
| public void test_getMetadataBy_shouldReturnNullForNullCountryCode() { | |||||
| assertNull(MapBackedMetadataContainer.byCountryCallingCode().getMetadataBy(null)); | |||||
| } | |||||
| public void test_getMetadataBy_shouldReturnNullForNonExistingCountryCode() { | |||||
| assertNull(MapBackedMetadataContainer.byCountryCallingCode().getMetadataBy(COUNTRY_CODE)); | |||||
| } | |||||
| public void test_getMetadataBy_shouldReturnMetadataForExistingCountryCode() { | |||||
| MapBackedMetadataContainer<Integer> metadataContainer = | |||||
| MapBackedMetadataContainer.byCountryCallingCode(); | |||||
| metadataContainer.accept(PHONE_METADATA); | |||||
| assertSame(PHONE_METADATA, metadataContainer.getMetadataBy(COUNTRY_CODE)); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,45 @@ | |||||
| /* | |||||
| * 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 junit.framework.TestCase; | |||||
| import org.junit.function.ThrowingRunnable; | |||||
| public final class MultiFileModeFileNameProviderTest extends TestCase { | |||||
| private final PhoneMetadataFileNameProvider metadataFileNameProvider = | |||||
| new MultiFileModeFileNameProvider("some/file"); | |||||
| public void test_getFor_shouldAppendKeyToTheBase() { | |||||
| String metadataFileName = metadataFileNameProvider.getFor("key1"); | |||||
| assertEquals("some/file_key1", metadataFileName); | |||||
| } | |||||
| public void test_getFor_shouldThrowExceptionForNonAlphanumericKey() { | |||||
| assertThrows( | |||||
| IllegalArgumentException.class, | |||||
| new ThrowingRunnable() { | |||||
| @Override | |||||
| public void run() { | |||||
| metadataFileNameProvider.getFor("\tkey1\n"); | |||||
| } | |||||
| }); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,31 @@ | |||||
| /* | |||||
| * 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 junit.framework.TestCase; | |||||
| public final class SingleFileModeFileNameProviderTest extends TestCase { | |||||
| private final PhoneMetadataFileNameProvider metadataFileNameProvider = | |||||
| new SingleFileModeFileNameProvider("some/file"); | |||||
| public void test_getFor_shouldReturnTheFileNameBase() { | |||||
| String metadataFileName = metadataFileNameProvider.getFor("key1"); | |||||
| assertEquals("some/file", metadataFileName); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,107 @@ | |||||
| /* | |||||
| * Copyright (C) 2012 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.buildtools; | |||||
| import java.io.BufferedInputStream; | |||||
| import java.io.File; | |||||
| import java.io.FileInputStream; | |||||
| import java.io.FileOutputStream; | |||||
| import java.io.IOException; | |||||
| import java.util.jar.Attributes; | |||||
| import java.util.jar.JarEntry; | |||||
| import java.util.jar.JarOutputStream; | |||||
| import java.util.jar.Manifest; | |||||
| /** | |||||
| * Implementation of the AbstractPhonePrefixDataIOHandler required by the GeneratePhonePrefixData | |||||
| * class used here to create the output files and add them to the resulting JAR. | |||||
| */ | |||||
| public class JarPhonePrefixDataIOHandler extends AbstractPhonePrefixDataIOHandler { | |||||
| // Base name of the output JAR files. It also forms part of the name of the package | |||||
| // containing the generated binary data. | |||||
| private final String jarBase; | |||||
| // The path to the output directory. | |||||
| private final File outputPath; | |||||
| // The JAR output stream used by the JarPhonePrefixDataIOHandler. | |||||
| private final JarOutputStream jarOutputStream; | |||||
| // The package that will be used to create the JAR entry file. | |||||
| private final Package outputPackage; | |||||
| public JarPhonePrefixDataIOHandler(File outputPath, String outputName, Package outputPackage) | |||||
| throws IOException { | |||||
| if (outputPath.exists()) { | |||||
| if (!outputPath.isDirectory()) { | |||||
| throw new IOException("Expected directory: " + outputPath.getAbsolutePath()); | |||||
| } | |||||
| } else { | |||||
| if (!outputPath.mkdirs()) { | |||||
| throw new IOException("Could not create directory " + outputPath.getAbsolutePath()); | |||||
| } | |||||
| } | |||||
| this.outputPath = outputPath; | |||||
| this.jarBase = outputName; | |||||
| this.outputPackage = outputPackage; | |||||
| jarOutputStream = createJar(); | |||||
| } | |||||
| private JarOutputStream createJar() throws IOException { | |||||
| Manifest manifest = new java.util.jar.Manifest(); | |||||
| manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); | |||||
| return new JarOutputStream(new FileOutputStream(new File(outputPath, jarBase + ".jar"))); | |||||
| } | |||||
| /** | |||||
| * Adds the provided file to the created JAR. | |||||
| */ | |||||
| @Override | |||||
| public void addFileToOutput(File file) throws IOException { | |||||
| JarEntry entry = | |||||
| new JarEntry( | |||||
| outputPackage.getName().replace('.', '/') | |||||
| + String.format("/%s/", jarBase) | |||||
| + file.getPath()); | |||||
| entry.setTime(file.lastModified()); | |||||
| jarOutputStream.putNextEntry(entry); | |||||
| BufferedInputStream bufferedInputStream = null; | |||||
| try { | |||||
| bufferedInputStream = new BufferedInputStream(new FileInputStream(file)); | |||||
| byte[] buffer = new byte[4096]; | |||||
| for (int read; (read = bufferedInputStream.read(buffer)) > 0; ) { | |||||
| jarOutputStream.write(buffer, 0, read); | |||||
| } | |||||
| if (!file.delete()) { | |||||
| throw new IOException("Could not delete: " + file.getAbsolutePath()); | |||||
| } | |||||
| } finally { | |||||
| jarOutputStream.closeEntry(); | |||||
| closeFile(bufferedInputStream); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public File createFile(String path) { | |||||
| return new File(path); | |||||
| } | |||||
| @Override | |||||
| public void close() { | |||||
| closeFile(jarOutputStream); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,78 @@ | |||||
| /* | |||||
| * Copyright (C) 2012 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.buildtools; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import java.util.Enumeration; | |||||
| import java.util.jar.JarEntry; | |||||
| import java.util.jar.JarFile; | |||||
| import java.util.logging.Level; | |||||
| import java.util.logging.Logger; | |||||
| import junit.framework.TestCase; | |||||
| /** | |||||
| * Unittests for JarPhonePrefixDataIOHandler.java | |||||
| */ | |||||
| public class JarPhonePrefixDataIOHandlerTest extends TestCase { | |||||
| private static final String TESTING_JAR_BASE = "testing_data"; | |||||
| private static final Logger logger = | |||||
| Logger.getLogger(JarPhonePrefixDataIOHandlerTest.class.getName()); | |||||
| public void testAddFileToOutput() { | |||||
| File outputFile = null; | |||||
| try { | |||||
| // Create the output jar. | |||||
| File outputPath = new File("/tmp/build"); | |||||
| Package outputPackage = JarPhonePrefixDataIOHandlerTest.class.getPackage(); | |||||
| JarPhonePrefixDataIOHandler ioHandler = | |||||
| new JarPhonePrefixDataIOHandler(outputPath, TESTING_JAR_BASE, outputPackage); | |||||
| outputFile = File.createTempFile("outputTestFile", "txt"); | |||||
| ioHandler.addFileToOutput(outputFile); | |||||
| ioHandler.close(); | |||||
| JarFile outputJar = new JarFile(new File(outputPath, TESTING_JAR_BASE + ".jar")); | |||||
| // Test if there is exactly one entry in the jar. | |||||
| Enumeration<JarEntry> entries = outputJar.entries(); | |||||
| int entriesCount = 0; | |||||
| while (entries.hasMoreElements()) { | |||||
| entriesCount++; | |||||
| entries.nextElement(); | |||||
| } | |||||
| assertEquals(1, entriesCount); | |||||
| // Test if the entry file in the jar has the expected path. | |||||
| String jarEntryPath = | |||||
| "com/google/i18n/phonenumbers/buildtools/" | |||||
| + TESTING_JAR_BASE | |||||
| + "/" | |||||
| + outputFile.getPath(); | |||||
| JarEntry jarEntry = outputJar.getJarEntry(jarEntryPath); | |||||
| assertNotNull("Output file not found inside the jar.", jarEntry); | |||||
| } catch (IOException e) { | |||||
| logger.log(Level.SEVERE, e.getMessage()); | |||||
| fail(); | |||||
| } finally { | |||||
| if (outputFile != null && outputFile.exists()) { | |||||
| outputFile.delete(); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||