1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.providers.contacts.aggregation.util;
18 
19 import android.database.Cursor;
20 import android.database.sqlite.SQLiteDatabase;
21 
22 import com.android.providers.contacts.ContactsDatabaseHelper.NicknameLookupColumns;
23 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
24 import com.google.android.collect.Maps;
25 
26 import java.lang.ref.SoftReference;
27 import java.util.BitSet;
28 import java.util.HashMap;
29 
30 /**
31  * Cache for common nicknames.
32  */
33 public class CommonNicknameCache  {
34 
35     // We will use this much memory (in bits) to optimize the nickname cluster lookup
36     private static final int NICKNAME_BLOOM_FILTER_SIZE = 0x1FFF;   // =long[128]
37     private BitSet mNicknameBloomFilter;
38 
39     private HashMap<String, SoftReference<String[]>> mNicknameClusterCache = Maps.newHashMap();
40 
41     private final SQLiteDatabase mDb;
42 
CommonNicknameCache(SQLiteDatabase db)43     public CommonNicknameCache(SQLiteDatabase db) {
44         mDb = db;
45     }
46 
47     private final static class NicknameLookupPreloadQuery {
48         public final static String TABLE = Tables.NICKNAME_LOOKUP;
49 
50         public final static String[] COLUMNS = new String[] {
51             NicknameLookupColumns.NAME
52         };
53 
54         public final static int NAME = 0;
55     }
56 
57     /**
58      * Read all known common nicknames from the database and populate a Bloom
59      * filter using the corresponding hash codes. The idea is to eliminate most
60      * of unnecessary database lookups for nicknames. Given a name, we will take
61      * its hash code and see if it is set in the Bloom filter. If not, we will know
62      * that the name is not in the database. If it is, we still need to run a
63      * query.
64      * <p>
65      * Given the size of the filter and the expected size of the nickname table,
66      * we should expect the combination of the Bloom filter and cache will
67      * prevent around 98-99% of unnecessary queries from running.
68      */
preloadNicknameBloomFilter()69     private void preloadNicknameBloomFilter() {
70         mNicknameBloomFilter = new BitSet(NICKNAME_BLOOM_FILTER_SIZE + 1);
71         Cursor cursor = mDb.query(NicknameLookupPreloadQuery.TABLE,
72                 NicknameLookupPreloadQuery.COLUMNS,
73                 null, null, null, null, null);
74         try {
75             int count = cursor.getCount();
76             for (int i = 0; i < count; i++) {
77                 cursor.moveToNext();
78                 String normalizedName = cursor.getString(NicknameLookupPreloadQuery.NAME);
79                 int hashCode = normalizedName.hashCode();
80                 mNicknameBloomFilter.set(hashCode & NICKNAME_BLOOM_FILTER_SIZE);
81             }
82         } finally {
83             cursor.close();
84         }
85     }
86 
87     /**
88      * Returns nickname cluster IDs or null. Maintains cache.
89      */
getCommonNicknameClusters(String normalizedName)90     public String[] getCommonNicknameClusters(String normalizedName) {
91         if (mNicknameBloomFilter == null) {
92             preloadNicknameBloomFilter();
93         }
94 
95         int hashCode = normalizedName.hashCode();
96         if (!mNicknameBloomFilter.get(hashCode & NICKNAME_BLOOM_FILTER_SIZE)) {
97             return null;
98         }
99 
100         SoftReference<String[]> ref;
101         String[] clusters = null;
102         synchronized (mNicknameClusterCache) {
103             if (mNicknameClusterCache.containsKey(normalizedName)) {
104                 ref = mNicknameClusterCache.get(normalizedName);
105                 if (ref == null) {
106                     return null;
107                 }
108                 clusters = ref.get();
109             }
110         }
111 
112         if (clusters == null) {
113             clusters = loadNicknameClusters(normalizedName);
114             ref = clusters == null ? null : new SoftReference<String[]>(clusters);
115             synchronized (mNicknameClusterCache) {
116                 mNicknameClusterCache.put(normalizedName, ref);
117             }
118         }
119         return clusters;
120     }
121 
122     private interface NicknameLookupQuery {
123         String TABLE = Tables.NICKNAME_LOOKUP;
124 
125         String[] COLUMNS = new String[] {
126             NicknameLookupColumns.CLUSTER
127         };
128 
129         int CLUSTER = 0;
130     }
131 
loadNicknameClusters(String normalizedName)132     protected String[] loadNicknameClusters(String normalizedName) {
133         String[] clusters = null;
134         Cursor cursor = mDb.query(NicknameLookupQuery.TABLE, NicknameLookupQuery.COLUMNS,
135                 NicknameLookupColumns.NAME + "=?", new String[] { normalizedName },
136                 null, null, null);
137         try {
138             int count = cursor.getCount();
139             if (count > 0) {
140                 clusters = new String[count];
141                 for (int i = 0; i < count; i++) {
142                     cursor.moveToNext();
143                     clusters[i] = cursor.getString(NicknameLookupQuery.CLUSTER);
144                 }
145             }
146         } finally {
147             cursor.close();
148         }
149         return clusters;
150     }
151 }
152