Widening and also NPE immediately if someone tries to add a null observer.
git-svn-id: https://samskivert.googlecode.com/svn/trunk@2482 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
@@ -29,15 +29,14 @@ import com.samskivert.util.StringUtil;
|
|||||||
import static com.samskivert.Log.log;
|
import static com.samskivert.Log.log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a simplified mechanism for maintaining a list of observers (or
|
* Provides a simplified mechanism for maintaining a list of observers (or listeners or whatever
|
||||||
* listeners or whatever you like to call them) and notifying those
|
* you like to call them) and notifying those observers when desired. Notification takes place
|
||||||
* observers when desired. Notification takes place through a {@link
|
* through a {@link ObserverOp} which allows the list maintainer to call an arbitrary method on its
|
||||||
* ObserverOp} which allows the list maintainer to call an arbitrary
|
* observers.
|
||||||
* method on its observers.
|
|
||||||
*
|
*
|
||||||
* <p> A couple of different usage patterns will help to illuminate. The
|
* <p> A couple of different usage patterns will help to illuminate. The first, where a new notify
|
||||||
* first, where a new notify operation is created every time the observers
|
* operation is created every time the observers are notified. This wins on brevity but loses on
|
||||||
* are notified. This wins on brevity but loses on performance.
|
* performance.
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* ...
|
* ...
|
||||||
@@ -53,8 +52,8 @@ import static com.samskivert.Log.log;
|
|||||||
* ...
|
* ...
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* The second where a singleton instance of the operation class is
|
* The second where a singleton instance of the operation class is maintained and is configured
|
||||||
* maintained and is configured each time notification is desired.
|
* each time notification is desired.
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* ...
|
* ...
|
||||||
@@ -86,38 +85,35 @@ import static com.samskivert.Log.log;
|
|||||||
*
|
*
|
||||||
* Bear in mind that this latter case is not thread safe.
|
* Bear in mind that this latter case is not thread safe.
|
||||||
*
|
*
|
||||||
* <p> Other usage patterns are most certainly conceivable, and hopefully
|
* <p> Other usage patterns are most certainly conceivable, and hopefully these two will give you a
|
||||||
* these two will give you a useful starting point for determining what is
|
* useful starting point for determining what is the most appropriate usage for your needs.
|
||||||
* the most appropriate usage for your needs.
|
|
||||||
*/
|
*/
|
||||||
public class ObserverList<T> extends ArrayList<T>
|
public class ObserverList<T> extends ArrayList<T>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Instances of this interface are used to apply methods to all
|
* Instances of this interface are used to apply methods to all observers in a list.
|
||||||
* observers in a list.
|
|
||||||
*/
|
*/
|
||||||
public static interface ObserverOp<T>
|
public static interface ObserverOp<T>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Called once for each observer in the list.
|
* Called once for each observer in the list.
|
||||||
*
|
*
|
||||||
* @return true if the observer should remain in the list, false
|
* @return true if the observer should remain in the list, false if it should be removed in
|
||||||
* if it should be removed in response to this application.
|
* response to this application.
|
||||||
*/
|
*/
|
||||||
public boolean apply (T observer);
|
public boolean apply (T observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A notification ordering policy indicating that the observers
|
/** A notification ordering policy indicating that the observers should be notified in the
|
||||||
* should be notified in the order they were added and that the
|
* order they were added and that the notification should be done on a snapshot of the
|
||||||
* notification should be done on a snapshot of the array. */
|
* array. */
|
||||||
public static final int SAFE_IN_ORDER_NOTIFY = 1;
|
public static final int SAFE_IN_ORDER_NOTIFY = 1;
|
||||||
|
|
||||||
/** A notification ordering policy wherein the observers are notified
|
/** A notification ordering policy wherein the observers are notified last to first so that
|
||||||
* last to first so that they can be removed during the notification
|
* they can be removed during the notification process and new observers added will not
|
||||||
* process and new observers added will not inadvertently be notified
|
* inadvertently be notified as well, but no copy of the observer list need be made. This will
|
||||||
* as well, but no copy of the observer list need be made. This will
|
* not work if observers are added or removed from arbitrary positions in the list during a
|
||||||
* not work if observers are added or removed from arbitrary positions
|
* notification call. */
|
||||||
* in the list during a notification call. */
|
|
||||||
public static final int FAST_UNSAFE_NOTIFY = 2;
|
public static final int FAST_UNSAFE_NOTIFY = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -148,11 +144,10 @@ public class ObserverList<T> extends ArrayList<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an empty observer list with the supplied notification
|
* Creates an empty observer list with the supplied notification policy and that only allows
|
||||||
* policy and that only allows observers to observe the list once.
|
* observers to observe the list once.
|
||||||
*
|
*
|
||||||
* @param notifyPolicy Either {@link #SAFE_IN_ORDER_NOTIFY} or {@link
|
* @param notifyPolicy Either {@link #SAFE_IN_ORDER_NOTIFY} or {@link #FAST_UNSAFE_NOTIFY}.
|
||||||
* #FAST_UNSAFE_NOTIFY}.
|
|
||||||
*/
|
*/
|
||||||
public ObserverList (int notifyPolicy)
|
public ObserverList (int notifyPolicy)
|
||||||
{
|
{
|
||||||
@@ -160,21 +155,16 @@ public class ObserverList<T> extends ArrayList<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an empty observer list with the supplied notification and
|
* Creates an empty observer list with the supplied notification and duplicate observer policy.
|
||||||
* duplicate observer policy.
|
|
||||||
*
|
*
|
||||||
* @param notifyPolicy Either {@link #SAFE_IN_ORDER_NOTIFY} or {@link
|
* @param notifyPolicy Either {@link #SAFE_IN_ORDER_NOTIFY} or {@link #FAST_UNSAFE_NOTIFY}.
|
||||||
* #FAST_UNSAFE_NOTIFY}.
|
* @param allowDups whether to allow observers to be added to the list more than once.
|
||||||
* @param allowDups whether to allow observers to be added to the list
|
|
||||||
* more than once.
|
|
||||||
*/
|
*/
|
||||||
public ObserverList (int notifyPolicy, boolean allowDups)
|
public ObserverList (int notifyPolicy, boolean allowDups)
|
||||||
{
|
{
|
||||||
// make sure the policy is valid
|
// make sure the policy is valid
|
||||||
if (notifyPolicy != SAFE_IN_ORDER_NOTIFY &&
|
if (notifyPolicy != SAFE_IN_ORDER_NOTIFY && notifyPolicy != FAST_UNSAFE_NOTIFY) {
|
||||||
notifyPolicy != FAST_UNSAFE_NOTIFY) {
|
throw new RuntimeException("Invalid notification policy [policy=" + notifyPolicy + "]");
|
||||||
throw new RuntimeException("Invalid notification policy " +
|
|
||||||
"[policy=" + notifyPolicy + "]");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_policy = notifyPolicy ;
|
_policy = notifyPolicy ;
|
||||||
@@ -184,6 +174,9 @@ public class ObserverList<T> extends ArrayList<T>
|
|||||||
@Override
|
@Override
|
||||||
public void add (int index, T element)
|
public void add (int index, T element)
|
||||||
{
|
{
|
||||||
|
if (element == null) {
|
||||||
|
throw new NullPointerException("Null observers not allowed.");
|
||||||
|
}
|
||||||
if (!isDuplicate(element)) {
|
if (!isDuplicate(element)) {
|
||||||
super.add(index, element);
|
super.add(index, element);
|
||||||
}
|
}
|
||||||
@@ -192,26 +185,31 @@ public class ObserverList<T> extends ArrayList<T>
|
|||||||
@Override
|
@Override
|
||||||
public boolean add (T element)
|
public boolean add (T element)
|
||||||
{
|
{
|
||||||
|
if (element == null) {
|
||||||
|
throw new NullPointerException("Null observers not allowed.");
|
||||||
|
}
|
||||||
return isDuplicate(element) ? false : super.add(element);
|
return isDuplicate(element) ? false : super.add(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean addAll (Collection<? extends T> c)
|
public boolean addAll (Collection<? extends T> c)
|
||||||
{
|
{
|
||||||
throw new UnsupportedOperationException(UNSUPPORTED_ADD_MESSAGE);
|
throw new UnsupportedOperationException(
|
||||||
|
"Observers may only be added via ObserverList.add(Object).");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean addAll (int index, Collection<? extends T> c)
|
public boolean addAll (int index, Collection<? extends T> c)
|
||||||
{
|
{
|
||||||
throw new UnsupportedOperationException(UNSUPPORTED_ADD_MESSAGE);
|
throw new UnsupportedOperationException(
|
||||||
|
"Observers may only be added via ObserverList.add(Object).");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int indexOf (Object element)
|
public int indexOf (Object element)
|
||||||
{
|
{
|
||||||
// indexOf and lastIndexOf are implemented using reference equality
|
// indexOf and lastIndexOf are implemented using reference equality so that contains() also
|
||||||
// so that contains() also uses reference equality.
|
// uses reference equality
|
||||||
for (int ii = 0, nn = size(); ii < nn; ii++) {
|
for (int ii = 0, nn = size(); ii < nn; ii++) {
|
||||||
if (element == get(ii)) {
|
if (element == get(ii)) {
|
||||||
return ii;
|
return ii;
|
||||||
@@ -223,8 +221,8 @@ public class ObserverList<T> extends ArrayList<T>
|
|||||||
@Override
|
@Override
|
||||||
public int lastIndexOf (Object element)
|
public int lastIndexOf (Object element)
|
||||||
{
|
{
|
||||||
// indexOf and lastIndexOf are implemented using reference equality
|
// indexOf and lastIndexOf are implemented using reference equality so that contains() also
|
||||||
// so that contains() also uses reference equality.
|
// uses reference equality
|
||||||
for (int ii = size() - 1; ii >= 0; ii--) {
|
for (int ii = size() - 1; ii >= 0; ii--) {
|
||||||
if (element == get(ii)) {
|
if (element == get(ii)) {
|
||||||
return ii;
|
return ii;
|
||||||
@@ -245,9 +243,8 @@ public class ObserverList<T> extends ArrayList<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the supplied observer operation to all observers in the
|
* Applies the supplied observer operation to all observers in the list in a manner conforming
|
||||||
* list in a manner conforming to the notification ordering policy
|
* to the notification ordering policy specified at construct time.
|
||||||
* specified at construct time.
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void apply (ObserverOp<T> obop)
|
public void apply (ObserverOp<T> obop)
|
||||||
@@ -257,12 +254,10 @@ public class ObserverList<T> extends ArrayList<T>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_policy == SAFE_IN_ORDER_NOTIFY) {
|
if (_policy == SAFE_IN_ORDER_NOTIFY) {
|
||||||
// if we have to notify our observers in order, we need to
|
// if we have to notify our observers in order, we need to create a snapshot of the
|
||||||
// create a snapshot of the observer array at the time we
|
// observer array at the time we start the notification to ensure that modifications to
|
||||||
// start the notification to ensure that modifications to the
|
// the array during notification don't hose us
|
||||||
// array during notification don't hose us
|
if (_snap == null || _snap.length < ocount || _snap.length > (ocount << 3)) {
|
||||||
if (_snap == null || _snap.length < ocount ||
|
|
||||||
_snap.length > (ocount << 3)) {
|
|
||||||
_snap = (T[])new Object[ocount];
|
_snap = (T[])new Object[ocount];
|
||||||
}
|
}
|
||||||
toArray(_snap);
|
toArray(_snap);
|
||||||
@@ -284,16 +279,14 @@ public class ObserverList<T> extends ArrayList<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true and issues a warning if this list does not allow duplicates
|
* Returns true and issues a warning if this list does not allow duplicates and the supplied
|
||||||
* and the supplied observer is already in the list. Returns false if the
|
* observer is already in the list. Returns false if the supplied observer is not a duplicate.
|
||||||
* supplied observer is not a duplicate.
|
|
||||||
*/
|
*/
|
||||||
protected boolean isDuplicate (T obs)
|
protected boolean isDuplicate (T obs)
|
||||||
{
|
{
|
||||||
// make sure we're not violating the list constraints
|
// make sure we're not violating the list constraints
|
||||||
if (!_allowDups && contains(obs)) {
|
if (!_allowDups && contains(obs)) {
|
||||||
log.warning("Observer attempted to observe list it's already " +
|
log.warning("Observer attempted to observe list it's already observing!", "obs", obs);
|
||||||
"observing! [obs=" + obs + "].");
|
|
||||||
Thread.dumpStack();
|
Thread.dumpStack();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -301,16 +294,15 @@ public class ObserverList<T> extends ArrayList<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the operation to the observer, catching and logging any
|
* Applies the operation to the observer, catching and logging any exceptions thrown in the
|
||||||
* exceptions thrown in the process.
|
* process.
|
||||||
*/
|
*/
|
||||||
protected static <T> boolean checkedApply (ObserverOp<T> obop, T obs)
|
protected static <T> boolean checkedApply (ObserverOp<T> obop, T obs)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return obop.apply(obs);
|
return obop.apply(obs);
|
||||||
} catch (Throwable thrown) {
|
} catch (Throwable thrown) {
|
||||||
log.warning("ObserverOp choked during notification [op=" + obop +
|
log.warning("ObserverOp choked during notification", "op", obop, "obs", obs, thrown);
|
||||||
", obs=" + StringUtil.safeToString(obs) + "].", thrown);
|
|
||||||
// if they booched it, definitely don't remove them
|
// if they booched it, definitely don't remove them
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -322,11 +314,7 @@ public class ObserverList<T> extends ArrayList<T>
|
|||||||
/** Whether to allow observers to observe more than once simultaneously. */
|
/** Whether to allow observers to observe more than once simultaneously. */
|
||||||
protected boolean _allowDups;
|
protected boolean _allowDups;
|
||||||
|
|
||||||
/** Used to avoid creating a new snapshot array every time we notify
|
/** Used to avoid creating a new snapshot array every time we notify our observers if the size
|
||||||
* our observers if the size has not changed. */
|
* has not changed. */
|
||||||
protected T[] _snap;
|
protected T[] _snap;
|
||||||
|
|
||||||
/** Message reported for unsupported <code>add()</code> variants. */
|
|
||||||
protected static final String UNSUPPORTED_ADD_MESSAGE =
|
|
||||||
"Observers may only be added via ObserverList.add(Object).";
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user