| /* |
| * Copyright (C) 2011 Google Inc. |
| * |
| * 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.android.i18n.phonenumbers.geocoding; |
| |
| import java.io.Externalizable; |
| import java.io.IOException; |
| import java.io.ObjectInput; |
| import java.io.ObjectOutput; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.SortedMap; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| |
| /** |
| * A utility which knows the data files that are available for the geocoder to use. The data files |
| * contain mappings from phone number prefixes to text descriptions, and are organized by country |
| * calling code and language that the text descriptions are in. |
| * |
| * @author Shaopeng Jia |
| */ |
| public class MappingFileProvider implements Externalizable { |
| private int numOfEntries = 0; |
| private int[] countryCallingCodes; |
| private List<Set<String>> availableLanguages; |
| private static final Map<String, String> LOCALE_NORMALIZATION_MAP; |
| |
| static { |
| Map<String, String> normalizationMap = new HashMap<String, String>(); |
| normalizationMap.put("zh_TW", "zh_Hant"); |
| normalizationMap.put("zh_HK", "zh_Hant"); |
| normalizationMap.put("zh_MO", "zh_Hant"); |
| |
| LOCALE_NORMALIZATION_MAP = Collections.unmodifiableMap(normalizationMap); |
| } |
| |
| /** |
| * Creates an empty {@link MappingFileProvider}. The default constructor is necessary for |
| * implementing {@link Externalizable}. The empty provider could later be populated by |
| * {@link #readFileConfigs(java.util.SortedMap)} or {@link #readExternal(java.io.ObjectInput)}. |
| */ |
| public MappingFileProvider() { |
| } |
| |
| /** |
| * Initializes an {@link MappingFileProvider} with {@code availableDataFiles}. |
| * |
| * @param availableDataFiles a map from country calling codes to sets of languages in which data |
| * files are available for the specific country calling code. The map is sorted in ascending |
| * order of the country calling codes as integers. |
| */ |
| public void readFileConfigs(SortedMap<Integer, Set<String>> availableDataFiles) { |
| numOfEntries = availableDataFiles.size(); |
| countryCallingCodes = new int[numOfEntries]; |
| availableLanguages = new ArrayList<Set<String>>(numOfEntries); |
| int index = 0; |
| for (int countryCallingCode : availableDataFiles.keySet()) { |
| countryCallingCodes[index++] = countryCallingCode; |
| availableLanguages.add(new HashSet<String>(availableDataFiles.get(countryCallingCode))); |
| } |
| } |
| |
| /** |
| * Supports Java Serialization. |
| */ |
| public void readExternal(ObjectInput objectInput) throws IOException { |
| numOfEntries = objectInput.readInt(); |
| if (countryCallingCodes == null || countryCallingCodes.length < numOfEntries) { |
| countryCallingCodes = new int[numOfEntries]; |
| } |
| if (availableLanguages == null) { |
| availableLanguages = new ArrayList<Set<String>>(); |
| } |
| for (int i = 0; i < numOfEntries; i++) { |
| countryCallingCodes[i] = objectInput.readInt(); |
| int numOfLangs = objectInput.readInt(); |
| Set<String> setOfLangs = new HashSet<String>(); |
| for (int j = 0; j < numOfLangs; j++) { |
| setOfLangs.add(objectInput.readUTF()); |
| } |
| availableLanguages.add(setOfLangs); |
| } |
| } |
| |
| /** |
| * Supports Java Serialization. |
| */ |
| public void writeExternal(ObjectOutput objectOutput) throws IOException { |
| objectOutput.writeInt(numOfEntries); |
| for (int i = 0; i < numOfEntries; i++) { |
| objectOutput.writeInt(countryCallingCodes[i]); |
| Set<String> setOfLangs = availableLanguages.get(i); |
| int numOfLangs = setOfLangs.size(); |
| objectOutput.writeInt(numOfLangs); |
| for (String lang : setOfLangs) { |
| objectOutput.writeUTF(lang); |
| } |
| } |
| } |
| |
| /** |
| * Returns a string representing the data in this class. The string contains one line for each |
| * country calling code. The country calling code is followed by a '|' and then a list of |
| * comma-separated languages sorted in ascending order. |
| */ |
| @Override |
| public String toString() { |
| StringBuilder output = new StringBuilder(); |
| for (int i = 0; i < numOfEntries; i++) { |
| output.append(countryCallingCodes[i]); |
| output.append('|'); |
| SortedSet<String> sortedSetOfLangs = new TreeSet<String>(availableLanguages.get(i)); |
| for (String lang : sortedSetOfLangs) { |
| output.append(lang); |
| output.append(','); |
| } |
| output.append('\n'); |
| } |
| return output.toString(); |
| } |
| |
| /** |
| * Gets the name of the file that contains the mapping data for the {@code countryCallingCode} in |
| * the language specified. |
| * |
| * @param countryCallingCode the country calling code of phone numbers which the data file |
| * contains |
| * @param language two-letter lowercase ISO language codes as defined by ISO 639-1 |
| * @param script four-letter titlecase (the first letter is uppercase and the rest of the letters |
| * are lowercase) ISO script codes as defined in ISO 15924 |
| * @param region two-letter uppercase ISO country codes as defined by ISO 3166-1 |
| * @return the name of the file, or empty string if no such file can be found |
| */ |
| String getFileName(int countryCallingCode, String language, String script, String region) { |
| if (language.length() == 0) { |
| return ""; |
| } |
| int index = Arrays.binarySearch(countryCallingCodes, countryCallingCode); |
| if (index < 0) { |
| return ""; |
| } |
| Set<String> setOfLangs = availableLanguages.get(index); |
| if (setOfLangs.size() > 0) { |
| String languageCode = findBestMatchingLanguageCode(setOfLangs, language, script, region); |
| if (languageCode.length() > 0) { |
| StringBuilder fileName = new StringBuilder(); |
| fileName.append(countryCallingCode).append('_').append(languageCode); |
| return fileName.toString(); |
| } |
| } |
| return ""; |
| } |
| |
| private String findBestMatchingLanguageCode( |
| Set<String> setOfLangs, String language, String script, String region) { |
| StringBuilder fullLocale = constructFullLocale(language, script, region); |
| String fullLocaleStr = fullLocale.toString(); |
| String normalizedLocale = LOCALE_NORMALIZATION_MAP.get(fullLocaleStr); |
| if (normalizedLocale != null) { |
| if (setOfLangs.contains(normalizedLocale)) { |
| return normalizedLocale; |
| } |
| } |
| if (setOfLangs.contains(fullLocaleStr)) { |
| return fullLocaleStr; |
| } |
| |
| if (onlyOneOfScriptOrRegionIsEmpty(script, region)) { |
| if (setOfLangs.contains(language)) { |
| return language; |
| } |
| } else if (script.length() > 0 && region.length() > 0) { |
| StringBuilder langWithScript = new StringBuilder(language).append('_').append(script); |
| String langWithScriptStr = langWithScript.toString(); |
| if (setOfLangs.contains(langWithScriptStr)) { |
| return langWithScriptStr; |
| } |
| |
| StringBuilder langWithRegion = new StringBuilder(language).append('_').append(region); |
| String langWithRegionStr = langWithRegion.toString(); |
| if (setOfLangs.contains(langWithRegionStr)) { |
| return langWithRegionStr; |
| } |
| |
| if (setOfLangs.contains(language)) { |
| return language; |
| } |
| } |
| return ""; |
| } |
| |
| private boolean onlyOneOfScriptOrRegionIsEmpty(String script, String region) { |
| return (script.length() == 0 && region.length() > 0) || |
| (region.length() == 0 && script.length() > 0); |
| } |
| |
| private StringBuilder constructFullLocale(String language, String script, String region) { |
| StringBuilder fullLocale = new StringBuilder(language); |
| appendSubsequentLocalePart(script, fullLocale); |
| appendSubsequentLocalePart(region, fullLocale); |
| return fullLocale; |
| } |
| |
| private void appendSubsequentLocalePart(String subsequentLocalePart, StringBuilder fullLocale) { |
| if (subsequentLocalePart.length() > 0) { |
| fullLocale.append('_').append(subsequentLocalePart); |
| } |
| } |
| } |