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