1 /* 2 * Copyright (C) 2011 The Libphonenumber Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.i18n.phonenumbers.prefixmapper; 18 19 import java.io.Externalizable; 20 import java.io.IOException; 21 import java.io.ObjectInput; 22 import java.io.ObjectOutput; 23 import java.util.ArrayList; 24 import java.util.Arrays; 25 import java.util.Collections; 26 import java.util.HashMap; 27 import java.util.HashSet; 28 import java.util.List; 29 import java.util.Map; 30 import java.util.Set; 31 import java.util.SortedMap; 32 import java.util.SortedSet; 33 import java.util.TreeSet; 34 35 /** 36 * A utility which knows the data files that are available for the phone prefix mappers to use. 37 * The data files contain mappings from phone number prefixes to text descriptions, and are 38 * organized by country calling code and language that the text descriptions are in. 39 * 40 * @author Shaopeng Jia 41 */ 42 public class MappingFileProvider implements Externalizable { 43 private int numOfEntries = 0; 44 private int[] countryCallingCodes; 45 private List<Set<String>> availableLanguages; 46 private static final Map<String, String> LOCALE_NORMALIZATION_MAP; 47 48 static { 49 Map<String, String> normalizationMap = new HashMap<String, String>(); 50 normalizationMap.put("zh_TW", "zh_Hant"); 51 normalizationMap.put("zh_HK", "zh_Hant"); 52 normalizationMap.put("zh_MO", "zh_Hant"); 53 54 LOCALE_NORMALIZATION_MAP = Collections.unmodifiableMap(normalizationMap); 55 } 56 57 /** 58 * Creates an empty {@link MappingFileProvider}. The default constructor is necessary for 59 * implementing {@link Externalizable}. The empty provider could later be populated by 60 * {@link #readFileConfigs(java.util.SortedMap)} or {@link #readExternal(java.io.ObjectInput)}. 61 */ 62 public MappingFileProvider() { 63 } 64 65 /** 66 * Initializes an {@link MappingFileProvider} with {@code availableDataFiles}. 67 * 68 * @param availableDataFiles a map from country calling codes to sets of languages in which data 69 * files are available for the specific country calling code. The map is sorted in ascending 70 * order of the country calling codes as integers. 71 */ 72 public void readFileConfigs(SortedMap<Integer, Set<String>> availableDataFiles) { 73 numOfEntries = availableDataFiles.size(); 74 countryCallingCodes = new int[numOfEntries]; 75 availableLanguages = new ArrayList<Set<String>>(numOfEntries); 76 int index = 0; 77 for (int countryCallingCode : availableDataFiles.keySet()) { 78 countryCallingCodes[index++] = countryCallingCode; 79 availableLanguages.add(new HashSet<String>(availableDataFiles.get(countryCallingCode))); 80 } 81 } 82 83 /** 84 * Supports Java Serialization. 85 */ 86 public void readExternal(ObjectInput objectInput) throws IOException { 87 numOfEntries = objectInput.readInt(); 88 if (countryCallingCodes == null || countryCallingCodes.length < numOfEntries) { 89 countryCallingCodes = new int[numOfEntries]; 90 } 91 if (availableLanguages == null) { 92 availableLanguages = new ArrayList<Set<String>>(); 93 } 94 for (int i = 0; i < numOfEntries; i++) { 95 countryCallingCodes[i] = objectInput.readInt(); 96 int numOfLangs = objectInput.readInt(); 97 Set<String> setOfLangs = new HashSet<String>(); 98 for (int j = 0; j < numOfLangs; j++) { 99 setOfLangs.add(objectInput.readUTF()); 100 } 101 availableLanguages.add(setOfLangs); 102 } 103 } 104 105 /** 106 * Supports Java Serialization. 107 */ 108 public void writeExternal(ObjectOutput objectOutput) throws IOException { 109 objectOutput.writeInt(numOfEntries); 110 for (int i = 0; i < numOfEntries; i++) { 111 objectOutput.writeInt(countryCallingCodes[i]); 112 Set<String> setOfLangs = availableLanguages.get(i); 113 int numOfLangs = setOfLangs.size(); 114 objectOutput.writeInt(numOfLangs); 115 for (String lang : setOfLangs) { 116 objectOutput.writeUTF(lang); 117 } 118 } 119 } 120 121 /** 122 * Returns a string representing the data in this class. The string contains one line for each 123 * country calling code. The country calling code is followed by a '|' and then a list of 124 * comma-separated languages sorted in ascending order. 125 */ 126 @Override 127 public String toString() { 128 StringBuilder output = new StringBuilder(); 129 for (int i = 0; i < numOfEntries; i++) { 130 output.append(countryCallingCodes[i]); 131 output.append('|'); 132 SortedSet<String> sortedSetOfLangs = new TreeSet<String>(availableLanguages.get(i)); 133 for (String lang : sortedSetOfLangs) { 134 output.append(lang); 135 output.append(','); 136 } 137 output.append('\n'); 138 } 139 return output.toString(); 140 } 141 142 /** 143 * Gets the name of the file that contains the mapping data for the {@code countryCallingCode} in 144 * the language specified. 145 * 146 * @param countryCallingCode the country calling code of phone numbers which the data file 147 * contains 148 * @param language two-letter lowercase ISO language codes as defined by ISO 639-1 149 * @param script four-letter titlecase (the first letter is uppercase and the rest of the letters 150 * are lowercase) ISO script codes as defined in ISO 15924 151 * @param region two-letter uppercase ISO country codes as defined by ISO 3166-1 152 * @return the name of the file, or empty string if no such file can be found 153 */ 154 String getFileName(int countryCallingCode, String language, String script, String region) { 155 if (language.length() == 0) { 156 return ""; 157 } 158 int index = Arrays.binarySearch(countryCallingCodes, countryCallingCode); 159 if (index < 0) { 160 return ""; 161 } 162 Set<String> setOfLangs = availableLanguages.get(index); 163 if (setOfLangs.size() > 0) { 164 String languageCode = findBestMatchingLanguageCode(setOfLangs, language, script, region); 165 if (languageCode.length() > 0) { 166 StringBuilder fileName = new StringBuilder(); 167 fileName.append(countryCallingCode).append('_').append(languageCode); 168 return fileName.toString(); 169 } 170 } 171 return ""; 172 } 173 174 private String findBestMatchingLanguageCode( 175 Set<String> setOfLangs, String language, String script, String region) { 176 StringBuilder fullLocale = constructFullLocale(language, script, region); 177 String fullLocaleStr = fullLocale.toString(); 178 String normalizedLocale = LOCALE_NORMALIZATION_MAP.get(fullLocaleStr); 179 if (normalizedLocale != null) { 180 if (setOfLangs.contains(normalizedLocale)) { 181 return normalizedLocale; 182 } 183 } 184 if (setOfLangs.contains(fullLocaleStr)) { 185 return fullLocaleStr; 186 } 187 188 if (onlyOneOfScriptOrRegionIsEmpty(script, region)) { 189 if (setOfLangs.contains(language)) { 190 return language; 191 } 192 } else if (script.length() > 0 && region.length() > 0) { 193 StringBuilder langWithScript = new StringBuilder(language).append('_').append(script); 194 String langWithScriptStr = langWithScript.toString(); 195 if (setOfLangs.contains(langWithScriptStr)) { 196 return langWithScriptStr; 197 } 198 199 StringBuilder langWithRegion = new StringBuilder(language).append('_').append(region); 200 String langWithRegionStr = langWithRegion.toString(); 201 if (setOfLangs.contains(langWithRegionStr)) { 202 return langWithRegionStr; 203 } 204 205 if (setOfLangs.contains(language)) { 206 return language; 207 } 208 } 209 return ""; 210 } 211 212 private boolean onlyOneOfScriptOrRegionIsEmpty(String script, String region) { 213 return (script.length() == 0 && region.length() > 0) || 214 (region.length() == 0 && script.length() > 0); 215 } 216 217 private StringBuilder constructFullLocale(String language, String script, String region) { 218 StringBuilder fullLocale = new StringBuilder(language); 219 appendSubsequentLocalePart(script, fullLocale); 220 appendSubsequentLocalePart(region, fullLocale); 221 return fullLocale; 222 } 223 224 private void appendSubsequentLocalePart(String subsequentLocalePart, StringBuilder fullLocale) { 225 if (subsequentLocalePart.length() > 0) { 226 fullLocale.append('_').append(subsequentLocalePart); 227 } 228 } 229 } 230