CallbackRegistry.java revision fead9ca09b117136b35bc5bf137340a754f9eddd
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package android.databinding;
17
18import java.util.ArrayList;
19import java.util.List;
20
21/**
22 * Tracks callbacks for the event. This class supports reentrant modification
23 * of the callbacks during notification without adversely disrupting notifications.
24 * A common pattern for callbacks is to receive a notification and then remove
25 * themselves. This class handles this behavior with constant memory under
26 * most circumstances.
27 *
28 * <p>A subclass of {@link CallbackRegistry.NotifierCallback} must be passed to
29 * the constructor to define how notifications should be called. That implementation
30 * does the actual notification on the listener.</p>
31 *
32 * <p>This class supports only callbacks with at most two parameters.
33 * Typically, these are the notification originator and a parameter, but these may
34 * be used as required. If more than two parameters are required or primitive types
35 * must be used, <code>A</code> should be some kind of containing structure that
36 * the subclass may reuse between notifications.</p>
37 *
38 * @param <C> The callback type.
39 * @param <T> The notification sender type. Typically this is the containing class.
40 * @param <A> Opaque argument used to pass additional data beyond an int.
41 */
42public class CallbackRegistry<C, T, A> implements Cloneable {
43    private static final String TAG = "CallbackRegistry";
44
45    /** An ordered collection of listeners waiting to be notified. */
46    private List<C> mCallbacks = new ArrayList<C>();
47
48    /**
49     * A bit flag for the first 64 listeners that are removed during notification.
50     * The lowest significant bit corresponds to the 0th index into mCallbacks.
51     * For a small number of callbacks, no additional array of objects needs to
52     * be allocated.
53     */
54    private long mFirst64Removed = 0x0;
55
56    /**
57     * Bit flags for the remaining callbacks that are removed during notification.
58     * When there are more than 64 callbacks and one is marked for removal, a dynamic
59     * array of bits are allocated for the callbacks.
60     */
61    private long[] mRemainderRemoved;
62
63    /** The recursion level of the notification */
64    private int mNotificationLevel;
65
66    /** The notification mechanism for notifying an event. */
67    private final NotifierCallback<C, T, A> mNotifier;
68
69    /**
70     * Creates an EventRegistry that notifies the event with notifier.
71     * @param notifier The class to use to notify events.
72     */
73    public CallbackRegistry(NotifierCallback<C, T, A> notifier) {
74        mNotifier = notifier;
75    }
76
77    /**
78     * Notify all callbacks.
79     *
80     * @param sender The originator. This is an opaque parameter passed to
81     *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
82     * @param arg An opaque parameter passed to
83     *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
84     * @param arg2 An opaque parameter passed to
85     *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
86     */
87    public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
88        mNotificationLevel++;
89        notifyRecurse(sender, arg, arg2);
90        mNotificationLevel--;
91        if (mNotificationLevel == 0) {
92            if (mRemainderRemoved != null) {
93                for (int i = mRemainderRemoved.length - 1; i >= 0; i--) {
94                    final long removedBits = mRemainderRemoved[i];
95                    if (removedBits != 0) {
96                        removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits);
97                        mRemainderRemoved[i] = 0;
98                    }
99                }
100            }
101            if (mFirst64Removed != 0) {
102                removeRemovedCallbacks(0, mFirst64Removed);
103                mFirst64Removed = 0;
104            }
105        }
106    }
107
108    /**
109     * Notify up to the first Long.SIZE callbacks that don't have a bit set in <code>removed</code>.
110     *
111     * @param sender The originator. This is an opaque parameter passed to
112     *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
113     * @param arg An opaque parameter passed to
114     *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
115     * @param arg2 An opaque parameter passed to
116     *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
117     */
118    private void notifyFirst64(T sender, int arg, A arg2) {
119        final int maxNotified = Math.min(Long.SIZE, mCallbacks.size());
120        notifyCallbacks(sender, arg, arg2, 0, maxNotified, mFirst64Removed);
121    }
122
123    /**
124     * Notify all callbacks using a recursive algorithm to avoid allocating on the heap.
125     * This part captures the callbacks beyond Long.SIZE that have no bits allocated for
126     * removal before it recurses into {@link #notifyRemainder(Object, int, A, int)}.
127     *
128     * <p>Recursion is used to avoid allocating temporary state on the heap.</p>
129     *
130     * @param sender The originator. This is an opaque parameter passed to
131     *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
132     * @param arg An opaque parameter passed to
133     *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
134     * @param arg2 An opaque parameter passed to
135     *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
136     */
137    private void notifyRecurse(T sender, int arg, A arg2) {
138        final int callbackCount = mCallbacks.size();
139        final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1;
140
141        // Now we've got all callbakcs that have no mRemainderRemoved value, so notify the
142        // others.
143        notifyRemainder(sender, arg, arg2, remainderIndex);
144
145        // notifyRemainder notifies all at maxIndex, so we'd normally start at maxIndex + 1
146        // However, we must also keep track of those in mFirst64Removed, so we add 2 instead:
147        final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE;
148
149        // The remaining have no bit set
150        notifyCallbacks(sender, arg, arg2, startCallbackIndex, callbackCount, 0);
151    }
152
153    /**
154     * Notify callbacks that have mRemainderRemoved bits set for remainderIndex. If
155     * remainderIndex is -1, the first 64 will be notified instead.
156     *
157     * @param sender The originator. This is an opaque parameter passed to
158     *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
159     * @param arg An opaque parameter passed to
160     *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
161     * @param arg2 An opaque parameter passed to
162     *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
163     * @param remainderIndex The index into mRemainderRemoved that should be notified.
164     */
165    private void notifyRemainder(T sender, int arg, A arg2, int remainderIndex) {
166        if (remainderIndex < 0) {
167            notifyFirst64(sender, arg, arg2);
168        } else {
169            final long bits = mRemainderRemoved[remainderIndex];
170            final int startIndex = (remainderIndex + 1) * Long.SIZE;
171            final int endIndex = Math.min(mCallbacks.size(), startIndex + Long.SIZE);
172            notifyRemainder(sender, arg, arg2, remainderIndex - 1);
173            notifyCallbacks(sender, arg, arg2, startIndex, endIndex, bits);
174        }
175    }
176
177    /**
178     * Notify callbacks from startIndex to endIndex, using bits as the bit status
179     * for whether they have been removed or not. bits should be from mRemainderRemoved or
180     * mFirst64Removed. bits set to 0 indicates that all callbacks from startIndex to
181     * endIndex should be notified.
182     *
183     * @param sender The originator. This is an opaque parameter passed to
184     *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
185     * @param arg An opaque parameter passed to
186     *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
187     * @param arg2 An opaque parameter passed to
188     *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
189     * @param startIndex The index into the mCallbacks to start notifying.
190     * @param endIndex One past the last index into mCallbacks to notify.
191     * @param bits A bit field indicating which callbacks have been removed and shouldn't
192     *             be notified.
193     */
194    private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,
195            final int endIndex, final long bits) {
196        long bitMask = 1;
197        for (int i = startIndex; i < endIndex; i++) {
198            if ((bits & bitMask) == 0) {
199                mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
200            }
201            bitMask <<= 1;
202        }
203    }
204
205    /**
206     * Add a callback to be notified. If the callback is already in the list, another won't
207     * be added. This does not affect current notifications.
208     * @param callback The callback to add.
209     */
210    public synchronized void add(C callback) {
211        int index = mCallbacks.lastIndexOf(callback);
212        if (index < 0 || isRemoved(index)) {
213            mCallbacks.add(callback);
214        }
215    }
216
217    /**
218     * Returns true if the callback at index has been marked for removal.
219     *
220     * @param index The index into mCallbacks to check.
221     * @return true if the callback at index has been marked for removal.
222     */
223    private boolean isRemoved(int index) {
224        if (index < Long.SIZE) {
225            // It is in the first 64 callbacks, just check the bit.
226            final long bitMask = 1L << index;
227            return (mFirst64Removed & bitMask) != 0;
228        } else if (mRemainderRemoved == null) {
229            // It is after the first 64 callbacks, but nothing else was marked for removal.
230            return false;
231        } else {
232            final int maskIndex = (index / Long.SIZE) - 1;
233            if (maskIndex >= mRemainderRemoved.length) {
234                // There are some items in mRemainderRemoved, but nothing at the given index.
235                return false;
236            } else {
237                // There is something marked for removal, so we have to check the bit.
238                final long bits = mRemainderRemoved[maskIndex];
239                final long bitMask = 1L << (index % Long.SIZE);
240                return (bits & bitMask) != 0;
241            }
242        }
243    }
244
245    /**
246     * Removes callbacks from startIndex to startIndex + Long.SIZE, based
247     * on the bits set in removed.
248     * @param startIndex The index into the mCallbacks to start removing callbacks.
249     * @param removed The bits indicating removal, where each bit is set for one callback
250     *                to be removed.
251     */
252    private void removeRemovedCallbacks(int startIndex, long removed) {
253        // The naive approach should be fine. There may be a better bit-twiddling approach.
254        final int endIndex = startIndex + Long.SIZE;
255
256        long bitMask = 1L << (Long.SIZE - 1);
257        for (int i = endIndex - 1; i >= startIndex; i--) {
258            if ((removed & bitMask) != 0) {
259                mCallbacks.remove(i);
260            }
261            bitMask >>>= 1;
262        }
263    }
264
265    /**
266     * Remove a callback. This callback won't be notified after this call completes.
267     * @param callback The callback to remove.
268     */
269    public synchronized void remove(C callback) {
270        if (mNotificationLevel == 0) {
271            mCallbacks.remove(callback);
272        } else {
273            int index = mCallbacks.lastIndexOf(callback);
274            if (index >= 0) {
275                setRemovalBit(index);
276            }
277        }
278    }
279
280    private void setRemovalBit(int index) {
281        if (index < Long.SIZE) {
282            // It is in the first 64 callbacks, just check the bit.
283            final long bitMask = 1L << index;
284            mFirst64Removed |= bitMask;
285        } else {
286            final int remainderIndex = (index / Long.SIZE) - 1;
287            if (mRemainderRemoved == null) {
288                mRemainderRemoved = new long[mCallbacks.size() / Long.SIZE];
289            } else if (mRemainderRemoved.length < remainderIndex) {
290                // need to make it bigger
291                long[] newRemainders = new long[mCallbacks.size() / Long.SIZE];
292                System.arraycopy(mRemainderRemoved, 0, newRemainders, 0, mRemainderRemoved.length);
293                mRemainderRemoved = newRemainders;
294            }
295            final long bitMask = 1L << (index % Long.SIZE);
296            mRemainderRemoved[remainderIndex] |= bitMask;
297        }
298    }
299
300    /*
301    private void clearRemovalBit(int index) {
302        if (index < Long.SIZE) {
303            // It is in the first 64 callbacks, just check the bit.
304            final long bitMask = 1L << index;
305            mFirst64Removed &= ~bitMask;
306        } else if (mRemainderRemoved != null) {
307            final int maskIndex = (index / Long.SIZE) - 1;
308            if (maskIndex < mRemainderRemoved.length) {
309                // There is something marked for removal, so we have to check the bit.
310                final long bitMask = 1L << (index % Long.SIZE);
311                mRemainderRemoved[maskIndex] &= ~bitMask;
312            }
313        }
314    }
315    */
316
317    /**
318     * Makes a copy of the registered callbacks and returns it.
319     *
320     * @return a copy of the registered callbacks.
321     */
322    public synchronized ArrayList<C> copyListeners() {
323        ArrayList<C> callbacks = new ArrayList<C>(mCallbacks.size());
324        int numListeners = mCallbacks.size();
325        for (int i = 0; i < numListeners; i++) {
326            if (!isRemoved(i)) {
327                callbacks.add(mCallbacks.get(i));
328            }
329        }
330        return callbacks;
331    }
332
333    /**
334     * Returns true if there are no registered callbacks or false otherwise.
335     *
336     * @return true if there are no registered callbacks or false otherwise.
337     */
338    public synchronized boolean isEmpty() {
339        if (mCallbacks.isEmpty()) {
340            return true;
341        } else if (mNotificationLevel == 0) {
342            return false;
343        } else {
344            int numListeners = mCallbacks.size();
345            for (int i = 0; i < numListeners; i++) {
346                if (!isRemoved(i)) {
347                    return false;
348                }
349            }
350            return true;
351        }
352    }
353
354    /**
355     * Removes all callbacks from the list.
356     */
357    public synchronized void clear() {
358        if (mNotificationLevel == 0) {
359            mCallbacks.clear();
360        } else if (!mCallbacks.isEmpty()) {
361            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
362                setRemovalBit(i);
363            }
364        }
365    }
366
367    public synchronized CallbackRegistry<C, T, A> clone() {
368        CallbackRegistry<C, T, A> clone = null;
369        try {
370            clone = (CallbackRegistry<C, T, A>) super.clone();
371            clone.mFirst64Removed = 0;
372            clone.mRemainderRemoved = null;
373            clone.mNotificationLevel = 0;
374            clone.mCallbacks = new ArrayList<C>();
375            final int numListeners = mCallbacks.size();
376            for (int i = 0; i < numListeners; i++) {
377                if (!isRemoved(i)) {
378                    clone.mCallbacks.add(mCallbacks.get(i));
379                }
380            }
381        } catch (CloneNotSupportedException e) {
382            e.printStackTrace();
383        }
384        return clone;
385    }
386
387    /**
388     * Class used to notify events from CallbackRegistry.
389     *
390     * @param <C> The callback type.
391     * @param <T> The notification sender type. Typically this is the containing class.
392     * @param <A> An opaque argument to pass to the notifier
393     */
394    public abstract static class NotifierCallback<C, T, A> {
395        /**
396         * Used to notify the callback.
397         *
398         * @param callback The callback to notify.
399         * @param sender The opaque sender object.
400         * @param arg The opaque notification parameter.
401         * @param arg2 An opaque argument passed in
402         *        {@link CallbackRegistry#notifyCallbacks}
403         * @see CallbackRegistry#CallbackRegistry(CallbackRegistry.NotifierCallback)
404         */
405        public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2);
406    }
407}
408