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.weblayer; 6 7 import java.util.ArrayList; 8 import java.util.Iterator; 9 import java.util.List; 10 import java.util.NoSuchElementException; 11 12 /** 13 * A container for a list of observers. 14 * <p/> 15 * This container can be modified during iteration without invalidating the iterator. 16 * So, it safely handles the case of an observer removing itself or other observers from the list 17 * while observers are being notified. 18 * <p/> 19 * The implementation (and the interface) is heavily influenced by the C++ ObserverList. 20 * Notable differences: 21 * - The iterator implements NOTIFY_EXISTING_ONLY. 22 * - The range-based for loop is left to the clients to implement in terms of iterator(). 23 * <p/> 24 * This class is not threadsafe. Observers MUST be added, removed and will be notified on the same 25 * thread this is created. 26 * 27 * @param <E> The type of observers that this list should hold. 28 */ 29 class ObserverList<E> implements Iterable<E> { 30 /** 31 * Extended iterator interface that provides rewind functionality. 32 */ 33 public interface RewindableIterator<E> extends Iterator<E> { 34 /** 35 * Rewind the iterator back to the beginning. 36 * 37 * If we need to iterate multiple times, we can avoid iterator object reallocation by using 38 * this method. 39 */ rewind()40 public void rewind(); 41 } 42 43 public final List<E> mObservers = new ArrayList<E>(); 44 private int mIterationDepth; 45 private int mCount; 46 private boolean mNeedsCompact; 47 ObserverList()48 public ObserverList() {} 49 50 /** 51 * Add an observer to the list. 52 * <p/> 53 * An observer should not be added to the same list more than once. If an iteration is already 54 * in progress, this observer will be not be visible during that iteration. 55 * 56 * @return true if the observer list changed as a result of the call. 57 */ addObserver(E obs)58 public boolean addObserver(E obs) { 59 // Avoid adding null elements to the list as they may be removed on a compaction. 60 if (obs == null || mObservers.contains(obs)) { 61 return false; 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 boolean result = mObservers.add(obs); 67 assert result; 68 69 ++mCount; 70 return true; 71 } 72 73 /** 74 * Remove an observer from the list if it is in the list. 75 * 76 * @return true if an element was removed as a result of this call. 77 */ removeObserver(E obs)78 public boolean removeObserver(E obs) { 79 if (obs == null) { 80 return false; 81 } 82 83 int index = mObservers.indexOf(obs); 84 if (index == -1) { 85 return false; 86 } 87 88 if (mIterationDepth == 0) { 89 // No one is iterating over the list. 90 mObservers.remove(index); 91 } else { 92 mNeedsCompact = true; 93 mObservers.set(index, null); 94 } 95 --mCount; 96 assert mCount >= 0; 97 98 return true; 99 } 100 hasObserver(E obs)101 public boolean hasObserver(E obs) { 102 return mObservers.contains(obs); 103 } 104 clear()105 public void clear() { 106 mCount = 0; 107 108 if (mIterationDepth == 0) { 109 mObservers.clear(); 110 return; 111 } 112 113 int size = mObservers.size(); 114 mNeedsCompact |= size != 0; 115 for (int i = 0; i < size; i++) { 116 mObservers.set(i, null); 117 } 118 } 119 120 @Override iterator()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 */ rewindableIterator()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 */ size()138 public int size() { 139 return mCount; 140 } 141 142 /** 143 * Returns true if the ObserverList contains no observers. 144 */ isEmpty()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 */ compact()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 incrementIterationDepth()163 private void incrementIterationDepth() { 164 mIterationDepth++; 165 } 166 decrementIterationDepthAndCompactIfNeeded()167 private void decrementIterationDepthAndCompactIfNeeded() { 168 mIterationDepth--; 169 assert mIterationDepth >= 0; 170 if (mIterationDepth > 0) return; 171 if (!mNeedsCompact) return; 172 mNeedsCompact = false; 173 compact(); 174 } 175 176 /** 177 * Returns the size of the underlying storage of the ObserverList. 178 * It will take into account the empty spaces inside |mObservers|. 179 */ capacity()180 private int capacity() { 181 return mObservers.size(); 182 } 183 getObserverAt(int index)184 private E getObserverAt(int index) { 185 return mObservers.get(index); 186 } 187 188 private class ObserverListIterator implements RewindableIterator<E> { 189 private int mListEndMarker; 190 private int mIndex; 191 private boolean mIsExhausted; 192 ObserverListIterator()193 private ObserverListIterator() { 194 ObserverList.this.incrementIterationDepth(); 195 mListEndMarker = ObserverList.this.capacity(); 196 } 197 198 @Override rewind()199 public void rewind() { 200 compactListIfNeeded(); 201 ObserverList.this.incrementIterationDepth(); 202 mListEndMarker = ObserverList.this.capacity(); 203 mIsExhausted = false; 204 mIndex = 0; 205 } 206 207 @Override hasNext()208 public boolean hasNext() { 209 int lookupIndex = mIndex; 210 while (lookupIndex < mListEndMarker 211 && ObserverList.this.getObserverAt(lookupIndex) == null) { 212 lookupIndex++; 213 } 214 if (lookupIndex < mListEndMarker) return true; 215 216 // We have reached the end of the list, allow for compaction. 217 compactListIfNeeded(); 218 return false; 219 } 220 221 @Override next()222 public E next() { 223 // Advance if the current element is null. 224 while (mIndex < mListEndMarker && ObserverList.this.getObserverAt(mIndex) == null) { 225 mIndex++; 226 } 227 if (mIndex < mListEndMarker) return ObserverList.this.getObserverAt(mIndex++); 228 229 // We have reached the end of the list, allow for compaction. 230 compactListIfNeeded(); 231 throw new NoSuchElementException(); 232 } 233 234 @Override remove()235 public void remove() { 236 throw new UnsupportedOperationException(); 237 } 238 compactListIfNeeded()239 private void compactListIfNeeded() { 240 if (!mIsExhausted) { 241 mIsExhausted = true; 242 ObserverList.this.decrementIterationDepthAndCompactIfNeeded(); 243 } 244 } 245 } 246 } 247