Home | History | Annotate | Download | only in databinding
      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      */
     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