| /* |
| * 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 com.android.i18n.phonenumbers.PhoneNumberUtil; |
| import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.Externalizable; |
| import java.io.IOException; |
| import java.io.ObjectInput; |
| import java.io.ObjectOutput; |
| import java.io.ObjectOutputStream; |
| import java.util.SortedMap; |
| import java.util.SortedSet; |
| import java.util.logging.Logger; |
| |
| /** |
| * A utility that maps phone number prefixes to a string describing the geographical area the prefix |
| * covers. |
| * |
| * @author Shaopeng Jia |
| */ |
| public class AreaCodeMap implements Externalizable { |
| private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); |
| private static final Logger LOGGER = Logger.getLogger(AreaCodeMap.class.getName()); |
| |
| private AreaCodeMapStorageStrategy areaCodeMapStorage; |
| |
| // @VisibleForTesting |
| AreaCodeMapStorageStrategy getAreaCodeMapStorage() { |
| return areaCodeMapStorage; |
| } |
| |
| /** |
| * Creates an empty {@link AreaCodeMap}. The default constructor is necessary for implementing |
| * {@link Externalizable}. The empty map could later be populated by |
| * {@link #readAreaCodeMap(java.util.SortedMap)} or {@link #readExternal(java.io.ObjectInput)}. |
| */ |
| public AreaCodeMap() {} |
| |
| /** |
| * Gets the size of the provided area code map storage. The map storage passed-in will be filled |
| * as a result. |
| */ |
| private static int getSizeOfAreaCodeMapStorage(AreaCodeMapStorageStrategy mapStorage, |
| SortedMap<Integer, String> areaCodeMap) throws IOException { |
| mapStorage.readFromSortedMap(areaCodeMap); |
| ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); |
| ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); |
| mapStorage.writeExternal(objectOutputStream); |
| objectOutputStream.flush(); |
| int sizeOfStorage = byteArrayOutputStream.size(); |
| objectOutputStream.close(); |
| return sizeOfStorage; |
| } |
| |
| private AreaCodeMapStorageStrategy createDefaultMapStorage() { |
| return new DefaultMapStorage(); |
| } |
| |
| private AreaCodeMapStorageStrategy createFlyweightMapStorage() { |
| return new FlyweightMapStorage(); |
| } |
| |
| /** |
| * Gets the smaller area code map storage strategy according to the provided area code map. It |
| * actually uses (outputs the data to a stream) both strategies and retains the best one which |
| * make this method quite expensive. |
| */ |
| // @VisibleForTesting |
| AreaCodeMapStorageStrategy getSmallerMapStorage(SortedMap<Integer, String> areaCodeMap) { |
| try { |
| AreaCodeMapStorageStrategy flyweightMapStorage = createFlyweightMapStorage(); |
| int sizeOfFlyweightMapStorage = getSizeOfAreaCodeMapStorage(flyweightMapStorage, areaCodeMap); |
| |
| AreaCodeMapStorageStrategy defaultMapStorage = createDefaultMapStorage(); |
| int sizeOfDefaultMapStorage = getSizeOfAreaCodeMapStorage(defaultMapStorage, areaCodeMap); |
| |
| return sizeOfFlyweightMapStorage < sizeOfDefaultMapStorage |
| ? flyweightMapStorage : defaultMapStorage; |
| } catch (IOException e) { |
| LOGGER.severe(e.getMessage()); |
| return createFlyweightMapStorage(); |
| } |
| } |
| |
| /** |
| * Creates an {@link AreaCodeMap} initialized with {@code sortedAreaCodeMap}. Note that the |
| * underlying implementation of this method is expensive thus should not be called by |
| * time-critical applications. |
| * |
| * @param sortedAreaCodeMap a map from phone number prefixes to descriptions of corresponding |
| * geographical areas, sorted in ascending order of the phone number prefixes as integers. |
| */ |
| public void readAreaCodeMap(SortedMap<Integer, String> sortedAreaCodeMap) { |
| areaCodeMapStorage = getSmallerMapStorage(sortedAreaCodeMap); |
| } |
| |
| /** |
| * Supports Java Serialization. |
| */ |
| public void readExternal(ObjectInput objectInput) throws IOException { |
| // Read the area code map storage strategy flag. |
| boolean useFlyweightMapStorage = objectInput.readBoolean(); |
| if (useFlyweightMapStorage) { |
| areaCodeMapStorage = new FlyweightMapStorage(); |
| } else { |
| areaCodeMapStorage = new DefaultMapStorage(); |
| } |
| areaCodeMapStorage.readExternal(objectInput); |
| } |
| |
| /** |
| * Supports Java Serialization. |
| */ |
| public void writeExternal(ObjectOutput objectOutput) throws IOException { |
| objectOutput.writeBoolean(areaCodeMapStorage.isFlyweight()); |
| areaCodeMapStorage.writeExternal(objectOutput); |
| } |
| |
| /** |
| * Returns the description of the geographical area the {@code number} corresponds to. |
| * |
| * @param number the phone number to look up |
| * @return the description of the geographical area |
| */ |
| String lookup(PhoneNumber number) { |
| int numOfEntries = areaCodeMapStorage.getNumOfEntries(); |
| if (numOfEntries == 0) { |
| return ""; |
| } |
| long phonePrefix = |
| Long.parseLong(number.getCountryCode() + phoneUtil.getNationalSignificantNumber(number)); |
| int currentIndex = numOfEntries - 1; |
| SortedSet<Integer> currentSetOfLengths = areaCodeMapStorage.getPossibleLengths(); |
| while (currentSetOfLengths.size() > 0) { |
| Integer possibleLength = currentSetOfLengths.last(); |
| String phonePrefixStr = String.valueOf(phonePrefix); |
| if (phonePrefixStr.length() > possibleLength) { |
| phonePrefix = Long.parseLong(phonePrefixStr.substring(0, possibleLength)); |
| } |
| currentIndex = binarySearch(0, currentIndex, phonePrefix); |
| if (currentIndex < 0) { |
| return ""; |
| } |
| int currentPrefix = areaCodeMapStorage.getPrefix(currentIndex); |
| if (phonePrefix == currentPrefix) { |
| return areaCodeMapStorage.getDescription(currentIndex); |
| } |
| currentSetOfLengths = currentSetOfLengths.headSet(possibleLength); |
| } |
| return ""; |
| } |
| |
| /** |
| * Does a binary search for {@code value} in the provided array from {@code start} to {@code end} |
| * (inclusive). Returns the position if {@code value} is found; otherwise, returns the |
| * position which has the largest value that is less than {@code value}. This means if |
| * {@code value} is the smallest, -1 will be returned. |
| */ |
| private int binarySearch(int start, int end, long value) { |
| int current = 0; |
| while (start <= end) { |
| current = (start + end) >>> 1; |
| int currentValue = areaCodeMapStorage.getPrefix(current); |
| if (currentValue == value) { |
| return current; |
| } else if (currentValue > value) { |
| current--; |
| end = current; |
| } else { |
| start = current + 1; |
| } |
| } |
| return current; |
| } |
| |
| /** |
| * Dumps the mappings contained in the area code map. |
| */ |
| @Override |
| public String toString() { |
| return areaCodeMapStorage.toString(); |
| } |
| } |