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