Home | History | Annotate | Download | only in research
      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