diff --git a/src/main/java/com/samskivert/util/ObserverList.java b/src/main/java/com/samskivert/util/ObserverList.java index f43711a9..fb35b237 100644 --- a/src/main/java/com/samskivert/util/ObserverList.java +++ b/src/main/java/com/samskivert/util/ObserverList.java @@ -23,6 +23,9 @@ package com.samskivert.util; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import static com.samskivert.Log.log; @@ -86,7 +89,7 @@ import static com.samskivert.Log.log; *

Other usage patterns are most certainly conceivable, and hopefully these two will give you a * useful starting point for determining what is the most appropriate usage for your needs. */ -public class ObserverList extends ArrayList +public abstract class ObserverList { /** * Instances of this interface are used to apply methods to all observers in a list. @@ -102,17 +105,26 @@ public class ObserverList extends ArrayList public boolean apply (T observer); } - /** A notification ordering policy indicating that the observers should be notified in the - * order they were added and that the notification should be done on a snapshot of the - * array. */ - public static final int SAFE_IN_ORDER_NOTIFY = 1; + /** Notification policies. */ + public enum Policy { + /** A notification ordering policy indicating that the observers should be notified in the + * order they were added and that the notification should be done on a snapshot of the + * array. */ + SAFE_IN_ORDER, - /** A notification ordering policy wherein the observers are notified last to first so that - * they can be removed during the notification process and new observers added will not - * inadvertently be notified 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 - * notification call. */ - public static final int FAST_UNSAFE_NOTIFY = 2; + /** A notification ordering policy wherein the observers are notified last to first so that + * they can be removed during the notification process and new observers added will not + * inadvertently be notified 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 notification call. */ + FAST_UNSAFE; + }; + + /** @deprecated Use {@link Policy.SAFE_IN_ORDER}. */ + @Deprecated public static final Policy SAFE_IN_ORDER_NOTIFY = Policy.SAFE_IN_ORDER; + + /** @deprecated Use {@link Policy.FAST_UNSAFE}. */ + @Deprecated public static final Policy FAST_UNSAFE_NOTIFY = Policy.FAST_UNSAFE; /** * A convenience method for creating an observer list that avoids duplicating the type @@ -120,7 +132,7 @@ public class ObserverList extends ArrayList */ public static ObserverList newSafeInOrder () { - return new ObserverList(SAFE_IN_ORDER_NOTIFY); + return newList(Policy.SAFE_IN_ORDER, false); } /** @@ -129,166 +141,146 @@ public class ObserverList extends ArrayList */ public static ObserverList newFastUnsafe () { - return new ObserverList(FAST_UNSAFE_NOTIFY); + return newList(Policy.FAST_UNSAFE, false); } /** * A convenience method for creating an observer list that avoids duplicating the type * parameter on the right hand side. */ - public static ObserverList newList (int notifyPolicy, boolean allowDups) + public static ObserverList newList (Policy notifyPolicy, boolean allowDups) { - return new ObserverList(notifyPolicy, allowDups); + return new Impl(notifyPolicy, allowDups); } /** - * Creates an empty observer list with the supplied notification policy and that only allows - * observers to observe the list once. - * - * @param notifyPolicy Either {@link #SAFE_IN_ORDER_NOTIFY} or {@link #FAST_UNSAFE_NOTIFY}. + * Adds an observer at the specified index. + * @return true if the observer was added, false if it was already in the list. */ - public ObserverList (int notifyPolicy) - { - this(notifyPolicy, false); - } + public abstract boolean add (int index, T element); /** - * Creates an empty observer list with the supplied notification and duplicate observer policy. - * - * @param notifyPolicy Either {@link #SAFE_IN_ORDER_NOTIFY} or {@link #FAST_UNSAFE_NOTIFY}. - * @param allowDups whether to allow observers to be added to the list more than once. + * Adds an observer to the end of the list. + * @return true if the observer was added, false if it was already in the list. */ - public ObserverList (int notifyPolicy, boolean allowDups) - { - // make sure the policy is valid - if (notifyPolicy != SAFE_IN_ORDER_NOTIFY && notifyPolicy != FAST_UNSAFE_NOTIFY) { - throw new RuntimeException("Invalid notification policy [policy=" + notifyPolicy + "]"); - } + public abstract boolean add (T element); - _policy = notifyPolicy ; - _allowDups = allowDups; - } - - @Override - public void add (int index, T element) - { - if (element == null) { - throw new NullPointerException("Null observers not allowed."); - } - if (!isDuplicate(element)) { - super.add(index, element); - } - } - - @Override - public boolean add (T element) - { - if (element == null) { - throw new NullPointerException("Null observers not allowed."); - } - return isDuplicate(element) ? false : super.add(element); - } - - @Override - public boolean addAll (Collection c) - { - throw new UnsupportedOperationException( - "Observers may only be added via ObserverList.add(Object)."); - } - - @Override - public boolean addAll (int index, Collection c) - { - throw new UnsupportedOperationException( - "Observers may only be added via ObserverList.add(Object)."); - } - - @Override - public int indexOf (Object element) - { - // indexOf and lastIndexOf are implemented using reference equality so that contains() also - // uses reference equality - for (int ii = 0, nn = size(); ii < nn; ii++) { - if (element == get(ii)) { - return ii; - } - } - return -1; - } - - @Override - public int lastIndexOf (Object element) - { - // indexOf and lastIndexOf are implemented using reference equality so that contains() also - // uses reference equality - for (int ii = size() - 1; ii >= 0; ii--) { - if (element == get(ii)) { - return ii; - } - } - return -1; - } - - @Override - public boolean remove (Object element) - { - int dex = indexOf(element); - if (dex == -1) { - return false; // not found - } - remove(dex); - return true; - } + /** + * Removes the specified observer from the list. + * @return true if the observer was located and removed, false if it does not exist. + */ + public abstract boolean remove (T element); /** * Applies the supplied observer operation to all observers in the list in a manner conforming * to the notification ordering policy specified at construct time. */ - @SuppressWarnings("unchecked") - public void apply (ObserverOp obop) - { - int ocount = size(); - if (ocount == 0) { - return; - } - if (_policy == SAFE_IN_ORDER_NOTIFY) { - // if we have to notify our observers in order, we need to create a snapshot of the - // observer array at the time we start the notification to ensure that modifications to - // the array during notification don't hose us - if (_snap == null || _snap.length < ocount || _snap.length > (ocount << 3)) { - _snap = (T[])new Object[ocount]; - } - toArray(_snap); - for (int ii = 0; ii < ocount; ii++) { - if (!checkedApply(obop, _snap[ii])) { - remove(_snap[ii]); - } - } - // clear out the snapshot so its contents can be gc'd - Arrays.fill(_snap, null); - - } else if (_policy == FAST_UNSAFE_NOTIFY) { - for (int ii = ocount-1; ii >= 0; ii--) { - if (!checkedApply(obop, get(ii))) { - remove(ii); - } - } - } - } + public abstract void apply (ObserverOp obop); /** - * Returns true and issues a warning if this list does not allow duplicates and the supplied - * observer is already in the list. Returns false if the supplied observer is not a duplicate. + * Returns the number of observers in this list. */ - protected boolean isDuplicate (T obs) - { - // make sure we're not violating the list constraints - if (!_allowDups && contains(obs)) { - log.warning("Observer attempted to observe list it's already observing!", "obs", obs, - new Exception()); + public abstract int size (); + + /** + * Clears all observers from the list. + */ + public abstract void clear (); + + protected static class Impl extends ObserverList { + protected Impl (Policy notifyPolicy, boolean allowDups) { + _policy = notifyPolicy; + _allowDups = allowDups; + _list = (_policy == Policy.SAFE_IN_ORDER) ? + new CopyOnWriteArrayList() : new ArrayList(); + } + + @Override public boolean add (int index, T element) { + if (element == null) throw new NullPointerException("Null observers not allowed."); + if (isDuplicate(element)) return false; + _list.add(index, element); return true; } - return false; + + @Override public boolean add (T element) { + if (element == null) throw new NullPointerException("Null observers not allowed."); + if (isDuplicate(element)) return false; + _list.add(element); + return true; + } + + @Override public boolean remove (T element) { + int idx = indexOf(element); + if (idx < 0) return false; + _list.remove(idx); + return true; + } + + @Override public void apply (ObserverOp obop) { + switch (_policy) { + case SAFE_IN_ORDER: + // our copy on write array list will prevent us from getting hosed if modifications + // take place during iteration + Iterator iter = _list.iterator(); + for (int ii = 0; iter.hasNext(); ii++) { + T elem = iter.next(); + if (!checkedApply(obop, elem)) { + // can't remove via COWArrayList iterator, and to be totally safe (because + // we don't know if any additions or removals have taken place while we + // were iterating), we have to scan the whole list to locate this element + // so that we can remove it + remove(elem); + } + } + break; + + case FAST_UNSAFE: + for (int ii = _list.size()-1; ii >= 0; ii--) { + if (!checkedApply(obop, _list.get(ii))) { + _list.remove(ii); + } + } + break; + } + } + + @Override public int size () { + return _list.size(); + } + + @Override public void clear () { + _list.clear(); + } + + /** Used to determine whether an element is in the list. */ + protected int indexOf (T element) { + for (int ii = 0, ll = _list.size(); ii < ll; ii++) { + if (_list.get(ii) == element) return ii; + } + return -1; + } + + /** Returns true and issues a warning if this list does not allow duplicates and the + * supplied observer is already in the list. Returns false if the supplied observer is not + * a duplicate. */ + protected boolean isDuplicate (T obs) { + // make sure we're not violating the list constraints + if (!_allowDups && (indexOf(obs) >= 0)) { + log.warning("Observer attempted to observe list it's already observing!", "obs", obs, + new Exception()); + return true; + } + return false; + } + + /** The notification policy. */ + protected Policy _policy; + + /** Whether to allow observers to observe more than once simultaneously. */ + protected boolean _allowDups; + + /** Our list of observers. */ + protected List _list; } /** @@ -305,14 +297,4 @@ public class ObserverList extends ArrayList return true; } } - - /** The notification policy. */ - protected int _policy; - - /** Whether to allow observers to observe more than once simultaneously. */ - protected boolean _allowDups; - - /** Used to avoid creating a new snapshot array every time we notify our observers if the size - * has not changed. */ - protected T[] _snap; } diff --git a/src/main/java/com/samskivert/util/ResultListenerList.java b/src/main/java/com/samskivert/util/ResultListenerList.java index 78c797d8..a5a38b14 100644 --- a/src/main/java/com/samskivert/util/ResultListenerList.java +++ b/src/main/java/com/samskivert/util/ResultListenerList.java @@ -23,23 +23,23 @@ package com.samskivert.util; /** * Multiplexes ResultListener responses to multiple ResultListeners. */ -public class ResultListenerList extends ObserverList> +public class ResultListenerList extends ObserverList.Impl> implements ResultListener { /** - * Create a ResultListenerList with the FAST_UNSAFE_NOTIFY policy. + * Create a ResultListenerList with the FAST_UNSAFE notification policy. */ public ResultListenerList () { - super(FAST_UNSAFE_NOTIFY); + super(Policy.FAST_UNSAFE, false); } /** * Create a ResultListenerList with your own notifyPolicy. */ - public ResultListenerList (int notifyPolicy) + public ResultListenerList (Policy notifyPolicy) { - super(notifyPolicy); + super(notifyPolicy, false); } /** diff --git a/src/main/java/com/samskivert/util/WeakObserverList.java b/src/main/java/com/samskivert/util/WeakObserverList.java index 27acc310..74143a29 100644 --- a/src/main/java/com/samskivert/util/WeakObserverList.java +++ b/src/main/java/com/samskivert/util/WeakObserverList.java @@ -30,7 +30,7 @@ import com.samskivert.util.ObserverList.ObserverOp; * An {@link ObserverList} equivalent that does not prevent added observers from being * garbage-collected. */ -public class WeakObserverList extends AbstractList +public class WeakObserverList extends ObserverList { /** * A convenience method for creating an observer list that avoids duplicating the type @@ -38,7 +38,7 @@ public class WeakObserverList extends AbstractList */ public static WeakObserverList newSafeInOrder () { - return new WeakObserverList(ObserverList.SAFE_IN_ORDER_NOTIFY); + return newList(Policy.SAFE_IN_ORDER, false); } /** @@ -47,111 +47,47 @@ public class WeakObserverList extends AbstractList */ public static WeakObserverList newFastUnsafe () { - return new WeakObserverList(ObserverList.FAST_UNSAFE_NOTIFY); + return newList(Policy.FAST_UNSAFE, false); } /** * A convenience method for creating an observer list that avoids duplicating the type * parameter on the right hand side. */ - public static WeakObserverList newList (int notifyPolicy, boolean allowDups) + public static WeakObserverList newList (Policy notifyPolicy, boolean allowDups) { return new WeakObserverList(notifyPolicy, allowDups); } - /** - * Creates an empty observer list with the supplied notification - * policy and that only allows observers to observe the list once. - * - * @param notifyPolicy Either {@link ObserverList#SAFE_IN_ORDER_NOTIFY} or {@link - * ObserverList#FAST_UNSAFE_NOTIFY}. - */ - public WeakObserverList (int notifyPolicy) + @Override public boolean add (int index, T element) { - this(notifyPolicy, false); + return _delegate.add(index, new WeakReference(element)); } - /** - * Creates an empty observer list with the supplied notification and - * duplicate observer policy. - * - * @param notifyPolicy Either {@link ObserverList#SAFE_IN_ORDER_NOTIFY} or {@link - * ObserverList#FAST_UNSAFE_NOTIFY}. - * @param allowDups whether to allow observers to be added to the list - * more than once. - */ - public WeakObserverList (int notifyPolicy, boolean allowDups) + @Override public boolean add (T element) { - _wrappedList = new WrappedList(notifyPolicy, allowDups); + return _delegate.add(new WeakReference(element)); } - @Override // documentation inherited - public int size () + @Override public boolean remove (T element) { - return _wrappedList.size(); + return _delegate.remove(new WeakReference(element)); } - @Override // documentation inherited - public boolean contains (Object element) - { - @SuppressWarnings("unchecked") T value = (T)element; - return _wrappedList.contains(new WeakReference(value)); - } - - @Override // documentation inherited - public boolean remove (Object element) - { - @SuppressWarnings("unchecked") T value = (T)element; - return _wrappedList.remove(new WeakReference(value)); - } - - @Override // documentation inherited - public int indexOf (Object element) - { - @SuppressWarnings("unchecked") T value = (T)element; - return _wrappedList.indexOf(new WeakReference(value)); - } - - @Override // documentation inherited - public int lastIndexOf (Object element) - { - @SuppressWarnings("unchecked") T value = (T)element; - return _wrappedList.lastIndexOf(new WeakReference(value)); - } - - @Override // documentation inherited - public T get (int index) - { - return _wrappedList.get(index).get(); - } - - @Override // documentation inherited - public T set (int index, T element) - { - return _wrappedList.set(index, new WeakReference(element)).get(); - } - - @Override // documentation inherited - public void add (int index, T element) - { - _wrappedList.add(index, new WeakReference(element)); - } - - @Override // documentation inherited - public T remove (int index) - { - return _wrappedList.remove(index).get(); - } - - /** - * Applies the supplied observer operation to all observers in the - * list in a manner conforming to the notification ordering policy - * specified at construct time. - */ - public void apply (ObserverOp obop) + @Override public void apply (ObserverOp obop) { _derefOp.init(obop); - _wrappedList.apply(_derefOp); + _delegate.apply(_derefOp); + } + + @Override public int size () + { + return _delegate.size(); + } + + @Override public void clear () + { + _delegate.clear(); } /** @@ -159,30 +95,31 @@ public class WeakObserverList extends AbstractList */ public void prune () { - for (int ii = _wrappedList.size() - 1; ii >= 0; ii--) { - if (_wrappedList.get(ii).get() == null) { - _wrappedList.remove(ii); + // applying an op prunes collected observers, so just apply a NOOP op + apply(new ObserverOp() { + public boolean apply (T obs) { + return true; } - } + }); + } + + protected WeakObserverList (Policy notifyPolicy, boolean allowDups) + { + _delegate = new WrappedList(notifyPolicy, allowDups); } /** * An operation that resolves a reference and applies a wrapped op. */ - protected static class DerefOp - implements ObserverOp> + protected static class DerefOp implements ObserverOp> { - /** - * (Re)initializes this op with a reference to the wrapped op. - */ - public void init (ObserverOp op) - { + /** (Re)initializes this op with a reference to the wrapped op. */ + public void init (ObserverOp op) { _op = op; } // documentation inherited from interface ObserverOp - public boolean apply (WeakReference ref) - { + public boolean apply (WeakReference ref) { T observer = ref.get(); return observer != null && _op.apply(observer); } @@ -194,37 +131,23 @@ public class WeakObserverList extends AbstractList /** * ObserverList extension that dereferences elements when searching for a value. */ - protected static class WrappedList extends ObserverList> + protected static class WrappedList extends ObserverList.Impl> { - public WrappedList (int notifyPolicy, boolean allowDups) { + public WrappedList (Policy notifyPolicy, boolean allowDups) { super(notifyPolicy, allowDups); } - @Override public int indexOf (Object element) { - @SuppressWarnings("unchecked") WeakReference ref = (WeakReference)element; + @Override protected int indexOf (WeakReference ref) { T value = ref.get(); - for (int ii = 0, nn = size(); ii < nn; ii++) { - if (value == get(ii).get()) { - return ii; - } - } - return -1; - } - - @Override public int lastIndexOf (Object element) { - @SuppressWarnings("unchecked") WeakReference ref = (WeakReference)element; - T value = ref.get(); - for (int ii = size() - 1; ii >= 0; ii--) { - if (value == get(ii).get()) { - return ii; - } + for (int ii = 0, ll = _list.size(); ii < ll; ii++) { + if (_list.get(ii).get() == value) return ii; } return -1; } } - /** The wrapped list. */ - protected WrappedList _wrappedList; + /** A delegate list that contains weak reference wrapped elements. */ + protected WrappedList _delegate; /** The wrapper op. */ protected DerefOp _derefOp = new DerefOp();