FixedLogBuffer.java revision bf62dc9460408dc37324c03735ab13c2cdf45396
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 17package com.android.inputmethod.research; 18 19import java.util.ArrayList; 20import 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 */ 34public 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.hasWord()) { 61 // This LogUnit isn't a word, so it doesn't count toward the word-limit. 62 super.shiftIn(newLogUnit); 63 return; 64 } 65 if (mNumActualWords >= mWordCapacity) { 66 // Give subclass a chance to handle the buffer full condition by shifting out logUnits. 67 onBufferFull(); 68 // If still full, evict. 69 if (mNumActualWords >= mWordCapacity) { 70 shiftOutWords(1); 71 } 72 } 73 super.shiftIn(newLogUnit); 74 mNumActualWords++; // Must be a word, or we wouldn't be here. 75 } 76 77 @Override 78 public LogUnit unshiftIn() { 79 final LogUnit logUnit = super.unshiftIn(); 80 if (logUnit != null && logUnit.hasWord()) { 81 mNumActualWords--; 82 } 83 return logUnit; 84 } 85 86 public int getNumWords() { 87 return mNumActualWords; 88 } 89 90 /** 91 * Removes all LogUnits from the buffer without calling onShiftOut(). 92 */ 93 @Override 94 public void clear() { 95 super.clear(); 96 mNumActualWords = 0; 97 } 98 99 /** 100 * Called when the buffer has just shifted in one more word than its maximum, and its about to 101 * shift out LogUnits to bring it back down to the maximum. 102 * 103 * Base class does nothing; subclasses may override if they want to record non-privacy sensitive 104 * events that fall off the end. 105 */ 106 protected void onBufferFull() { 107 } 108 109 @Override 110 public LogUnit shiftOut() { 111 final LogUnit logUnit = super.shiftOut(); 112 if (logUnit != null && logUnit.hasWord()) { 113 mNumActualWords--; 114 } 115 return logUnit; 116 } 117 118 /** 119 * Remove LogUnits from the front of the LogBuffer until {@code numWords} have been removed. 120 * 121 * If there are less than {@code numWords} word-containing {@link LogUnit}s, shifts out 122 * all {@code LogUnit}s in the buffer. 123 * 124 * @param numWords the number of word-containing {@link LogUnit}s to shift out 125 * @return the number of actual {@code LogUnit}s shifted out 126 */ 127 protected int shiftOutWords(final int numWords) { 128 int numWordContainingLogUnitsShiftedOut = 0; 129 for (LogUnit logUnit = shiftOut(); logUnit != null 130 && numWordContainingLogUnitsShiftedOut < numWords; logUnit = shiftOut()) { 131 if (logUnit.hasWord()) { 132 numWordContainingLogUnitsShiftedOut++; 133 } 134 } 135 return numWordContainingLogUnitsShiftedOut; 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 associated words. No 148 * more than {@code n} LogUnits will have words associated with them. If there are not enough 149 * LogUnits in the buffer to meet the word requirement, returns the all LogUnits. 150 * 151 * @param n The maximum number of {@link LogUnit}s with words to return. 152 * @return The list of the {@link LogUnit}s containing the first n words 153 */ 154 public ArrayList<LogUnit> peekAtFirstNWords(int n) { 155 final LinkedList<LogUnit> logUnits = getLogUnits(); 156 final int length = logUnits.size(); 157 // Allocate space for n*2 logUnits. There will be at least n, one for each word, and 158 // there may be additional for punctuation, between-word commands, etc. This should be 159 // enough that reallocation won't be necessary. 160 final ArrayList<LogUnit> list = new ArrayList<LogUnit>(n * 2); 161 for (int i = 0; i < length && n > 0; i++) { 162 final LogUnit logUnit = logUnits.get(i); 163 list.add(logUnit); 164 if (logUnit.hasWord()) { 165 n--; 166 } 167 } 168 return list; 169 } 170} 171