| /* |
| * Copyright (C) 2012 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.research; |
| |
| import java.util.ArrayList; |
| import java.util.LinkedList; |
| |
| /** |
| * A buffer that holds a fixed number of LogUnits. |
| * |
| * LogUnits are added in and shifted out in temporal order. Only a subset of the LogUnits are |
| * actual words; the other LogUnits do not count toward the word limit. Once the buffer reaches |
| * capacity, adding another LogUnit that is a word evicts the oldest LogUnits out one at a time to |
| * stay under the capacity limit. |
| * |
| * This variant of a LogBuffer has a limited memory footprint because of its limited size. This |
| * makes it useful, for example, for recording a window of the user's most recent actions in case |
| * they want to report an observed error that they do not know how to reproduce. |
| */ |
| public class FixedLogBuffer extends LogBuffer { |
| /* package for test */ int mWordCapacity; |
| // The number of members of mLogUnits that are actual words. |
| private int mNumActualWords; |
| |
| /** |
| * Create a new LogBuffer that can hold a fixed number of LogUnits that are words (and |
| * unlimited number of non-word LogUnits), and that outputs its result to a researchLog. |
| * |
| * @param wordCapacity maximum number of words |
| */ |
| public FixedLogBuffer(final int wordCapacity) { |
| super(); |
| if (wordCapacity <= 0) { |
| throw new IllegalArgumentException("wordCapacity must be 1 or greater."); |
| } |
| mWordCapacity = wordCapacity; |
| mNumActualWords = 0; |
| } |
| |
| /** |
| * Adds a new LogUnit to the front of the LIFO queue, evicting existing LogUnit's |
| * (oldest first) if word capacity is reached. |
| */ |
| @Override |
| public void shiftIn(final LogUnit newLogUnit) { |
| if (!newLogUnit.hasOneOrMoreWords()) { |
| // This LogUnit doesn't contain any word, so it doesn't count toward the word-limit. |
| super.shiftIn(newLogUnit); |
| return; |
| } |
| final int numWordsIncoming = newLogUnit.getNumWords(); |
| if (mNumActualWords >= mWordCapacity) { |
| // Give subclass a chance to handle the buffer full condition by shifting out logUnits. |
| onBufferFull(); |
| // If still full, evict. |
| if (mNumActualWords >= mWordCapacity) { |
| shiftOutWords(numWordsIncoming); |
| } |
| } |
| super.shiftIn(newLogUnit); |
| mNumActualWords += numWordsIncoming; |
| } |
| |
| @Override |
| public LogUnit unshiftIn() { |
| final LogUnit logUnit = super.unshiftIn(); |
| if (logUnit != null && logUnit.hasOneOrMoreWords()) { |
| mNumActualWords -= logUnit.getNumWords(); |
| } |
| return logUnit; |
| } |
| |
| public int getNumWords() { |
| return mNumActualWords; |
| } |
| |
| /** |
| * Removes all LogUnits from the buffer without calling onShiftOut(). |
| */ |
| @Override |
| public void clear() { |
| super.clear(); |
| mNumActualWords = 0; |
| } |
| |
| /** |
| * Called when the buffer has just shifted in one more word than its maximum, and its about to |
| * shift out LogUnits to bring it back down to the maximum. |
| * |
| * Base class does nothing; subclasses may override if they want to record non-privacy sensitive |
| * events that fall off the end. |
| */ |
| protected void onBufferFull() { |
| } |
| |
| @Override |
| public LogUnit shiftOut() { |
| final LogUnit logUnit = super.shiftOut(); |
| if (logUnit != null && logUnit.hasOneOrMoreWords()) { |
| mNumActualWords -= logUnit.getNumWords(); |
| } |
| return logUnit; |
| } |
| |
| /** |
| * Remove LogUnits from the front of the LogBuffer until {@code numWords} have been removed. |
| * |
| * If there are less than {@code numWords} word-containing {@link LogUnit}s, shifts out |
| * all {@code LogUnit}s in the buffer. |
| * |
| * @param numWords the minimum number of word-containing {@link LogUnit}s to shift out |
| * @return the number of actual {@code LogUnit}s shifted out |
| */ |
| protected int shiftOutWords(final int numWords) { |
| int numWordContainingLogUnitsShiftedOut = 0; |
| for (LogUnit logUnit = shiftOut(); logUnit != null |
| && numWordContainingLogUnitsShiftedOut < numWords; logUnit = shiftOut()) { |
| if (logUnit.hasOneOrMoreWords()) { |
| numWordContainingLogUnitsShiftedOut += logUnit.getNumWords(); |
| } |
| } |
| return numWordContainingLogUnitsShiftedOut; |
| } |
| |
| public void shiftOutAll() { |
| final LinkedList<LogUnit> logUnits = getLogUnits(); |
| while (!logUnits.isEmpty()) { |
| shiftOut(); |
| } |
| mNumActualWords = 0; |
| } |
| |
| /** |
| * Returns a list of {@link LogUnit}s at the front of the buffer that have words associated with |
| * them. |
| * |
| * There will be no more than {@code n} words in the returned list. So if 2 words are |
| * requested, and the first LogUnit has 3 words, it is not returned. If 2 words are requested, |
| * and the first LogUnit has only 1 word, and the next LogUnit 2 words, only the first LogUnit |
| * is returned. If the first LogUnit has no words associated with it, and the second LogUnit |
| * has three words, then only the first LogUnit (which has no associated words) is returned. If |
| * there are not enough LogUnits in the buffer to meet the word requirement, then all LogUnits |
| * will be returned. |
| * |
| * @param n The maximum number of {@link LogUnit}s with words to return. |
| * @return The list of the {@link LogUnit}s containing the first n words |
| */ |
| public ArrayList<LogUnit> peekAtFirstNWords(int n) { |
| final LinkedList<LogUnit> logUnits = getLogUnits(); |
| // Allocate space for n*2 logUnits. There will be at least n, one for each word, and |
| // there may be additional for punctuation, between-word commands, etc. This should be |
| // enough that reallocation won't be necessary. |
| final ArrayList<LogUnit> resultList = new ArrayList<LogUnit>(n * 2); |
| for (final LogUnit logUnit : logUnits) { |
| n -= logUnit.getNumWords(); |
| if (n < 0) break; |
| resultList.add(logUnit); |
| } |
| return resultList; |
| } |
| } |