1 /* 2 * Copyright (C) 2012 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.research; 18 19 import java.util.ArrayList; 20 import java.util.LinkedList; 21 22 /** 23 * A buffer that holds a fixed number of LogUnits. 24 * 25 * LogUnits are added in and shifted out in temporal order. Only a subset of the LogUnits are 26 * actual words; the other LogUnits do not count toward the word limit. Once the buffer reaches 27 * capacity, adding another LogUnit that is a word evicts the oldest LogUnits out one at a time to 28 * stay under the capacity limit. 29 * 30 * This variant of a LogBuffer has a limited memory footprint because of its limited size. This 31 * makes it useful, for example, for recording a window of the user's most recent actions in case 32 * they want to report an observed error that they do not know how to reproduce. 33 */ 34 public class FixedLogBuffer extends LogBuffer { 35 /* package for test */ int mWordCapacity; 36 // The number of members of mLogUnits that are actual words. 37 private int mNumActualWords; 38 39 /** 40 * Create a new LogBuffer that can hold a fixed number of LogUnits that are words (and 41 * unlimited number of non-word LogUnits), and that outputs its result to a researchLog. 42 * 43 * @param wordCapacity maximum number of words 44 */ 45 public FixedLogBuffer(final int wordCapacity) { 46 super(); 47 if (wordCapacity <= 0) { 48 throw new IllegalArgumentException("wordCapacity must be 1 or greater."); 49 } 50 mWordCapacity = wordCapacity; 51 mNumActualWords = 0; 52 } 53 54 /** 55 * Adds a new LogUnit to the front of the LIFO queue, evicting existing LogUnit's 56 * (oldest first) if word capacity is reached. 57 */ 58 @Override 59 public void shiftIn(final LogUnit newLogUnit) { 60 if (!newLogUnit.hasOneOrMoreWords()) { 61 // This LogUnit doesn't contain any word, so it doesn't count toward the word-limit. 62 super.shiftIn(newLogUnit); 63 return; 64 } 65 final int numWordsIncoming = newLogUnit.getNumWords(); 66 if (mNumActualWords >= mWordCapacity) { 67 // Give subclass a chance to handle the buffer full condition by shifting out logUnits. 68 // TODO: Tell onBufferFull() how much space it needs to make to avoid forced eviction. 69 onBufferFull(); 70 // If still full, evict. 71 if (mNumActualWords >= mWordCapacity) { 72 shiftOutWords(numWordsIncoming); 73 } 74 } 75 super.shiftIn(newLogUnit); 76 mNumActualWords += numWordsIncoming; 77 } 78 79 @Override 80 public LogUnit unshiftIn() { 81 final LogUnit logUnit = super.unshiftIn(); 82 if (logUnit != null && logUnit.hasOneOrMoreWords()) { 83 mNumActualWords -= logUnit.getNumWords(); 84 } 85 return logUnit; 86 } 87 88 public int getNumWords() { 89 return mNumActualWords; 90 } 91 92 /** 93 * Removes all LogUnits from the buffer without calling onShiftOut(). 94 */ 95 @Override 96 public void clear() { 97 super.clear(); 98 mNumActualWords = 0; 99 } 100 101 /** 102 * Called when the buffer has just shifted in one more word than its maximum, and its about to 103 * shift out LogUnits to bring it back down to the maximum. 104 * 105 * Base class does nothing; subclasses may override if they want to record non-privacy sensitive 106 * events that fall off the end. 107 */ 108 protected void onBufferFull() { 109 } 110 111 @Override 112 public LogUnit shiftOut() { 113 final LogUnit logUnit = super.shiftOut(); 114 if (logUnit != null && logUnit.hasOneOrMoreWords()) { 115 mNumActualWords -= logUnit.getNumWords(); 116 } 117 return logUnit; 118 } 119 120 /** 121 * Remove LogUnits from the front of the LogBuffer until {@code numWords} have been removed. 122 * 123 * If there are less than {@code numWords} in the buffer, shifts out all {@code LogUnit}s. 124 * 125 * @param numWords the minimum number of words in {@link LogUnit}s to shift out 126 * @return the number of actual words LogUnit}s shifted out 127 */ 128 protected int shiftOutWords(final int numWords) { 129 int numWordsShiftedOut = 0; 130 do { 131 final LogUnit logUnit = shiftOut(); 132 if (logUnit == null) break; 133 numWordsShiftedOut += logUnit.getNumWords(); 134 } while (numWordsShiftedOut < numWords); 135 return numWordsShiftedOut; 136 } 137 138 public void shiftOutAll() { 139 final LinkedList<LogUnit> logUnits = getLogUnits(); 140 while (!logUnits.isEmpty()) { 141 shiftOut(); 142 } 143 mNumActualWords = 0; 144 } 145 146 /** 147 * Returns a list of {@link LogUnit}s at the front of the buffer that have words associated with 148 * them. 149 * 150 * There will be no more than {@code n} words in the returned list. So if 2 words are 151 * requested, and the first LogUnit has 3 words, it is not returned. If 2 words are requested, 152 * and the first LogUnit has only 1 word, and the next LogUnit 2 words, only the first LogUnit 153 * is returned. If the first LogUnit has no words associated with it, and the second LogUnit 154 * has three words, then only the first LogUnit (which has no associated words) is returned. If 155 * there are not enough LogUnits in the buffer to meet the word requirement, then all LogUnits 156 * will be returned. 157 * 158 * @param n The maximum number of {@link LogUnit}s with words to return. 159 * @return The list of the {@link LogUnit}s containing the first n words 160 */ 161 public ArrayList<LogUnit> peekAtFirstNWords(int n) { 162 final LinkedList<LogUnit> logUnits = getLogUnits(); 163 // Allocate space for n*2 logUnits. There will be at least n, one for each word, and 164 // there may be additional for punctuation, between-word commands, etc. This should be 165 // enough that reallocation won't be necessary. 166 final ArrayList<LogUnit> resultList = new ArrayList<LogUnit>(n * 2); 167 for (final LogUnit logUnit : logUnits) { 168 n -= logUnit.getNumWords(); 169 if (n < 0) break; 170 resultList.add(logUnit); 171 } 172 return resultList; 173 } 174 } 175