• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         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      */
isRemoved(int index)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      */
removeRemovedCallbacks(int startIndex, long removed)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      */
remove(C callback)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 
setRemovalBit(int index)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      */
copyCallbacks()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      */
copyCallbacks(List<C> callbacks)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      */
isEmpty()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      */
clear()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")
clone()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          */
onNotifyCallback(C callback, T sender, int arg, A arg2)415         public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2);
416     }
417 }
418