// PropertyChannel.java
//
// Created: September 2005
//
// This file is a component of the BioMoby project.
// Copyright Martin Senger (martin.senger@gmail.com).
//

package org.biomoby.service.dashboard;

import org.biomoby.shared.Utils;
import org.biomoby.shared.MobyService;
import org.tulsoft.shared.UUtils;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeSupport;
import java.util.Hashtable;

/**
 * PropertyChannel is a shared storage for properties created by
 * panels and their models. It is a Hashtable but extends it because
 * whenever a value is created or updated, it also fires an event to
 * inform about it its listeners. In other words, this channel
 * provides its stored properties in two ways: <ul>
 *
 * <li> A <em>channel push</em> way when an event is fired whenever
 * something new happens, and
 *
 * <li> A <em>panel/model pull</em> way when anytime any client can
 * ask what is the current status of stored properties.
 *
 * </ul><p>
 *
 * Additionally, it also serves as a PropertyChangeSupport object: it
 * can register and deregister PropertyChangeListeners, and it can
 * fire an event to them on behave of other objects. The other objects
 * either identify themselves (see method {@link
 * #fire(Object,String,Object)}), or use this channel for firing
 * anonymous messages (see method {@link #fire(String,Object)}) - in
 * which case the message source will be this class. <p>
 *
 * The fired events are stored here under their names. That means that
 * you can always get the last event of a particular name. <p>
 *
 * The panels/models are sending here properties by calling method
 * <tt>put</tt>. If we find, in the future, that sending properties
 * here is better to do by listening to them (being a property change
 * listener) we may re-consider this desision. At the moment, I do not
 * see any advantage of it. <p>
 *
 * Concrete property names are of no interest for this class, but it
 * is good to remind that they should be collected in the class {@link
 * DashboardProperties}. <p>

 * @author <A HREF="mailto:martin.senger@gmail.com">Martin Senger</A>
 * @version $Id: PropertyChannel.java,v 1.12 2006/02/19 18:42:55 senger Exp $
 */

public class PropertyChannel
    extends Hashtable {

    private static org.apache.commons.logging.Log log =
       org.apache.commons.logging.LogFactory.getLog (PropertyChannel.class);

    private PropertyChangeSupport support;

    /*********************************************************************
     * Default constructor.
     ********************************************************************/
    public PropertyChannel() {
	super();
	support = new PropertyChangeSupport (this);
    }

    /*********************************************************************
     * Maps the specified key to the specified value in this
     * hashtable. Neither the key nor the value can be null. <p>
     *
     * The given value, apart from being stored, is also propagated by
     * firing a property change event (this is called a <em>channel
     * push</em> style). The event is anonymous. If you need to send
     * the source of the message to the listeners, use instead {@link
     * #put(Object,Object,Object)}. <p>
     *
     * @param key the hashtable key
     * @param value the value
     * @return the previous value of the specified key in this
     * hashtable, or null if it did not have one
     ********************************************************************/
    public Object put (Object key, Object value) {
	Object previous = super.put (key, value);
	fire (key.toString(), value);
	return previous;
    }

    /*********************************************************************
     * Maps the specified key to the specified value in this
     * hashtable. Neither the key nor the value can be null. <p>
     *
     * The given value, apart from being stored, is also propagated by
     * firing a property change event (this is called a <em>channel
     * push</em> style). The event contains also its 'source'. For
     * anonymously sent events, use instead {@link
     * #put(Object,Object)}. <p>
     *
     * @param source that initiated this event
     * @param key the hashtable key
     * @param value the value
     * @return the previous value of the specified key in this
     * hashtable, or null if it did not have one
     ********************************************************************/
    public Object put (Object source, Object key, Object value) {
	Object previous = super.put (key, value);
	fire (source, key.toString(), value);
	return previous;
    }

    /*********************************************************************
     *
     ********************************************************************/
    public String getString (Object key) {
	Object value = get (key);
	if (value == null) return "";
	return value.toString();
    }

    /*********************************************************************
     *
     ********************************************************************/
    public boolean getBoolean (Object key, boolean defaultValue) {
	Object value = get (key);
	if (value == null) return defaultValue;
	if (value instanceof Boolean)
	    return ((Boolean)value).booleanValue();
	return UUtils.is (value.toString());
    }

    /*********************************************************************
     * Fire an event to any registered listeners. The source of this
     * event will be this class. <p>
     *
     * @param key is a name of the fired event
     * @param value is a value associated with this event
     ********************************************************************/
    public void fire (String key, Object value) {
	if (log.isDebugEnabled())
	    logDebug (null, key, value);
	support.firePropertyChange (key.toString(), null, value);
    }

    /*********************************************************************
     * Fire an event to any registered listeners. The source of this
     * event will be this class. <p>
     *
     * @param source that initiated the event
     * @param key is a name of the fired event
     * @param value is a value associated with this event
     ********************************************************************/
    public void fire (Object source, String key, Object value) {
	if (log.isDebugEnabled())
	    logDebug (source, key, value);
	support.firePropertyChange
	    (new PropertyChangeEvent (source, key.toString(), null, value));
    }

    /*********************************************************************
     * Register listeners.
     ********************************************************************/
    public void addPropertyChangeListener (PropertyChangeListener l) {
        if (support != null)
	    support.addPropertyChangeListener(l);
    }

    /*********************************************************************
     * Unregister listeners.
     ********************************************************************/
    public void removePropertyChangeListener (PropertyChangeListener l) {
        if (support != null)
	    support.removePropertyChangeListener(l);
    }

    /*********************************************************************
     * Log a debug message.
     ********************************************************************/
    protected void logDebug (Object source, String key, Object value) {

        if (DashboardProperties.DP_STATUS_MSG.equals (key))
        	return;
        if (DashboardProperties.DP_S_SELECTED.equals (key))
        	return;
	StringBuffer buf = new StringBuffer (100);
	if (source != null) {
	    buf.append ("[");
	    if (source instanceof DashboardPanel)
		buf.append ( Utils.simpleClassName (((DashboardPanel)source).getName()) );
	    else
		buf.append ( Utils.simpleClassName (source.getClass().getName()) );
	    buf.append ("] ");
	}
	buf.append (key);
	buf.append (": ");
        if (value != null) {
	    if (value instanceof MobyService)
		buf.append ( ((MobyService)value).getName());
	    else
		buf.append (value.toString());
	}
	log.debug (buf);
    }

}
