* 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/2751/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(); | |||
| } | |||
| } | |||
| } | |||
| } | |||