• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Chromium Authors
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 final ThreadUtils.ThreadChecker mThreadChecker;
48     private int mIterationDepth;
49     private int mCount;
50     private boolean mNeedsCompact;
51     private boolean mEnableThreadAsserts = true;
52 
ObserverList()53     public ObserverList() {
54         mThreadChecker = new ThreadUtils.ThreadChecker();
55     }
56 
57     /**
58      * Disable thread assertions for this instance of ObserverList. In nearly all instances, using
59      * this API indicates a bug.
60      */
disableThreadAsserts()61     public void disableThreadAsserts() {
62         mEnableThreadAsserts = false;
63     }
64 
65     /**
66      * Add an observer to the list.
67      * <p/>
68      * An observer should not be added to the same list more than once. If an iteration is already
69      * in progress, this observer will be not be visible during that iteration.
70      *
71      * @return true if the observer list changed as a result of the call.
72      */
addObserver(E obs)73     public boolean addObserver(E obs) {
74         if (mEnableThreadAsserts) mThreadChecker.assertOnValidThread();
75 
76         // Avoid adding null elements to the list as they may be removed on a compaction.
77         if (obs == null || mObservers.contains(obs)) {
78             return false;
79         }
80 
81         // Structurally modifying the underlying list here. This means we
82         // cannot use the underlying list's iterator to iterate over the list.
83         boolean result = mObservers.add(obs);
84         assert result;
85 
86         ++mCount;
87         return true;
88     }
89 
90     /**
91      * Remove an observer from the list if it is in the list.
92      *
93      * @return true if an element was removed as a result of this call.
94      */
removeObserver(E obs)95     public boolean removeObserver(E obs) {
96         if (mEnableThreadAsserts) mThreadChecker.assertOnValidThread();
97 
98         if (obs == null) {
99             return false;
100         }
101 
102         int index = mObservers.indexOf(obs);
103         if (index == -1) {
104             return false;
105         }
106 
107         if (mIterationDepth == 0) {
108             // No one is iterating over the list.
109             mObservers.remove(index);
110         } else {
111             mNeedsCompact = true;
112             mObservers.set(index, null);
113         }
114         --mCount;
115         assert mCount >= 0;
116 
117         return true;
118     }
119 
hasObserver(E obs)120     public boolean hasObserver(E obs) {
121         if (mEnableThreadAsserts) mThreadChecker.assertOnValidThread();
122 
123         return mObservers.contains(obs);
124     }
125 
clear()126     public void clear() {
127         if (mEnableThreadAsserts) mThreadChecker.assertOnValidThread();
128 
129         mCount = 0;
130 
131         if (mIterationDepth == 0) {
132             mObservers.clear();
133             return;
134         }
135 
136         int size = mObservers.size();
137         mNeedsCompact |= size != 0;
138         for (int i = 0; i < size; i++) {
139             mObservers.set(i, null);
140         }
141     }
142 
143     @Override
iterator()144     public Iterator<E> iterator() {
145         if (mEnableThreadAsserts) mThreadChecker.assertOnValidThread();
146 
147         return new ObserverListIterator();
148     }
149 
150     /**
151      * It's the same as {@link ObserverList#iterator()} but the return type is
152      * {@link RewindableIterator}. Use this iterator type if you need to use
153      * {@link RewindableIterator#rewind()}.
154      */
rewindableIterator()155     public RewindableIterator<E> rewindableIterator() {
156         if (mEnableThreadAsserts) mThreadChecker.assertOnValidThread();
157 
158         return new ObserverListIterator();
159     }
160 
161     /**
162      * Returns the number of observers currently registered in the ObserverList.
163      * This is equivalent to the number of non-empty spaces in |mObservers|.
164      */
size()165     public int size() {
166         if (mEnableThreadAsserts) mThreadChecker.assertOnValidThread();
167 
168         return mCount;
169     }
170 
171     /**
172      * Returns true if the ObserverList contains no observers.
173      */
isEmpty()174     public boolean isEmpty() {
175         if (mEnableThreadAsserts) mThreadChecker.assertOnValidThread();
176 
177         return mCount == 0;
178     }
179 
180     /**
181      * Compact the underlying list be removing null elements.
182      * <p/>
183      * Should only be called when mIterationDepth is zero.
184      */
compact()185     private void compact() {
186         assert mIterationDepth == 0;
187         for (int i = mObservers.size() - 1; i >= 0; i--) {
188             if (mObservers.get(i) == null) {
189                 mObservers.remove(i);
190             }
191         }
192     }
193 
incrementIterationDepth()194     private void incrementIterationDepth() {
195         mIterationDepth++;
196     }
197 
decrementIterationDepthAndCompactIfNeeded()198     private void decrementIterationDepthAndCompactIfNeeded() {
199         mIterationDepth--;
200         assert mIterationDepth >= 0;
201         if (mIterationDepth > 0) return;
202         if (!mNeedsCompact) return;
203         mNeedsCompact = false;
204         compact();
205     }
206 
207     /**
208      * Returns the size of the underlying storage of the ObserverList.
209      * It will take into account the empty spaces inside |mObservers|.
210      */
capacity()211     private int capacity() {
212         return mObservers.size();
213     }
214 
getObserverAt(int index)215     private E getObserverAt(int index) {
216         return mObservers.get(index);
217     }
218 
219     private class ObserverListIterator implements RewindableIterator<E> {
220         private int mListEndMarker;
221         private int mIndex;
222         private boolean mIsExhausted;
223 
ObserverListIterator()224         private ObserverListIterator() {
225             ObserverList.this.incrementIterationDepth();
226             mListEndMarker = ObserverList.this.capacity();
227         }
228 
229         @Override
rewind()230         public void rewind() {
231             if (mEnableThreadAsserts) mThreadChecker.assertOnValidThread();
232 
233             compactListIfNeeded();
234             ObserverList.this.incrementIterationDepth();
235             mListEndMarker = ObserverList.this.capacity();
236             mIsExhausted = false;
237             mIndex = 0;
238         }
239 
240         @Override
hasNext()241         public boolean hasNext() {
242             if (mEnableThreadAsserts) mThreadChecker.assertOnValidThread();
243 
244             int lookupIndex = mIndex;
245             while (lookupIndex < mListEndMarker
246                     && ObserverList.this.getObserverAt(lookupIndex) == null) {
247                 lookupIndex++;
248             }
249             if (lookupIndex < mListEndMarker) return true;
250 
251             // We have reached the end of the list, allow for compaction.
252             compactListIfNeeded();
253             return false;
254         }
255 
256         @Override
next()257         public E next() {
258             if (mEnableThreadAsserts) mThreadChecker.assertOnValidThread();
259 
260             // Advance if the current element is null.
261             while (mIndex < mListEndMarker && ObserverList.this.getObserverAt(mIndex) == null) {
262                 mIndex++;
263             }
264             if (mIndex < mListEndMarker) return ObserverList.this.getObserverAt(mIndex++);
265 
266             // We have reached the end of the list, allow for compaction.
267             compactListIfNeeded();
268             throw new NoSuchElementException();
269         }
270 
271         @Override
remove()272         public void remove() {
273             throw new UnsupportedOperationException();
274         }
275 
compactListIfNeeded()276         private void compactListIfNeeded() {
277             if (!mIsExhausted) {
278                 mIsExhausted = true;
279                 ObserverList.this.decrementIterationDepthAndCompactIfNeeded();
280             }
281         }
282     }
283 }
284