package org.jcon.util.msg;

import org.jcon.util.DataLib;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

/**
 * This class allows event sources and listeners to
 * collaborate with loose coupling using Messages. This is
 * a reusable event routing service.
 * <p>
 * Note that it is best for a Mediator to establish event
 * relationships, rather than a listener or source doing
 * the subscribing. This promotes low coupling. Example:
 * <p> <pre>
 * view.getMessageRouter().addListener("Delete", stateMachine);
 * <p> </pre>
 * Here a mediator links the view Delete event to the
 * stateMachine. The view is the source and the
 * stateMachine is the listener. The view and stateMachine
 * do not know about each other, so they are decoupled.
 *
 * @author Jack Harich
 */
public class MessageRouter {

//------------ Private Fields ----------------------------
// Key = eventName, Object = Vector of MessageListener
private Hashtable eventListeners = new Hashtable();

private boolean listEventsAsFired = false; // For testing
private Object autoSource;

//------------ Initialization ----------------------------
/**
 * The source must be provided when firing an event.
 */
public MessageRouter() {
    // Provide source when firing event
}
/**
 * The autoSource will be used when firing an event. This
 * makes the fire() call more concise.
 */
public MessageRouter(Object autoSource) {
    this.autoSource = autoSource;
}
//------------ Properties --------------------------------
/**
 * Sets the AutoSource property. This is useful for
 * monitoring message traffic. The AutoSource is the
 * default source for messages sent.
 */
public void setAutoSource(Object autoSource) {
    this.autoSource = autoSource;
}
//------------ Public Methods ----------------------------
//---------- Listener Mutators
/**
 * Listeners may listen to events of interest by subscribing
 * to semantic events, each of which has an eventName.
 * Event names must be unique per router.
 *
 * @param eventName  the event to subscribe the listener to.
 * @param listener   the listener to notify when the event happens.
 */
public synchronized void addListener(String eventName,
                        MessageListener listener) {
    Vector listeners = (Vector)eventListeners.get(eventName);
    if (listeners == null) {
        listeners = new Vector();
        eventListeners.put(eventName, listeners);
    }
    listeners.addElement(listener);
}
/**
 * Adds the listener for all events it is interested
 * in. This is a convenience method, and is the same as
 * calling addListener() for each interest. If the listener
 * has no interests, nothing happens.
 *
 * @param listener  the listener to notify when the event happens.
 */
public synchronized void addListenerInterests(
                        MessageListener listener) {

    String[] interests = listener.loadMessageInterests();
    for (int i = interests.length; --i >= 0; ) {
        addListener(interests[i], listener);
    }
}
/**
 * Listeners may stop listening to events of interest.
 *
 * @param eventName  the event of previous interest.
 * @param listener   the listener to remove for notification
 *                   of this event.
 */
public synchronized void removeListener(String eventName,
        MessageListener listener) {
    Vector listeners = (Vector)eventListeners.get(eventName);
    if (listeners != null) {
        listeners.removeElement(listener);
        if (listeners.isEmpty()) {
            eventListeners.remove(eventName);
        }
    }
}
/**
 * Removes the listener from all events it may have been
 * listening to.
 *
 * @param listener  the listener to remove for all notification.
 */
public synchronized void removeListener(
                            MessageListener listener) {
    Enumeration keys = eventListeners.keys();
    while (keys.hasMoreElements()) {
        String eventName = (String)keys.nextElement();
        // Load removeListeners
        Vector removeListeners = new Vector();
        Vector listeners = (Vector)eventListeners.get(eventName);
        for (int i = 0; i < listeners.size(); i++) {
            Object aListener = listeners.elementAt(i);
            if (aListener == listener) {
                removeListeners.addElement(aListener);
            }
        }
        // Use removeListeners
        for (int i = 0; i < removeListeners.size(); i++) {
            Object aListener = removeListeners.elementAt(i);
            listeners.removeElement(aListener);
        }
        if (listeners.size() == 0) eventListeners.remove(eventName);
    }
}
//---------- Accessors
/**
 * Determines if the eventName has the listener.
 *
 * @param   eventName  the event to check.
 * @param   listener   the listener to check.
 * @return  true if the listener is subscribed to the
 *          eventName, false if not.
 */
public synchronized boolean hasListener(String eventName,
        MessageListener listener) {
    Vector listeners = (Vector)eventListeners.get(eventName);
    if (listeners == null) return false;
    return listeners.contains(listener);
}
/**
 * Determines if an event has any listeners. This is
 * useful to determine if an event should be fired.
 *
 * @param eventName  the event to check.
 * @param true if at least one listener is subscribed to
 *        the eventName, false if none are subscribed.
 */
public synchronized boolean hasListener(String eventName) {
    return (eventListeners.get(eventName) != null ? true : false);
}
/**
 * Gets all the event names currently subscribed to.
 *
 * @return a sorted array of the event names we have
 *         listeners for.
 */
public String[] getEventNames() {
    // Populate namesVector
    Vector namesVector = new Vector();
    Enumeration enum = eventListeners.keys();
    while (enum.hasMoreElements()) {
        namesVector.addElement(enum.nextElement());
    }
    // Convert to array and sort
    return DataLib.sortStringVectorToArray(namesVector);
}
/**
 * Gets all the listeners subscribed to an event.
 *
 * @return a shallow copy of the Vector containing the
 *         listeners for the eventName or null if none.
 */
public Vector getEventListeners(String eventName) {
    Vector listeners = (Vector)eventListeners.get(eventName);
    if (listeners == null) {
        return null;
    } else {
        return (Vector)listeners.clone();
    }
}
/**
 * Gets the event names a listener is subscribed to.
 *
 * @return a sorted array of all the event names that the
 *         listener is subscribed to. No check for duplicates.
 */
public String[] getEventNamesWithListener(MessageListener listener) {
    // Populate namesVector
    Vector namesVector = new Vector();
    Enumeration enum = eventListeners.keys();
    while (enum.hasMoreElements()) {
        String eventName = (String)enum.nextElement();
        Vector listeners = (Vector)eventListeners.get(eventName);
        for (int i = 0; i < listeners.size(); i++) {
            Object object = listeners.elementAt(i);
            if (object == listener) namesVector.addElement(eventName);
        }
    }
    // Convert to array and sort
    return DataLib.sortStringVectorToArray(namesVector);
}

//---------- Fire event variations -----------------------
/**
 * Multicasts the event to its listeners.
 * Returns true if at least one listener exists for event,
 * false otherwise. Thus true means the event was caught.
 * <p>
 * Sources may collaborate with listeners by
 * firing events. These are broadcast to all listeners
 * for that event. The source argument is for future use.
 * The source is NOT passed to the listener, since that
 * would promote tighter coupling.
 */
public boolean fire(Message evt, Object source) {

    Vector listeners = (Vector)eventListeners.get(evt.getName());
    if (listeners != null) {
        Vector list;
        synchronized(this) {
            list = (Vector)listeners.clone();
        }
        for (int i = 0; i < list.size(); i++) {
            MessageListener listener
                = (MessageListener)list.elementAt(i);
            if (listEventsAsFired) {
                System.out.println("MR dispatched " +
                    evt.getName() + " to " + listener.getClass().getName());
            }
            listener.processMessage(evt);
        }
        return true;
    } else {
        return false;
    }
}
/**
 * A convenience method that uses the AutoSource from
 * new MessageRouter(Object source).
 */
public boolean fire(Message evt) {
    return fire(evt, autoSource);
}
/**
 * Fires a Message containing no properties and named
 * messageName. Conceptually similar to ActionCommand.
 */
public boolean fire(String messageName, Object source) {
    return fire(new Message(messageName), source);
}
/**
 * A convenience method that uses the AutoSource from
 * new MessageRouter(Object source).
 */
public boolean fire(String messageName) {
    return fire(messageName, autoSource);
}
/**
 * Fires a Message named messageName with a property
 * named propName containing the value.
 */
public boolean fire(String messageName,
        String propName, Object value, Object source) {

    Message message = new Message(messageName);
    message.set(propName, value);
    return fire(message, source);
}
/**
 * A convenience method that uses the AutoSource from
 * new MessageRouter(Object source).
 */
public boolean fire(String messageName,
        String propName, Object value) {
    return fire(messageName, propName, value, autoSource);
}
//---------- Other
/**
 * Clears all listeners. The source is not cleared.
 */
public synchronized void clear() {
    eventListeners.clear();
}
//---------- Private Methods -----------------------------
//--- Std
private static void print(String text) {
    System.out.println("MessageRouter" + text);
}


} // End class
