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