Browse Source

Refactoring MetadataManager (#2732)

* 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 cases
pull/2751/head
Tijana Vislavski Gradina 4 years ago
committed by GitHub
parent
commit
5a17acd2e8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 1941 additions and 734 deletions
  1. +4
    -6
      java/carrier/src/com/google/i18n/phonenumbers/PhoneNumberToCarrierMapper.java
  2. +4
    -4
      java/geocoder/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java
  3. +7
    -6
      java/internal/prefixmapper/src/com/google/i18n/phonenumbers/prefixmapper/PrefixFileReader.java
  4. BIN
      java/lib/mockito-all-1.10.19.jar
  5. +1
    -1
      java/libphonenumber/src/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java
  6. +0
    -233
      java/libphonenumber/src/com/google/i18n/phonenumbers/MetadataManager.java
  7. +9
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/MissingMetadataException.java
  8. +0
    -86
      java/libphonenumber/src/com/google/i18n/phonenumbers/MultiFileMetadataSourceImpl.java
  9. +4
    -1
      java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java
  10. +54
    -26
      java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java
  11. +38
    -28
      java/libphonenumber/src/com/google/i18n/phonenumbers/ShortNumberInfo.java
  12. +0
    -65
      java/libphonenumber/src/com/google/i18n/phonenumbers/SingleFileMetadataSourceImpl.java
  13. +56
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/internal/GeoEntityUtility.java
  14. +115
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/DefaultMetadataDependenciesProvider.java
  15. +42
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/ClassPathResourceMetadataLoader.java
  16. +108
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/MetadataParser.java
  17. +78
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuard.java
  18. +69
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainer.java
  19. +37
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSource.java
  20. +57
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSourceImpl.java
  21. +74
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainer.java
  22. +35
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataBootstrappingGuard.java
  23. +8
    -15
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataContainer.java
  24. +21
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSource.java
  25. +72
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSourceImpl.java
  26. +42
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProvider.java
  27. +47
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/NonGeographicalEntityMetadataSource.java
  28. +36
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/PhoneMetadataFileNameProvider.java
  29. +40
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSource.java
  30. +62
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSourceImpl.java
  31. +35
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProvider.java
  32. +38
    -37
      java/libphonenumber/test/com/google/i18n/phonenumbers/ExampleNumbersTest.java
  33. +0
    -88
      java/libphonenumber/test/com/google/i18n/phonenumbers/MetadataManagerTest.java
  34. +0
    -62
      java/libphonenumber/test/com/google/i18n/phonenumbers/MultiFileMetadataSourceImplTest.java
  35. +45
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java
  36. +0
    -48
      java/libphonenumber/test/com/google/i18n/phonenumbers/SingleFileMetadataSourceImplTest.java
  37. +11
    -3
      java/libphonenumber/test/com/google/i18n/phonenumbers/TestMetadataTestCase.java
  38. +42
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/internal/GeoEntityUtilityTest.java
  39. +21
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/PhoneMetadataCollectionUtil.java
  40. +88
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/init/MetadataParserTest.java
  41. +102
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuardTest.java
  42. +66
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainerTest.java
  43. +62
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainerTest.java
  44. +45
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProviderTest.java
  45. +31
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProviderTest.java
  46. +6
    -0
      java/pom.xml
  47. +3
    -3
      tools/java/java-build/src/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java
  48. +15
    -9
      tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/GeneratePhonePrefixDataEntryPoint.java
  49. +13
    -6
      tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapDataEntryPoint.java
  50. +107
    -0
      tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/JarPhonePrefixDataIOHandler.java
  51. +13
    -7
      tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/PhonePrefixDataIOHandler.java
  52. +78
    -0
      tools/java/java-build/test/com/google/i18n/phonenumbers/buildtools/JarPhonePrefixDataIOHandlerTest.java

+ 4
- 6
java/carrier/src/com/google/i18n/phonenumbers/PhoneNumberToCarrierMapper.java View File

@ -16,11 +16,10 @@
package com.google.i18n.phonenumbers;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.google.i18n.phonenumbers.metadata.DefaultMetadataDependenciesProvider;
import com.google.i18n.phonenumbers.prefixmapper.PrefixFileReader;
import java.util.Locale;
/**
@ -30,9 +29,7 @@ import java.util.Locale;
*/
public class PhoneNumberToCarrierMapper {
private static PhoneNumberToCarrierMapper instance = null;
private static final String MAPPING_DATA_DIRECTORY =
"/com/google/i18n/phonenumbers/carrier/data/";
private PrefixFileReader prefixFileReader = null;
private final PrefixFileReader prefixFileReader;
private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
@ -51,7 +48,8 @@ public class PhoneNumberToCarrierMapper {
*/
public static synchronized PhoneNumberToCarrierMapper getInstance() {
if (instance == null) {
instance = new PhoneNumberToCarrierMapper(MAPPING_DATA_DIRECTORY);
instance = new PhoneNumberToCarrierMapper(DefaultMetadataDependenciesProvider.getInstance()
.getCarrierDataDirectory());
}
return instance;
}


+ 4
- 4
java/geocoder/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java View File

@ -20,6 +20,7 @@ import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.google.i18n.phonenumbers.metadata.DefaultMetadataDependenciesProvider;
import com.google.i18n.phonenumbers.prefixmapper.PrefixFileReader;
import java.util.List;
@ -32,9 +33,7 @@ import java.util.Locale;
*/
public class PhoneNumberOfflineGeocoder {
private static PhoneNumberOfflineGeocoder instance = null;
private static final String MAPPING_DATA_DIRECTORY =
"/com/google/i18n/phonenumbers/geocoding/data/";
private PrefixFileReader prefixFileReader = null;
private final PrefixFileReader prefixFileReader;
private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
@ -54,7 +53,8 @@ public class PhoneNumberOfflineGeocoder {
*/
public static synchronized PhoneNumberOfflineGeocoder getInstance() {
if (instance == null) {
instance = new PhoneNumberOfflineGeocoder(MAPPING_DATA_DIRECTORY);
instance = new PhoneNumberOfflineGeocoder(DefaultMetadataDependenciesProvider.getInstance()
.getGeocodingDataDirectory());
}
return instance;
}


+ 7
- 6
java/internal/prefixmapper/src/com/google/i18n/phonenumbers/prefixmapper/PrefixFileReader.java View File

@ -16,8 +16,10 @@
package com.google.i18n.phonenumbers.prefixmapper;
import com.google.i18n.phonenumbers.MetadataLoader;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.google.i18n.phonenumbers.metadata.DefaultMetadataDependenciesProvider;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
@ -40,17 +42,17 @@ public class PrefixFileReader {
private MappingFileProvider mappingFileProvider = new MappingFileProvider();
// A mapping from countryCallingCode_lang to the corresponding phone prefix map that has been
// loaded.
private Map<String, PhonePrefixMap> availablePhonePrefixMaps =
new HashMap<String, PhonePrefixMap>();
private Map<String, PhonePrefixMap> availablePhonePrefixMaps = new HashMap<>();
private final MetadataLoader metadataLoader;
public PrefixFileReader(String phonePrefixDataDirectory) {
this.phonePrefixDataDirectory = phonePrefixDataDirectory;
this.metadataLoader = DefaultMetadataDependenciesProvider.getInstance().getMetadataLoader();
loadMappingFileProvider();
}
private void loadMappingFileProvider() {
InputStream source =
PrefixFileReader.class.getResourceAsStream(phonePrefixDataDirectory + "config");
InputStream source = metadataLoader.loadMetadata(phonePrefixDataDirectory + "config");
ObjectInputStream in = null;
try {
in = new ObjectInputStream(source);
@ -75,8 +77,7 @@ public class PrefixFileReader {
}
private void loadPhonePrefixMapFromFile(String fileName) {
InputStream source =
PrefixFileReader.class.getResourceAsStream(phonePrefixDataDirectory + fileName);
InputStream source = metadataLoader.loadMetadata(phonePrefixDataDirectory + fileName);
ObjectInputStream in = null;
try {
in = new ObjectInputStream(source);


BIN
java/lib/mockito-all-1.10.19.jar View File


+ 1
- 1
java/libphonenumber/src/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java View File

@ -30,7 +30,7 @@ public class CountryCodeToRegionCodeMap {
// country/region represented by that country code. In the case of multiple
// countries sharing a calling code, such as the NANPA countries, the one
// indicated with "isMainCountryForCode" in the metadata should be first.
static Map<Integer, List<String>> getCountryCodeToRegionCodeMap() {
public static Map<Integer, List<String>> getCountryCodeToRegionCodeMap() {
// The capacity is set to 286 as there are 215 different entries,
// and this offers a load factor of roughly 0.75.
Map<Integer, List<String>> countryCodeToRegionCodeMap =


+ 0
- 233
java/libphonenumber/src/com/google/i18n/phonenumbers/MetadataManager.java View File

@ -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);
}
}
}
}

+ 9
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/MissingMetadataException.java View File

@ -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);
}
}

+ 0
- 86
java/libphonenumber/src/com/google/i18n/phonenumbers/MultiFileMetadataSourceImpl.java View File

@ -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)));
}
}

