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