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             * ID for serialization.
041             */
042            private static final long serialVersionUID = -7971346159857861210L;
043    
044            /**
045         * A null array to be shared by all empty listener lists
046         */
047        private final static Object[] NULL_ARRAY = new Object[0];
048    
049        /**
050         * The list of ListenerType - Listener pairs. We need to redefine this as it would otherwise be stored by
051         * the standard {@link javax.swing.event.EventListenerList} mechanism.
052         *
053         * PENDING (sz9): Need to replace direct references to listeners by SoftReferences, in order to avoid
054         * cycles and thereby memory leaks. Example: Stocks listen to their catalogs in order to maintain
055         * referential integrity. They also store a pointer to their catalog in order to be able to reference
056         * it. A cycle is created, which means that creating lots of temporary stocks is a potential memory
057         * problem.
058         */
059        protected transient Object[] aoListeners = NULL_ARRAY;
060    
061        /**
062         * The listener that owns this listener helper.
063         *
064         * @serial
065         */
066        protected HelpableListener m_hlOwner;
067    
068        /**
069         * Construct a new ListenerHelper. Using this constructor will only use the improved serialization support,
070         * but not the subscribe/unsubscribe functionality.
071         */
072        public ListenerHelper() {
073            this(null);
074        }
075    
076        /**
077         * Create a new ListenerHelper.
078         *
079         * @param hlOwner the HelpableListener that is associated to this ListenerHelper.
080         */
081        public ListenerHelper(HelpableListener hlOwner) {
082            super();
083    
084            m_hlOwner = hlOwner;
085        }
086    
087        /**
088         * This passes back the event listener list as an array
089         * of ListenerType - listener pairs.  Note that for
090         * performance reasons, this implementation passes back
091         * the actual data structure in which the listner data
092         * is stored internally!
093         * This method is guaranteed to pass back a non-null
094         * array, so that no null-checking is required in
095         * fire methods.  A zero-length array of Object should
096         * be returned if there are currently no listeners.
097         *
098         * WARNING!!! Absolutely NO modification of
099         * the data contained in this array should be made -- if
100         * any such manipulation is necessary, it should be done
101         * on a copy of the array returned rather than the array
102         * itself.
103         */
104        public Object[] getListenerList() {
105            return aoListeners;
106        }
107    
108        /**
109         * Return the total number of listeners for this listenerlist
110         */
111        public int getListenerCount() {
112            return aoListeners.length / 2;
113        }
114    
115        /**
116         * Return the total number of listeners of the supplied type
117         * for this listenerlist.
118         */
119        public int getListenerCount(Class t) {
120            int count = 0;
121            Object[] lList = aoListeners;
122    
123            for (int i = 0; i < lList.length; i += 2) {
124                if (t == ((Class)lList[i])) {
125                    count++;
126                }
127            }
128    
129            return count;
130        }
131    
132        /**
133         * Add the listener as a listener of the specified type.
134         *
135         * @param t the type of the listener to be added
136         * @param l the listener to be added
137         */
138        public synchronized <T extends EventListener> void add(Class<T> t, T l) {
139            if (!t.isInstance(l)) {
140                throw new IllegalArgumentException("Listener " + l + " is not of type " + t);
141            }
142    
143            if (l == null) {
144                throw new IllegalArgumentException("Listener " + l + " is null");
145            }
146    
147            if (aoListeners == NULL_ARRAY) {
148                // if this is the first listener added,
149                // initialize the lists
150                aoListeners = new Object[] {
151                        t, l};
152    
153                // .. and have the owner subscribe.
154                if (m_hlOwner != null) {
155                    m_hlOwner.subscribe();
156                    // a last time so that we start with the most accurate model possible
157                    m_hlOwner.updateModel();
158                }
159            } else {
160                // Otherwise copy the array and add the new listener
161                int i = aoListeners.length;
162                Object[] tmp = new Object[i + 2];
163                System.arraycopy(aoListeners, 0, tmp, 0, i);
164    
165                tmp[i] = t;
166                tmp[i + 1] = l;
167    
168                aoListeners = tmp;
169            }
170        }
171    
172        /**
173         * Remove the listener as a listener of the specified type.
174         *
175         * @param t the type of the listener to be removed
176         * @param l the listener to be removed
177         */
178        public synchronized <T extends EventListener> void remove(Class<T> t, T l) {
179            if (!t.isInstance(l)) {
180                throw new IllegalArgumentException("Listener " + l + " is not of type " + t);
181            }
182    
183            if (l == null) {
184                throw new IllegalArgumentException("Listener " + l + " is null");
185            }
186    
187            // Is l on the list?
188            int index = -1;
189            for (int i = aoListeners.length - 2; i >= 0; i -= 2) {
190                if ((aoListeners[i] == t) && (aoListeners[i + 1].equals(l))) {
191                    index = i;
192                    break;
193                }
194            }
195    
196            // If so,  remove it
197            if (index != -1) {
198                Object[] tmp = new Object[aoListeners.length - 2];
199                // Copy the list up to index
200                System.arraycopy(aoListeners, 0, tmp, 0, index);
201    
202                // Copy from two past the index, up to
203                // the end of tmp (which is two elements
204                // shorter than the old list)
205                if (index < tmp.length) {
206                    System.arraycopy(aoListeners, index + 2, tmp, index, tmp.length - index);
207                }
208    
209                // set the listener array to the new array or null
210                aoListeners = (tmp.length == 0) ? NULL_ARRAY : tmp;
211    
212                        if (tmp.length == 0) {
213                    if (m_hlOwner != null) {
214                        m_hlOwner.unsubscribe();
215                    }
216                }
217            }
218        }
219    
220        /**
221         * Make sure, the owner's model is up to date. If necessary call
222         * {@link util.HelpableListener#updateModel} in the owner.
223         */
224        public void needModelUpdate() {
225            if (getListenerCount() == 0) {
226    
227                if (m_hlOwner != null) {
228                    m_hlOwner.updateModel();
229                }
230            }
231        }
232    
233        /**
234         * A SerializableListenerHelper will store all the listeners that implement {@link SerializableListener}.
235         *
236         * @serialData tuples of (&lt;classname&gt;, &lt;listener&gt;).
237         */
238        private void writeObject(ObjectOutputStream s) throws IOException {
239            Object[] lList = aoListeners;
240            s.defaultWriteObject();
241    
242            // Save the non-null event listeners:
243            for (int i = 0; i < lList.length; i += 2) {
244                Class t = (Class)lList[i];
245                EventListener l = (EventListener)lList[i + 1];
246    
247                if ((l != null) && (l instanceof SerializableListener)) {
248                    s.writeObject(t.getName());
249                    s.writeObject(l);
250                }
251            }
252    
253            s.writeObject(null);
254        }
255    
256        /**
257         * A SerializableListenerHelper will store all the listeners that implement {@link SerializableListener}.
258         * 
259         * <p><b>Attention:</b><br />
260         * This code is not 100% Java 1.5 compatible. Had to avoid the warning with SuppressWarnings 
261         * because there is no clean solution for this in 1.5 yet.</p>
262         */
263            @SuppressWarnings("unchecked")
264            private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
265            aoListeners = NULL_ARRAY;
266    
267            s.defaultReadObject();
268            Object listenerTypeOrNull;
269            
270            while (null != (listenerTypeOrNull = s.readObject())) {
271                    
272                    System.out.println(Class.forName((String)listenerTypeOrNull));
273                EventListener l = (EventListener) s.readObject();
274                add((Class<EventListener>) Class.forName((String)listenerTypeOrNull), l);
275            }
276        }
277    
278        /**
279         * Return a string representation of the SerializableListenerHelper.
280         */
281        public String toString() {
282            Object[] lList = aoListeners;
283            String s = "SerializableListenerHelper: ";
284            s += lList.length / 2 + " listeners: ";
285            for (int i = 0; i <= lList.length - 2; i += 2) {
286                s += " type " + ((Class)lList[i]).getName();
287                s += " listener " + lList[i + 1];
288            }
289    
290            return s;
291        }
292    }