Browse Source

JAVA: Check in source code for offline geocoder v1.2

pull/567/head
Shaopeng Jia 15 years ago
committed by Mihaela Rosca
parent
commit
bf8b0360bf
8 changed files with 759 additions and 98 deletions
  1. +4
    -4
      java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java
  2. +100
    -53
      java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMap.java
  3. +164
    -0
      java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMapStorageStrategy.java
  4. +103
    -0
      java/src/com/google/i18n/phonenumbers/geocoding/DefaultMapStorage.java
  5. +261
    -0
      java/src/com/google/i18n/phonenumbers/geocoding/FlyweightMapStorage.java
  6. +6
    -3
      java/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java
  7. +112
    -38
      java/test/com/google/i18n/phonenumbers/geocoding/AreaCodeMapTest.java
  8. +9
    -0
      java/test/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoderTest.java

+ 4
- 4
java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java View File

@ -1596,11 +1596,11 @@ public class PhoneNumberUtil {
}
/**
* Checks whether countryCode represents the country calling code from a region whose national
* significant number could contain a leading zero. An example of such a region is Italy. Returns
* false if no metadata for the country is found.
* Checks whether the country calling code is from a region whose national significant number
* could contain a leading zero. An example of such a region is Italy. Returns false if no
* metadata for the country is found.
*/
boolean isLeadingZeroPossible(int countryCallingCode) {
public boolean isLeadingZeroPossible(int countryCallingCode) {
PhoneMetadata mainMetadataForCallingCode = getMetadataForRegion(
getRegionCodeForCountryCode(countryCallingCode));
if (mainMetadataForCallingCode == null) {


+ 100
- 53
java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMap.java View File

@ -19,13 +19,15 @@ package com.google.i18n.phonenumbers.geocoding;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.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.TreeSet;
import java.util.logging.Logger;
/**
* A utility that maps phone number prefixes to a string describing the geographical area the prefix
@ -34,73 +36,109 @@ import java.util.TreeSet;
* @author Shaopeng Jia
*/
public class AreaCodeMap implements Externalizable {
private int numOfEntries = 0;
private TreeSet<Integer> possibleLengths = new TreeSet<Integer>();
private int[] phoneNumberPrefixes;
private String[] descriptions;
private final int countryCallingCode;
private final boolean isLeadingZeroPossible;
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 populated by
* {@link #readAreaCodeMap(java.util.SortedMap)} or {@link #readExternal(java.io.ObjectInput)}.
*
* @param countryCallingCode the country calling code for the region that the area code map
* belongs to.
*/
public AreaCodeMap(int countryCallingCode) {
this.countryCallingCode = countryCallingCode;
isLeadingZeroPossible = phoneUtil.isLeadingZeroPossible(countryCallingCode);
}
/**
* 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(countryCallingCode, isLeadingZeroPossible);
}
private AreaCodeMapStorageStrategy createFlyweightMapStorage() {
return new FlyweightMapStorage(countryCallingCode, isLeadingZeroPossible);
}
/**
* 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.
*/
public AreaCodeMap() {}
// @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}.
* 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) {
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);
areaCodeMapStorage = getSmallerMapStorage(sortedAreaCodeMap);
}
/**
* 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());
// Read the area code map storage strategy flag.
boolean useFlyweightMapStorage = objectInput.readBoolean();
if (useFlyweightMapStorage) {
areaCodeMapStorage = new FlyweightMapStorage(countryCallingCode, isLeadingZeroPossible);
} else {
areaCodeMapStorage = new DefaultMapStorage(countryCallingCode, isLeadingZeroPossible);
}
areaCodeMapStorage.readExternal(objectInput);
}
/**
* 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);
}
objectOutput.writeBoolean(areaCodeMapStorage.isFlyweight());
areaCodeMapStorage.writeExternal(objectOutput);
}
/**
@ -110,13 +148,15 @@ public class AreaCodeMap implements Externalizable {
* @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));
long phonePrefix = isLeadingZeroPossible
? Long.parseLong(number.getCountryCode() + phoneUtil.getNationalSignificantNumber(number))
: Long.parseLong(phoneUtil.getNationalSignificantNumber(number));
int currentIndex = numOfEntries - 1;
SortedSet<Integer> currentSetOfLengths = possibleLengths;
SortedSet<Integer> currentSetOfLengths = areaCodeMapStorage.getPossibleLengths();
while (currentSetOfLengths.size() > 0) {
Integer possibleLength = currentSetOfLengths.last();
String phonePrefixStr = String.valueOf(phonePrefix);
@ -127,17 +167,18 @@ public class AreaCodeMap implements Externalizable {
if (currentIndex < 0) {
return "";
}
if (phonePrefix == phoneNumberPrefixes[currentIndex]) {
return descriptions[currentIndex];
int currentPrefix = areaCodeMapStorage.getPrefix(currentIndex);
if (phonePrefix == currentPrefix) {
return areaCodeMapStorage.getDescription(currentIndex);
}
currentSetOfLengths = possibleLengths.headSet(possibleLength);
currentSetOfLengths = currentSetOfLengths.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
* 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.
*/
@ -145,9 +186,10 @@ public class AreaCodeMap implements Externalizable {
int current = 0;
while (start <= end) {
current = (start + end) / 2;
if (phoneNumberPrefixes[current] == value) {
int currentValue = areaCodeMapStorage.getPrefix(current);
if (currentValue == value) {
return current;
} else if (phoneNumberPrefixes[current] > value) {
} else if (currentValue > value) {
current--;
end = current;
} else {
@ -163,10 +205,15 @@ public class AreaCodeMap implements Externalizable {
@Override
public String toString() {
StringBuilder output = new StringBuilder();
int numOfEntries = areaCodeMapStorage.getNumOfEntries();
for (int i = 0; i < numOfEntries; i++) {
output.append(phoneNumberPrefixes[i]);
if (!isLeadingZeroPossible) {
output.append(countryCallingCode);
}
output.append(areaCodeMapStorage.getPrefix(i));
output.append("|");
output.append(descriptions[i]);
output.append(areaCodeMapStorage.getDescription(i));
output.append("\n");
}
return output.toString();


+ 164
- 0
java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMapStorageStrategy.java View File

@ -0,0 +1,164 @@
/*
* 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.geocoding;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.SortedMap;
import java.util.TreeSet;
/**
* Abstracts the way area code data is stored into memory and serialized to a stream.
*
* @author Philippe Liard
*/
// @VisibleForTesting
abstract class AreaCodeMapStorageStrategy {
protected final int countryCallingCode;
protected final boolean isLeadingZeroPossible;
protected int numOfEntries = 0;
protected final TreeSet<Integer> possibleLengths = new TreeSet<Integer>();
/**
* Constructs a new area code map storage strategy from the provided country calling code and
* boolean parameter.
*
* @param countryCallingCode the country calling code of the number prefixes contained in the map
* @param isLeadingZeroPossible whether the phone number prefixes belong to a region which
* {@link PhoneNumberUtil#isLeadingZeroPossible isLeadingZeroPossible}
*/
public AreaCodeMapStorageStrategy(int countryCallingCode, boolean isLeadingZeroPossible) {
this.countryCallingCode = countryCallingCode;
this.isLeadingZeroPossible = isLeadingZeroPossible;
}
/**
* Returns whether the underlying implementation of this abstract class is flyweight.
* It is expected to be flyweight if it implements the {@code FlyweightMapStorage} class.
*
* @return whether the underlying implementation of this abstract class is flyweight
*/
public abstract boolean isFlyweight();
/**
* @return the number of entries contained in the area code map
*/
public int getNumOfEntries() {
return numOfEntries;
}
/**
* @return the set containing the possible lengths of prefixes
*/
public TreeSet<Integer> getPossibleLengths() {
return possibleLengths;
}
/**
* Gets the phone number prefix located at the provided {@code index}.
*
* @param index the index of the prefix that needs to be returned
* @return the phone number prefix at the provided index
*/
public abstract int getPrefix(int index);
/**
* Gets the description corresponding to the phone number prefix located at the provided {@code
* index}.
*
* @param index the index of the phone number prefix that needs to be returned
* @return the description corresponding to the phone number prefix at the provided index
*/
public abstract String getDescription(int index);
/**
* Sets the internal state of the underlying storage implementation from the provided {@code
* sortedAreaCodeMap} that maps phone number prefixes to description strings.
*
* @param sortedAreaCodeMap a sorted map that maps phone number prefixes including country
* calling code to description strings
*/
public abstract void readFromSortedMap(SortedMap<Integer, String> sortedAreaCodeMap);
/**
* Sets the internal state of the underlying storage implementation reading the provided {@code
* objectInput}.
*
* @param objectInput the object input stream from which the area code map is read
* @throws IOException if an error occurred reading the provided input stream
*/
public abstract void readExternal(ObjectInput objectInput) throws IOException;
/**
* Writes the internal state of the underlying storage implementation to the provided {@code
* objectOutput}.
*
* @param objectOutput the object output stream to which the area code map is written
* @throws IOException if an error occurred writing to the provided output stream
*/
public abstract void writeExternal(ObjectOutput objectOutput) throws IOException;
/**
* Utility class used to pass arguments by "reference".
*/
protected static class Reference<T> {
private T data;
T get () {
return data;
}
void set (T data) {
this.data = data;
}
}
/**
* Removes the country calling code from the provided {@code prefix} if the country can't have any
* leading zero; otherwise it is left as it is. Sets the provided {@code lengthOfPrefixRef}
* parameter to the length of the resulting prefix.
*
* @param prefix a phone number prefix containing a leading country calling code
* @param lengthOfPrefixRef a "reference" to an integer set to the length of the resulting
* prefix. This parameter is ignored when set to null.
* @return the resulting prefix which may have been stripped
*/
protected int stripPrefix(int prefix, Reference<Integer> lengthOfPrefixRef) {
int lengthOfCountryCode = (int) Math.log10(countryCallingCode) + 1;
int lengthOfPrefix = (int) Math.log10(prefix) + 1;
if (!isLeadingZeroPossible) {
lengthOfPrefix -= lengthOfCountryCode;
prefix -= countryCallingCode * (int) Math.pow(10, lengthOfPrefix);
}
if (lengthOfPrefixRef != null) {
lengthOfPrefixRef.set(lengthOfPrefix);
}
return prefix;
}
/**
* Removes the country calling code from the provided {@code prefix} if the country can't have any
* leading zero; otherwise it is left as it is.
*
* @param prefix a phone number prefix containing a leading country calling code
* @return the resulting prefix which may have been stripped
*/
protected int stripPrefix(int prefix) {
return stripPrefix(prefix, null);
}
}

+ 103
- 0
java/src/com/google/i18n/phonenumbers/geocoding/DefaultMapStorage.java View File

@ -0,0 +1,103 @@
/*
* 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.geocoding;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.SortedMap;
/**
* Default area code map storage strategy that is used for data not containing description
* duplications. It is mainly intended to avoid the overhead of the string table management when it
* is actually unnecessary (i.e no string duplication).
*
* @author Shaopeng Jia
*/
class DefaultMapStorage extends AreaCodeMapStorageStrategy {
public DefaultMapStorage(int countryCallingCode, boolean isLeadingZeroPossible) {
super(countryCallingCode, isLeadingZeroPossible);
}
private int[] phoneNumberPrefixes;
private String[] descriptions;
@Override
public boolean isFlyweight() {
return false;
}
@Override
public int getPrefix(int index) {
return phoneNumberPrefixes[index];
}
@Override
public String getDescription(int index) {
return descriptions[index];
}
@Override
public void readFromSortedMap(SortedMap<Integer, String> sortedAreaCodeMap) {
numOfEntries = sortedAreaCodeMap.size();
phoneNumberPrefixes = new int[numOfEntries];
descriptions = new String[numOfEntries];
int index = 0;
for (int prefix : sortedAreaCodeMap.keySet()) {
Reference<Integer> lengthOfPrefixRef = new Reference<Integer>();
int strippedPrefix = stripPrefix(prefix, lengthOfPrefixRef);
phoneNumberPrefixes[index++] = strippedPrefix;
possibleLengths.add(lengthOfPrefixRef.get());
}
sortedAreaCodeMap.values().toArray(descriptions);
}
@Override
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());
}
}
@Override
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);
}
}
}

+ 261
- 0
java/src/com/google/i18n/phonenumbers/geocoding/FlyweightMapStorage.java View File

@ -0,0 +1,261 @@
/*
* 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.geocoding;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Flyweight area code map storage strategy that uses a table to store unique strings and shorts to
* store the prefix and description indexes when possible. It is particularly space-efficient when
* the provided area code map contains a lot of description duplicates.
*
* @author Philippe Liard
*/
class FlyweightMapStorage extends AreaCodeMapStorageStrategy {
// Size of short and integer types in bytes.
private static final int SHORT_SIZE = Short.SIZE / 8;
private static final int INT_SIZE = Integer.SIZE / 8;
// The number of bytes used to store a phone number prefix.
private int prefixSizeInBytes;
// The number of bytes used to store a description index. It is computed from the size of the
// description pool containing all the strings.
private int descIndexSizeInBytes;
// Byte buffer of stripped phone number prefixes. A stripped phone number prefix is a phone number
// prefix omitting the country code.
private ByteBuffer phoneNumberPrefixes;
private ByteBuffer descriptionIndexes;
// Sorted string array of unique description strings.
private String[] descriptionPool;
public FlyweightMapStorage(int countryCallingCode, boolean isLeadingZeroPossible) {
super(countryCallingCode, isLeadingZeroPossible);
}
@Override
public boolean isFlyweight() {
return true;
}
/**
* Gets the minimum number of bytes that can be used to store the provided {@code value}.
*/
private static int getOptimalNumberOfBytesForValue(int value) {
return value <= Short.MAX_VALUE ? SHORT_SIZE : INT_SIZE;
}
/**
* Stores the provided {@code value} to the provided byte {@code buffer} at the specified {@code
* index} using the provided {@code wordSize} in bytes. Note that only integer and short sizes are
* supported.
*
* @param buffer the byte buffer to which the value is stored
* @param wordSize the number of bytes used to store the provided value
* @param index the index in bytes to which the value is stored
* @param value the value that is stored assuming it does not require more than the specified
* number of bytes.
*/
private static void storeWordInBuffer(ByteBuffer buffer, int wordSize, int index, int value) {
index *= wordSize;
if (wordSize == SHORT_SIZE) {
buffer.putShort(index, (short) value);
} else {
buffer.putInt(index, value);
}
}
/**
* Reads the {@code value} at the specified {@code index} from the provided byte {@code buffer}.
* Note that only integer and short sizes are supported.
*
* @param buffer the byte buffer from which the value is read
* @param wordSize the number of bytes used to store the value
* @param index the index in bytes where the value is read from
*
* @return the value read from the buffer
*/
private static int readWordFromBuffer(ByteBuffer buffer, int wordSize, int index) {
index *= wordSize;
return wordSize == SHORT_SIZE ? buffer.getShort(index) : buffer.getInt(index);
}
@Override
public int getPrefix(int index) {
return readWordFromBuffer(phoneNumberPrefixes, prefixSizeInBytes, index);
}
@Override
public String getDescription(int index) {
return descriptionPool[readWordFromBuffer(descriptionIndexes, descIndexSizeInBytes, index)];
}
@Override
public void readFromSortedMap(SortedMap<Integer, String> sortedAreaCodeMap) {
SortedSet<String> descriptionsSet = new TreeSet<String>();
numOfEntries = sortedAreaCodeMap.size();
prefixSizeInBytes = getOptimalNumberOfBytesForValue(stripPrefix(sortedAreaCodeMap.lastKey()));
phoneNumberPrefixes = ByteBuffer.allocate(numOfEntries * prefixSizeInBytes);
Map<Integer, Integer> strippedToUnstrippedPrefixes = new HashMap<Integer, Integer>();
// Fill the phone number prefixes byte buffer, the set of possible lengths of prefixes and the
// description set.
int index = 0;
for (Entry<Integer, String> entry : sortedAreaCodeMap.entrySet()) {
int prefix = entry.getKey();
Reference<Integer> lengthOfPrefixRef = new Reference<Integer>();
int strippedPrefix = stripPrefix(prefix, lengthOfPrefixRef);
strippedToUnstrippedPrefixes.put(strippedPrefix, prefix);
storeWordInBuffer(phoneNumberPrefixes, prefixSizeInBytes, index++, strippedPrefix);
possibleLengths.add(lengthOfPrefixRef.get());
descriptionsSet.add(entry.getValue());
}
// Create the description pool.
descIndexSizeInBytes = getOptimalNumberOfBytesForValue(descriptionsSet.size() - 1);
descriptionIndexes = ByteBuffer.allocate(numOfEntries * descIndexSizeInBytes);
descriptionPool = new String[descriptionsSet.size()];
descriptionsSet.toArray(descriptionPool);
// Map the phone number prefixes to the descriptions.
index = 0;
for (int i = 0; i < numOfEntries; i++) {
int strippedPrefix = readWordFromBuffer(phoneNumberPrefixes, prefixSizeInBytes, i);
int prefix = strippedToUnstrippedPrefixes.get(strippedPrefix);
String description = sortedAreaCodeMap.get(prefix);
int positionIndescriptionPool =
Arrays.binarySearch(descriptionPool, description, new Comparator<String>() {
public int compare(String o1, String o2) { return o1.compareTo(o2); }
});
storeWordInBuffer(descriptionIndexes, descIndexSizeInBytes, index++,
positionIndescriptionPool);
}
}
/**
* Stores a value which is read from the provided {@code objectInput} to the provided byte {@code
* buffer} at the specified {@code index}.
*
* @param objectInput the object input stream from which the value is read
* @param wordSize the number of bytes used to store the value read from the stream
* @param outputBuffer the byte buffer to which the value is stored
* @param index the index in bytes where the value is stored in the buffer
* @throws IOException if an error occurred reading from the object input stream
*/
private static void readExternalWord(ObjectInput objectInput, int wordSize,
ByteBuffer outputBuffer, int index) throws IOException {
index *= wordSize;
if (wordSize == SHORT_SIZE) {
outputBuffer.putShort(index, objectInput.readShort());
} else {
outputBuffer.putInt(index, objectInput.readInt());
}
}
@Override
public void readExternal(ObjectInput objectInput) throws IOException {
// Read binary words sizes.
prefixSizeInBytes = objectInput.readInt();
descIndexSizeInBytes = objectInput.readInt();
// Read possible lengths.
int sizeOfLengths = objectInput.readInt();
possibleLengths.clear();
for (int i = 0; i < sizeOfLengths; i++) {
possibleLengths.add(objectInput.readInt());
}
// Read description pool size.
int descriptionPoolSize = objectInput.readInt();
// Read description pool.
if (descriptionPool == null || descriptionPool.length < descriptionPoolSize) {
descriptionPool = new String[descriptionPoolSize];
}
for (int i = 0; i < descriptionPoolSize; i++) {
String description = objectInput.readUTF();
descriptionPool[i] = description;
}
// Read entries.
numOfEntries = objectInput.readInt();
if (phoneNumberPrefixes == null || phoneNumberPrefixes.capacity() < numOfEntries) {
phoneNumberPrefixes = ByteBuffer.allocate(numOfEntries * prefixSizeInBytes);
}
if (descriptionIndexes == null || descriptionIndexes.capacity() < numOfEntries) {
descriptionIndexes = ByteBuffer.allocate(numOfEntries * descIndexSizeInBytes);
}
for (int i = 0; i < numOfEntries; i++) {
readExternalWord(objectInput, prefixSizeInBytes, phoneNumberPrefixes, i);
readExternalWord(objectInput, descIndexSizeInBytes, descriptionIndexes, i);
}
}
/**
* Writes the value read from the provided byte {@code buffer} at the specified {@code index} to
* the provided {@code objectOutput}.
*
* @param objectOutput the object output stream to which the value is written
* @param wordSize the number of bytes used to store the value
* @param inputBuffer the byte buffer from which the value is read
* @param index the index of the value in the the byte buffer
* @throws IOException if an error occurred writing to the provided object output stream
*/
private static void writeExternalWord(ObjectOutput objectOutput, int wordSize,
ByteBuffer inputBuffer, int index) throws IOException {
index *= wordSize;
if (wordSize == SHORT_SIZE) {
objectOutput.writeShort(inputBuffer.getShort(index));
} else {
objectOutput.writeInt(inputBuffer.getInt(index));
}
}
@Override
public void writeExternal(ObjectOutput objectOutput) throws IOException {
// Write binary words sizes.
objectOutput.writeInt(prefixSizeInBytes);
objectOutput.writeInt(descIndexSizeInBytes);
// Write possible lengths.
int sizeOfLengths = possibleLengths.size();
objectOutput.writeInt(sizeOfLengths);
for (Integer length : possibleLengths) {
objectOutput.writeInt(length);
}
// Write description pool size.
objectOutput.writeInt(descriptionPool.length);
// Write description pool.
for (String description : descriptionPool) {
objectOutput.writeUTF(description);
}
// Write entries.
objectOutput.writeInt(numOfEntries);
for (int i = 0; i < numOfEntries; i++) {
writeExternalWord(objectOutput, prefixSizeInBytes, phoneNumberPrefixes, i);
writeExternalWord(objectOutput, descIndexSizeInBytes, descriptionIndexes, i);
}
}
}

+ 6
- 3
java/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java View File

@ -79,18 +79,18 @@ public class PhoneNumberOfflineGeocoder {
return null;
}
if (!availablePhonePrefixMaps.containsKey(fileName)) {
loadAreaCodeMapFromFile(fileName);
loadAreaCodeMapFromFile(fileName, countryCallingCode);
}
return availablePhonePrefixMaps.get(fileName);
}
private void loadAreaCodeMapFromFile(String fileName) {
private void loadAreaCodeMapFromFile(String fileName, int countryCallingCode) {
InputStream source =
PhoneNumberOfflineGeocoder.class.getResourceAsStream(phonePrefixDataDirectory + fileName);
ObjectInputStream in;
try {
in = new ObjectInputStream(source);
AreaCodeMap map = new AreaCodeMap();
AreaCodeMap map = new AreaCodeMap(countryCallingCode);
map.readExternal(in);
availablePhonePrefixMaps.put(fileName, map);
} catch (IOException e) {
@ -147,6 +147,9 @@ public class PhoneNumberOfflineGeocoder {
* @return a text description for the given language code for the given phone number
*/
public String getDescriptionForNumber(PhoneNumber number, Locale languageCode) {
if (!phoneUtil.isValidNumber(number)) {
return "";
}
String areaDescription =
getAreaDescriptionForNumber(
number, languageCode.getLanguage(), "", // No script is specified.


+ 112
- 38
java/test/com/google/i18n/phonenumbers/geocoding/AreaCodeMapTest.java View File

@ -26,8 +26,6 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Unittests for AreaCodeMap.java
@ -35,85 +33,161 @@ import java.util.logging.Logger;
* @author Shaopeng Jia
*/
public class AreaCodeMapTest extends TestCase {
private final AreaCodeMap areaCodeMap = new AreaCodeMap();
private final AreaCodeMap areaCodeMapForUS = new AreaCodeMap(1);
private final AreaCodeMap areaCodeMapForIT = new AreaCodeMap(39);
private PhoneNumber number = new PhoneNumber();
private static final Logger LOGGER = Logger.getLogger(AreaCodeMapTest.class.getName());
static final String TEST_META_DATA_FILE_PREFIX =
"/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting";
public AreaCodeMapTest() {
SortedMap<Integer, String> sortedMapForUS = new TreeMap<Integer, String>();
sortedMapForUS.put(1212, "New York");
sortedMapForUS.put(1480, "Arizona");
sortedMapForUS.put(1650, "California");
sortedMapForUS.put(1907, "Alaska");
sortedMapForUS.put(1201664, "Westwood, NJ");
sortedMapForUS.put(1480893, "Phoenix, AZ");
sortedMapForUS.put(1501372, "Little Rock, AR");
sortedMapForUS.put(1626308, "Alhambra, CA");
sortedMapForUS.put(1650345, "San Mateo, CA");
sortedMapForUS.put(1867993, "Dawson, YT");
sortedMapForUS.put(1972480, "Richardson, TX");
areaCodeMapForUS.readAreaCodeMap(sortedMapForUS);
SortedMap<Integer, String> sortedMapForIT = new TreeMap<Integer, String>();
sortedMapForIT.put(3902, "Milan");
sortedMapForIT.put(3906, "Rome");
sortedMapForIT.put(39010, "Genoa");
sortedMapForIT.put(390131, "Alessandria");
sortedMapForIT.put(390321, "Novara");
sortedMapForIT.put(390975, "Potenza");
areaCodeMapForIT.readAreaCodeMap(sortedMapForIT);
}
private static SortedMap<Integer, String> createDefaultStorageMapCandidate() {
SortedMap<Integer, String> sortedMap = new TreeMap<Integer, String>();
// Make the area codes bigger to store them using integer.
sortedMap.put(121212345, "New York");
sortedMap.put(148034434, "Arizona");
return sortedMap;
}
private static SortedMap<Integer, String> createFlyweightStorageMapCandidate() {
SortedMap<Integer, String> sortedMap = new TreeMap<Integer, String>();
sortedMap.put(1212, "New York");
sortedMap.put(1213, "New York");
sortedMap.put(1214, "New York");
sortedMap.put(1480, "Arizona");
sortedMap.put(1650, "California");
sortedMap.put(1907, "Alaska");
sortedMap.put(1201664, "Westwood, NJ");
sortedMap.put(1480893, "Phoenix, AZ");
sortedMap.put(1501372, "Little Rock, AR");
sortedMap.put(1626308, "Alhambra, CA");
sortedMap.put(1650345, "San Mateo, CA");
sortedMap.put(1867993, "Dawson, YT");
sortedMap.put(1972480, "Richardson, TX");
return sortedMap;
}
public void testGetSmallerMapStorageChoosesDefaultImpl() {
AreaCodeMapStorageStrategy mapStorage =
new AreaCodeMap(1).getSmallerMapStorage(createDefaultStorageMapCandidate());
assertFalse(mapStorage.isFlyweight());
}
areaCodeMap.readAreaCodeMap(sortedMap);
public void testGetSmallerMapStorageChoosesFlyweightImpl() {
AreaCodeMapStorageStrategy mapStorage =
new AreaCodeMap(1).getSmallerMapStorage(createFlyweightStorageMapCandidate());
assertTrue(mapStorage.isFlyweight());
}
public void testLookupInvalidNumber_US() {
// central office code cannot start with 1.
number.setCountryCode(1).setNationalNumber(2121234567L);
assertEquals("New York", areaCodeMap.lookup(number));
assertEquals("New York", areaCodeMapForUS.lookup(number));
}
public void testLookupNumber_NJ() {
number.setCountryCode(1).setNationalNumber(2016641234L);
assertEquals("Westwood, NJ", areaCodeMap.lookup(number));
assertEquals("Westwood, NJ", areaCodeMapForUS.lookup(number));
}
public void testLookupNumber_NY() {
number.setCountryCode(1).setNationalNumber(2126641234L);
assertEquals("New York", areaCodeMap.lookup(number));
assertEquals("New York", areaCodeMapForUS.lookup(number));
}
public void testLookupNumber_CA_1() {
number.setCountryCode(1).setNationalNumber(6503451234L);
assertEquals("San Mateo, CA", areaCodeMap.lookup(number));
assertEquals("San Mateo, CA", areaCodeMapForUS.lookup(number));
}
public void testLookupNumber_CA_2() {
number.setCountryCode(1).setNationalNumber(6502531234L);
assertEquals("California", areaCodeMap.lookup(number));
assertEquals("California", areaCodeMapForUS.lookup(number));
}
public void testLookupNumberFound_TX() {
number.setCountryCode(1).setNationalNumber(9724801234L);
assertEquals("Richardson, TX", areaCodeMap.lookup(number));
assertEquals("Richardson, TX", areaCodeMapForUS.lookup(number));
}
public void testLookupNumberNotFound_TX() {
number.setCountryCode(1).setNationalNumber(9724811234L);
assertEquals("", areaCodeMap.lookup(number));
assertEquals("", areaCodeMapForUS.lookup(number));
}
public void testLookupNumber_CH() {
number.setCountryCode(41).setNationalNumber(446681300L);
assertEquals("", areaCodeMap.lookup(number));
assertEquals("", areaCodeMapForUS.lookup(number));
}
public void testLookupNumber_IT() {
number.setCountryCode(39).setNationalNumber(212345678L).setItalianLeadingZero(true);
assertEquals("Milan", areaCodeMapForIT.lookup(number));
number.setNationalNumber(612345678L);
assertEquals("Rome", areaCodeMapForIT.lookup(number));
number.setNationalNumber(3211234L);
assertEquals("Novara", areaCodeMapForIT.lookup(number));
// A mobile number
number.setNationalNumber(321123456L).setItalianLeadingZero(false);
assertEquals("", areaCodeMapForIT.lookup(number));
// An invalid number (too short)
number.setNationalNumber(321123L).setItalianLeadingZero(true);
assertEquals("Novara", areaCodeMapForIT.lookup(number));
}
public void testReadWriteExternal() {
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
areaCodeMap.writeExternal(objectOutputStream);
objectOutputStream.flush();
/**
* Creates a new area code map serializing the provided area code map to a stream and then reading
* this stream. The resulting area code map is expected to be strictly equal to the provided one
* from which it was generated.
*/
private static AreaCodeMap createNewAreaCodeMap(AreaCodeMap areaCodeMap)
throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
areaCodeMap.writeExternal(objectOutputStream);
objectOutputStream.flush();
AreaCodeMap newAreaCodeMap = new AreaCodeMap(1);
newAreaCodeMap.readExternal(
new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())));
return newAreaCodeMap;
}
public void testReadWriteExternalWithDefaultStrategy() throws IOException {
AreaCodeMap localAreaCodeMap = new AreaCodeMap(1);
localAreaCodeMap.readAreaCodeMap(createDefaultStorageMapCandidate());
assertFalse(localAreaCodeMap.getAreaCodeMapStorage().isFlyweight());
AreaCodeMap newAreaCodeMap;
newAreaCodeMap = createNewAreaCodeMap(localAreaCodeMap);
assertEquals(localAreaCodeMap.toString(), newAreaCodeMap.toString());
}
AreaCodeMap newAreaCodeMap = new AreaCodeMap();
newAreaCodeMap.readExternal(
new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())));
public void testReadWriteExternalWithFlyweightStrategy() throws IOException {
AreaCodeMap localAreaCodeMap = new AreaCodeMap(1);
localAreaCodeMap.readAreaCodeMap(createFlyweightStorageMapCandidate());
assertTrue(localAreaCodeMap.getAreaCodeMapStorage().isFlyweight());
assertEquals(areaCodeMap.toString(), newAreaCodeMap.toString());
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getMessage());
fail();
}
AreaCodeMap newAreaCodeMap;
newAreaCodeMap = createNewAreaCodeMap(localAreaCodeMap);
assertEquals(localAreaCodeMap.toString(), newAreaCodeMap.toString());
}
}

+ 9
- 0
java/test/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoderTest.java View File

@ -39,12 +39,16 @@ public class PhoneNumberOfflineGeocoderTest extends TestCase {
new PhoneNumber().setCountryCode(82).setNationalNumber(322123456L);
private static final PhoneNumber KO_NUMBER3 =
new PhoneNumber().setCountryCode(82).setNationalNumber(6421234567L);
private static final PhoneNumber KO_INVALID_NUMBER =
new PhoneNumber().setCountryCode(82).setNationalNumber(1234L);
private static final PhoneNumber US_NUMBER1 =
new PhoneNumber().setCountryCode(1).setNationalNumber(6502530000L);
private static final PhoneNumber US_NUMBER2 =
new PhoneNumber().setCountryCode(1).setNationalNumber(6509600000L);
private static final PhoneNumber US_NUMBER3 =
new PhoneNumber().setCountryCode(1).setNationalNumber(2128120000L);
private static final PhoneNumber US_INVALID_NUMBER =
new PhoneNumber().setCountryCode(1).setNationalNumber(1234567890L);
private static final PhoneNumber BS_NUMBER1 =
new PhoneNumber().setCountryCode(1).setNationalNumber(2423651234L);
private static final PhoneNumber AU_NUMBER =
@ -90,4 +94,9 @@ public class PhoneNumberOfflineGeocoderTest extends TestCase {
assertEquals("\uC81C\uC8FC",
geocoder.getDescriptionForNumber(KO_NUMBER3, Locale.KOREAN));
}
public void testGetDescritionForInvaildNumber() {
assertEquals("", geocoder.getDescriptionForNumber(KO_INVALID_NUMBER, Locale.ENGLISH));
assertEquals("", geocoder.getDescriptionForNumber(US_INVALID_NUMBER, Locale.ENGLISH));
}
}

Loading…
Cancel
Save