• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
FixedLogBuffer(final int wordCapacity)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
shiftIn(final LogUnit newLogUnit)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
unshiftIn()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 
getNumWords()88     public int getNumWords() {
89         return mNumActualWords;
90     }
91 
92     /**
93      * Removes all LogUnits from the buffer without calling onShiftOut().
94      */
95     @Override
clear()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      */
onBufferFull()108     protected void onBufferFull() {
109     }
110 
111     @Override
shiftOut()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      */
shiftOutWords(final int numWords)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 
shiftOutAll()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      */
peekAtFirstNWords(int n)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