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