diff options
Diffstat (limited to 'libphonenumber/src/com/google/i18n/phonenumbers/MetadataManager.java')
-rw-r--r-- | libphonenumber/src/com/google/i18n/phonenumbers/MetadataManager.java | 230 |
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); - } } |