package util;

import java.io.*;
import java.util.*;

/**
  * Helper class that supports Listeners which themselves can be listened to.
  *
  * <p>The main intent is to support garbage collection by avoiding cyclic links between
  * the listener and its event source. Listeners that use ListenerHelper (and implement
  * HelpableListener) will be registered with their event source only as long as there
  * are any listeners registered with them. They will however guarantee up-to-date-ness
  * indepent of whether a listener is registered with them or not, by polling their
  * model whenever a query is performed against them and they have no listeners.</p>
  *
  * <p>For this mechanism to work properly such listeners must abide by the following
  * rules:</p>
  *
  * <ul>
  *   <li>They must implement HelpableListener.</li>
  *   <li>They must use an instance of ListenerHelper to manage their list of listeners.</li>
  *   <li>At the start of any query method that depends on the listener's own event
  *       source they are to call {@link #needModelUpdate}</li>
  * </ul>
  *
  * <p>As the Swing serialization is buggy, this class will serialize only listeners that implement
  * {@link SerializableListener}. For this purpose, however, all methods in
  * {@link javax.swing.event.EventListenerList} need to be overridden.</p>
  *
  * @see HelpableListener
  * @see #needModelUpdate
  *
  * @author Steffen Zschaler
  * @version 2.0 02/06/1999
  * @since v2.0
  */
public class ListenerHelper extends javax.swing.event.EventListenerList {

  /**
    * A null array to be shared by all empty listener lists
    */
  private final static Object[] NULL_ARRAY = new Object[0];

  /**
    * The list of ListenerType - Listener pairs. We need to redefine this as it would otherwise be stored by
    * the standard {@link javax.swing.event.EventListenerList} mechanism.
    *
    * PENDING (sz9): Need to replace direct references to listeners by SoftReferences, in order to avoid 
    * cycles and thereby memory leaks. Example: Stocks listen to their catalogs in order to maintain 
    * referential integrity. They also store a pointer to their catalog in order to be able to reference
    * it. A cycle is created, which means that creating lots of temporary stocks is a potential memory 
    * problem.
    */
  protected transient Object[] aoListeners = NULL_ARRAY;

  /**
    * The listener that owns this listener helper.
    *
    * @serial
    */
  protected HelpableListener m_hlOwner;

  /**
    * Construct a new ListenerHelper. Using this constructor will only use the improved serialization support,
    * but not the subscribe/unsubscribe functionality.
    */
  public ListenerHelper() {
    this (null);
  }

  /**
    * Create a new ListenerHelper.
    *
    * @param hlOwner the HelpableListener that is associated to this ListenerHelper.
    */
  public ListenerHelper (HelpableListener hlOwner) {
    super();

    m_hlOwner = hlOwner;
  }

  /**
    * This passes back the event listener list as an array
    * of ListenerType - listener pairs.  Note that for
    * performance reasons, this implementation passes back
    * the actual data structure in which the listner data
    * is stored internally!
    * This method is guaranteed to pass back a non-null
    * array, so that no null-checking is required in
    * fire methods.  A zero-length array of Object should
    * be returned if there are currently no listeners.
    *
    * WARNING!!! Absolutely NO modification of
    * the data contained in this array should be made -- if
    * any such manipulation is necessary, it should be done
    * on a copy of the array returned rather than the array
    * itself.
    */
  public Object[] getListenerList() {
    return aoListeners;
  }

  /**
    * Return the total number of listeners for this listenerlist
    */
  public int getListenerCount() {
    return aoListeners.length / 2;
  }

  /**
    * Return the total number of listeners of the supplied type
    * for this listenerlist.
    */
  public int getListenerCount(Class t) {
    int count = 0;
    Object[] lList = aoListeners;

    for (int i = 0; i < lList.length; i += 2) {
      if (t == ((Class) lList[i])) {
        count++;
      }
    }

    return count;
  }

  /**
    * Add the listener as a listener of the specified type.
    *
    * @param t the type of the listener to be added
    * @param l the listener to be added
    */
  public synchronized void add (Class t, EventListener l) {
    if (!t.isInstance(l)) {
      throw new IllegalArgumentException ("Listener " + l + " is not of type " + t);
    }

    if (l ==null) {
      throw new IllegalArgumentException("Listener " + l + " is null");
    }

    if (aoListeners == NULL_ARRAY) {
      // if this is the first listener added,
      // initialize the lists
      aoListeners = new Object[] { t, l };

      // .. and have the owner subscribe.
      if (m_hlOwner != null) {
        m_hlOwner.subscribe();
        // a last time so that we start with the most accurate model possible
        m_hlOwner.updateModel();
      }
    }
    else {
      // Otherwise copy the array and add the new listener
      int i = aoListeners.length;
      Object[] tmp = new Object[i+2];
      System.arraycopy(aoListeners, 0, tmp, 0, i);

      tmp[i] = t;
      tmp[i+1] = l;

      aoListeners = tmp;
    }
  }

  /**
    * Remove 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 void remove(Class t, EventListener l) {
    if (!t.isInstance(l)) {
      throw new IllegalArgumentException("Listener " + l + " is not of type " + t);
    }

    if (l ==null) {
      throw new IllegalArgumentException("Listener " + l + " is null");
    }

    // Is l on the list?
    int index = -1;
    for (int i = aoListeners.length-2; i >= 0; i -= 2) {
      if ((aoListeners[i] == t) &&
          (aoListeners[i+1].equals(l))) {
        index = i;
        break;
      }
    }

    // If so,  remove it
    if (index != -1) {
      Object[] tmp = new Object[aoListeners.length-2];
      // Copy the list up to index
      System.arraycopy(aoListeners, 0, tmp, 0, index);

      // Copy from two past the index, up to
      // the end of tmp (which is two elements
      // shorter than the old list)
      if (index < tmp.length) {
        System.arraycopy(aoListeners, index + 2, tmp, index, tmp.length - index);
      }

      // set the listener array to the new array or null
      aoListeners = (tmp.length == 0) ? NULL_ARRAY : tmp;

      if (tmp.length == 0) {
        if (m_hlOwner != null) {
          m_hlOwner.unsubscribe();
        }
      }
    }
  }

  /**
    * Make sure, the owner's model is up to date. If necessary call
    * {@link util.HelpableListener#updateModel} in the owner.
    */
  public void needModelUpdate() {
    if (getListenerCount() == 0) {

      if (m_hlOwner != null) {
        m_hlOwner.updateModel();
      }
    }
  }

  /**
    * A SerializableListenerHelper will store all the listeners that implement {@link SerializableListener}.
    *
    * @serialData tuples of (&lt;classname&gt;, &lt;listener&gt;).
    */
  private void writeObject(ObjectOutputStream s) throws IOException {
    Object[] lList = aoListeners;
    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 SerializableListener)) {
        s.writeObject(t.getName());
        s.writeObject(l);
      }
    }

    s.writeObject(null);
  }

  /**
    * A SerializableListenerHelper will store all the listeners that implement {@link SerializableListener}.
    */
  private void readObject(ObjectInputStream s)
    throws IOException, ClassNotFoundException {

    aoListeners = NULL_ARRAY;

    s.defaultReadObject();
    Object listenerTypeOrNull;

    while (null != (listenerTypeOrNull = s.readObject())) {
      EventListener l = (EventListener) s.readObject();
      add(Class.forName ((String) listenerTypeOrNull), l);
    }
  }

  /**
    * Return a string representation of the SerializableListenerHelper.
    */
  public String toString() {
    Object[] lList = aoListeners;
    String s = "SerializableListenerHelper: ";
    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;
  }
}