am fa216411: Merge "Add CallbackRegistry." into mnc-dev
* commit 'fa21641122170777818ff838c1c19505313e83c3': Add CallbackRegistry.
This commit is contained in:
395
core/java/com/android/internal/util/CallbackRegistry.java
Normal file
395
core/java/com/android/internal/util/CallbackRegistry.java
Normal file
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.internal.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tracks callbacks for the event. This class supports reentrant modification
|
||||
* of the callbacks during notification without adversely disrupting notifications.
|
||||
* A common pattern for callbacks is to receive a notification and then remove
|
||||
* themselves. This class handles this behavior with constant memory under
|
||||
* most circumstances.
|
||||
*
|
||||
* <p>A subclass of {@link CallbackRegistry.NotifierCallback} must be passed to
|
||||
* the constructor to define how notifications should be called. That implementation
|
||||
* does the actual notification on the listener.</p>
|
||||
*
|
||||
* <p>This class supports only callbacks with at most two parameters.
|
||||
* Typically, these are the notification originator and a parameter, but these may
|
||||
* be used as required. If more than two parameters are required or primitive types
|
||||
* must be used, <code>A</code> should be some kind of containing structure that
|
||||
* the subclass may reuse between notifications.</p>
|
||||
*
|
||||
* @param <C> The callback type.
|
||||
* @param <T> The notification sender type. Typically this is the containing class.
|
||||
* @param <A> Opaque argument used to pass additional data beyond an int.
|
||||
*/
|
||||
public class CallbackRegistry<C, T, A> implements Cloneable {
|
||||
private static final String TAG = "CallbackRegistry";
|
||||
|
||||
/** An ordered collection of listeners waiting to be notified. */
|
||||
private List<C> mCallbacks = new ArrayList<C>();
|
||||
|
||||
/**
|
||||
* A bit flag for the first 64 listeners that are removed during notification.
|
||||
* The lowest significant bit corresponds to the 0th index into mCallbacks.
|
||||
* For a small number of callbacks, no additional array of objects needs to
|
||||
* be allocated.
|
||||
*/
|
||||
private long mFirst64Removed = 0x0;
|
||||
|
||||
/**
|
||||
* Bit flags for the remaining callbacks that are removed during notification.
|
||||
* When there are more than 64 callbacks and one is marked for removal, a dynamic
|
||||
* array of bits are allocated for the callbacks.
|
||||
*/
|
||||
private long[] mRemainderRemoved;
|
||||
|
||||
/**
|
||||
* The reentrancy level of the notification. When we notify a callback, it may cause
|
||||
* further notifications. The reentrancy level must be tracked to let us clean up
|
||||
* the callback state when all notifications have been processed.
|
||||
*/
|
||||
private int mNotificationLevel;
|
||||
|
||||
/** The notification mechanism for notifying an event. */
|
||||
private final NotifierCallback<C, T, A> mNotifier;
|
||||
|
||||
/**
|
||||
* Creates an EventRegistry that notifies the event with notifier.
|
||||
* @param notifier The class to use to notify events.
|
||||
*/
|
||||
public CallbackRegistry(NotifierCallback<C, T, A> notifier) {
|
||||
mNotifier = notifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all callbacks.
|
||||
*
|
||||
* @param sender The originator. This is an opaque parameter passed to
|
||||
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
|
||||
* @param arg An opaque parameter passed to
|
||||
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
|
||||
* @param arg2 An opaque parameter passed to
|
||||
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
|
||||
*/
|
||||
public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
|
||||
mNotificationLevel++;
|
||||
notifyRecurseLocked(sender, arg, arg2);
|
||||
mNotificationLevel--;
|
||||
if (mNotificationLevel == 0) {
|
||||
if (mRemainderRemoved != null) {
|
||||
for (int i = mRemainderRemoved.length - 1; i >= 0; i--) {
|
||||
final long removedBits = mRemainderRemoved[i];
|
||||
if (removedBits != 0) {
|
||||
removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits);
|
||||
mRemainderRemoved[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mFirst64Removed != 0) {
|
||||
removeRemovedCallbacks(0, mFirst64Removed);
|
||||
mFirst64Removed = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify up to the first Long.SIZE callbacks that don't have a bit set in <code>removed</code>.
|
||||
*
|
||||
* @param sender The originator. This is an opaque parameter passed to
|
||||
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
|
||||
* @param arg An opaque parameter passed to
|
||||
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
|
||||
* @param arg2 An opaque parameter passed to
|
||||
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
|
||||
*/
|
||||
private void notifyFirst64Locked(T sender, int arg, A arg2) {
|
||||
final int maxNotified = Math.min(Long.SIZE, mCallbacks.size());
|
||||
notifyCallbacksLocked(sender, arg, arg2, 0, maxNotified, mFirst64Removed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all callbacks using a recursive algorithm to avoid allocating on the heap.
|
||||
* This part captures the callbacks beyond Long.SIZE that have no bits allocated for
|
||||
* removal before it recurses into {@link #notifyRemainderLocked(Object, int, A, int)}.
|
||||
* <p>
|
||||
* Recursion is used to avoid allocating temporary state on the heap. Each stack has one
|
||||
* long (64 callbacks) worth of information of which has been removed.
|
||||
*
|
||||
* @param sender The originator. This is an opaque parameter passed to
|
||||
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
|
||||
* @param arg An opaque parameter passed to
|
||||
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
|
||||
* @param arg2 An opaque parameter passed to
|
||||
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
|
||||
*/
|
||||
private void notifyRecurseLocked(T sender, int arg, A arg2) {
|
||||
final int callbackCount = mCallbacks.size();
|
||||
final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1;
|
||||
|
||||
// Now we've got all callbacks that have no mRemainderRemoved value, so notify the
|
||||
// others.
|
||||
notifyRemainderLocked(sender, arg, arg2, remainderIndex);
|
||||
|
||||
// notifyRemainderLocked notifies all at maxIndex, so we'd normally start at maxIndex + 1
|
||||
// However, we must also keep track of those in mFirst64Removed, so we add 2 instead:
|
||||
final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE;
|
||||
|
||||
// The remaining have no bit set
|
||||
notifyCallbacksLocked(sender, arg, arg2, startCallbackIndex, callbackCount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify callbacks that have mRemainderRemoved bits set for remainderIndex. If
|
||||
* remainderIndex is -1, the first 64 will be notified instead.
|
||||
*
|
||||
* @param sender The originator. This is an opaque parameter passed to
|
||||
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
|
||||
* @param arg An opaque parameter passed to
|
||||
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
|
||||
* @param arg2 An opaque parameter passed to
|
||||
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
|
||||
* @param remainderIndex The index into mRemainderRemoved that should be notified.
|
||||
*/
|
||||
private void notifyRemainderLocked(T sender, int arg, A arg2, int remainderIndex) {
|
||||
if (remainderIndex < 0) {
|
||||
notifyFirst64Locked(sender, arg, arg2);
|
||||
} else {
|
||||
final long bits = mRemainderRemoved[remainderIndex];
|
||||
final int startIndex = (remainderIndex + 1) * Long.SIZE;
|
||||
final int endIndex = Math.min(mCallbacks.size(), startIndex + Long.SIZE);
|
||||
notifyRemainderLocked(sender, arg, arg2, remainderIndex - 1);
|
||||
notifyCallbacksLocked(sender, arg, arg2, startIndex, endIndex, bits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify callbacks from startIndex to endIndex, using bits as the bit status
|
||||
* for whether they have been removed or not. bits should be from mRemainderRemoved or
|
||||
* mFirst64Removed. bits set to 0 indicates that all callbacks from startIndex to
|
||||
* endIndex should be notified.
|
||||
*
|
||||
* @param sender The originator. This is an opaque parameter passed to
|
||||
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
|
||||
* @param arg An opaque parameter passed to
|
||||
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
|
||||
* @param arg2 An opaque parameter passed to
|
||||
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
|
||||
* @param startIndex The index into the mCallbacks to start notifying.
|
||||
* @param endIndex One past the last index into mCallbacks to notify.
|
||||
* @param bits A bit field indicating which callbacks have been removed and shouldn't
|
||||
* be notified.
|
||||
*/
|
||||
private void notifyCallbacksLocked(T sender, int arg, A arg2, final int startIndex,
|
||||
final int endIndex, final long bits) {
|
||||
long bitMask = 1;
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
if ((bits & bitMask) == 0) {
|
||||
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
|
||||
}
|
||||
bitMask <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to be notified. If the callback is already in the list, another won't
|
||||
* be added. This does not affect current notifications.
|
||||
* @param callback The callback to add.
|
||||
*/
|
||||
public synchronized void add(C callback) {
|
||||
int index = mCallbacks.lastIndexOf(callback);
|
||||
if (index < 0 || isRemovedLocked(index)) {
|
||||
mCallbacks.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the callback at index has been marked for removal.
|
||||
*
|
||||
* @param index The index into mCallbacks to check.
|
||||
* @return true if the callback at index has been marked for removal.
|
||||
*/
|
||||
private boolean isRemovedLocked(int index) {
|
||||
if (index < Long.SIZE) {
|
||||
// It is in the first 64 callbacks, just check the bit.
|
||||
final long bitMask = 1L << index;
|
||||
return (mFirst64Removed & bitMask) != 0;
|
||||
} else if (mRemainderRemoved == null) {
|
||||
// It is after the first 64 callbacks, but nothing else was marked for removal.
|
||||
return false;
|
||||
} else {
|
||||
final int maskIndex = (index / Long.SIZE) - 1;
|
||||
if (maskIndex >= mRemainderRemoved.length) {
|
||||
// There are some items in mRemainderRemoved, but nothing at the given index.
|
||||
return false;
|
||||
} else {
|
||||
// There is something marked for removal, so we have to check the bit.
|
||||
final long bits = mRemainderRemoved[maskIndex];
|
||||
final long bitMask = 1L << (index % Long.SIZE);
|
||||
return (bits & bitMask) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes callbacks from startIndex to startIndex + Long.SIZE, based
|
||||
* on the bits set in removed.
|
||||
* @param startIndex The index into the mCallbacks to start removing callbacks.
|
||||
* @param removed The bits indicating removal, where each bit is set for one callback
|
||||
* to be removed.
|
||||
*/
|
||||
private void removeRemovedCallbacks(int startIndex, long removed) {
|
||||
// The naive approach should be fine. There may be a better bit-twiddling approach.
|
||||
final int endIndex = startIndex + Long.SIZE;
|
||||
|
||||
long bitMask = 1L << (Long.SIZE - 1);
|
||||
for (int i = endIndex - 1; i >= startIndex; i--) {
|
||||
if ((removed & bitMask) != 0) {
|
||||
mCallbacks.remove(i);
|
||||
}
|
||||
bitMask >>>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a callback. This callback won't be notified after this call completes.
|
||||
* @param callback The callback to remove.
|
||||
*/
|
||||
public synchronized void remove(C callback) {
|
||||
if (mNotificationLevel == 0) {
|
||||
mCallbacks.remove(callback);
|
||||
} else {
|
||||
int index = mCallbacks.lastIndexOf(callback);
|
||||
if (index >= 0) {
|
||||
setRemovalBitLocked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setRemovalBitLocked(int index) {
|
||||
if (index < Long.SIZE) {
|
||||
// It is in the first 64 callbacks, just check the bit.
|
||||
final long bitMask = 1L << index;
|
||||
mFirst64Removed |= bitMask;
|
||||
} else {
|
||||
final int remainderIndex = (index / Long.SIZE) - 1;
|
||||
if (mRemainderRemoved == null) {
|
||||
mRemainderRemoved = new long[mCallbacks.size() / Long.SIZE];
|
||||
} else if (mRemainderRemoved.length < remainderIndex) {
|
||||
// need to make it bigger
|
||||
long[] newRemainders = new long[mCallbacks.size() / Long.SIZE];
|
||||
System.arraycopy(mRemainderRemoved, 0, newRemainders, 0, mRemainderRemoved.length);
|
||||
mRemainderRemoved = newRemainders;
|
||||
}
|
||||
final long bitMask = 1L << (index % Long.SIZE);
|
||||
mRemainderRemoved[remainderIndex] |= bitMask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a copy of the registered callbacks and returns it.
|
||||
*
|
||||
* @return a copy of the registered callbacks.
|
||||
*/
|
||||
public synchronized ArrayList<C> copyListeners() {
|
||||
ArrayList<C> callbacks = new ArrayList<C>(mCallbacks.size());
|
||||
int numListeners = mCallbacks.size();
|
||||
for (int i = 0; i < numListeners; i++) {
|
||||
if (!isRemovedLocked(i)) {
|
||||
callbacks.add(mCallbacks.get(i));
|
||||
}
|
||||
}
|
||||
return callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there are no registered callbacks or false otherwise.
|
||||
*
|
||||
* @return true if there are no registered callbacks or false otherwise.
|
||||
*/
|
||||
public synchronized boolean isEmpty() {
|
||||
if (mCallbacks.isEmpty()) {
|
||||
return true;
|
||||
} else if (mNotificationLevel == 0) {
|
||||
return false;
|
||||
} else {
|
||||
int numListeners = mCallbacks.size();
|
||||
for (int i = 0; i < numListeners; i++) {
|
||||
if (!isRemovedLocked(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all callbacks from the list.
|
||||
*/
|
||||
public synchronized void clear() {
|
||||
if (mNotificationLevel == 0) {
|
||||
mCallbacks.clear();
|
||||
} else if (!mCallbacks.isEmpty()) {
|
||||
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
|
||||
setRemovalBitLocked(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized CallbackRegistry<C, T, A> clone() {
|
||||
CallbackRegistry<C, T, A> clone = null;
|
||||
try {
|
||||
clone = (CallbackRegistry<C, T, A>) super.clone();
|
||||
clone.mFirst64Removed = 0;
|
||||
clone.mRemainderRemoved = null;
|
||||
clone.mNotificationLevel = 0;
|
||||
clone.mCallbacks = new ArrayList<C>();
|
||||
final int numListeners = mCallbacks.size();
|
||||
for (int i = 0; i < numListeners; i++) {
|
||||
if (!isRemovedLocked(i)) {
|
||||
clone.mCallbacks.add(mCallbacks.get(i));
|
||||
}
|
||||
}
|
||||
} catch (CloneNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class used to notify events from CallbackRegistry.
|
||||
*
|
||||
* @param <C> The callback type.
|
||||
* @param <T> The notification sender type. Typically this is the containing class.
|
||||
* @param <A> An opaque argument to pass to the notifier
|
||||
*/
|
||||
public abstract static class NotifierCallback<C, T, A> {
|
||||
/**
|
||||
* Used to notify the callback.
|
||||
*
|
||||
* @param callback The callback to notify.
|
||||
* @param sender The opaque sender object.
|
||||
* @param arg The opaque notification parameter.
|
||||
* @param arg2 An opaque argument passed in
|
||||
* {@link CallbackRegistry#notifyCallbacks}
|
||||
* @see CallbackRegistry#CallbackRegistry(CallbackRegistry.NotifierCallback)
|
||||
*/
|
||||
public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.internal.util;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class CallbackRegistryTest extends TestCase {
|
||||
|
||||
final Integer callback1 = 1;
|
||||
final Integer callback2 = 2;
|
||||
final Integer callback3 = 3;
|
||||
CallbackRegistry<Integer, CallbackRegistryTest, Integer> registry;
|
||||
int notify1;
|
||||
int notify2;
|
||||
int notify3;
|
||||
int[] deepNotifyCount = new int[300];
|
||||
Integer argValue;
|
||||
|
||||
private void addNotifyCount(Integer callback) {
|
||||
if (callback == callback1) {
|
||||
notify1++;
|
||||
} else if (callback == callback2) {
|
||||
notify2++;
|
||||
} else if (callback == callback3) {
|
||||
notify3++;
|
||||
}
|
||||
deepNotifyCount[callback]++;
|
||||
}
|
||||
|
||||
public void testAddListener() {
|
||||
CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
|
||||
new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
|
||||
@Override
|
||||
public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
|
||||
int arg, Integer arg2) {
|
||||
}
|
||||
};
|
||||
registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
|
||||
Integer callback = 0;
|
||||
|
||||
assertNotNull(registry.copyListeners());
|
||||
assertEquals(0, registry.copyListeners().size());
|
||||
|
||||
registry.add(callback);
|
||||
ArrayList<Integer> callbacks = registry.copyListeners();
|
||||
assertEquals(1, callbacks.size());
|
||||
assertEquals(callback, callbacks.get(0));
|
||||
|
||||
registry.add(callback);
|
||||
callbacks = registry.copyListeners();
|
||||
assertEquals(1, callbacks.size());
|
||||
assertEquals(callback, callbacks.get(0));
|
||||
|
||||
Integer otherListener = 1;
|
||||
registry.add(otherListener);
|
||||
callbacks = registry.copyListeners();
|
||||
assertEquals(2, callbacks.size());
|
||||
assertEquals(callback, callbacks.get(0));
|
||||
assertEquals(otherListener, callbacks.get(1));
|
||||
|
||||
registry.remove(callback);
|
||||
registry.add(callback);
|
||||
callbacks = registry.copyListeners();
|
||||
assertEquals(2, callbacks.size());
|
||||
assertEquals(callback, callbacks.get(1));
|
||||
assertEquals(otherListener, callbacks.get(0));
|
||||
}
|
||||
|
||||
public void testSimpleNotify() {
|
||||
CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
|
||||
new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
|
||||
@Override
|
||||
public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
|
||||
int arg1, Integer arg) {
|
||||
assertEquals(arg1, (int) arg);
|
||||
addNotifyCount(callback);
|
||||
argValue = arg;
|
||||
}
|
||||
};
|
||||
registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
|
||||
registry.add(callback2);
|
||||
Integer arg = 1;
|
||||
registry.notifyCallbacks(this, arg, arg);
|
||||
assertEquals(arg, argValue);
|
||||
assertEquals(1, notify2);
|
||||
}
|
||||
|
||||
public void testRemoveWhileNotifying() {
|
||||
CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
|
||||
new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
|
||||
@Override
|
||||
public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
|
||||
int arg1, Integer arg) {
|
||||
addNotifyCount(callback);
|
||||
if (callback == callback1) {
|
||||
registry.remove(callback1);
|
||||
registry.remove(callback2);
|
||||
}
|
||||
}
|
||||
};
|
||||
registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
|
||||
registry.add(callback1);
|
||||
registry.add(callback2);
|
||||
registry.add(callback3);
|
||||
registry.notifyCallbacks(this, 0, null);
|
||||
assertEquals(1, notify1);
|
||||
assertEquals(1, notify2);
|
||||
assertEquals(1, notify3);
|
||||
|
||||
ArrayList<Integer> callbacks = registry.copyListeners();
|
||||
assertEquals(1, callbacks.size());
|
||||
assertEquals(callback3, callbacks.get(0));
|
||||
}
|
||||
|
||||
public void testDeepRemoveWhileNotifying() {
|
||||
CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
|
||||
new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
|
||||
@Override
|
||||
public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
|
||||
int arg1, Integer arg) {
|
||||
addNotifyCount(callback);
|
||||
registry.remove(callback);
|
||||
registry.notifyCallbacks(CallbackRegistryTest.this, arg1, null);
|
||||
}
|
||||
};
|
||||
registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
|
||||
registry.add(callback1);
|
||||
registry.add(callback2);
|
||||
registry.add(callback3);
|
||||
registry.notifyCallbacks(this, 0, null);
|
||||
assertEquals(1, notify1);
|
||||
assertEquals(2, notify2);
|
||||
assertEquals(3, notify3);
|
||||
|
||||
ArrayList<Integer> callbacks = registry.copyListeners();
|
||||
assertEquals(0, callbacks.size());
|
||||
}
|
||||
|
||||
public void testAddRemovedListener() {
|
||||
|
||||
CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
|
||||
new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
|
||||
@Override
|
||||
public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
|
||||
int arg1, Integer arg) {
|
||||
addNotifyCount(callback);
|
||||
if (callback == callback1) {
|
||||
registry.remove(callback2);
|
||||
} else if (callback == callback3) {
|
||||
registry.add(callback2);
|
||||
}
|
||||
}
|
||||
};
|
||||
registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
|
||||
|
||||
registry.add(callback1);
|
||||
registry.add(callback2);
|
||||
registry.add(callback3);
|
||||
registry.notifyCallbacks(this, 0, null);
|
||||
|
||||
ArrayList<Integer> callbacks = registry.copyListeners();
|
||||
assertEquals(3, callbacks.size());
|
||||
assertEquals(callback1, callbacks.get(0));
|
||||
assertEquals(callback3, callbacks.get(1));
|
||||
assertEquals(callback2, callbacks.get(2));
|
||||
assertEquals(1, notify1);
|
||||
assertEquals(1, notify2);
|
||||
assertEquals(1, notify3);
|
||||
}
|
||||
|
||||
public void testVeryDeepRemoveWhileNotifying() {
|
||||
final Integer[] callbacks = new Integer[deepNotifyCount.length];
|
||||
for (int i = 0; i < callbacks.length; i++) {
|
||||
callbacks[i] = i;
|
||||
}
|
||||
CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
|
||||
new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
|
||||
@Override
|
||||
public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
|
||||
int arg1, Integer arg) {
|
||||
addNotifyCount(callback);
|
||||
registry.remove(callback);
|
||||
registry.remove(callbacks[callbacks.length - callback - 1]);
|
||||
registry.notifyCallbacks(CallbackRegistryTest.this, arg1, null);
|
||||
}
|
||||
};
|
||||
registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
|
||||
for (int i = 0; i < callbacks.length; i++) {
|
||||
registry.add(callbacks[i]);
|
||||
}
|
||||
registry.notifyCallbacks(this, 0, null);
|
||||
for (int i = 0; i < deepNotifyCount.length; i++) {
|
||||
int expectedCount = Math.min(i + 1, deepNotifyCount.length - i);
|
||||
assertEquals(expectedCount, deepNotifyCount[i]);
|
||||
}
|
||||
|
||||
ArrayList<Integer> callbackList = registry.copyListeners();
|
||||
assertEquals(0, callbackList.size());
|
||||
}
|
||||
|
||||
public void testClear() {
|
||||
CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
|
||||
new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
|
||||
@Override
|
||||
public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
|
||||
int arg1, Integer arg) {
|
||||
addNotifyCount(callback);
|
||||
}
|
||||
};
|
||||
registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
|
||||
for (int i = 0; i < deepNotifyCount.length; i++) {
|
||||
registry.add(i);
|
||||
}
|
||||
registry.clear();
|
||||
|
||||
ArrayList<Integer> callbackList = registry.copyListeners();
|
||||
assertEquals(0, callbackList.size());
|
||||
|
||||
registry.notifyCallbacks(this, 0, null);
|
||||
for (int i = 0; i < deepNotifyCount.length; i++) {
|
||||
assertEquals(0, deepNotifyCount[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void testNestedClear() {
|
||||
CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
|
||||
new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
|
||||
@Override
|
||||
public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
|
||||
int arg1, Integer arg) {
|
||||
addNotifyCount(callback);
|
||||
registry.clear();
|
||||
}
|
||||
};
|
||||
registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
|
||||
for (int i = 0; i < deepNotifyCount.length; i++) {
|
||||
registry.add(i);
|
||||
}
|
||||
registry.notifyCallbacks(this, 0, null);
|
||||
for (int i = 0; i < deepNotifyCount.length; i++) {
|
||||
assertEquals(1, deepNotifyCount[i]);
|
||||
}
|
||||
|
||||
ArrayList<Integer> callbackList = registry.copyListeners();
|
||||
assertEquals(0, callbackList.size());
|
||||
}
|
||||
|
||||
public void testIsEmpty() throws Exception {
|
||||
CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
|
||||
new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
|
||||
@Override
|
||||
public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
|
||||
int arg, Integer arg2) {
|
||||
}
|
||||
};
|
||||
registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
|
||||
Integer callback = 0;
|
||||
|
||||
assertTrue(registry.isEmpty());
|
||||
registry.add(callback);
|
||||
assertFalse(registry.isEmpty());
|
||||
}
|
||||
|
||||
public void testClone() throws Exception {
|
||||
CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
|
||||
new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
|
||||
@Override
|
||||
public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
|
||||
int arg, Integer arg2) {
|
||||
}
|
||||
};
|
||||
registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
|
||||
|
||||
assertTrue(registry.isEmpty());
|
||||
CallbackRegistry<Integer, CallbackRegistryTest, Integer> registry2 = registry.clone();
|
||||
Integer callback = 0;
|
||||
registry.add(callback);
|
||||
assertFalse(registry.isEmpty());
|
||||
assertTrue(registry2.isEmpty());
|
||||
registry2 = registry.clone();
|
||||
assertFalse(registry2.isEmpty());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user