From a562767a14c7bbac95b25e69e360fc28d6ce9e33 Mon Sep 17 00:00:00 2001
From: Jean Chalard <jchalard@google.com>
Date: Tue, 16 Aug 2011 17:37:18 +0900
Subject: [PATCH] Have a pool of dictionaries to check spelling.

The dictionaries and proximities are not thread-safe. In order to
be able to check spelling in parallel, make a dictionary pool to
call upon when a spelling check is necessary.

Bug: 5156851
Change-Id: Ie3796164187dd7b7abf5ccd5d014073d43d74408
---
 .../inputmethod/keyboard/ProximityInfo.java   |  6 +-
 .../AndroidSpellCheckerService.java           | 61 +++++++++++--------
 .../latin/spellcheck/DictAndProximity.java    | 32 ++++++++++
 .../latin/spellcheck/DictionaryPool.java      | 55 +++++++++++++++++
 4 files changed, 127 insertions(+), 27 deletions(-)
 create mode 100644 java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
 create mode 100644 java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java

diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 5e73d6300e..7190b051dc 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -56,12 +56,12 @@ public class ProximityInfo {
         computeNearestNeighbors(keyWidth, keys);
     }
 
-    public static ProximityInfo getDummyProximityInfo() {
+    public static ProximityInfo createDummyProximityInfo() {
         return new ProximityInfo(1, 1, 1, 1, 1, Collections.<Key>emptyList());
     }
 
-    public static ProximityInfo getSpellCheckerProximityInfo() {
-        final ProximityInfo spellCheckerProximityInfo = getDummyProximityInfo();
+    public static ProximityInfo createSpellCheckerProximityInfo() {
+        final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
         spellCheckerProximityInfo.mNativeProximityInfo =
                 spellCheckerProximityInfo.setProximityInfoNative(
                         SpellCheckerProximityInfo.ROW_SIZE,
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index d2b6bcdf20..c71841042e 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -19,6 +19,7 @@ package com.android.inputmethod.latin.spellcheck;
 import android.content.res.Resources;
 import android.service.textservice.SpellCheckerService;
 import android.service.textservice.SpellCheckerService.Session;
+import android.util.Log;
 import android.view.textservice.SuggestionsInfo;
 import android.view.textservice.TextInfo;
 
@@ -34,8 +35,6 @@ import com.android.inputmethod.latin.WordComposer;
 
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.List;
-import java.util.LinkedList;
 import java.util.Locale;
 import java.util.Map;
 import java.util.TreeMap;
@@ -45,12 +44,12 @@ import java.util.TreeMap;
  */
 public class AndroidSpellCheckerService extends SpellCheckerService {
     private static final String TAG = AndroidSpellCheckerService.class.getSimpleName();
-    private static final boolean DBG = true;
+    private static final boolean DBG = false;
+    private static final int POOL_SIZE = 2;
 
     private final static String[] emptyArray = new String[0];
-    private final ProximityInfo mProximityInfo = ProximityInfo.getSpellCheckerProximityInfo();
-    private final Map<String, Dictionary> mDictionaries =
-            Collections.synchronizedMap(new TreeMap<String, Dictionary>());
+    private final Map<String, DictionaryPool> mDictionaryPools =
+            Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
 
     @Override
     public Session createSession() {
@@ -105,22 +104,32 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
         }
     }
 
-    private Dictionary getDictionary(final String locale) {
-        Dictionary dictionary = mDictionaries.get(locale);
-        if (null == dictionary) {
-            final Resources resources = getResources();
-            final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources);
+    private DictionaryPool getDictionaryPool(final String locale) {
+        DictionaryPool pool = mDictionaryPools.get(locale);
+        if (null == pool) {
             final Locale localeObject = Utils.constructLocaleFromString(locale);
-            dictionary = DictionaryFactory.createDictionaryFromManager(this, localeObject,
-                    fallbackResourceId);
-            mDictionaries.put(locale, dictionary);
+            pool = new DictionaryPool(POOL_SIZE, this, localeObject);
+            mDictionaryPools.put(locale, pool);
         }
-        return dictionary;
+        return pool;
+    }
+
+    public DictAndProximity createDictAndProximity(final Locale locale) {
+        final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo();
+        final Resources resources = getResources();
+        final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources);
+        final Dictionary dictionary =
+                DictionaryFactory.createDictionaryFromManager(this, locale, fallbackResourceId);
+        return new DictAndProximity(dictionary, proximityInfo);
     }
 
     private class AndroidSpellCheckerSession extends Session {
+        // Immutable, but need the locale which is not available in the constructor yet
+        DictionaryPool mDictionaryPool;
+
         @Override
         public void onCreate() {
+            mDictionaryPool = getDictionaryPool(getLocale());
         }
 
         // Note : this must be reentrant
@@ -132,8 +141,6 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
         @Override
         public SuggestionsInfo onGetSuggestions(final TextInfo textInfo,
                 final int suggestionsLimit) {
-            final String locale = getLocale();
-            final Dictionary dictionary = getDictionary(locale);
             final String text = textInfo.getText();
 
             final SuggestionsGatherer suggestionsGatherer =
@@ -153,15 +160,21 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
                 composer.add(character, proximities,
                         WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
             }
-            final boolean isInDict;
-            final String[] suggestions;
-            synchronized(dictionary) {
-                // TODO: make the dictionary reentrant so that we don't have to synchronize here
-                dictionary.getWords(composer, suggestionsGatherer, mProximityInfo);
-                isInDict = dictionary.isValidWord(text);
-                suggestions = suggestionsGatherer.getGatheredSuggestions();
+
+            boolean isInDict = true;
+            try {
+                final DictAndProximity dictInfo = mDictionaryPool.take();
+                dictInfo.mDictionary.getWords(composer, suggestionsGatherer,
+                        dictInfo.mProximityInfo);
+                isInDict = dictInfo.mDictionary.isValidWord(text);
+                mDictionaryPool.offer(dictInfo);
+            } catch (InterruptedException e) {
+                // I don't think this can happen.
+                return new SuggestionsInfo(0, new String[0]);
             }
 
+            final String[] suggestions = suggestionsGatherer.getGatheredSuggestions();
+
             final int flags =
                     (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY : 0)
                             | (null != suggestions
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
new file mode 100644
index 0000000000..3dbbd40cdc
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 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.inputmethod.latin.spellcheck;
+
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
+/**
+ * A simple container for both a Dictionary and a ProximityInfo.
+ */
+public class DictAndProximity {
+    public final Dictionary mDictionary;
+    public final ProximityInfo mProximityInfo;
+    public DictAndProximity(final Dictionary dictionary, final ProximityInfo proximityInfo) {
+        mDictionary = dictionary;
+        mProximityInfo = proximityInfo;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
new file mode 100644
index 0000000000..dfbfcc7f61
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 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.inputmethod.latin.spellcheck;
+
+import android.content.Context;
+
+import java.util.Locale;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * A blocking queue that creates dictionaries up to a certain limit as necessary.
+ */
+public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
+    private final AndroidSpellCheckerService mService;
+    private final int mMaxSize;
+    private final Locale mLocale;
+    private int mSize = 0;
+
+    public DictionaryPool(final int maxSize, final AndroidSpellCheckerService service,
+            final Locale locale) {
+        super();
+        mMaxSize = maxSize;
+        mService = service;
+        mLocale = locale;
+    }
+
+    @Override
+    public DictAndProximity take() throws InterruptedException {
+        final DictAndProximity dict = poll();
+        if (null != dict) return dict;
+        synchronized(this) {
+            if (mSize >= mMaxSize) {
+                // Our pool is already full. Wait until some dictionary is ready.
+                return super.take();
+            } else {
+                ++mSize;
+                return mService.createDictAndProximity(mLocale);
+            }
+        }
+    }
+}
-- 
GitLab