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