+ 4
- 1
java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java View File

@ -24,6 +24,7 @@ import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.google.i18n.phonenumbers.internal.RegexCache;
import com.google.i18n.phonenumbers.metadata.DefaultMetadataDependenciesProvider;
import java.lang.Character.UnicodeBlock;
import java.util.Iterator;
import java.util.NoSuchElementException;
@ -575,7 +576,9 @@ final class PhoneNumberMatcher implements Iterator<PhoneNumberMatch> {
}
// If this didn't pass, see if there are any alternate formats that match, and try them instead.
PhoneMetadata alternateFormats =
MetadataManager.getAlternateFormatsForCountry(number.getCountryCode());
DefaultMetadataDependenciesProvider.getInstance()
.getAlternateFormatsMetadataSource()
.getFormattingMetadataForCountryCallingCode(number.getCountryCode());
String nationalSignificantNumber = util.getNationalSignificantNumber(number);
if (alternateFormats != null) {
for (NumberFormat alternateFormat : alternateFormats.getNumberFormatList()) {


+ 54
- 26
java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java View File

@ -24,11 +24,12 @@ import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource;
import com.google.i18n.phonenumbers.internal.MatcherApi;
import com.google.i18n.phonenumbers.internal.RegexBasedMatcher;
import com.google.i18n.phonenumbers.internal.RegexCache;
import com.google.i18n.phonenumbers.metadata.DefaultMetadataDependenciesProvider;
import com.google.i18n.phonenumbers.metadata.source.MetadataSource;
import com.google.i18n.phonenumbers.metadata.source.MetadataSourceImpl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -121,16 +122,16 @@ public class PhoneNumberUtil {
private static final Map<Character, Character> ALL_PLUS_NUMBER_GROUPING_SYMBOLS;
static {
HashMap<Integer, String> mobileTokenMap = new HashMap<Integer, String>();
HashMap<Integer, String> mobileTokenMap = new HashMap<>();
mobileTokenMap.put(54, "9");
MOBILE_TOKEN_MAPPINGS = Collections.unmodifiableMap(mobileTokenMap);
HashSet<Integer> geoMobileCountriesWithoutMobileAreaCodes = new HashSet<Integer>();
HashSet<Integer> geoMobileCountriesWithoutMobileAreaCodes = new HashSet<>();
geoMobileCountriesWithoutMobileAreaCodes.add(86); // China
GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES =
Collections.unmodifiableSet(geoMobileCountriesWithoutMobileAreaCodes);
HashSet<Integer> geoMobileCountries = new HashSet<Integer>();
HashSet<Integer> geoMobileCountries = new HashSet<>();
geoMobileCountries.add(52); // Mexico
geoMobileCountries.add(54); // Argentina
geoMobileCountries.add(55); // Brazil
@ -140,7 +141,7 @@ public class PhoneNumberUtil {
// Simple ASCII digits map used to populate ALPHA_PHONE_MAPPINGS and
// ALL_PLUS_NUMBER_GROUPING_SYMBOLS.
HashMap<Character, Character> asciiDigitMappings = new HashMap<Character, Character>();
HashMap<Character, Character> asciiDigitMappings = new HashMap<>();
asciiDigitMappings.put('0', '0');
asciiDigitMappings.put('1', '1');
asciiDigitMappings.put('2', '2');
@ -152,7 +153,7 @@ public class PhoneNumberUtil {
asciiDigitMappings.put('8', '8');
asciiDigitMappings.put('9', '9');
HashMap<Character, Character> alphaMap = new HashMap<Character, Character>(40);
HashMap<Character, Character> alphaMap = new HashMap<>(40);
alphaMap.put('A', '2');
alphaMap.put('B', '2');
alphaMap.put('C', '2');
@ -181,19 +182,19 @@ public class PhoneNumberUtil {
alphaMap.put('Z', '9');
ALPHA_MAPPINGS = Collections.unmodifiableMap(alphaMap);
HashMap<Character, Character> combinedMap = new HashMap<Character, Character>(100);
HashMap<Character, Character> combinedMap = new HashMap<>(100);
combinedMap.putAll(ALPHA_MAPPINGS);
combinedMap.putAll(asciiDigitMappings);
ALPHA_PHONE_MAPPINGS = Collections.unmodifiableMap(combinedMap);
HashMap<Character, Character> diallableCharMap = new HashMap<Character, Character>();
HashMap<Character, Character> diallableCharMap = new HashMap<>();
diallableCharMap.putAll(asciiDigitMappings);
diallableCharMap.put(PLUS_SIGN, PLUS_SIGN);
diallableCharMap.put('*', '*');
diallableCharMap.put('#', '#');
DIALLABLE_CHAR_MAPPINGS = Collections.unmodifiableMap(diallableCharMap);
HashMap<Character, Character> allPlusNumberGroupings = new HashMap<Character, Character>();
HashMap<Character, Character> allPlusNumberGroupings = new HashMap<>();
// Put (lower letter -> upper letter) and (upper letter -> upper letter) mappings.
for (char c : ALPHA_MAPPINGS.keySet()) {
allPlusNumberGroupings.put(Character.toLowerCase(c), c);
@ -308,8 +309,8 @@ public class PhoneNumberUtil {
// version.
private static final String EXTN_PATTERNS_FOR_PARSING = createExtnPattern(true);
static final String EXTN_PATTERNS_FOR_MATCHING = createExtnPattern(false);
/**
/**
* Helper method for constructing regular expressions for parsing. Creates an expression that
* captures up to maxLength digits.
*/
@ -659,7 +660,7 @@ public class PhoneNumberUtil {
// The set of regions that share country calling code 1.
// There are roughly 26 regions.
// We set the initial capacity of the HashSet to 35 to offer a load factor of roughly 0.75.
private final Set<String> nanpaRegions = new HashSet<String>(35);
private final Set<String> nanpaRegions = new HashSet<>(35);
// A cache for frequently used region-specific regular expressions.
// The initial capacity is set to 100 as this seems to be an optimal value for Android, based on
@ -669,11 +670,11 @@ public class PhoneNumberUtil {
// The set of regions the library supports.
// There are roughly 240 of them and we set the initial capacity of the HashSet to 320 to offer a
// load factor of roughly 0.75.
private final Set<String> supportedRegions = new HashSet<String>(320);
private final Set<String> supportedRegions = new HashSet<>(320);
// The set of country calling codes that map to the non-geo entity region ("001"). This set
// currently contains < 12 elements so the default capacity of 16 (load factor=0.75) is fine.
private final Set<Integer> countryCodesForNonGeographicalRegion = new HashSet<Integer>();
private final Set<Integer> countryCodesForNonGeographicalRegion = new HashSet<>();
/**
* This class implements a singleton, the constructor is only visible to facilitate testing.
@ -1089,7 +1090,7 @@ public class PhoneNumberUtil {
* be non-null.
*/
private Set<PhoneNumberType> getSupportedTypesForMetadata(PhoneMetadata metadata) {
Set<PhoneNumberType> types = new TreeSet<PhoneNumberType>();
Set<PhoneNumberType> types = new TreeSet<>();
for (PhoneNumberType type : PhoneNumberType.values()) {
if (type == PhoneNumberType.FIXED_LINE_OR_MOBILE || type == PhoneNumberType.UNKNOWN) {
// Never return FIXED_LINE_OR_MOBILE (it is a convenience type, and represents that a
@ -1149,7 +1150,9 @@ public class PhoneNumberUtil {
*/
public static synchronized PhoneNumberUtil getInstance() {
if (instance == null) {
setInstance(createInstance(MetadataManager.DEFAULT_METADATA_LOADER));
MetadataLoader metadataLoader = DefaultMetadataDependenciesProvider.getInstance()
.getMetadataLoader();
setInstance(createInstance(metadataLoader));
}
return instance;
}
@ -1170,7 +1173,11 @@ public class PhoneNumberUtil {
if (metadataLoader == null) {
throw new IllegalArgumentException("metadataLoader could not be null.");
}
return createInstance(new MultiFileMetadataSourceImpl(metadataLoader));
return createInstance(new MetadataSourceImpl(
DefaultMetadataDependenciesProvider.getInstance().getPhoneNumberMetadataFileNameProvider(),
metadataLoader,
DefaultMetadataDependenciesProvider.getInstance().getMetadataParser()
));
}
/**
@ -1699,7 +1706,7 @@ public class PhoneNumberUtil {
NumberFormat.Builder numFormatCopy = NumberFormat.newBuilder();
numFormatCopy.mergeFrom(formatRule);
numFormatCopy.clearNationalPrefixFormattingRule();
List<NumberFormat> numberFormats = new ArrayList<NumberFormat>(1);
List<NumberFormat> numberFormats = new ArrayList<>(1);
numberFormats.add(numFormatCopy.build());
formattedNumber = formatByPattern(number, PhoneNumberFormat.NATIONAL, numberFormats);
break;
@ -2275,21 +2282,42 @@ public class PhoneNumberUtil {
}
/**
* Returns the metadata for the given region code or {@code null} if the region code is invalid
* or unknown.
* Returns the metadata for the given region code or {@code null} if the region code is invalid or
* unknown.
*
* @throws MissingMetadataException if the region code is valid, but metadata cannot be found.
*/
PhoneMetadata getMetadataForRegion(String regionCode) {
if (!isValidRegionCode(regionCode)) {
return null;
}
return metadataSource.getMetadataForRegion(regionCode);
PhoneMetadata phoneMetadata = metadataSource.getMetadataForRegion(regionCode);
ensureMetadataIsNonNull(phoneMetadata, "Missing metadata for region code " + regionCode);
return phoneMetadata;
}
/**
* Returns the metadata for the given country calling code or {@code null} if the country calling
* code is invalid or unknown.
*
* @throws MissingMetadataException if the country calling code is valid, but metadata cannot be
* found.
*/
PhoneMetadata getMetadataForNonGeographicalRegion(int countryCallingCode) {
if (!countryCallingCodeToRegionCodeMap.containsKey(countryCallingCode)) {
if (!countryCodesForNonGeographicalRegion.contains(countryCallingCode)) {
return null;
}
return metadataSource.getMetadataForNonGeographicalRegion(countryCallingCode);
PhoneMetadata phoneMetadata = metadataSource.getMetadataForNonGeographicalRegion(
countryCallingCode);
ensureMetadataIsNonNull(phoneMetadata,
"Missing metadata for country code " + countryCallingCode);
return phoneMetadata;
}
private static void ensureMetadataIsNonNull(PhoneMetadata phoneMetadata, String message) {
if (phoneMetadata == null) {
throw new MissingMetadataException(message);
}
}
boolean isNumberMatchingDesc(String nationalNumber, PhoneNumberDesc numberDesc) {
@ -2585,7 +2613,7 @@ public class PhoneNumberUtil {
PhoneNumberDesc mobileDesc = getNumberDescByType(metadata, PhoneNumberType.MOBILE);
if (descHasPossibleNumberData(mobileDesc)) {
// Merge the mobile data in if there was any. We have to make a copy to do this.
possibleLengths = new ArrayList<Integer>(possibleLengths);
possibleLengths = new ArrayList<>(possibleLengths);
// Note that when adding the possible lengths from mobile, we have to again check they
// aren't empty since if they are this indicates they are the same as the general desc and
// should be obtained from there.
@ -2599,7 +2627,7 @@ public class PhoneNumberUtil {
if (localLengths.isEmpty()) {
localLengths = mobileDesc.getPossibleLengthLocalOnlyList();
} else {
localLengths = new ArrayList<Integer>(localLengths);
localLengths = new ArrayList<>(localLengths);
localLengths.addAll(mobileDesc.getPossibleLengthLocalOnlyList());
Collections.sort(localLengths);
}


+ 38
- 28
java/libphonenumber/src/com/google/i18n/phonenumbers/ShortNumberInfo.java View File

@ -22,6 +22,8 @@ import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.google.i18n.phonenumbers.metadata.DefaultMetadataDependenciesProvider;
import com.google.i18n.phonenumbers.metadata.source.RegionMetadataSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -44,12 +46,13 @@ public class ShortNumberInfo {
private static final Logger logger = Logger.getLogger(ShortNumberInfo.class.getName());
private static final ShortNumberInfo INSTANCE =
new ShortNumberInfo(RegexBasedMatcher.create());
new ShortNumberInfo(
RegexBasedMatcher.create(),
DefaultMetadataDependenciesProvider.getInstance().getShortNumberMetadataSource());
// In these countries, if extra digits are added to an emergency number, it no longer connects
// to the emergency service.
private static final Set<String> REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT =
new HashSet<String>();
private static final Set<String> REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT = new HashSet<>();
static {
REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT.add("BR");
REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT.add("CL");
@ -61,7 +64,7 @@ public class ShortNumberInfo {
TOLL_FREE,
STANDARD_RATE,
PREMIUM_RATE,
UNKNOWN_COST;
UNKNOWN_COST
}
/** Returns the singleton instance of the ShortNumberInfo. */
@ -79,9 +82,13 @@ public class ShortNumberInfo {
// first.
private final Map<Integer, List<String>> countryCallingCodeToRegionCodeMap;
private final RegionMetadataSource shortNumberMetadataSource;
// @VisibleForTesting
ShortNumberInfo(MatcherApi matcherApi) {
ShortNumberInfo(MatcherApi matcherApi,
RegionMetadataSource shortNumberMetadataSource) {
this.matcherApi = matcherApi;
this.shortNumberMetadataSource = shortNumberMetadataSource;
// TODO: Create ShortNumberInfo for a given map
this.countryCallingCodeToRegionCodeMap =
CountryCodeToRegionCodeMap.getCountryCodeToRegionCodeMap();
@ -108,6 +115,21 @@ public class ShortNumberInfo {
return regionCodes.contains(regionDialingFrom);
}
/**
* A thin wrapper around {@code shortNumberMetadataSource} which catches {@link
* IllegalArgumentException} for invalid region code and instead returns {@code null}
*/
private PhoneMetadata getShortNumberMetadataForRegion(String regionCode) {
if (regionCode == null) {
return null;
}
try {
return shortNumberMetadataSource.getMetadataForRegion(regionCode);
} catch (IllegalArgumentException e) {
return null;
}
}
/**
* Check whether a short number is a possible number when dialed from the given region. This
* provides a more lenient check than {@link #isValidShortNumberForRegion}.
@ -120,8 +142,7 @@ public class ShortNumberInfo {
if (!regionDialingFromMatchesNumber(number, regionDialingFrom)) {
return false;
}
PhoneMetadata phoneMetadata =
MetadataManager.getShortNumberMetadataForRegion(regionDialingFrom);
PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionDialingFrom);
if (phoneMetadata == null) {
return false;
}
@ -142,7 +163,7 @@ public class ShortNumberInfo {
List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode());
int shortNumberLength = getNationalSignificantNumber(number).length();
for (String region : regionCodes) {
PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(region);
PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(region);
if (phoneMetadata == null) {
continue;
}
@ -166,8 +187,7 @@ public class ShortNumberInfo {
if (!regionDialingFromMatchesNumber(number, regionDialingFrom)) {
return false;
}
PhoneMetadata phoneMetadata =
MetadataManager.getShortNumberMetadataForRegion(regionDialingFrom);
PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionDialingFrom);
if (phoneMetadata == null) {
return false;
}
@ -228,8 +248,7 @@ public class ShortNumberInfo {
return ShortNumberCost.UNKNOWN_COST;
}
// Note that regionDialingFrom may be null, in which case phoneMetadata will also be null.
PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(
regionDialingFrom);
PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionDialingFrom);
if (phoneMetadata == null) {
return ShortNumberCost.UNKNOWN_COST;
}
@ -326,7 +345,7 @@ public class ShortNumberInfo {
}
String nationalNumber = getNationalSignificantNumber(number);
for (String regionCode : regionCodes) {
PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode);
PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionCode);
if (phoneMetadata != null
&& matchesPossibleNumberAndNationalNumber(nationalNumber, phoneMetadata.getShortCode())) {
// The number is valid for this region.
@ -336,13 +355,6 @@ public class ShortNumberInfo {
return null;
}
/**
* Convenience method to get a list of what regions the library has metadata for.
*/
Set<String> getSupportedRegions() {
return MetadataManager.getSupportedShortNumberRegions();
}
/**
* Gets a valid short number for the specified region.
*
@ -352,7 +364,7 @@ public class ShortNumberInfo {
*/
// @VisibleForTesting
String getExampleShortNumber(String regionCode) {
PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode);
PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionCode);
if (phoneMetadata == null) {
return "";
}
@ -373,7 +385,7 @@ public class ShortNumberInfo {
*/
// @VisibleForTesting
String getExampleShortNumberForCost(String regionCode, ShortNumberCost cost) {
PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode);
PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionCode);
if (phoneMetadata == null) {
return "";
}
@ -441,7 +453,7 @@ public class ShortNumberInfo {
// add additional logic here to handle it.
return false;
}
PhoneMetadata metadata = MetadataManager.getShortNumberMetadataForRegion(regionCode);
PhoneMetadata metadata = getShortNumberMetadataForRegion(regionCode);
if (metadata == null || !metadata.hasEmergency()) {
return false;
}
@ -468,7 +480,7 @@ public class ShortNumberInfo {
List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode());
String regionCode = getRegionCodeForShortNumberFromRegionList(number, regionCodes);
String nationalNumber = getNationalSignificantNumber(number);
PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode);
PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionCode);
return (phoneMetadata != null)
&& (matchesPossibleNumberAndNationalNumber(nationalNumber,
phoneMetadata.getCarrierSpecific()));
@ -492,8 +504,7 @@ public class ShortNumberInfo {
return false;
}
String nationalNumber = getNationalSignificantNumber(number);
PhoneMetadata phoneMetadata =
MetadataManager.getShortNumberMetadataForRegion(regionDialingFrom);
PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionDialingFrom);
return (phoneMetadata != null)
&& (matchesPossibleNumberAndNationalNumber(nationalNumber,
phoneMetadata.getCarrierSpecific()));
@ -516,8 +527,7 @@ public class ShortNumberInfo {
if (!regionDialingFromMatchesNumber(number, regionDialingFrom)) {
return false;
}
PhoneMetadata phoneMetadata =
MetadataManager.getShortNumberMetadataForRegion(regionDialingFrom);
PhoneMetadata phoneMetadata = getShortNumberMetadataForRegion(regionDialingFrom);
return phoneMetadata != null
&& matchesPossibleNumberAndNationalNumber(getNationalSignificantNumber(number),
phoneMetadata.getSmsServices());


+ 0
- 65
java/libphonenumber/src/com/google/i18n/phonenumbers/SingleFileMetadataSourceImpl.java View File

@ -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);
}
}

+ 56
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/internal/GeoEntityUtility.java View File

@ -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() {}
}

+ 115
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/DefaultMetadataDependenciesProvider.java View File

@ -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/";
}
}

+ 42
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/ClassPathResourceMetadataLoader.java View 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.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;
}
}

+ 108
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/MetadataParser.java View File

@ -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);
}
}
}

+ 78
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuard.java View File

@ -0,0 +1,78 @@
/*
* Copyright (C) 2022 The Libphonenumber Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.i18n.phonenumbers.metadata.source;
import 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);
}
}
}

+ 69
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainer.java View File

@ -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);
}
}
}

+ 37
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSource.java View File

@ -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);
}

+ 57
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSourceImpl.java View File

@ -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);
}
}

+ 74
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainer.java View File

@ -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);
}
}

+ 35
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataBootstrappingGuard.java View File

@ -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);
}

java/libphonenumber/src/com/google/i18n/phonenumbers/MetadataSource.java → java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataContainer.java View File


+ 21
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSource.java View File

@ -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 {
}

+ 72
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSourceImpl.java View File

@ -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);
}
}

+ 42
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProvider.java View 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.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;
}
}

+ 47
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/NonGeographicalEntityMetadataSource.java View File

@ -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);
}

+ 36
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/PhoneMetadataFileNameProvider.java View File

@ -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);
}

+ 40
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSource.java View File

@ -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);
}

+ 62
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSourceImpl.java View File

@ -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);
}
}

+ 35
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProvider.java View File

@ -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;
}
}

+ 38
- 37
java/libphonenumber/test/com/google/i18n/phonenumbers/ExampleNumbersTest.java View File

@ -19,15 +19,15 @@ package com.google.i18n.phonenumbers;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import junit.framework.TestCase;
import com.google.i18n.phonenumbers.metadata.DefaultMetadataDependenciesProvider;
import com.google.i18n.phonenumbers.metadata.source.RegionMetadataSource;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import junit.framework.TestCase;
/**
* Verifies all of the example numbers in the metadata are valid and of the correct type. If no
@ -37,10 +37,14 @@ import java.util.logging.Logger;
*/
public class ExampleNumbersTest extends TestCase {
private static final Logger logger = Logger.getLogger(ExampleNumbersTest.class.getName());
private PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
private ShortNumberInfo shortNumberInfo = ShortNumberInfo.getInstance();
private List<PhoneNumber> invalidCases = new ArrayList<PhoneNumber>();
private List<PhoneNumber> wrongTypeCases = new ArrayList<PhoneNumber>();
private final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
private final ShortNumberInfo shortNumberInfo = ShortNumberInfo.getInstance();
private final RegionMetadataSource shortNumberMetadataSource =
DefaultMetadataDependenciesProvider.getInstance().getShortNumberMetadataSource();
private final List<PhoneNumber> invalidCases = new ArrayList<>();
private final List<PhoneNumber> wrongTypeCases = new ArrayList<>();
private final Set<String> shortNumberSupportedRegions = ShortNumbersRegionCodeSet.getRegionCodeSet();
/**
* @param exampleNumberRequestedType type we are requesting an example number for
@ -55,14 +59,14 @@ public class ExampleNumbersTest extends TestCase {
if (exampleNumber != null) {
if (!phoneNumberUtil.isValidNumber(exampleNumber)) {
invalidCases.add(exampleNumber);
logger.log(Level.SEVERE, "Failed validation for " + exampleNumber.toString());
logger.log(Level.SEVERE, "Failed validation for " + exampleNumber);
} else {
// We know the number is valid, now we check the type.
PhoneNumberType exampleNumberType = phoneNumberUtil.getNumberType(exampleNumber);
if (!possibleExpectedTypes.contains(exampleNumberType)) {
wrongTypeCases.add(exampleNumber);
logger.log(Level.SEVERE, "Wrong type for "
+ exampleNumber.toString()
+ exampleNumber
+ ": got " + exampleNumberType);
logger.log(Level.WARNING, "Expected types: ");
for (PhoneNumberType type : possibleExpectedTypes) {
@ -74,7 +78,7 @@ public class ExampleNumbersTest extends TestCase {
}
}
public void testFixedLine() throws Exception {
public void testFixedLine() {
Set<PhoneNumberType> fixedLineTypes = EnumSet.of(PhoneNumberType.FIXED_LINE,
PhoneNumberType.FIXED_LINE_OR_MOBILE);
checkNumbersValidAndCorrectType(PhoneNumberType.FIXED_LINE, fixedLineTypes);
@ -82,7 +86,7 @@ public class ExampleNumbersTest extends TestCase {
assertEquals(0, wrongTypeCases.size());
}
public void testMobile() throws Exception {
public void testMobile() {
Set<PhoneNumberType> mobileTypes = EnumSet.of(PhoneNumberType.MOBILE,
PhoneNumberType.FIXED_LINE_OR_MOBILE);
checkNumbersValidAndCorrectType(PhoneNumberType.MOBILE, mobileTypes);
@ -90,56 +94,56 @@ public class ExampleNumbersTest extends TestCase {
assertEquals(0, wrongTypeCases.size());
}
public void testTollFree() throws Exception {
public void testTollFree() {
Set<PhoneNumberType> tollFreeTypes = EnumSet.of(PhoneNumberType.TOLL_FREE);
checkNumbersValidAndCorrectType(PhoneNumberType.TOLL_FREE, tollFreeTypes);
assertEquals(0, invalidCases.size());
assertEquals(0, wrongTypeCases.size());
}
public void testPremiumRate() throws Exception {
public void testPremiumRate() {
Set<PhoneNumberType> premiumRateTypes = EnumSet.of(PhoneNumberType.PREMIUM_RATE);
checkNumbersValidAndCorrectType(PhoneNumberType.PREMIUM_RATE, premiumRateTypes);
assertEquals(0, invalidCases.size());
assertEquals(0, wrongTypeCases.size());
}
public void testVoip() throws Exception {
public void testVoip() {
Set<PhoneNumberType> voipTypes = EnumSet.of(PhoneNumberType.VOIP);
checkNumbersValidAndCorrectType(PhoneNumberType.VOIP, voipTypes);
assertEquals(0, invalidCases.size());
assertEquals(0, wrongTypeCases.size());
}
public void testPager() throws Exception {
public void testPager() {
Set<PhoneNumberType> pagerTypes = EnumSet.of(PhoneNumberType.PAGER);
checkNumbersValidAndCorrectType(PhoneNumberType.PAGER, pagerTypes);
assertEquals(0, invalidCases.size());
assertEquals(0, wrongTypeCases.size());
}
public void testUan() throws Exception {
public void testUan() {
Set<PhoneNumberType> uanTypes = EnumSet.of(PhoneNumberType.UAN);
checkNumbersValidAndCorrectType(PhoneNumberType.UAN, uanTypes);
assertEquals(0, invalidCases.size());
assertEquals(0, wrongTypeCases.size());
}
public void testVoicemail() throws Exception {
public void testVoicemail() {
Set<PhoneNumberType> voicemailTypes = EnumSet.of(PhoneNumberType.VOICEMAIL);
checkNumbersValidAndCorrectType(PhoneNumberType.VOICEMAIL, voicemailTypes);
assertEquals(0, invalidCases.size());
assertEquals(0, wrongTypeCases.size());
}
public void testSharedCost() throws Exception {
public void testSharedCost() {
Set<PhoneNumberType> sharedCostTypes = EnumSet.of(PhoneNumberType.SHARED_COST);
checkNumbersValidAndCorrectType(PhoneNumberType.SHARED_COST, sharedCostTypes);
assertEquals(0, invalidCases.size());
assertEquals(0, wrongTypeCases.size());
}
public void testCanBeInternationallyDialled() throws Exception {
public void testCanBeInternationallyDialled() {
for (String regionCode : phoneNumberUtil.getSupportedRegions()) {
PhoneNumber exampleNumber = null;
PhoneNumberDesc desc =
@ -153,41 +157,41 @@ public class ExampleNumbersTest extends TestCase {
}
if (exampleNumber != null && phoneNumberUtil.canBeInternationallyDialled(exampleNumber)) {
wrongTypeCases.add(exampleNumber);
logger.log(Level.SEVERE, "Number " + exampleNumber.toString()
logger.log(Level.SEVERE, "Number " + exampleNumber
+ " should not be internationally diallable");
}
}
assertEquals(0, wrongTypeCases.size());
}
public void testGlobalNetworkNumbers() throws Exception {
public void testGlobalNetworkNumbers() {
for (Integer callingCode : phoneNumberUtil.getSupportedGlobalNetworkCallingCodes()) {
PhoneNumber exampleNumber =
phoneNumberUtil.getExampleNumberForNonGeoEntity(callingCode);
assertNotNull("No example phone number for calling code " + callingCode, exampleNumber);
if (!phoneNumberUtil.isValidNumber(exampleNumber)) {
invalidCases.add(exampleNumber);
logger.log(Level.SEVERE, "Failed validation for " + exampleNumber.toString());
logger.log(Level.SEVERE, "Failed validation for " + exampleNumber);
}
}
assertEquals(0, invalidCases.size());
}
public void testEveryRegionHasAnExampleNumber() throws Exception {
public void testEveryRegionHasAnExampleNumber() {
for (String regionCode : phoneNumberUtil.getSupportedRegions()) {
PhoneNumber exampleNumber = phoneNumberUtil.getExampleNumber(regionCode);
assertNotNull("No example number found for region " + regionCode, exampleNumber);
}
}
public void testEveryRegionHasAnInvalidExampleNumber() throws Exception {
public void testEveryRegionHasAnInvalidExampleNumber() {
for (String regionCode : phoneNumberUtil.getSupportedRegions()) {
PhoneNumber exampleNumber = phoneNumberUtil.getInvalidExampleNumber(regionCode);
assertNotNull("No invalid example number found for region " + regionCode, exampleNumber);
}
}
public void testEveryTypeHasAnExampleNumber() throws Exception {
public void testEveryTypeHasAnExampleNumber() {
for (PhoneNumberUtil.PhoneNumberType type : PhoneNumberUtil.PhoneNumberType.values()) {
if (type == PhoneNumberType.UNKNOWN) {
continue;
@ -198,8 +202,8 @@ public class ExampleNumbersTest extends TestCase {
}
public void testShortNumbersValidAndCorrectCost() throws Exception {
List<String> invalidStringCases = new ArrayList<String>();
for (String regionCode : shortNumberInfo.getSupportedRegions()) {
List<String> invalidStringCases = new ArrayList<>();
for (String regionCode : shortNumberSupportedRegions) {
String exampleShortNumber = shortNumberInfo.getExampleShortNumber(regionCode);
if (!shortNumberInfo.isValidShortNumberForRegion(
phoneNumberUtil.parse(exampleShortNumber, regionCode), regionCode)) {
@ -211,7 +215,7 @@ public class ExampleNumbersTest extends TestCase {
PhoneNumber phoneNumber = phoneNumberUtil.parse(exampleShortNumber, regionCode);
if (!shortNumberInfo.isValidShortNumber(phoneNumber)) {
invalidCases.add(phoneNumber);
logger.log(Level.SEVERE, "Failed validation for " + phoneNumber.toString());
logger.log(Level.SEVERE, "Failed validation for " + phoneNumber);
}
for (ShortNumberInfo.ShortNumberCost cost : ShortNumberInfo.ShortNumberCost.values()) {
@ -236,9 +240,8 @@ public class ExampleNumbersTest extends TestCase {
public void testEmergency() throws Exception {
int wrongTypeCounter = 0;
for (String regionCode : shortNumberInfo.getSupportedRegions()) {
PhoneNumberDesc desc =
MetadataManager.getShortNumberMetadataForRegion(regionCode).getEmergency();
for (String regionCode : shortNumberSupportedRegions) {
PhoneNumberDesc desc = shortNumberMetadataSource.getMetadataForRegion(regionCode).getEmergency();
if (desc.hasExampleNumber()) {
String exampleNumber = desc.getExampleNumber();
PhoneNumber phoneNumber = phoneNumberUtil.parse(exampleNumber, regionCode);
@ -258,9 +261,8 @@ public class ExampleNumbersTest extends TestCase {
public void testCarrierSpecificShortNumbers() throws Exception {
int wrongTagCounter = 0;
for (String regionCode : shortNumberInfo.getSupportedRegions()) {
PhoneNumberDesc desc =
MetadataManager.getShortNumberMetadataForRegion(regionCode).getCarrierSpecific();
for (String regionCode : shortNumberSupportedRegions) {
PhoneNumberDesc desc = shortNumberMetadataSource.getMetadataForRegion(regionCode).getCarrierSpecific();
if (desc.hasExampleNumber()) {
String exampleNumber = desc.getExampleNumber();
PhoneNumber carrierSpecificNumber = phoneNumberUtil.parse(exampleNumber, regionCode);
@ -276,9 +278,8 @@ public class ExampleNumbersTest extends TestCase {
public void testSmsServiceShortNumbers() throws Exception {
int wrongTagCounter = 0;
for (String regionCode : shortNumberInfo.getSupportedRegions()) {
PhoneNumberDesc desc =
MetadataManager.getShortNumberMetadataForRegion(regionCode).getSmsServices();
for (String regionCode : shortNumberSupportedRegions) {
PhoneNumberDesc desc = shortNumberMetadataSource.getMetadataForRegion(regionCode).getSmsServices();
if (desc.hasExampleNumber()) {
String exampleNumber = desc.getExampleNumber();
PhoneNumber smsServiceNumber = phoneNumberUtil.parse(exampleNumber, regionCode);


+ 0
- 88
java/libphonenumber/test/com/google/i18n/phonenumbers/MetadataManagerTest.java View File

@ -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"));
}
}
}

+ 0
- 62
java/libphonenumber/test/com/google/i18n/phonenumbers/MultiFileMetadataSourceImplTest.java View File

@ -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"));
}
}
}

+ 45
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java View File

@ -16,6 +16,8 @@
package com.google.i18n.phonenumbers;
import static org.junit.Assert.assertThrows;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
import com.google.i18n.phonenumbers.PhoneNumberUtil.ValidationResult;
@ -25,9 +27,13 @@ import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource;
import com.google.i18n.phonenumbers.metadata.source.MetadataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.junit.Assert;
import org.junit.function.ThrowingRunnable;
import org.mockito.Mockito;
/**
* Unit tests for PhoneNumberUtil.java
@ -119,6 +125,11 @@ public class PhoneNumberUtilTest extends TestMetadataTestCase {
private static final PhoneNumber UNKNOWN_COUNTRY_CODE_NO_RAW_INPUT =
new PhoneNumber().setCountryCode(2).setNationalNumber(12345L);
private final MetadataSource mockedMetadataSource = Mockito.mock(MetadataSource.class);
private final PhoneNumberUtil phoneNumberUtilWithMissingMetadata =
new PhoneNumberUtil(mockedMetadataSource,
CountryCodeToRegionCodeMapForTesting.getCountryCodeToRegionCodeMap());
public void testGetSupportedRegions() {
assertTrue(phoneUtil.getSupportedRegions().size() > 0);
}
@ -3160,4 +3171,38 @@ public class PhoneNumberUtilTest extends TestMetadataTestCase {
assertFalse(phoneUtil.isMobileNumberPortableRegion(RegionCode.AE));
assertFalse(phoneUtil.isMobileNumberPortableRegion(RegionCode.BS));
}
public void testGetMetadataForRegionForNonGeoEntity_shouldBeNull() {
assertNull(phoneUtil.getMetadataForRegion(RegionCode.UN001));
}
public void testGetMetadataForRegionForUnknownRegion_shouldBeNull() {
assertNull(phoneUtil.getMetadataForRegion(RegionCode.ZZ));
}
public void testGetMetadataForNonGeographicalRegionForGeoRegion_shouldBeNull() {
assertNull(phoneUtil.getMetadataForNonGeographicalRegion(/* countryCallingCode = */ 1));
}
public void testGetMetadataForRegionForMissingMetadata() {
assertThrows(
MissingMetadataException.class,
new ThrowingRunnable() {
@Override
public void run() {
phoneNumberUtilWithMissingMetadata.getMetadataForRegion(RegionCode.US);
}
});
}
public void testGetMetadataForNonGeographicalRegionForMissingMetadata() {
assertThrows(
MissingMetadataException.class,
new ThrowingRunnable() {
@Override
public void run() {
phoneNumberUtilWithMissingMetadata.getMetadataForNonGeographicalRegion(800);
}
});
}
}

+ 0
- 48
java/libphonenumber/test/com/google/i18n/phonenumbers/SingleFileMetadataSourceImplTest.java View 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"));
}
}
}

+ 11
- 3
java/libphonenumber/test/com/google/i18n/phonenumbers/TestMetadataTestCase.java View File

@ -16,6 +16,9 @@
package com.google.i18n.phonenumbers;
import com.google.i18n.phonenumbers.metadata.DefaultMetadataDependenciesProvider;
import com.google.i18n.phonenumbers.metadata.source.MetadataSourceImpl;
import com.google.i18n.phonenumbers.metadata.source.MultiFileModeFileNameProvider;
import junit.framework.TestCase;
/**
@ -33,15 +36,20 @@ import junit.framework.TestCase;
* @author Shaopeng Jia
*/
public class TestMetadataTestCase extends TestCase {
private static final String TEST_METADATA_FILE_PREFIX =
"/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting";
/** An instance of PhoneNumberUtil that uses test metadata. */
/**
* An instance of PhoneNumberUtil that uses test metadata.
*/
protected final PhoneNumberUtil phoneUtil;
public TestMetadataTestCase() {
phoneUtil = new PhoneNumberUtil(new MultiFileMetadataSourceImpl(TEST_METADATA_FILE_PREFIX,
MetadataManager.DEFAULT_METADATA_LOADER),
phoneUtil = new PhoneNumberUtil(
new MetadataSourceImpl(new MultiFileModeFileNameProvider(TEST_METADATA_FILE_PREFIX),
DefaultMetadataDependenciesProvider.getInstance().getMetadataLoader(),
DefaultMetadataDependenciesProvider.getInstance().getMetadataParser()),
CountryCodeToRegionCodeMapForTesting.getCountryCodeToRegionCodeMap());
}


+ 42
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/internal/GeoEntityUtilityTest.java View 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));
}
}

+ 21
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/PhoneMetadataCollectionUtil.java View File

@ -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;
}
}

