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 */ rewind()43 public void rewind(); 44 } 45 46 public final List<E> mObservers = new ArrayList<E>(); 47 private int mIterationDepth = 0; 48 ObserverList()49 public ObserverList() {} 50 51 /** 52 * Add an observer to the list. 53 * <p/> 54 * An observer should not be added to the same list more than once. If an iteration is already 55 * in progress, this observer will be not be visible during that iteration. 56 */ addObserver(E obs)57 public void addObserver(E obs) { 58 // Avoid adding null elements to the list as they may be removed on a compaction. 59 if (obs == null || mObservers.contains(obs)) { 60 assert false; 61 return; 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 mObservers.add(obs); 67 } 68 69 /** 70 * Remove an observer from the list if it is in the list. 71 */ removeObserver(E obs)72 public void removeObserver(E obs) { 73 int index = mObservers.indexOf(obs); 74 75 if (index == -1) 76 return; 77 78 if (mIterationDepth == 0) { 79 // No one is iterating over the list. 80 mObservers.remove(obs); 81 } else { 82 mObservers.set(index, null); 83 } 84 } 85 hasObserver(E obs)86 public boolean hasObserver(E obs) { 87 return mObservers.contains(obs); 88 } 89 clear()90 public void clear() { 91 if (mIterationDepth == 0) { 92 mObservers.clear(); 93 return; 94 } 95 96 int size = mObservers.size(); 97 for (int i = 0; i < size; i++) { 98 mObservers.set(i, null); 99 } 100 } 101 102 @Override iterator()103 public Iterator<E> iterator() { 104 return new ObserverListIterator(); 105 } 106 107 /** 108 * It's the same as {@link ObserverList#iterator()} but the return type is 109 * {@link RewindableIterator}. Use this iterator type if you need to use 110 * {@link RewindableIterator#rewind()}. 111 */ rewindableIterator()112 public RewindableIterator<E> rewindableIterator() { 113 return new ObserverListIterator(); 114 } 115 116 /** 117 * Compact the underlying list be removing null elements. 118 * <p/> 119 * Should only be called when mIterationDepth is zero. 120 */ compact()121 private void compact() { 122 assert mIterationDepth == 0; 123 for (int i = mObservers.size() - 1; i >= 0; i--) { 124 if (mObservers.get(i) == null) { 125 mObservers.remove(i); 126 } 127 } 128 } 129 incrementIterationDepth()130 private void incrementIterationDepth() { 131 mIterationDepth++; 132 } 133 decrementIterationDepthAndCompactIfNeeded()134 private void decrementIterationDepthAndCompactIfNeeded() { 135 mIterationDepth--; 136 assert mIterationDepth >= 0; 137 if (mIterationDepth == 0) 138 compact(); 139 } 140 getSize()141 private int getSize() { 142 return mObservers.size(); 143 } 144 getObserverAt(int index)145 private E getObserverAt(int index) { 146 return mObservers.get(index); 147 } 148 149 private class ObserverListIterator implements RewindableIterator<E> { 150 private int mListEndMarker; 151 private int mIndex = 0; 152 private boolean mIsExhausted = false; 153 ObserverListIterator()154 private ObserverListIterator() { 155 ObserverList.this.incrementIterationDepth(); 156 mListEndMarker = ObserverList.this.getSize(); 157 } 158 159 @Override rewind()160 public void rewind() { 161 compactListIfNeeded(); 162 ObserverList.this.incrementIterationDepth(); 163 mListEndMarker = ObserverList.this.getSize(); 164 mIsExhausted = false; 165 mIndex = 0; 166 } 167 168 @Override hasNext()169 public boolean hasNext() { 170 int lookupIndex = mIndex; 171 while (lookupIndex < mListEndMarker && 172 ObserverList.this.getObserverAt(lookupIndex) == null) { 173 lookupIndex++; 174 } 175 if (lookupIndex < mListEndMarker) return true; 176 177 // We have reached the end of the list, allow for compaction. 178 compactListIfNeeded(); 179 return false; 180 } 181 182 @Override next()183 public E next() { 184 // Advance if the current element is null. 185 while (mIndex < mListEndMarker && ObserverList.this.getObserverAt(mIndex) == null) { 186 mIndex++; 187 } 188 if (mIndex < mListEndMarker) return ObserverList.this.getObserverAt(mIndex++); 189 190 // We have reached the end of the list, allow for compaction. 191 compactListIfNeeded(); 192 throw new NoSuchElementException(); 193 } 194 195 @Override remove()196 public void remove() { 197 throw new UnsupportedOperationException(); 198 } 199 compactListIfNeeded()200 private void compactListIfNeeded() { 201 if (!mIsExhausted) { 202 mIsExhausted = true; 203 ObserverList.this.decrementIterationDepthAndCompactIfNeeded(); 204 } 205 } 206 } 207 } 208