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