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          */
rewind()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 
ObserverList()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      */
addObserver(E obs)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      */
removeObserver(E obs)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 
hasObserver(E obs)104     public boolean hasObserver(E obs) {
105         return mObservers.contains(obs);
106     }
107 
clear()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
iterator()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      */
rewindableIterator()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      */
size()141     public int size() {
142         return mCount;
143     }
144 
145     /**
146      * Returns true if the ObserverList contains no observers.
147      */
isEmpty()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      */
compact()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 
incrementIterationDepth()166     private void incrementIterationDepth() {
167         mIterationDepth++;
168     }
169 
decrementIterationDepthAndCompactIfNeeded()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      */
capacity()183     private int capacity() {
184         return mObservers.size();
185     }
186 
getObserverAt(int index)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 
ObserverListIterator()196         private ObserverListIterator() {
197             ObserverList.this.incrementIterationDepth();
198             mListEndMarker = ObserverList.this.capacity();
199         }
200 
201         @Override
rewind()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
hasNext()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
next()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
remove()238         public void remove() {
239             throw new UnsupportedOperationException();
240         }
241 
compactListIfNeeded()242         private void compactListIfNeeded() {
243             if (!mIsExhausted) {
244                 mIsExhausted = true;
245                 ObserverList.this.decrementIterationDepthAndCompactIfNeeded();
246             }
247         }
248     }
249 }
250