/*
* (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* IBM Corporation - initial API and implementation
*/
package org.nuxeo.common.collections;
import java.util.Arrays;
import java.util.Comparator;
/**
* Internal class to maintain a list of listeners. This class is a thread-safe list that is optimized for frequent reads
* and infrequent writes. Copy on write is used to ensure readers can access the list without synchronization overhead.
* Readers are given access to the underlying array data structure for reading, with the trust that they will not modify
* the underlying array.
* <p>
* Contains code from Eclipse org.eclipse.core.runtime.ListenerList class
*
* @see http://www.eclipse.org
*/
public class ListenerList {
/**
* Mode constant (value 0) indicating that listeners should be compared using equality.
*/
public static final int EQUALITY = 0;
/**
* Mode constant (value 1) indicating that listeners should be compared using identity.
*/
public static final int IDENTITY = 1;
/**
* The empty array singleton instance.
*/
private static final Object[] EMPTY_ARRAY = new Object[0];
/**
* Indicates the comparison mode used to determine if two listeners are equivalent.
*/
private final int compareMode;
/**
* The list of listeners. Initially <code>null</code> but initialized to an array of size capacity the first time a
* listener is added. Maintains invariant: <code>listeners != null</code>.
*/
private volatile Object[] listeners = EMPTY_ARRAY;
/**
* Used to order listeners
*/
private final Comparator<?> comparator;
/**
* Creates a listener list.
*/
public ListenerList() {
this(EQUALITY, null);
}
public ListenerList(Comparator<?> comparator) {
this(EQUALITY, comparator);
}
/**
* Creates a listener list using the provided comparison mode.
*/
public ListenerList(int mode, Comparator<?> comparator) {
compareMode = mode;
this.comparator = comparator;
}
/**
* Adds the given listener to this list. Has no effect if an equal listener is already registered.
* <p>
* This method is synchronized to protect against multiple threads adding or removing listeners concurrently. This
* does not block concurrent readers.
*
* @param listener the listener to add
*/
public synchronized void add(Object listener) {
// check for duplicates
final int oldSize = listeners.length;
for (int i = 0; i < oldSize; ++i) {
if (same(listener, listeners[i])) {
return;
}
}
// Thread safety: create new array to avoid affecting concurrent readers
Object[] newListeners = new Object[oldSize + 1];
System.arraycopy(listeners, 0, newListeners, 0, oldSize);
newListeners[oldSize] = listener;
if (comparator != null) {
Arrays.sort(newListeners, (Comparator<Object>) comparator);
}
// atomic assignment
listeners = newListeners;
}
/**
* Returns an array containing all the registered listeners. The resulting array is unaffected by subsequent adds or
* removes. If there are no listeners registered, the result is an empty array singleton instance (no garbage is
* created). Use this method when notifying listeners, so that any modifications to the listener list during the
* notification will have no effect on the notification itself.
* <p>
* Note: callers must not modify the returned array.
*
* @return the list of registered listeners
*/
public Object[] getListeners() {
return listeners;
}
public synchronized Object[] getListenersCopy() {
Object[] tmp = new Object[listeners.length];
System.arraycopy(listeners, 0, tmp, 0, listeners.length);
return tmp;
}
/**
* Returns whether this listener list is empty.
*
* @return <code>true</code> if there are no registered listeners, and <code>false</code> otherwise
*/
public boolean isEmpty() {
return listeners.length == 0;
}
/**
* Removes the given listener from this list. Has no effect if an identical listener was not already registered.
* <p>
* This method is synchronized to protect against multiple threads adding or removing listeners concurrently. This
* does not block concurrent readers.
*
* @param listener the listener
*/
public synchronized void remove(Object listener) {
int oldSize = listeners.length;
for (int i = 0; i < oldSize; ++i) {
if (same(listener, listeners[i])) {
if (oldSize == 1) {
listeners = EMPTY_ARRAY;
} else {
// Thread safety: create new array to avoid affecting concurrent readers
Object[] newListeners = new Object[oldSize - 1];
System.arraycopy(listeners, 0, newListeners, 0, i);
System.arraycopy(listeners, i + 1, newListeners, i, oldSize - i - 1);
// atomic assignment to field
listeners = newListeners;
}
return;
}
}
}
/**
* Returns <code>true</code> if the two listeners are the same based on the specified comparison mode, and
* <code>false</code> otherwise.
*/
private boolean same(Object listener1, Object listener2) {
return compareMode == IDENTITY ? listener1 == listener2 : listener1.equals(listener2);
}
/**
* Returns the number of registered listeners.
*
* @return the number of registered listeners
*/
public int size() {
return listeners.length;
}
}