+ 88
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/init/MetadataParserTest.java View File

@ -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());
}
}

+ 102
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuardTest.java View File

@ -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);
}
}
}

+ 66
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainerTest.java View 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));
}
}

+ 62
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainerTest.java View File

@ -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));
}
}

+ 45
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProviderTest.java View File

@ -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");
}
});
}
}

+ 31
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProviderTest.java View File

@ -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);
}
}

+ 6
- 0
java/pom.xml View File

@ -235,6 +235,12 @@
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

+ 3
- 3
tools/java/java-build/src/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java View File

@ -254,7 +254,7 @@ public class BuildMetadataProtoFromXml extends Command {
writer.addToImports("java.util.List");
writer.addToImports("java.util.Map");
writer.addToBody(" static Map<Integer, List<String>> getCountryCodeToRegionCodeMap() {\n");
writer.addToBody(" public static Map<Integer, List<String>> getCountryCodeToRegionCodeMap() {\n");
writer.formatToBody(CAPACITY_COMMENT, capacity, countryCodeToRegionCodeMap.size());
writer.addToBody(" Map<Integer, List<String>> countryCodeToRegionCodeMap =\n");
writer.addToBody(" new HashMap<Integer, List<String>>(" + capacity + ");\n");
@ -286,7 +286,7 @@ public class BuildMetadataProtoFromXml extends Command {
writer.addToImports("java.util.HashSet");
writer.addToImports("java.util.Set");
writer.addToBody(" static Set<String> getRegionCodeSet() {\n");
writer.addToBody(" public static Set<String> getRegionCodeSet() {\n");
writer.formatToBody(CAPACITY_COMMENT, capacity, regionCodeList.size());
writer.addToBody(" Set<String> regionCodeSet = new HashSet<String>(" + capacity + ");\n");
writer.addToBody("\n");
@ -307,7 +307,7 @@ public class BuildMetadataProtoFromXml extends Command {
writer.addToImports("java.util.HashSet");
writer.addToImports("java.util.Set");
writer.addToBody(" static Set<Integer> getCountryCodeSet() {\n");
writer.addToBody(" public static Set<Integer> getCountryCodeSet() {\n");
writer.formatToBody(CAPACITY_COMMENT, capacity, countryCodeSet.size());
writer.addToBody(" Set<Integer> countryCodeSet = new HashSet<Integer>(" + capacity + ");\n");
writer.addToBody("\n");


+ 15
- 9
tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/GeneratePhonePrefixDataEntryPoint.java View File

@ -25,11 +25,13 @@ import java.util.logging.Logger;
/**
* Entry point class used to invoke the generation of the binary phone prefix data files.
*
* @author Philippe Liard
*/
public class GeneratePhonePrefixDataEntryPoint extends Command {
private static final Logger logger = Logger.getLogger(GeneratePhonePrefixData.class.getName());
private static final String USAGE_DESCRIPTION =
"usage: GeneratePhonePrefixData /path/to/input/directory /path/to/output/directory"
+ " [outputJarName]";
@Override
public String getCommandName() {
@ -40,16 +42,20 @@ public class GeneratePhonePrefixDataEntryPoint extends Command {
public boolean start() {
String[] args = getArgs();
if (args.length != 3) {
logger.log(Level.SEVERE,
"usage: GeneratePhonePrefixData /path/to/input/directory "
+ "/path/to/output/directory");
if (args.length < 3 || args.length > 4) {
logger.log(Level.SEVERE, USAGE_DESCRIPTION);
return false;
}
try {
GeneratePhonePrefixData generatePhonePrefixData =
new GeneratePhonePrefixData(new File(args[1]), new PhonePrefixDataIOHandler(new File(args[2])));
generatePhonePrefixData.run();
File inputPath = new File(args[1]);
File outputPath = new File(args[2]);
AbstractPhonePrefixDataIOHandler ioHandler =
args.length == 3
? new PhonePrefixDataIOHandler(outputPath)
: new JarPhonePrefixDataIOHandler(
outputPath, args[3], GeneratePhonePrefixData.class.getPackage());
GeneratePhonePrefixData dataGenerator = new GeneratePhonePrefixData(inputPath, ioHandler);
dataGenerator.run();
} catch (IOException e) {
logger.log(Level.SEVERE, e.getMessage());
return false;


+ 13
- 6
tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapDataEntryPoint.java View File

@ -31,6 +31,9 @@ import java.util.logging.Logger;
*/
public class GenerateTimeZonesMapDataEntryPoint extends Command {
private static final Logger logger = Logger.getLogger(GenerateTimeZonesMapData.class.getName());
private static final String USAGE_DESCRIPTION =
"usage: GenerateTimeZonesMapData /path/to/input/directory /path/to/output/directory"
+ " [outputJarName]";
@Override
public String getCommandName() {
@ -41,15 +44,19 @@ public class GenerateTimeZonesMapDataEntryPoint extends Command {
public boolean start() {
String[] args = getArgs();
if (args.length != 3) {
logger.log(Level.SEVERE,
"usage: GenerateTimeZonesMapData /path/to/input/text_file "
+ "/path/to/output/directory");
if (args.length < 3 || args.length > 4) {
logger.log(Level.SEVERE, USAGE_DESCRIPTION);
return false;
}
try {
GenerateTimeZonesMapData generateTimeZonesMapData = new GenerateTimeZonesMapData(
new File(args[1]), new PhonePrefixDataIOHandler(new File(args[2])));
File inputPath = new File(args[1]);
File outputPath = new File(args[2]);
AbstractPhonePrefixDataIOHandler ioHandler =
args.length == 3
? new PhonePrefixDataIOHandler(outputPath)
: new JarPhonePrefixDataIOHandler(
outputPath, args[3], GeneratePhonePrefixData.class.getPackage());
GenerateTimeZonesMapData generateTimeZonesMapData = new GenerateTimeZonesMapData(inputPath, ioHandler);
generateTimeZonesMapData.run();
} catch (IOException e) {
logger.log(Level.SEVERE, e.getMessage());


+ 107
- 0
tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/JarPhonePrefixDataIOHandler.java View File

@ -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);
}
}

+ 13
- 7
tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/PhonePrefixDataIOHandler.java View File

@ -20,10 +20,11 @@ import java.io.File;
import java.io.IOException;
/**
* Implementation of the IOHandler required by the GeneratePhonePrefixData class used here to create
* the output files.
* Implementation of the AbstractPhonePrefixDataIOHandler required by the GeneratePhonePrefixData
* class used here to create the output files.
*/
class PhonePrefixDataIOHandler extends AbstractPhonePrefixDataIOHandler {
// The path to the output directory.
private final File outputPath;
@ -40,11 +41,14 @@ class PhonePrefixDataIOHandler extends AbstractPhonePrefixDataIOHandler {
this.outputPath = outputPath;
}
/**
* This is a <b>no-op</b>.
*
* <p>This would be the place dealing with the addition of the provided file to the resulting JAR
* if the global output was a JAR instead of a directory containing the binary files.
*/
@Override
public void addFileToOutput(File file) throws IOException {
// Do nothing. This would be the place dealing with the addition of the provided file to the
// resulting JAR if the global output was a JAR instead of a directory containing the binary
// files.
public void addFileToOutput(File file) {
}
@Override
@ -52,8 +56,10 @@ class PhonePrefixDataIOHandler extends AbstractPhonePrefixDataIOHandler {
return new File(outputPath, path);
}
/**
* This is a <b>no-op</b>, as no resource needs to be released.
*/
@Override
public void close() {
// Do nothing as no resource needs to be released.
}
}

+ 78
- 0
tools/java/java-build/test/com/google/i18n/phonenumbers/buildtools/JarPhonePrefixDataIOHandlerTest.java View File

@ -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();
}
}
}
}

Loading…
Cancel
Save