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
5package org.chromium.base;
6
7import java.util.ArrayList;
8import java.util.Iterator;
9import java.util.List;
10import java.util.NoSuchElementException;
11
12import 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
32public 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
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     */
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     */
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
86    public boolean hasObserver(E obs) {
87        return mObservers.contains(obs);
88    }
89
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
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     */
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     */
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
130    private void incrementIterationDepth() {
131        mIterationDepth++;
132    }
133
134    private void decrementIterationDepthAndCompactIfNeeded() {
135        mIterationDepth--;
136        assert mIterationDepth >= 0;
137        if (mIterationDepth == 0)
138            compact();
139    }
140
141    private int getSize() {
142        return mObservers.size();
143    }
144
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
154        private ObserverListIterator() {
155            ObserverList.this.incrementIterationDepth();
156            mListEndMarker = ObserverList.this.getSize();
157        }
158
159        @Override
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
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
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
196        public void remove() {
197            throw new UnsupportedOperationException();
198        }
199
200        private void compactListIfNeeded() {
201            if (!mIsExhausted) {
202                mIsExhausted = true;
203                ObserverList.this.decrementIterationDepthAndCompactIfNeeded();
204            }
205        }
206    }
207}
208