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