blob: 78dc59562c012a6e977e0ccc5476ab9f207b5ace [file] [log] [blame]
/*
* 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;
}
protected int getNumActualWords() {
return mNumActualWords;
}
/**
* 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.hasWord()) {
// This LogUnit isn't a word, so it doesn't count toward the word-limit.
super.shiftIn(newLogUnit);
return;
}
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(1);
}
}
super.shiftIn(newLogUnit);
mNumActualWords++; // Must be a word, or we wouldn't be here.
}
@Override
public LogUnit unshiftIn() {
final LogUnit logUnit = super.unshiftIn();
if (logUnit != null && logUnit.hasWord()) {
mNumActualWords--;
}
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.hasWord()) {
mNumActualWords--;
}
return logUnit;
}
protected void shiftOutWords(final int numWords) {
final int targetNumWords = mNumActualWords - numWords;
final LinkedList<LogUnit> logUnits = getLogUnits();
while (mNumActualWords > targetNumWords && !logUnits.isEmpty()) {
shiftOut();
}
}
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 associated words. No
* more than {@code n} LogUnits will have words associated with them. If there are not enough
* LogUnits in the buffer to meet the word requirement, returns the all LogUnits.
*
* @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();
final int length = logUnits.size();
// 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> list = new ArrayList<LogUnit>(n * 2);
for (int i = 0; i < length && n > 0; i++) {
final LogUnit logUnit = logUnits.get(i);
list.add(logUnit);
if (logUnit.hasWord()) {
n--;
}
}
return list;
}
}