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    
015    package org.apache.hivemind.util;
016    
017    import 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     */
029    public 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    }