176 lines
6.6 KiB
Java
176 lines
6.6 KiB
Java
/*
|
|
* 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;
|
|
}
|
|
}
|