/*
 * $Id: WeakEventListenerList.java,v 1.3 2009/01/20 17:47:52 kschaefe Exp $
 *
 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jdesktop.swingx.event;

import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;

/**
 * A class that holds a list of EventListeners.  A single instance
 * can be used to hold all listeners (of all types) for the instance
 * using the list.  It is the responsibility of the class using the
 * EventListenerList to provide type-safe API (preferably conforming
 * to the JavaBeans spec) and methods which dispatch event notification
 * methods to appropriate Event Listeners on the list.
 * 
 * The main benefit that this class provides is that it releases
 * garbage collected listeners (internally uses weak references). <p>
 * 
 * PENDING: serialization support
 * 
 *
 * Usage example:
 *    Say one is defining a class that sends out FooEvents, and one wants
 * to allow users of the class to register FooListeners and receive 
 * notification when FooEvents occur.  The following should be added
 * to the class definition:
 * <pre>
 * EventListenerList listenerList = new EventListenerList();
 * FooEvent fooEvent = null;
 *
 * public void addFooListener(FooListener l) {
 *     listenerList.add(FooListener.class, l);
 * }
 *
 * public void removeFooListener(FooListener l) {
 *     listenerList.remove(FooListener.class, l);
 * }
 *
 *
 * // Notify all listeners that have registered interest for
 * // notification on this event type.  The event instance 
 * // is lazily created using the parameters passed into 
 * // the fire method.
 *
 * 
 * protected void fireFooXXX() {
 *     // Guaranteed to return a non-null array
 *     FooListener[] listeners = listenerList.getListeners(FooListener.class);
 *     // Process the listeners last to first, notifying
 *     // those that are interested in this event
 *     for (FooListener listener: listeners) {
 *             // Lazily create the event:
 *             if (fooEvent == null)
 *                 fooEvent = new FooEvent(this);
 *             listener.fooXXX(fooEvent);
 *         }
 *     }
 * }
 * </pre>
 * foo should be changed to the appropriate name, and fireFooXxx to the
 * appropriate method name.  One fire method should exist for each
 * notification method in the FooListener interface.
 * <p>
 * <strong>Warning:</strong>
 * Serialized objects of this class will not be compatible with
 * future Swing releases. The current serialization support is
 * appropriate for short term storage or RMI between applications running
 * the same version of Swing.  As of 1.4, support for long term storage
 * of all JavaBeans<sup><font size="-2">TM</font></sup>
 * has been added to the <code>java.beans</code> package.
 * Please see {@link java.beans.XMLEncoder}.
 *
 * @version 1.37 11/17/05
 * @author Georges Saab
 * @author Hans Muller
 * @author James Gosling
 */
public class WeakEventListenerList implements Serializable {

    protected transient List<WeakReference<? extends EventListener>> weakReferences;
    protected transient List<Class<? extends EventListener>> classes;
    
    /**
     * Passes back the event listener list as an array
     * of ListenerType-listener pairs.  
     * As a side-effect, cleans out any 
     * garbage collected listeners before building the array.
     * 
     * @return a array of listenerType-listener pairs.
     */
    public Object[] getListenerList() {
        List<? extends EventListener> listeners = cleanReferences();
        Object[] result = new Object[listeners.size() * 2];
        for (int i = 0; i < listeners.size(); i++) {
            result[2*i + 1] = listeners.get(i);
            result[2*i] = getClasses().get(i);
        }
        return result;
    }

    /**
     * Returns a list of strongly referenced EventListeners. Removes
     * internal weak references to garbage collected listeners.
     * 
     * @return
     */
    @SuppressWarnings("unchecked")
    private synchronized <T extends EventListener> List<T> cleanReferences() {
        List<T> listeners = new ArrayList<T>();
        for (int i = getReferences().size() - 1; i >= 0; i--) {
            
            Object listener = getReferences().get(i).get();
            if (listener == null) {
                getReferences().remove(i);
                getClasses().remove(i);
            } else {
                listeners.add(0, (T) listener);
            }
        }
        return listeners;
    }
    
