001    package util;
002    
003    import java.io.*;
004    import java.util.*;
005    
006    /**
007     * Helper class that supports Listeners which themselves can be listened to.
008     *
009     * <p>The main intent is to support garbage collection by avoiding cyclic links between
010     * the listener and its event source. Listeners that use ListenerHelper (and implement
011     * HelpableListener) will be registered with their event source only as long as there
012     * are any listeners registered with them. They will however guarantee up-to-date-ness
013     * indepent of whether a listener is registered with them or not, by polling their
014     * model whenever a query is performed against them and they have no listeners.</p>
015     *
016     * <p>For this mechanism to work properly such listeners must abide by the following
017     * rules:</p>
018     *
019     * <ul>
020     *   <li>They must implement HelpableListener.</li>
021     *   <li>They must use an instance of ListenerHelper to manage their list of listeners.</li>
022     *   <li>At the start of any query method that depends on the listener's own event
023     *       source they are to call {@link #needModelUpdate}</li>
024     * </ul>
025     *
026     * <p>As the Swing serialization is buggy, this class will serialize only listeners that implement
027     * {@link SerializableListener}. For this purpose, however, all methods in
028     * {@link javax.swing.event.EventListenerList} need to be overridden.</p>
029     *
030     * @see HelpableListener
031     * @see #needModelUpdate
032     *
033     * @author Steffen Zschaler
034     * @version 2.0 02/06/1999
035     * @since v2.0
036     */
037    public class ListenerHelper extends javax.swing.event.EventListenerList {
038    
039        /**
040         * A null array to be shared by all empty listener lists
041         */
042        private final static Object[] NULL_ARRAY = new Object[0];
043    
044        /**
045         * The list of ListenerType - Listener pairs. We need to redefine this as it would otherwise be stored by
046         * the standard {@link javax.swing.event.EventListenerList} mechanism.
047         *
048         * PENDING (sz9): Need to replace direct references to listeners by SoftReferences, in order to avoid
049         * cycles and thereby memory leaks. Example: Stocks listen to their catalogs in order to maintain
050         * referential integrity. They also store a pointer to their catalog in order to be able to reference
051         * it. A cycle is created, which means that creating lots of temporary stocks is a potential memory
052         * problem.
053         */
054        protected transient Object[] aoListeners = NULL_ARRAY;
055    
056        /**
057         * The listener that owns this listener helper.
058         *
059         * @serial
060         */
061        protected HelpableListener m_hlOwner;
062    
063        /**
064         * Construct a new ListenerHelper. Using this constructor will only use the improved serialization support,
065         * but not the subscribe/unsubscribe functionality.
066         */
067        public ListenerHelper() {
068            this(null);
069        }
070    
071        /**
072         * Create a new ListenerHelper.
073         *
074         * @param hlOwner the HelpableListener that is associated to this ListenerHelper.
075         */
076        public ListenerHelper(HelpableListener hlOwner) {
077            super();
078    
079            m_hlOwner = hlOwner;
080        }
081    
082        /**
083         * This passes back the event listener list as an array
084         * of ListenerType - listener pairs.  Note that for
085         * performance reasons, this implementation passes back
086         * the actual data structure in which the listner data
087         * is stored internally!
088         * This method is guaranteed to pass back a non-null
089         * array, so that no null-checking is required in
090         * fire methods.  A zero-length array of Object should
091         * be returned if there are currently no listeners.
092         *
093         * WARNING!!! Absolutely NO modification of
094         * the data contained in this array should be made -- if
095         * any such manipulation is necessary, it should be done
096         * on a copy of the array returned rather than the array
097         * itself.
098         */
099        public Object[] getListenerList() {
100            return aoListeners;
101        }
102    
103        /**
104         * Return the total number of listeners for this listenerlist
105         */
106        public int getListenerCount() {
107            return aoListeners.length / 2;
108        }
109    
110        /**
111         * Return the total number of listeners of the supplied type
112         * for this listenerlist.
113         */
114        public int getListenerCount(Class t) {
115            int count = 0;
116            Object[] lList = aoListeners;
117    
118            for (int i = 0; i < lList.length; i += 2) {
119                if (t == ((Class)lList[i])) {
120                    count++;
121                }
122            }
123    
124            return count;
125        }
126    
127        /**
128         * Add the listener as a listener of the specified type.
129         *
130         * @param t the type of the listener to be added
131         * @param l the listener to be added
132         */
133        public synchronized void add(Class t, EventListener l) {
134            if (!t.isInstance(l)) {
135                throw new IllegalArgumentException("Listener " + l + " is not of type " + t);
136            }
137    
138            if (l == null) {
139                throw new IllegalArgumentException("Listener " + l + " is null");
140            }
141    
142            if (aoListeners == NULL_ARRAY) {
143                // if this is the first listener added,
144                // initialize the lists
145                aoListeners = new Object[] {
146                        t, l};
147    
148                // .. and have the owner subscribe.
149                if (m_hlOwner != null) {
150                    m_hlOwner.subscribe();
151                    // a last time so that we start with the most accurate model possible
152                    m_hlOwner.updateModel();
153                }
154            } else {
155                // Otherwise copy the array and add the new listener
156                int i = aoListeners.length;
157                Object[] tmp = new Object[i + 2];
158                System.arraycopy(aoListeners, 0, tmp, 0, i);
159    
160                tmp[i] = t;
161                tmp[i + 1] = l;
162    
163                aoListeners = tmp;
164            }
165        }
166    
167        /**
168         * Remove the listener as a listener of the specified type.
169         *
170         * @param t the type of the listener to be removed
171         * @param l the listener to be removed
172         */
173        public synchronized void remove(Class t, EventListener l) {
174            if (!t.isInstance(l)) {
175                throw new IllegalArgumentException("Listener " + l + " is not of type " + t);
176            }
177    
178            if (l == null) {
179                throw new IllegalArgumentException("Listener " + l + " is null");
180            }
181    
182            // Is l on the list?
183            int index = -1;
184            for (int i = aoListeners.length - 2; i >= 0; i -= 2) {
185                if ((aoListeners[i] == t) && (aoListeners[i + 1].equals(l))) {
186                    index = i;
187                    break;
188                }
189            }
190    
191            // If so,  remove it
192            if (index != -1) {
193                Object[] tmp = new Object[aoListeners.length - 2];
194                // Copy the list up to index
195                System.arraycopy(aoListeners, 0, tmp, 0, index);
196    
197                // Copy from two past the index, up to
198                // the end of tmp (which is two elements
199                // shorter than the old list)
200                if (index < tmp.length) {
201                    System.arraycopy(aoListeners, index + 2, tmp, index, tmp.length - index);
202                }
203    
204                // set the listener array to the new array or null
205                aoListeners = (tmp.length == 0) ? NULL_ARRAY : tmp;
206    
207                        if (tmp.length == 0) {
208                    if (m_hlOwner != null) {
209                        m_hlOwner.unsubscribe();
210                    }
211                }
212            }
213        }
214    
215        /**
216         * Make sure, the owner's model is up to date. If necessary call
217         * {@link util.HelpableListener#updateModel} in the owner.
218         */
219        public void needModelUpdate() {
220            if (getListenerCount() == 0) {
221    
222                if (m_hlOwner != null) {
223                    m_hlOwner.updateModel();
224                }
225            }
226        }
227    
228        /**
229         * A SerializableListenerHelper will store all the listeners that implement {@link SerializableListener}.
230         *
231         * @serialData tuples of (<classname>, <listener>).
232         */
233        private void writeObject(ObjectOutputStream s) throws IOException {
234            Object[] lList = aoListeners;
235            s.defaultWriteObject();
236    
237            // Save the non-null event listeners:
238            for (int i = 0; i < lList.length; i += 2) {
239                Class t = (Class)lList[i];
240                EventListener l = (EventListener)lList[i + 1];
241    
242                if ((l != null) && (l instanceof SerializableListener)) {
243                    s.writeObject(t.getName());
244                    s.writeObject(l);
245                }
246            }
247    
248            s.writeObject(null);
249        }
250    
251        /**
252         * A SerializableListenerHelper will store all the listeners that implement {@link SerializableListener}.
253         */
254        private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
255    
256            aoListeners = NULL_ARRAY;
257    
258            s.defaultReadObject();
259            Object listenerTypeOrNull;
260    
261            while (null != (listenerTypeOrNull = s.readObject())) {
262                EventListener l = (EventListener)s.readObject();
263                add(Class.forName((String)listenerTypeOrNull), l);
264            }
265        }
266    
267        /**
268         * Return a string representation of the SerializableListenerHelper.
269         */
270        public String toString() {
271            Object[] lList = aoListeners;
272            String s = "SerializableListenerHelper: ";
273            s += lList.length / 2 + " listeners: ";
274            for (int i = 0; i <= lList.length - 2; i += 2) {
275                s += " type " + ((Class)lList[i]).getName();
276                s += " listener " + lList[i + 1];
277            }
278    
279            return s;
280        }
281    }