• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 FOR_EACH_OBSERVER closure 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 = 0;
48 
ObserverList()49     public ObserverList() {}
50 
51     /**
52      * Add an observer to the list.
53      * <p/>
54      * An observer should not be added to the same list more than once. If an iteration is already
55      * in progress, this observer will be not be visible during that iteration.
56      */
addObserver(E obs)57     public void addObserver(E obs) {
58         // Avoid adding null elements to the list as they may be removed on a compaction.
59         if (obs == null || mObservers.contains(obs)) {
60             assert false;
61             return;
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         mObservers.add(obs);
67     }
68 
69     /**
70      * Remove an observer from the list if it is in the list.
71      */
removeObserver(E obs)72     public void removeObserver(E obs) {
73         int index = mObservers.indexOf(obs);
74 
75         if (index == -1)
76             return;
77 
78         if (mIterationDepth == 0) {
79             // No one is iterating over the list.
80             mObservers.remove(obs);
81         } else {
82             mObservers.set(index, null);
83         }
84     }
85 
hasObserver(E obs)86     public boolean hasObserver(E obs) {
87         return mObservers.contains(obs);
88     }
89 
clear()90     public void clear() {
91         if (mIterationDepth == 0) {
92             mObservers.clear();
93             return;
94         }
95 
96         int size = mObservers.size();
97         for (int i = 0; i < size; i++) {
98             mObservers.set(i, null);
99         }
100     }
101 
102     @Override
iterator()103     public Iterator<E> iterator() {
104         return new ObserverListIterator();
105     }
106 
107     /**
108      * It's the same as {@link ObserverList#iterator()} but the return type is
109      * {@link RewindableIterator}. Use this iterator type if you need to use
110      * {@link RewindableIterator#rewind()}.
111      */
rewindableIterator()112     public RewindableIterator<E> rewindableIterator() {
113         return new ObserverListIterator();
114     }
115 
116     /**
117      * Compact the underlying list be removing null elements.
118      * <p/>
119      * Should only be called when mIterationDepth is zero.
120      */
compact()121     private void compact() {
122         assert mIterationDepth == 0;
123         for (int i = mObservers.size() - 1; i >= 0; i--) {
124             if (mObservers.get(i) == null) {
125                 mObservers.remove(i);
126             }
127         }
128     }
129 
incrementIterationDepth()130     private void incrementIterationDepth() {
131         mIterationDepth++;
132     }
133 
decrementIterationDepthAndCompactIfNeeded()134     private void decrementIterationDepthAndCompactIfNeeded() {
135         mIterationDepth--;
136         assert mIterationDepth >= 0;
137         if (mIterationDepth == 0)
138             compact();
139     }
140 
getSize()141     private int getSize() {
142         return mObservers.size();
143     }
144 
getObserverAt(int index)145     private E getObserverAt(int index) {
146         return mObservers.get(index);
147     }
148 
149     private class ObserverListIterator implements RewindableIterator<E> {
150         private int mListEndMarker;
151         private int mIndex = 0;
152         private boolean mIsExhausted = false;
153 
ObserverListIterator()154         private ObserverListIterator() {
155             ObserverList.this.incrementIterationDepth();
156             mListEndMarker = ObserverList.this.getSize();
157         }
158 
159         @Override
rewind()160         public void rewind() {
161             compactListIfNeeded();
162             ObserverList.this.incrementIterationDepth();
163             mListEndMarker = ObserverList.this.getSize();
164             mIsExhausted = false;
165             mIndex = 0;
166         }
167 
168         @Override
hasNext()169         public boolean hasNext() {
170             int lookupIndex = mIndex;
171             while (lookupIndex < mListEndMarker &&
172                     ObserverList.this.getObserverAt(lookupIndex) == null) {
173                 lookupIndex++;
174             }
175             if (lookupIndex < mListEndMarker) return true;
176 
177             // We have reached the end of the list, allow for compaction.
178             compactListIfNeeded();
179             return false;
180         }
181 
182         @Override
next()183         public E next() {
184             // Advance if the current element is null.
185             while (mIndex < mListEndMarker && ObserverList.this.getObserverAt(mIndex) == null) {
186                 mIndex++;
187             }
188             if (mIndex < mListEndMarker) return ObserverList.this.getObserverAt(mIndex++);
189 
190             // We have reached the end of the list, allow for compaction.
191             compactListIfNeeded();
192             throw new NoSuchElementException();
193         }
194 
195         @Override
remove()196         public void remove() {
197             throw new UnsupportedOperationException();
198         }
199 
compactListIfNeeded()200         private void compactListIfNeeded() {
201             if (!mIsExhausted) {
202                 mIsExhausted = true;
203                 ObserverList.this.decrementIterationDepthAndCompactIfNeeded();
204             }
205         }
206     }
207 }
208