summaryrefslogtreecommitdiff
path: root/libphonenumber/src/com/google/i18n/phonenumbers/MetadataManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'libphonenumber/src/com/google/i18n/phonenumbers/MetadataManager.java')
-rw-r--r--libphonenumber/src/com/google/i18n/phonenumbers/MetadataManager.java230
1 files changed, 154 insertions, 76 deletions
diff --git a/libphonenumber/src/com/google/i18n/phonenumbers/MetadataManager.java b/libphonenumber/src/com/google/i18n/phonenumbers/MetadataManager.java
index df72a608..5c072d6a 100644
--- a/libphonenumber/src/com/google/i18n/phonenumbers/MetadataManager.java
+++ b/libphonenumber/src/com/google/i18n/phonenumbers/MetadataManager.java
@@ -18,56 +18,191 @@ 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.Iterator;
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;
/**
- * Class encapsulating loading of PhoneNumber Metadata information. Currently this is used only for
- * additional data files such as PhoneNumberAlternateFormats, but in the future it is envisaged it
- * would handle the main metadata file (PhoneNumberMetadata.xml) as well.
+ * 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());
- private static final Map<Integer, PhoneMetadata> callingCodeToAlternateFormatsMap =
- Collections.synchronizedMap(new HashMap<Integer, PhoneMetadata>());
- private static final Map<String, PhoneMetadata> regionCodeToShortNumberMetadataMap =
- Collections.synchronizedMap(new HashMap<String, PhoneMetadata>());
+ // 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 set of which country calling codes there are alternate format data for. If the set has an
- // entry for a code, then there should be data for that code linked into the resources.
- private static final Set<Integer> countryCodeSet =
+ // 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();
- // A set of which region codes there are short number data for. If the set has an entry for a
- // code, then there should be data for that code linked into the resources.
- private static final Set<String> regionCodeSet = ShortNumbersRegionCodeSet.getRegionCodeSet();
+ // 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 MetadataManager() {
+ 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 object from the given stream and closes the stream.
+ * 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 object
+ * @return the loaded metadata
*/
- static PhoneMetadataCollection loadMetadataAndCloseInput(InputStream source) {
+ private static PhoneMetadataCollection loadMetadataAndCloseInput(InputStream source) {
ObjectInputStream ois = null;
try {
try {
@@ -95,61 +230,4 @@ final class MetadataManager {
}
}
}
-
- private static void loadAlternateFormatsMetadataFromFile(int countryCallingCode) {
- String fileName = ALTERNATE_FORMATS_FILE_PREFIX + "_" + countryCallingCode;
- InputStream source = MetadataManager.class.getResourceAsStream(fileName);
- if (source == null) {
- // Sanity check; this should not happen since we only load things based on the expectation
- // that they are present, by checking the map of available data first.
- throw new IllegalStateException("missing metadata: " + fileName);
- }
- PhoneMetadataCollection alternateFormats = loadMetadataAndCloseInput(source);
- for (PhoneMetadata metadata : alternateFormats.getMetadataList()) {
- callingCodeToAlternateFormatsMap.put(metadata.getCountryCode(), metadata);
- }
- }
-
- static PhoneMetadata getAlternateFormatsForCountry(int countryCallingCode) {
- if (!countryCodeSet.contains(countryCallingCode)) {
- return null;
- }
- synchronized (callingCodeToAlternateFormatsMap) {
- if (!callingCodeToAlternateFormatsMap.containsKey(countryCallingCode)) {
- loadAlternateFormatsMetadataFromFile(countryCallingCode);
- }
- }
- return callingCodeToAlternateFormatsMap.get(countryCallingCode);
- }
-
- private static void loadShortNumberMetadataFromFile(String regionCode) {
- String fileName = SHORT_NUMBER_METADATA_FILE_PREFIX + "_" + regionCode;
- InputStream source = MetadataManager.class.getResourceAsStream(fileName);
- if (source == null) {
- // Sanity check; this should not happen since we only load things based on the expectation
- // that they are present, by checking the map of available data first.
- throw new IllegalStateException("missing metadata: " + fileName);
- }
- PhoneMetadataCollection shortNumberMetadata = loadMetadataAndCloseInput(source);
- for (PhoneMetadata metadata : shortNumberMetadata.getMetadataList()) {
- regionCodeToShortNumberMetadataMap.put(regionCode, metadata);
- }
- }
-
- // @VisibleForTesting
- static Set<String> getShortNumberMetadataSupportedRegions() {
- return regionCodeSet;
- }
-
- static PhoneMetadata getShortNumberMetadataForRegion(String regionCode) {
- if (!regionCodeSet.contains(regionCode)) {
- return null;
- }
- synchronized (regionCodeToShortNumberMetadataMap) {
- if (!regionCodeToShortNumberMetadataMap.containsKey(regionCode)) {
- loadShortNumberMetadataFromFile(regionCode);
- }
- }
- return regionCodeToShortNumberMetadataMap.get(regionCode);
- }
}