001/*
002 * $Id: WeakEventListenerList.java 3190 2009-01-20 17:47:52Z kschaefe $
003 *
004 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021package org.jdesktop.swingx.event;
022
023import java.io.Serializable;
024import java.lang.ref.WeakReference;
025import java.lang.reflect.Array;
026import java.util.ArrayList;
027import java.util.EventListener;
028import java.util.List;
029
030/**
031 * A class that holds a list of EventListeners.  A single instance
032 * can be used to hold all listeners (of all types) for the instance
033 * using the list.  It is the responsibility of the class using the
034 * EventListenerList to provide type-safe API (preferably conforming
035 * to the JavaBeans spec) and methods which dispatch event notification
036 * methods to appropriate Event Listeners on the list.
037 * 
038 * The main benefit that this class provides is that it releases
039 * garbage collected listeners (internally uses weak references). <p>
040 * 
041 * PENDING: serialization support
042 * 
043 *
044 * Usage example:
045 *    Say one is defining a class that sends out FooEvents, and one wants
046 * to allow users of the class to register FooListeners and receive 
047 * notification when FooEvents occur.  The following should be added
048 * to the class definition:
049 * <pre>
050 * EventListenerList listenerList = new EventListenerList();
051 * FooEvent fooEvent = null;
052 *
053 * public void addFooListener(FooListener l) {
054 *     listenerList.add(FooListener.class, l);
055 * }
056 *
057 * public void removeFooListener(FooListener l) {
058 *     listenerList.remove(FooListener.class, l);
059 * }
060 *
061 *
062 * // Notify all listeners that have registered interest for
063 * // notification on this event type.  The event instance 
064 * // is lazily created using the parameters passed into 
065 * // the fire method.
066 *
067 * 
068 * protected void fireFooXXX() {
069 *     // Guaranteed to return a non-null array
070 *     FooListener[] listeners = listenerList.getListeners(FooListener.class);
071 *     // Process the listeners last to first, notifying
072 *     // those that are interested in this event
073 *     for (FooListener listener: listeners) {
074 *             // Lazily create the event:
075 *             if (fooEvent == null)
076 *                 fooEvent = new FooEvent(this);
077 *             listener.fooXXX(fooEvent);
078 *         }
079 *     }
080 * }
081 * </pre>
082 * foo should be changed to the appropriate name, and fireFooXxx to the
083 * appropriate method name.  One fire method should exist for each
084 * notification method in the FooListener interface.
085 * <p>
086 * <strong>Warning:</strong>
087 * Serialized objects of this class will not be compatible with
088 * future Swing releases. The current serialization support is
089 * appropriate for short term storage or RMI between applications running
090 * the same version of Swing.  As of 1.4, support for long term storage
091 * of all JavaBeans<sup><font size="-2">TM</font></sup>
092 * has been added to the <code>java.beans</code> package.
093 * Please see {@link java.beans.XMLEncoder}.
094 *
095 * @version 1.37 11/17/05
096 * @author Georges Saab
097 * @author Hans Muller
098 * @author James Gosling
099 */
100public class WeakEventListenerList implements Serializable {
101
102    protected transient List<WeakReference<? extends EventListener>> weakReferences;
103    protected transient List<Class<? extends EventListener>> classes;
104    
105    /**
106     * Passes back the event listener list as an array
107     * of ListenerType-listener pairs.  
108     * As a side-effect, cleans out any 
109     * garbage collected listeners before building the array.
110     * 
111     * @return a array of listenerType-listener pairs.
112     */
113    public Object[] getListenerList() {
114        List<? extends EventListener> listeners = cleanReferences();
115        Object[] result = new Object[listeners.size() * 2];
116        for (int i = 0; i < listeners.size(); i++) {
117            result[2*i + 1] = listeners.get(i);
118            result[2*i] = getClasses().get(i);
119        }
120        return result;
121    }
122
123    /**
124     * Returns a list of strongly referenced EventListeners. Removes
125     * internal weak references to garbage collected listeners.
126     * 
127     * @return
128     */
129    @SuppressWarnings("unchecked")
130    private synchronized <T extends EventListener> List<T> cleanReferences() {
131        List<T> listeners = new ArrayList<T>();
132        for (int i = getReferences().size() - 1; i >= 0; i--) {
133            
134            Object listener = getReferences().get(i).get();
135            if (listener == null) {
136                getReferences().remove(i);
137                getClasses().remove(i);
138            } else {
139                listeners.add(0, (T) listener);
140            }
141        }
142        return listeners;
143    }
144    
145    private List<WeakReference<? extends EventListener>> getReferences() {
146        if (weakReferences == null) {
147            weakReferences = new ArrayList<WeakReference<? extends EventListener>>();
148        }
149        return weakReferences;
150    }
151    
152    private List<Class<? extends EventListener>> getClasses() {
153        if (classes == null) {
154            classes = new ArrayList<Class<? extends EventListener>>();
155            
156        }
157        return classes;
158    }
159    /**
160     * Return an array of all the listeners of the given type. 
161     * As a side-effect, cleans out any 
162     * garbage collected listeners before building the array.
163     * @return all of the listeners of the specified type. 
164     * @exception  ClassCastException if the supplied class
165     *          is not assignable to EventListener
166     * 
167     * @since 1.3
168     */
169    @SuppressWarnings("unchecked")
170    public <T extends EventListener> T[] getListeners(Class<T> t) {
171        List<T> liveListeners = cleanReferences();
172        List<T> listeners = new ArrayList<T>();
173        for (int i = 0; i < liveListeners.size(); i++) {
174            if (getClasses().get(i) == t) {
175                listeners.add(liveListeners.get(i));
176            }
177        }
178        T[] result = (T[])Array.newInstance(t, listeners.size()); 
179        return listeners.toArray(result);
180    }
181
182    /**
183     * Adds the listener as a listener of the specified type.
184      * As a side-effect, cleans out any garbage collected
185     * listeners before adding.
186    * @param t the type of the listener to be added
187     * @param l the listener to be added
188     */
189    public synchronized <T extends EventListener> void add(Class<T> t, T l) {
190        if (l==null) {
191            // In an ideal world, we would do an assertion here
192            // to help developers know they are probably doing
193            // something wrong
194            return;
195        }
196        if (!t.isInstance(l)) {
197            throw new IllegalArgumentException("Listener " + l +
198                                         " is not of type " + t);
199        }
200        cleanReferences();
201        getReferences().add(new WeakReference<T>(l));
202        getClasses().add(t);
203    }
204
205    /**
206     * Removes the listener as a listener of the specified type.
207     * @param t the type of the listener to be removed
208     * @param l the listener to be removed
209     */
210    public synchronized <T extends EventListener> void remove(Class<T> t, T l) {
211        if (l ==null) {
212            // In an ideal world, we would do an assertion here
213            // to help developers know they are probably doing
214            // something wrong
215            return;
216        }
217        if (!t.isInstance(l)) {
218            throw new IllegalArgumentException("Listener " + l +
219                                         " is not of type " + t);
220        }
221        for (int i = 0; i < getReferences().size(); i++) {
222           if (l.equals(getReferences().get(i).get()) && 
223                   (t == getClasses().get(i))) {
224               getReferences().remove(i);
225               getClasses().remove(i);
226               break;
227           }
228        }
229    }
230
231//    // Serialization support.  
232//    private void writeObject(ObjectOutputStream s) throws IOException {
233//        Object[] lList = listenerList;
234//        s.defaultWriteObject();
235//        
236//        // Save the non-null event listeners:
237//        for (int i = 0; i < lList.length; i+=2) {
238//            Class t = (Class)lList[i];
239//            EventListener l = (EventListener)lList[i+1];
240//            if ((l!=null) && (l instanceof Serializable)) {
241//                s.writeObject(t.getName());
242//                s.writeObject(l);
243//            }
244//        }
245//        
246//        s.writeObject(null);
247//    }
248//
249//    private void readObject(ObjectInputStream s) 
250//        throws IOException, ClassNotFoundException {
251//        listenerList = NULL_ARRAY;
252//        s.defaultReadObject();
253//        Object listenerTypeOrNull;
254//        
255//        while (null != (listenerTypeOrNull = s.readObject())) {
256//            ClassLoader cl = Thread.currentThread().getContextClassLoader();
257//            EventListener l = (EventListener)s.readObject();
258//            add((Class<EventListener>)Class.forName((String)listenerTypeOrNull, true, cl), l);
259//        }           
260//    }
261
262//    /**
263//     * Returns a string representation of the EventListenerList.
264//     */
265//    public String toString() {
266//        Object[] lList = listenerList;
267//        String s = "EventListenerList: ";
268//        s += lList.length/2 + " listeners: ";
269//        for (int i = 0 ; i <= lList.length-2 ; i+=2) {
270//            s += " type " + ((Class)lList[i]).getName();
271//            s += " listener " + lList[i+1];
272//        }
273//        return s;
274//    }
275}