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