blob: 9643d81535f026f686d99b5b8c689d350dc6db39 [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* 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.providers.contacts.aggregation.util;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.android.providers.contacts.ContactsDatabaseHelper.NicknameLookupColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
import com.google.android.collect.Maps;
import java.lang.ref.SoftReference;
import java.util.BitSet;
import java.util.HashMap;
/**
* Cache for common nicknames.
*/
public class CommonNicknameCache {
// We will use this much memory (in bits) to optimize the nickname cluster lookup
private static final int NICKNAME_BLOOM_FILTER_SIZE = 0x1FFF; // =long[128]
private BitSet mNicknameBloomFilter;
private HashMap<String, SoftReference<String[]>> mNicknameClusterCache = Maps.newHashMap();
private final SQLiteDatabase mDb;
public CommonNicknameCache(SQLiteDatabase db) {
mDb = db;
}
private final static class NicknameLookupPreloadQuery {
public final static String TABLE = Tables.NICKNAME_LOOKUP;
public final static String[] COLUMNS = new String[] {
NicknameLookupColumns.NAME
};
public final static int NAME = 0;
}
/**
* Read all known common nicknames from the database and populate a Bloom
* filter using the corresponding hash codes. The idea is to eliminate most
* of unnecessary database lookups for nicknames. Given a name, we will take
* its hash code and see if it is set in the Bloom filter. If not, we will know
* that the name is not in the database. If it is, we still need to run a
* query.
* <p>
* Given the size of the filter and the expected size of the nickname table,
* we should expect the combination of the Bloom filter and cache will
* prevent around 98-99% of unnecessary queries from running.
*/
private void preloadNicknameBloomFilter() {
mNicknameBloomFilter = new BitSet(NICKNAME_BLOOM_FILTER_SIZE + 1);
Cursor cursor = mDb.query(NicknameLookupPreloadQuery.TABLE,
NicknameLookupPreloadQuery.COLUMNS,
null, null, null, null, null);
try {
int count = cursor.getCount();
for (int i = 0; i < count; i++) {
cursor.moveToNext();
String normalizedName = cursor.getString(NicknameLookupPreloadQuery.NAME);
int hashCode = normalizedName.hashCode();
mNicknameBloomFilter.set(hashCode & NICKNAME_BLOOM_FILTER_SIZE);
}
} finally {
cursor.close();
}
}
/**
* Returns nickname cluster IDs or null. Maintains cache.
*/
public String[] getCommonNicknameClusters(String normalizedName) {
if (mNicknameBloomFilter == null) {
preloadNicknameBloomFilter();
}
int hashCode = normalizedName.hashCode();
if (!mNicknameBloomFilter.get(hashCode & NICKNAME_BLOOM_FILTER_SIZE)) {
return null;
}
SoftReference<String[]> ref;
String[] clusters = null;
synchronized (mNicknameClusterCache) {
if (mNicknameClusterCache.containsKey(normalizedName)) {
ref = mNicknameClusterCache.get(normalizedName);
if (ref == null) {
return null;
}
clusters = ref.get();
}
}
if (clusters == null) {
clusters = loadNicknameClusters(normalizedName);
ref = clusters == null ? null : new SoftReference<String[]>(clusters);
synchronized (mNicknameClusterCache) {
mNicknameClusterCache.put(normalizedName, ref);
}
}
return clusters;
}
private interface NicknameLookupQuery {
String TABLE = Tables.NICKNAME_LOOKUP;
String[] COLUMNS = new String[] {
NicknameLookupColumns.CLUSTER
};
int CLUSTER = 0;
}
protected String[] loadNicknameClusters(String normalizedName) {
String[] clusters = null;
Cursor cursor = mDb.query(NicknameLookupQuery.TABLE, NicknameLookupQuery.COLUMNS,
NicknameLookupColumns.NAME + "=?", new String[] { normalizedName },
null, null, null);
try {
int count = cursor.getCount();
if (count > 0) {
clusters = new String[count];
for (int i = 0; i < count; i++) {
cursor.moveToNext();
clusters[i] = cursor.getString(NicknameLookupQuery.CLUSTER);
}
}
} finally {
cursor.close();
}
return clusters;
}
}