Home | History | Annotate | Download | only in base
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 package org.chromium.base;
      6 
      7 import java.util.ArrayList;
      8 import java.util.Iterator;
      9 import java.util.List;
     10 import java.util.NoSuchElementException;
     11 
     12 import javax.annotation.concurrent.NotThreadSafe;
     13 
     14 /**
     15  * A container for a list of observers.
     16  * <p/>
     17  * This container can be modified during iteration without invalidating the iterator.
     18  * So, it safely handles the case of an observer removing itself or other observers from the list
     19  * while observers are being notified.
     20  * <p/>
     21  * The implementation (and the interface) is heavily influenced by the C++ ObserverList.
     22  * Notable differences:
     23  *   - The iterator implements NOTIFY_EXISTING_ONLY.
     24  *   - The range-based for loop is left to the clients to implement in terms of iterator().
     25  * <p/>
     26  * This class is not threadsafe. Observers MUST be added, removed and will be notified on the same
     27  * thread this is created.
     28  *
     29  * @param <E> The type of observers that this list should hold.
     30  */
     31 @NotThreadSafe
     32 public class ObserverList<E> implements Iterable<E> {
     33     /**
     34      * Extended iterator interface that provides rewind functionality.
     35      */
     36     public interface RewindableIterator<E> extends Iterator<E> {
     37         /**
     38          * Rewind the iterator back to the beginning.
     39          *
     40          * If we need to iterate multiple times, we can avoid iterator object reallocation by using
     41          * this method.
     42          */
     43         public void rewind();
     44     }
     45 
     46     public final List<E> mObservers = new ArrayList<E>();
     47     private int mIterationDepth;
     48     private int mCount;
     49     private boolean mNeedsCompact;
     50 
     51     public ObserverList() {}
     52 
     53     /**
     54      * Add an observer to the list.
     55      * <p/>
     56      * An observer should not be added to the same list more than once. If an iteration is already
     57      * in progress, this observer will be not be visible during that iteration.
     58      *
     59      * @return true if the observer list changed as a result of the call.
     60      */
     61     public boolean addObserver(E obs) {
     62         // Avoid adding null elements to the list as they may be removed on a compaction.
     63         if (obs == null || mObservers.contains(obs)) {
     64             return false;
     65         }
     66 
     67         // Structurally modifying the underlying list here. This means we
     68         // cannot use the underlying list's iterator to iterate over the list.
     69         boolean result = mObservers.add(obs);
     70         assert result;
     71 
     72         ++mCount;
     73         return true;
     74     }
     75 
     76     /**
     77      * Remove an observer from the list if it is in the list.
     78      *
     79      * @return true if an element was removed as a result of this call.
     80      */
     81     public boolean removeObserver(E obs) {
     82         if (obs == null) {
     83             return false;
     84         }
     85 
     86         int index = mObservers.indexOf(obs);
     87         if (index == -1) {
     88             return false;
     89         }
     90 
     91         if (mIterationDepth == 0) {
     92             // No one is iterating over the list.
     93             mObservers.remove(index);
     94         } else {
     95             mNeedsCompact = true;
     96             mObservers.set(index, null);
     97         }
     98         --mCount;
     99         assert mCount >= 0;
    100 
    101         return true;
    102     }
    103 
    104     public boolean hasObserver(E obs) {
    105         return mObservers.contains(obs);
    106     }
    107 
    108     public void clear() {
    109         mCount = 0;
    110 
    111         if (mIterationDepth == 0) {
    112             mObservers.clear();
    113             return;
    114         }
    115 
    116         int size = mObservers.size();
    117         mNeedsCompact |= size != 0;
    118         for (int i = 0; i < size; i++) {
    119             mObservers.set(i, null);
    120         }
    121     }
    122 
    123     @Override
    124     public Iterator<E> iterator() {
    125         return new ObserverListIterator();
    126     }
    127 
    128     /**
    129      * It's the same as {@link ObserverList#iterator()} but the return type is
    130      * {@link RewindableIterator}. Use this iterator type if you need to use
    131      * {@link RewindableIterator#rewind()}.
    132      */
    133     public RewindableIterator<E> rewindableIterator() {
    134         return new ObserverListIterator();
    135     }
    136 
    137     /**
    138      * Returns the number of observers currently registered in the ObserverList.
    139      * This is equivalent to the number of non-empty spaces in |mObservers|.
    140      */
    141     public int size() {
    142         return mCount;
    143     }
    144 
    145     /**
    146      * Returns true if the ObserverList contains no observers.
    147      */
    148     public boolean isEmpty() {
    149         return mCount == 0;
    150     }
    151 
    152     /**
    153      * Compact the underlying list be removing null elements.
    154      * <p/>
    155      * Should only be called when mIterationDepth is zero.
    156      */
    157     private void compact() {
    158         assert mIterationDepth == 0;
    159         for (int i = mObservers.size() - 1; i >= 0; i--) {
    160             if (mObservers.get(i) == null) {
    161                 mObservers.remove(i);
    162             }
    163         }
    164     }
    165 
    166     private void incrementIterationDepth() {
    167         mIterationDepth++;
    168     }
    169 
    170     private void decrementIterationDepthAndCompactIfNeeded() {
    171         mIterationDepth--;
    172         assert mIterationDepth >= 0;
    173         if (mIterationDepth > 0) return;
    174         if (!mNeedsCompact) return;
    175         mNeedsCompact = false;
    176         compact();
    177     }
    178 
    179     /**
    180      * Returns the size of the underlying storage of the ObserverList.
    181      * It will take into account the empty spaces inside |mObservers|.
    182      */
    183     private int capacity() {
    184         return mObservers.size();
    185     }
    186 
    187     private E getObserverAt(int index) {
    188         return mObservers.get(index);
    189     }
    190 
    191     private class ObserverListIterator implements RewindableIterator<E> {
    192         private int mListEndMarker;
    193         private int mIndex;
    194         private boolean mIsExhausted;
    195 
    196         private ObserverListIterator() {
    197             ObserverList.this.incrementIterationDepth();
    198             mListEndMarker = ObserverList.this.capacity();
    199         }
    200 
    201         @Override
    202         public void rewind() {
    203             compactListIfNeeded();
    204             ObserverList.this.incrementIterationDepth();
    205             mListEndMarker = ObserverList.this.capacity();
    206             mIsExhausted = false;
    207             mIndex = 0;
    208         }
    209 
    210         @Override
    211         public boolean hasNext() {
    212             int lookupIndex = mIndex;
    213             while (lookupIndex < mListEndMarker
    214                     && ObserverList.this.getObserverAt(lookupIndex) == null) {
    215                 lookupIndex++;
    216             }
    217             if (lookupIndex < mListEndMarker) return true;
    218 
    219             // We have reached the end of the list, allow for compaction.
    220             compactListIfNeeded();
    221             return false;
    222         }
    223 
    224         @Override
    225         public E next() {
    226             // Advance if the current element is null.
    227             while (mIndex < mListEndMarker && ObserverList.this.getObserverAt(mIndex) == null) {
    228                 mIndex++;
    229             }
    230             if (mIndex < mListEndMarker) return ObserverList.this.getObserverAt(mIndex++);
    231 
    232             // We have reached the end of the list, allow for compaction.
    233             compactListIfNeeded();
    234             throw new NoSuchElementException();
    235         }
    236 
    237         @Override
    238         public void remove() {
    239             throw new UnsupportedOperationException();
    240         }
    241 
    242         private void compactListIfNeeded() {
    243             if (!mIsExhausted) {
    244                 mIsExhausted = true;
    245                 ObserverList.this.decrementIterationDepthAndCompactIfNeeded();
    246             }
    247         }
    248     }
    249 }
    250