001/*
002 * (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     IBM Corporation - initial API and implementation
018 */
019package org.nuxeo.common.collections;
020
021import java.util.Arrays;
022import java.util.Comparator;
023
024/**
025 * Internal class to maintain a list of listeners. This class is a thread-safe list that is optimized for frequent reads
026 * and infrequent writes. Copy on write is used to ensure readers can access the list without synchronization overhead.
027 * Readers are given access to the underlying array data structure for reading, with the trust that they will not modify
028 * the underlying array.
029 * <p>
030 * Contains code from Eclipse org.eclipse.core.runtime.ListenerList class
031 *
032 * @see <a href="http://www.eclipse.org">Eclipse.org</a>
033 */
034public class ListenerList {
035
036    /**
037     * Mode constant (value 0) indicating that listeners should be compared using equality.
038     */
039    public static final int EQUALITY = 0;
040
041    /**
042     * Mode constant (value 1) indicating that listeners should be compared using identity.
043     */
044    public static final int IDENTITY = 1;
045
046    /**
047     * The empty array singleton instance.
048     */
049    private static final Object[] EMPTY_ARRAY = new Object[0];
050
051    /**
052     * Indicates the comparison mode used to determine if two listeners are equivalent.
053     */
054    private final int compareMode;
055
056    /**
057     * The list of listeners. Initially <code>null</code> but initialized to an array of size capacity the first time a
058     * listener is added. Maintains invariant: <code>listeners != null</code>.
059     */
060    private volatile Object[] listeners = EMPTY_ARRAY;
061
062    /**
063     * Used to order listeners
064     */
065    private final Comparator<?> comparator;
066
067    /**
068     * Creates a listener list.
069     */
070    public ListenerList() {
071        this(EQUALITY, null);
072    }
073
074    public ListenerList(Comparator<?> comparator) {
075        this(EQUALITY, comparator);
076    }
077
078    /**
079     * Creates a listener list using the provided comparison mode.
080     */
081    public ListenerList(int mode, Comparator<?> comparator) {
082        compareMode = mode;
083        this.comparator = comparator;
084    }
085
086    /**
087     * Adds the given listener to this list. Has no effect if an equal listener is already registered.
088     * <p>
089     * This method is synchronized to protect against multiple threads adding or removing listeners concurrently. This
090     * does not block concurrent readers.
091     *
092     * @param listener the listener to add
093     */
094    public synchronized void add(Object listener) {
095        // check for duplicates
096        final int oldSize = listeners.length;
097        for (int i = 0; i < oldSize; ++i) {
098            if (same(listener, listeners[i])) {
099                return;
100            }
101        }
102        // Thread safety: create new array to avoid affecting concurrent readers
103        Object[] newListeners = new Object[oldSize + 1];
104        System.arraycopy(listeners, 0, newListeners, 0, oldSize);
105        newListeners[oldSize] = listener;
106        if (comparator != null) {
107            Arrays.sort(newListeners, (Comparator<Object>) comparator);
108        }
109        // atomic assignment
110        listeners = newListeners;
111    }
112
113    /**
114     * Returns an array containing all the registered listeners. The resulting array is unaffected by subsequent adds or
115     * removes. If there are no listeners registered, the result is an empty array singleton instance (no garbage is
116     * created). Use this method when notifying listeners, so that any modifications to the listener list during the
117     * notification will have no effect on the notification itself.
118     * <p>
119     * Note: callers must not modify the returned array.
120     *
121     * @return the list of registered listeners
122     */
123    public Object[] getListeners() {
124        return listeners;
125    }
126
127    public synchronized Object[] getListenersCopy() {
128        Object[] tmp = new Object[listeners.length];
129        System.arraycopy(listeners, 0, tmp, 0, listeners.length);
130        return tmp;
131    }
132
133    /**
134     * Returns whether this listener list is empty.
135     *
136     * @return <code>true</code> if there are no registered listeners, and <code>false</code> otherwise
137     */
138    public boolean isEmpty() {
139        return listeners.length == 0;
140    }
141
142    /**
143     * Removes the given listener from this list. Has no effect if an identical listener was not already registered.
144     * <p>
145     * This method is synchronized to protect against multiple threads adding or removing listeners concurrently. This
146     * does not block concurrent readers.
147     *
148     * @param listener the listener
149     */
150    public synchronized void remove(Object listener) {
151        int oldSize = listeners.length;
152        for (int i = 0; i < oldSize; ++i) {
153            if (same(listener, listeners[i])) {
154                if (oldSize == 1) {
155                    listeners = EMPTY_ARRAY;
156                } else {
157                    // Thread safety: create new array to avoid affecting concurrent readers
158                    Object[] newListeners = new Object[oldSize - 1];
159                    System.arraycopy(listeners, 0, newListeners, 0, i);
160                    System.arraycopy(listeners, i + 1, newListeners, i, oldSize - i - 1);
161                    // atomic assignment to field
162                    listeners = newListeners;
163                }
164                return;
165            }
166        }
167    }
168
169    /**
170     * Returns <code>true</code> if the two listeners are the same based on the specified comparison mode, and
171     * <code>false</code> otherwise.
172     */
173    private boolean same(Object listener1, Object listener2) {
174        return compareMode == IDENTITY ? listener1 == listener2 : listener1.equals(listener2);
175    }
176
177    /**
178     * Returns the number of registered listeners.
179     *
180     * @return the number of registered listeners
181     */
182    public int size() {
183        return listeners.length;
184    }
185
186}