/* * 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.google.i18n.phonenumbers; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeSet; /** * 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 int numOfEntries = 0; private TreeSet possibleLengths = new TreeSet(); private int[] phoneNumberPrefixes; private String[] descriptions; private PhoneNumberUtil phoneUtil; /** * Creates an empty {@link AreaCodeMap}. The default constructor is necessary for implementing * {@link Externalizable}. The empty map could later populated by * {@link #readAreaCodeMap(java.util.SortedMap)} or {@link #readExternal(java.io.ObjectInput)}. */ public AreaCodeMap() { phoneUtil = PhoneNumberUtil.getInstance(); } // @VisibleForTesting AreaCodeMap(PhoneNumberUtil phoneUtil) { this.phoneUtil = phoneUtil; } /** * Creates an {@link AreaCodeMap} initialized with {@code sortedAreaCodeMap}. * * @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 sortedAreaCodeMap) { numOfEntries = sortedAreaCodeMap.size(); phoneNumberPrefixes = new int[numOfEntries]; descriptions = new String[numOfEntries]; int index = 0; for (int prefix : sortedAreaCodeMap.keySet()) { phoneNumberPrefixes[index++] = prefix; possibleLengths.add((int) Math.log10(prefix) + 1); } sortedAreaCodeMap.values().toArray(descriptions); } /** * Supports Java Serialization. */ public void readExternal(ObjectInput objectInput) throws IOException { numOfEntries = objectInput.readInt(); if (phoneNumberPrefixes == null || phoneNumberPrefixes.length < numOfEntries) { phoneNumberPrefixes = new int[numOfEntries]; } if (descriptions == null || descriptions.length < numOfEntries) { descriptions = new String[numOfEntries]; } for (int i = 0; i < numOfEntries; i++) { phoneNumberPrefixes[i] = objectInput.readInt(); descriptions[i] = objectInput.readUTF(); } int sizeOfLengths = objectInput.readInt(); possibleLengths.clear(); for (int i = 0; i < sizeOfLengths; i++) { possibleLengths.add(objectInput.readInt()); } } /** * Supports Java Serialization. */ public void writeExternal(ObjectOutput objectOutput) throws IOException { objectOutput.writeInt(numOfEntries); for (int i = 0; i < numOfEntries; i++) { objectOutput.writeInt(phoneNumberPrefixes[i]); objectOutput.writeUTF(descriptions[i]); } int sizeOfLengths = possibleLengths.size(); objectOutput.writeInt(sizeOfLengths); for (Integer length : possibleLengths) { objectOutput.writeInt(length); } } /** * 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) { if (numOfEntries == 0) { return ""; } long phonePrefix = Long.parseLong(number.getCountryCode() + phoneUtil.getNationalSignificantNumber(number)); int currentIndex = numOfEntries - 1; SortedSet currentSetOfLengths = possibleLengths; 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 ""; } if (phonePrefix == phoneNumberPrefixes[currentIndex]) { return descriptions[currentIndex]; } currentSetOfLengths = possibleLengths.headSet(possibleLength); } return ""; } /** * Does a binary search for {@code value} in the phoneNumberPrefixes 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) / 2; if (phoneNumberPrefixes[current] == value) { return current; } else if (phoneNumberPrefixes[current] > value) { current--; end = current; } else { start = current + 1; } } return current; } /** * Dumps the mappings contained in the area code map. */ @Override public String toString() { StringBuilder output = new StringBuilder(); for (int i = 0; i < numOfEntries; i++) { output.append(phoneNumberPrefixes[i]); output.append("|"); output.append(descriptions[i]); output.append("\n"); } return output.toString(); } }