// ============================================================================
// File:               $File$
//
// Project:            
//
// Purpose:            
//
// Author:             Rammi
//
// Copyright Notice:   (c) 2005  Rammi (rammi@caff.de)
//                     This code is in the public domain.
//                     Use at own risk.
//                     No guarantees given.
//
// Latest change:      $Date$
//
// History:	       $Log$
//=============================================================================
package de.caff.util.settings;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.ref.WeakReference;

/**
 *  Basic implementation of a preference property.
 *
 *  It takes care of registering the listeners and provides a name used to
 *  store and retrieve the property value. It provides several 
 *  <code>firePropertyChange()</code> method. Each implementing class has
 *  to call one of these methods whenever the underlying value is changed.
 *
 *  @author <a href="mailto:rammi@caff.de">Rammi</a>
 *  @version $Revision$
 */
public abstract class AbstractBasicPreferenceProperty
        implements PreferenceProperty
{

  /** The basic name. This may be the complete name of the property, or just the common prefix. */
  private final String basicName;
  /** The property change support. */
  private final PropertyChangeSupport propertyChangeSupport;

  /**
   * Internal class providing weakly referenced property listeners.
   */
  private class WeakPropertyChangeListenerProxy
    implements PropertyChangeListener
  {
    /** Weakly referenced wrapped PropertyChangeListener. */
    private final WeakReference wrapped;

    /**
     *  Constructor.
     *  @param listener property change listener which handles the original calls
     */
    WeakPropertyChangeListenerProxy(PropertyChangeListener listener)
    {
      wrapped = new WeakReference(listener);
    }

    /**
     * This method gets called when a bound property is changed.
     *
     * @param evt A PropertyChangeEvent object describing the event source
     *            and the property that has changed.
     */
    public void propertyChange(PropertyChangeEvent evt)
    {
      PropertyChangeListener listener = (PropertyChangeListener)wrapped.get();
      if (listener != null) {
        listener.propertyChange(evt);
      }
      else {
        propertyChangeSupport.removePropertyChangeListener(this);
      }
    }

    /**
     *  Get the wrapped listener.
     *  @return wrapped listener or <code>null</code> if the listener is already reclaimed
     */
    PropertyChangeListener getWrapped()
    {
      return (PropertyChangeListener)wrapped.get();
    }
  }


  /**
   * Constructor.
   * Construct a property with the given name of name prefix.
   * @param basicName name or name prefix
   */
  protected AbstractBasicPreferenceProperty(String basicName)
  {
    this.basicName = basicName;
    propertyChangeSupport = new PropertyChangeSupport(this);
  }

  /**
   *  Get the basic name of the property.
   *  This may be the complete name or a common prefix.
   *  @return the basic name
   */
  protected String getBasicName()
  {
    return basicName;
  }

  /**
   *  Fire a property change.
   *  @param evt property change event
   */
  protected void firePropertyChange(PropertyChangeEvent evt)
  {
    propertyChangeSupport.firePropertyChange(evt);
  }

  /**
   *  Add a listener which will be called for all value changes.
   *
   *  @param listener value change listener to add
   *  @see #addValueChangeListenerWeakly(java.beans.PropertyChangeListener)
   */
  public void addValueChangeListener(PropertyChangeListener listener)
  {
    propertyChangeSupport.addPropertyChangeListener(listener);
  }

  /**
   *  Add a listener which will be called for all value changes.
   *  The listener is registered with a weak reference, allowing it to be freed if
   *  this is the only reference.
   *  <p>
   *  If you want to remove the listener please use the corresponding
   *  {@link #removeValueChangeListenerWeakly(java.beans.PropertyChangeListener)}
   *  method.
   *
   *  @param listener value change listener to add
   *  @see #addValueChangeListener(java.beans.PropertyChangeListener)
   */
  public void addValueChangeListenerWeakly(PropertyChangeListener listener)
  {
    propertyChangeSupport.addPropertyChangeListener(new WeakPropertyChangeListenerProxy(listener));
  }

  /**
   *  Remove a listener which will be called for all value changes.
   *
   *  @param listener value change listener to remove
   *  @see #removeValueChangeListenerWeakly(java.beans.PropertyChangeListener)
   */
  public void removeValueChangeListener(PropertyChangeListener listener)
  {
    propertyChangeSupport.removePropertyChangeListener(listener);
  }

  /**
   *  Remove a weakly referenced listener which will be called for all value changes.
   *
   *  @param listener value change listener to remove
   *  @see #removeValueChangeListener(java.beans.PropertyChangeListener)
   */
  public void removeValueChangeListenerWeakly(PropertyChangeListener listener)
  {
    PropertyChangeListener[] listeners = propertyChangeSupport.getPropertyChangeListeners();
    for (int l = listeners.length-1;  l >= 0;  --l) {
      if (listeners[l] instanceof WeakPropertyChangeListenerProxy) {
        WeakPropertyChangeListenerProxy proxy = (WeakPropertyChangeListenerProxy)listeners[l];
        Object wrapped = proxy.getWrapped();
        if (wrapped == null  ||  listener.equals(wrapped)) {
          propertyChangeSupport.removePropertyChangeListener(proxy);
        }
      }
    }
  }

  /**
   *  Fire a generic value property change.
   *  @param propertyName property name
   *  @param oldValue old value
   *  @param newValue new value
   */
  protected void fireValueChange(String propertyName, Object oldValue, Object newValue)
  {
    propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
  }

  /**
   *  Fire a value property change for a boolean value.
   *  @param propertyName property name
   *  @param oldValue     old value
   *  @param newValue     new value
   */
  protected void fireValueChange(String propertyName, boolean oldValue, boolean newValue)
  {
    propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
  }

  /**
   *  Fire a value property change for a integer value.
   *  @param propertyName property name
   *  @param oldValue     old value
   *  @param newValue     new value
   */
  protected void fireValueChange(String propertyName, int oldValue, int newValue)
  {
    propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
  }

  /**
   *  Get the name of the property.
   *  This default implementation returns the basic name.
   *  @return property name
   */
  public String getPropertyName()
  {
    return getBasicName();
  }
}
