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