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