Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b8753eb3 authored by Jean Chalard's avatar Jean Chalard Committed by Android (Google) Code Review
Browse files

Merge "Kill the StringBuilderPool."

parents 8e08bfb8 adf218ee
Loading
Loading
Loading
Loading
+0 −70
Original line number Diff line number Diff line
/*
 * 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;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * A pool of string builders to be used from anywhere.
 */
public class StringBuilderPool {
    // Singleton
    private static final StringBuilderPool sInstance = new StringBuilderPool();
    private static final boolean DEBUG = false;
    private StringBuilderPool() {}
    // TODO: Make this a normal array with a size of 20, or a ConcurrentQueue
    private final List<StringBuilder> mPool =
            Collections.synchronizedList(new ArrayList<StringBuilder>());

    public static StringBuilder getStringBuilder(final int initialSize) {
        // TODO: although the pool is synchronized, the following is not thread-safe.
        // Two threads entering this at the same time could take the same size of the pool and the
        // second to attempt removing this index from the pool would crash with an
        // IndexOutOfBoundsException.
        // At the moment this pool is only used in Suggest.java and only in one thread so it's
        // okay. The simplest thing to do here is probably to replace the ArrayList with a
        // ConcurrentQueue.
        final int poolSize = sInstance.mPool.size();
        final StringBuilder sb = poolSize > 0 ? (StringBuilder) sInstance.mPool.remove(poolSize - 1)
                : new StringBuilder(initialSize);
        sb.setLength(0);
        return sb;
    }

    public static void recycle(final StringBuilder garbage) {
        if (DEBUG) {
            final int gid = garbage.hashCode();
            for (final StringBuilder q : sInstance.mPool) {
                if (gid == q.hashCode()) throw new RuntimeException("Duplicate id " + gid);
            }
        }
        sInstance.mPool.add(garbage);
    }

    public static void ensureCapacity(final int capacity, final int initialSize) {
        for (int i = sInstance.mPool.size(); i < capacity; ++i) {
            final StringBuilder sb = new StringBuilder(initialSize);
            sInstance.mPool.add(sb);
        }
    }

