/* * 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> 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. *

* 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 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(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; } }