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  */
16 package android.databinding;
17 
18 import java.util.ArrayList;
19 import 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  */
44 public 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      */
CallbackRegistry(NotifierCallback<C, T, A> notifier)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      */
notifyCallbacks(T sender, int arg, A arg2)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      */
notifyFirst64(T sender, int arg, A arg2)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      */
notifyRecurse(T sender, int arg, A arg2)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      */
notifyRemainder(T sender, int arg, A arg2, int remainderIndex)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      */
notifyCallbacks(T sender, int arg, A arg2, final int startIndex, final int endIndex, final long bits)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      */
add(C callback)212     public synchronized void add(C callback) {
213         int index = mCallbacks.lastIndexOf(callback);
214         if (index < 0 || isRemoved(index)) {
215             mCallbacks.add(callback);
216         }
217     }
218 
219     /**
220      * Returns true if the callback at index has been marked for removal.
221      *
222      * @param index The index into mCallbacks to check.
223      * @return true if the callback at index has been marked for removal.
224      */
isRemoved(int index)225     private boolean isRemoved(int index) {
226         if (index < Long.SIZE) {
227             // It is in the first 64 callbacks, just check the bit.
228             final long bitMask = 1L << index;
229             return (mFirst64Removed & bitMask) != 0;
230         } else if (mRemainderRemoved == null) {
231             // It is after the first 64 callbacks, but nothing else was marked for removal.
232             return false;
233         } else {
234             final int maskIndex = (index / Long.SIZE) - 1;
235             if (maskIndex >= mRemainderRemoved.length) {
236                 // There are some items in mRemainderRemoved, but nothing at the given index.
237                 return false;
238             } else {
239                 // There is something marked for removal, so we have to check the bit.
240                 final long bits = mRemainderRemoved[maskIndex];
241                 final long bitMask = 1L << (index % Long.SIZE);
242                 return (bits & bitMask) != 0;
243             }
244         }
245     }
246 
247     /**
248      * Removes callbacks from startIndex to startIndex + Long.SIZE, based
249      * on the bits set in removed.
250      *
251      * @param startIndex The index into the mCallbacks to start removing callbacks.
252      * @param removed The bits indicating removal, where each bit is set for one callback
253      *                to be removed.
254      */
removeRemovedCallbacks(int startIndex, long removed)255     private void removeRemovedCallbacks(int startIndex, long removed) {
256         // The naive approach should be fine. There may be a better bit-twiddling approach.
257         final int endIndex = startIndex + Long.SIZE;
258 
259         long bitMask = 1L << (Long.SIZE - 1);
260         for (int i = endIndex - 1; i >= startIndex; i--) {
261             if ((removed & bitMask) != 0) {
262                 mCallbacks.remove(i);
263             }
264             bitMask >>>= 1;
265         }
266     }
267 
268     /**
269      * Remove a callback. This callback won't be notified after this call completes.
270      *
271      * @param callback The callback to remove.
272      */
remove(C callback)273     public synchronized void remove(C callback) {
274         if (mNotificationLevel == 0) {
275             mCallbacks.remove(callback);
276         } else {
277             int index = mCallbacks.lastIndexOf(callback);
278             if (index >= 0) {
279                 setRemovalBit(index);
280             }
281         }
282     }
283 
setRemovalBit(int index)284     private void setRemovalBit(int index) {
285         if (index < Long.SIZE) {
286             // It is in the first 64 callbacks, just check the bit.
287             final long bitMask = 1L << index;
288             mFirst64Removed |= bitMask;
289         } else {
290             final int remainderIndex = (index / Long.SIZE) - 1;
291             if (mRemainderRemoved == null) {
292                 mRemainderRemoved = new long[mCallbacks.size() / Long.SIZE];
293             } else if (mRemainderRemoved.length < remainderIndex) {
294                 // need to make it bigger
295                 long[] newRemainders = new long[mCallbacks.size() / Long.SIZE];
296                 System.arraycopy(mRemainderRemoved, 0, newRemainders, 0, mRemainderRemoved.length);
297                 mRemainderRemoved = newRemainders;
298             }
299             final long bitMask = 1L << (index % Long.SIZE);
300             mRemainderRemoved[remainderIndex] |= bitMask;
301         }
302     }
303 
304     /**
305      * Makes a copy of the registered callbacks and returns it.
306      *
307      * @return a copy of the registered callbacks.
308      */
copyCallbacks()309     public synchronized ArrayList<C> copyCallbacks() {
310         ArrayList<C> callbacks = new ArrayList<C>(mCallbacks.size());
311         int numListeners = mCallbacks.size();
312         for (int i = 0; i < numListeners; i++) {
313             if (!isRemoved(i)) {
314                 callbacks.add(mCallbacks.get(i));
315             }
316         }
317         return callbacks;
318     }
319 
320     /**
321      * Modifies <code>callbacks</code> to contain all callbacks in the CallbackRegistry.
322      *
323      * @param callbacks modified to contain all callbacks registered to receive events.
324      */
copyCallbacks(List<C> callbacks)325     public synchronized void copyCallbacks(List<C> callbacks) {
326         callbacks.clear();
327         int numListeners = mCallbacks.size();
328         for (int i = 0; i < numListeners; i++) {
329             if (!isRemoved(i)) {
330                 callbacks.add(mCallbacks.get(i));
331             }
332         }
333     }
334 
335     /**
336      * Returns true if there are no registered callbacks or false otherwise.
337      *
338      * @return true if there are no registered callbacks or false otherwise.
339      */
isEmpty()340     public synchronized boolean isEmpty() {
341         if (mCallbacks.isEmpty()) {
342             return true;
343         } else if (mNotificationLevel == 0) {
344             return false;
345         } else {
346             int numListeners = mCallbacks.size();
347             for (int i = 0; i < numListeners; i++) {
348                 if (!isRemoved(i)) {
349                     return false;
350                 }
351             }
352             return true;
353         }
354     }
355 
356     /**
357      * Removes all callbacks from the list.
358      */
clear()359     public synchronized void clear() {
360         if (mNotificationLevel == 0) {
361             mCallbacks.clear();
362         } else if (!mCallbacks.isEmpty()) {
363             for (int i = mCallbacks.size() - 1; i >= 0; i--) {
364                 setRemovalBit(i);
365             }
366         }
367     }
368 
369     /**
370      * @return A copy of the CallbackRegistry with all callbacks listening to both instances.
371      */
372     @SuppressWarnings("unchecked")
clone()373     public synchronized CallbackRegistry<C, T, A> clone() {
374         CallbackRegistry<C, T, A> clone = null;
375         try {
376             clone = (CallbackRegistry<C, T, A>) super.clone();
377             clone.mFirst64Removed = 0;
378             clone.mRemainderRemoved = null;
379             clone.mNotificationLevel = 0;
380             clone.mCallbacks = new ArrayList<C>();
381             final int numListeners = mCallbacks.size();
382             for (int i = 0; i < numListeners; i++) {
383                 if (!isRemoved(i)) {
384                     clone.mCallbacks.add(mCallbacks.get(i));
385                 }
386             }
387         } catch (CloneNotSupportedException e) {
388             e.printStackTrace();
389         }
390         return clone;
391     }
392 
393     /**
394      * Class used to notify events from CallbackRegistry.
395      *
396      * @param <C> The callback type.
397      * @param <T> The notification sender type. Typically this is the containing class.
398      * @param <A> An opaque argument to pass to the notifier
399      */
400     public abstract static class NotifierCallback<C, T, A> {
401         /**
402          * Called by CallbackRegistry during
403          * {@link CallbackRegistry#notifyCallbacks(Object, int, Object)}} to notify the callback.
404          *
405          * @param callback The callback to notify.
406          * @param sender The opaque sender object.
407          * @param arg The opaque notification parameter.
408          * @param arg2 An opaque argument passed in
409          *        {@link CallbackRegistry#notifyCallbacks}
410          * @see CallbackRegistry#CallbackRegistry(CallbackRegistry.NotifierCallback)
411          */
onNotifyCallback(C callback, T sender, int arg, A arg2)412         public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2);
413     }
414 }
415