1 /* 2 * Copyright (C) 2011 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.inputmethod.latin.spellcheck; 18 19 import android.util.Log; 20 21 import com.android.inputmethod.keyboard.ProximityInfo; 22 import com.android.inputmethod.latin.CollectionUtils; 23 import com.android.inputmethod.latin.Dictionary; 24 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; 25 import com.android.inputmethod.latin.WordComposer; 26 27 import java.util.ArrayList; 28 import java.util.Locale; 29 import java.util.concurrent.LinkedBlockingQueue; 30 import java.util.concurrent.TimeUnit; 31 32 /** 33 * A blocking queue that creates dictionaries up to a certain limit as necessary. 34 * As a deadlock-detecting device, if waiting for more than TIMEOUT = 3 seconds, we 35 * will clear the queue and generate its contents again. This is transparent for 36 * the client code, but may help with sloppy clients. 37 */ 38 @SuppressWarnings("serial") 39 public final class DictionaryPool extends LinkedBlockingQueue<DictAndKeyboard> { 40 private final static String TAG = DictionaryPool.class.getSimpleName(); 41 // How many seconds we wait for a dictionary to become available. Past this delay, we give up in 42 // fear some bug caused a deadlock, and reset the whole pool. 43 private final static int TIMEOUT = 3; 44 private final AndroidSpellCheckerService mService; 45 private final int mMaxSize; 46 private final Locale mLocale; 47 private int mSize; 48 private volatile boolean mClosed; 49 final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList(); 50 private final static DictAndKeyboard dummyDict = new DictAndKeyboard( 51 new Dictionary(Dictionary.TYPE_MAIN) { 52 @Override 53 public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, 54 final String prevWord, final ProximityInfo proximityInfo, 55 final boolean blockOffensiveWords) { 56 return noSuggestions; 57 } 58 @Override 59 public boolean isValidWord(final String word) { 60 // This is never called. However if for some strange reason it ever gets 61 // called, returning true is less destructive (it will not underline the 62 // word in red). 63 return true; 64 } 65 }, null); 66 67 static public boolean isAValidDictionary(final DictAndKeyboard dictInfo) { 68 return null != dictInfo && dummyDict != dictInfo; 69 } 70 71 public DictionaryPool(final int maxSize, final AndroidSpellCheckerService service, 72 final Locale locale) { 73 super(); 74 mMaxSize = maxSize; 75 mService = service; 76 mLocale = locale; 77 mSize = 0; 78 mClosed = false; 79 } 80 81 @Override 82 public DictAndKeyboard poll(final long timeout, final TimeUnit unit) 83 throws InterruptedException { 84 final DictAndKeyboard dict = poll(); 85 if (null != dict) return dict; 86 synchronized(this) { 87 if (mSize >= mMaxSize) { 88 // Our pool is already full. Wait until some dictionary is ready, or TIMEOUT 89 // expires to avoid a deadlock. 90 final DictAndKeyboard result = super.poll(timeout, unit); 91 if (null == result) { 92 Log.e(TAG, "Deadlock detected ! Resetting dictionary pool"); 93 clear(); 94 mSize = 1; 95 return mService.createDictAndKeyboard(mLocale); 96 } else { 97 return result; 98 } 99 } else { 100 ++mSize; 101 return mService.createDictAndKeyboard(mLocale); 102 } 103 } 104 } 105 106 // Convenience method 107 public DictAndKeyboard pollWithDefaultTimeout() { 108 try { 109 return poll(TIMEOUT, TimeUnit.SECONDS); 110 } catch (InterruptedException e) { 111 return null; 112 } 113 } 114 115 public void close() { 116 synchronized(this) { 117 mClosed = true; 118 for (DictAndKeyboard dict : this) { 119 dict.mDictionary.close(); 120 } 121 clear(); 122 } 123 } 124 125 @Override 126 public boolean offer(final DictAndKeyboard dict) { 127 if (mClosed) { 128 dict.mDictionary.close(); 129 return super.offer(dummyDict); 130 } else { 131 return super.offer(dict); 132 } 133 } 134 } 135