    public static int getSize() {
        return sInstance.mPool.size();
    }
}
+1 −9
Original line number Diff line number Diff line
@@ -142,7 +142,7 @@ public class StringUtils {
            for (int j = 0; j < i; j++) {
                CharSequence previous = suggestions.get(j);
                if (TextUtils.equals(cur, previous)) {
                    removeFromSuggestions(suggestions, i);
                    suggestions.remove(i);
                    i--;
                    break;
                }
@@ -151,14 +151,6 @@ public class StringUtils {
        }
    }

    private static void removeFromSuggestions(final ArrayList<CharSequence> suggestions,
            final int index) {
        final CharSequence garbage = suggestions.remove(index);
        if (garbage instanceof StringBuilder) {
            StringBuilderPool.recycle((StringBuilder)garbage);
        }
    }

    public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
        if (returnsNameInThisLocale) {
            return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
+10 −38
Original line number Diff line number Diff line
@@ -98,7 +98,7 @@ public class Suggest implements Dictionary.WordCallback {
    private int[] mBigramScores = new int[PREF_MAX_BIGRAMS];

    private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
    ArrayList<CharSequence> mBigramSuggestions  = new ArrayList<CharSequence>();
    private ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>();
    private CharSequence mConsideredWord;

    // TODO: Remove these member variables by passing more context to addWord() callback method
@@ -122,7 +122,6 @@ public class Suggest implements Dictionary.WordCallback {
    private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
        mWhiteListDictionary = new WhitelistDictionary(context, locale);
        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary);
        StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength());
    }

    private void initAsynchronously(final Context context, final int dictionaryResId,
@@ -229,14 +228,13 @@ public class Suggest implements Dictionary.WordCallback {
        mPrefMaxSuggestions = maxSuggestions;
        mScores = new int[mPrefMaxSuggestions];
        mBigramScores = new int[PREF_MAX_BIGRAMS];
        collectGarbage(mSuggestions, mPrefMaxSuggestions);
        StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength());
        mSuggestions = new ArrayList<CharSequence>(mPrefMaxSuggestions);
    }

    private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) {
        if (TextUtils.isEmpty(word) || !(all || first)) return word;
        final int wordLength = word.length();
        final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
        final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
        // TODO: Must pay attention to locale when changing case.
        if (all) {
            sb.append(word.toString().toUpperCase());
@@ -250,12 +248,7 @@ public class Suggest implements Dictionary.WordCallback {
    }

    protected void addBigramToSuggestions(CharSequence bigram) {
        // TODO: Try to be a little more shrewd with resource allocation.
        // At the moment we copy this object because the StringBuilders are pooled (see
        // StringBuilderPool.java) and when we are finished using mSuggestions and
        // mBigramSuggestions we will take everything from both and insert them back in the
        // pool, so we can't allow the same object to be in both lists at the same time.
        final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
        final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
        sb.append(bigram);
        mSuggestions.add(sb);
    }
@@ -266,7 +259,7 @@ public class Suggest implements Dictionary.WordCallback {
        mIsFirstCharCapitalized = false;
        mIsAllUpperCase = false;
        mTrailingSingleQuotesCount = 0;
        collectGarbage(mSuggestions, mPrefMaxSuggestions);
        mSuggestions = new ArrayList<CharSequence>(mPrefMaxSuggestions);
        Arrays.fill(mScores, 0);

        // Treating USER_TYPED as UNIGRAM suggestion for logging now.
@@ -274,7 +267,7 @@ public class Suggest implements Dictionary.WordCallback {
        mConsideredWord = "";

        Arrays.fill(mBigramScores, 0);
        collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
        mBigramSuggestions = new ArrayList<CharSequence>(PREF_MAX_BIGRAMS);

        CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
        if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) {
@@ -305,7 +298,7 @@ public class Suggest implements Dictionary.WordCallback {
        mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
        mIsAllUpperCase = wordComposer.isAllUpperCase();
        mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
        collectGarbage(mSuggestions, mPrefMaxSuggestions);
        mSuggestions = new ArrayList<CharSequence>(mPrefMaxSuggestions);
        Arrays.fill(mScores, 0);

        final String typedWord = wordComposer.getTypedWord();
@@ -328,7 +321,7 @@ public class Suggest implements Dictionary.WordCallback {
        if (wordComposer.size() <= 1 && (correctionMode == CORRECTION_FULL_BIGRAM)) {
            // At first character typed, search only the bigrams
            Arrays.fill(mBigramScores, 0);
            collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
            mBigramSuggestions = new ArrayList<CharSequence>(PREF_MAX_BIGRAMS);

            if (!TextUtils.isEmpty(prevWordForBigram)) {
                CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
@@ -542,7 +535,7 @@ public class Suggest implements Dictionary.WordCallback {

        System.arraycopy(sortedScores, pos, sortedScores, pos + 1, prefMaxSuggestions - pos - 1);
        sortedScores[pos] = score;
        final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
        final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
        // TODO: Must pay attention to locale when changing case.
        if (mIsAllUpperCase) {
            sb.append(new String(word, offset, length).toUpperCase());
@@ -559,10 +552,7 @@ public class Suggest implements Dictionary.WordCallback {
        }
        suggestions.add(pos, sb);
        if (suggestions.size() > prefMaxSuggestions) {
            final CharSequence garbage = suggestions.remove(prefMaxSuggestions);
            if (garbage instanceof StringBuilder) {
                StringBuilderPool.recycle((StringBuilder)garbage);
            }
            suggestions.remove(prefMaxSuggestions);
        } else {
            LatinImeLogger.onAddSuggestedWord(sb.toString(), dicTypeId, dataTypeForLog);
        }
@@ -589,24 +579,6 @@ public class Suggest implements Dictionary.WordCallback {
        return -1;
    }

    private static void collectGarbage(ArrayList<CharSequence> suggestions,
            int prefMaxSuggestions) {
        int poolSize = StringBuilderPool.getSize();
        int garbageSize = suggestions.size();
        while (poolSize < prefMaxSuggestions && garbageSize > 0) {
            final CharSequence garbage = suggestions.get(garbageSize - 1);
            if (garbage instanceof StringBuilder) {
                StringBuilderPool.recycle((StringBuilder)garbage);
                poolSize++;
            }
            garbageSize--;
        }
        if (poolSize == prefMaxSuggestions + 1) {
            Log.w("Suggest", "String pool got too big: " + poolSize);
        }
        suggestions.clear();
    }

    public void close() {
        final Set<Dictionary> dictionaries = new HashSet<Dictionary>();
        dictionaries.addAll(mUnigramDictionaries.values());