001// Copyright 2004, 2005 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007//     http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.hivemind.util;
016
017import java.util.Iterator;
018
019/**
020 * Convienience class for tracking a list of event listeners. Works efficiently
021 * (using a copy-on-write approach) to iterating through the listeners in
022 * the list even when the list of listeners may be modified.
023 * 
024 * <p>
025 * EventListenerList <em>is</em> thread-safe.
026 *
027 * @author Howard Lewis Ship
028 */
029public class EventListenerList
030{
031    private static final int START_SIZE = 5;
032
033    private Object[] _listeners;
034    private int _count;
035    private int _iteratorCount;
036    private int _uid;
037
038    private class ListenerIterator implements Iterator
039    {
040        private Object[] _localListeners;
041        private int _localCount;
042        private int _localUid;
043        private int _pos;
044
045        private ListenerIterator()
046        {
047            _localListeners = _listeners;
048            _localCount = _count;
049            _localUid = _uid;
050        }
051
052        public boolean hasNext()
053        {
054            if (_pos >= _localCount)
055            {
056                // If _listeners has not been recopied during the lifespan
057                // of this iterator, then knock the count down by one.
058
059                adjustIteratorCount(_localUid);
060
061                _localListeners = null;
062                _localCount = 0;
063                _localUid = -1;
064                _pos = 0;
065
066                return false;
067            }
068
069            return true;
070        }
071
072        public Object next()
073        {
074            return _localListeners[_pos++];
075        }
076
077        public void remove()
078        {
079            throw new UnsupportedOperationException();
080        }
081
082    }
083
084    /**
085     * Returns an Iterator used to find all the listeners previously added.
086     * The order in which listeners are returned is not guaranteed.
087     * Currently, you may not invoke <code>remove()</code> on the Iterator.
088     * 
089     * <p>
090     * Invoking this method takes a "snapshot" of the current list of listeners. 
091     * You may invoke {@link #addListener(Object)} or {@link #removeListener(Object)},
092     * but that won't affect the sequence of listeners returned by the Iterator.
093     */
094    public synchronized Iterator getListeners()
095    {
096        _iteratorCount++;
097
098        return new ListenerIterator();
099    }
100
101    /**
102     * Adds a new listener to the list of listeners. The same instance
103     * will may be added multiple times.
104     */
105    public synchronized void addListener(Object listener)
106    {
107        copyOnWrite(_count + 1);
108
109        _listeners[_count] = listener;
110
111        _count++;
112    }
113
114    /**
115     * Removes a listener from the list.  Does nothing if the listener
116     * is not already in the list. Comparison is based on identity, not equality.
117     * If the listener is in the list multiple times, only a single
118     * instance is removed.
119     */
120    public synchronized void removeListener(Object listener)
121    {
122        for (int i = 0; i < _count; i++)
123        {
124            if (_listeners[i] == listener)
125            {
126                removeListener(i);
127                return;
128            }
129        }
130    }
131
132    private void removeListener(int index)
133    {
134        copyOnWrite(_count);
135
136        // Move the last listener in the list into the index to be removed.
137
138        _listeners[index] = _listeners[_count - 1];
139
140        // Null out the old position.
141
142        _listeners[_count - 1] = null;
143
144        _count--;
145    }
146
147    /**
148     * Copies the array before an update operation if necessary (because there
149     * is a known iterator for the current array, or because the 
150     * array is not large enough).
151     */
152    private void copyOnWrite(int requiredSize)
153    {
154        int size = _listeners == null ? 0 : _listeners.length;
155
156        if (_iteratorCount > 0 || size < requiredSize)
157        {
158            int nominalSize = (size == 0) ? START_SIZE : 2 * size;
159
160            // Don't grow the array if we don't need to...
161            if (size >= requiredSize)
162            {
163                nominalSize = size;
164            }
165
166            int newSize = Math.max(requiredSize, nominalSize);
167
168            Object[] newListeners = new Object[newSize];
169
170            if (_count > 0)
171                System.arraycopy(_listeners, 0, newListeners, 0, _count);
172
173            _listeners = newListeners;
174
175            // No iterators on the *new* array
176            _iteratorCount = 0;
177            _uid++;
178        }
179    }
180
181    private synchronized void adjustIteratorCount(int iteratorUid)
182    {
183        if (_uid == iteratorUid)
184            _iteratorCount--;
185    }
186}