    private List<WeakReference<? extends EventListener>> getReferences() {
        if (weakReferences == null) {
            weakReferences = new ArrayList<WeakReference<? extends EventListener>>();
        }
        return weakReferences;
    }
    
    private List<Class<? extends EventListener>> getClasses() {
        if (classes == null) {
            classes = new ArrayList<Class<? extends EventListener>>();
            
        }
        return classes;
    }
    /**
     * Return an array of all the listeners of the given type. 
     * As a side-effect, cleans out any 
     * garbage collected listeners before building the array.
     * @return all of the listeners of the specified type. 
     * @exception  ClassCastException if the supplied class
     *          is not assignable to EventListener
     * 
     * @since 1.3
     */
    @SuppressWarnings("unchecked")
    public <T extends EventListener> T[] getListeners(Class<T> t) {
        List<T> liveListeners = cleanReferences();
        List<T> listeners = new ArrayList<T>();
        for (int i = 0; i < liveListeners.size(); i++) {
            if (getClasses().get(i) == t) {
                listeners.add(liveListeners.get(i));
            }
        }
        T[] result = (T[])Array.newInstance(t, listeners.size()); 
        return listeners.toArray(result);
    }

    /**
     * Adds the listener as a listener of the specified type.
      * As a side-effect, cleans out any garbage collected
     * listeners before adding.
    * @param t the type of the listener to be added
     * @param l the listener to be added
     */
    public synchronized <T extends EventListener> void add(Class<T> t, T l) {
        if (l==null) {
            // In an ideal world, we would do an assertion here
            // to help developers know they are probably doing
            // something wrong
            return;
        }
        if (!t.isInstance(l)) {
            throw new IllegalArgumentException("Listener " + l +
                                         " is not of type " + t);
        }
        cleanReferences();
        getReferences().add(new WeakReference<T>(l));
        getClasses().add(t);
    }

    /**
     * Removes the listener as a listener of the specified type.
     * @param t the type of the listener to be removed
     * @param l the listener to be removed
     */
    public synchronized <T extends EventListener> void remove(Class<T> t, T l) {
        if (l ==null) {
            // In an ideal world, we would do an assertion here
            // to help developers know they are probably doing
            // something wrong
            return;
        }
        if (!t.isInstance(l)) {
            throw new IllegalArgumentException("Listener " + l +
                                         " is not of type " + t);
        }
        for (int i = 0; i < getReferences().size(); i++) {
           if (l.equals(getReferences().get(i).get()) && 
                   (t == getClasses().get(i))) {
               getReferences().remove(i);
               getClasses().remove(i);
               break;
           }
        }
    }

//    // Serialization support.  
//    private void writeObject(ObjectOutputStream s) throws IOException {
//        Object[] lList = listenerList;
//        s.defaultWriteObject();
//        
//        // Save the non-null event listeners:
//        for (int i = 0; i < lList.length; i+=2) {
//            Class t = (Class)lList[i];
//            EventListener l = (EventListener)lList[i+1];
//            if ((l!=null) && (l instanceof Serializable)) {
//                s.writeObject(t.getName());
//                s.writeObject(l);
//            }
//        }
//        
//        s.writeObject(null);
//    }
//
//    private void readObject(ObjectInputStream s) 
//        throws IOException, ClassNotFoundException {
//        listenerList = NULL_ARRAY;
//        s.defaultReadObject();
//        Object listenerTypeOrNull;
//        
//        while (null != (listenerTypeOrNull = s.readObject())) {
//            ClassLoader cl = Thread.currentThread().getContextClassLoader();
//            EventListener l = (EventListener)s.readObject();
//            add((Class<EventListener>)Class.forName((String)listenerTypeOrNull, true, cl), l);
//        }           
//    }

//    /**
//     * Returns a string representation of the EventListenerList.
//     */
//    public String toString() {
//        Object[] lList = listenerList;
//        String s = "EventListenerList: ";
//        s += lList.length/2 + " listeners: ";
//        for (int i = 0 ; i <= lList.length-2 ; i+=2) {
//            s += " type " + ((Class)lList[i]).getName();
//            s += " listener " + lList[i+1];
//        }
//        return s;
//    }
}
