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