/*
 * PropertyListenerMgr.java
 * Created on April 29, 2005, 3:52 PM
 */
package nxm.sys.lib;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.util.List;
import java.util.Vector;

import nxm.sys.inc.PropertyViewedListener;
import nxm.sys.inc.PropertyViewedListener.PropertyViewEvent;

/** This class manages the property listeners for another class. Other classes (such as
    {@link Table}) can use one of these to manage their property change listeners.

    @author  J. Czechowski IV
    @since   NeXtMidas 2.1.0
 */
public final class PropertyListeners {
  /** The source object.             */ private final Object source;
  /** The property change listeners. */ private final List<PropertyChangeListener> changeListeners;
  /** The vetoable change listeners. */ private final List<VetoableChangeListener> vetoListeners;
  /** The property viewed listeners. */ private final List<PropertyViewedListener> viewedListeners;


  /** Creates a new instance of PropertyListenerMgr
      @param source the source object */
  public PropertyListeners(Object source) {
    this.source          = source;
    this.changeListeners = new Vector<>();
    this.vetoListeners   = new Vector<>();
    this.viewedListeners = new Vector<>();
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////

  /** Registers a {@link PropertyChangeListener}.

      @param listener The listener.
   */
  public void addPropertyChangeListener(PropertyChangeListener listener) {
    changeListeners.add(listener);
  }

  /** Registers a {@link PropertyChangeListener}.

      @param listener The listener.
   */
  public void addVetoableChangeListener(VetoableChangeListener listener) {
    vetoListeners.add(listener);
  }

  /** Registers a {@link PropertyChangeListener}.

      @param listener The listener.
   */
  public void addPropertyViewedListener(PropertyViewedListener listener) {
    viewedListeners.add(listener);
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////

  /** De-registers a {@link PropertyChangeListener}.

      @param listener The listener.
   */
  public void removePropertyChangeListener(PropertyChangeListener listener) {
    changeListeners.remove(listener);
  }

  /** De-registers a {@link PropertyChangeListener}.

      @param listener The listener.
   */
  public void removeVetoableChangeListener(VetoableChangeListener listener) {
    vetoListeners.remove(listener);
  }

  /** De-registers a {@link PropertyChangeListener}.

      @param listener The listener.
   */
  public void removePropertyViewedListener(PropertyViewedListener listener) {
    viewedListeners.remove(listener);
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////

  /** Lists all registered {@link PropertyChangeListener}s.

      @return The listeners.
   */
  public PropertyChangeListener[] getPropertyChangeListeners() {
    return changeListeners.toArray(new PropertyChangeListener[0]);
  }

  /** Lists all registered {@link PropertyChangeListener}s.

       @return The listeners.
   */
  public VetoableChangeListener[] getVetoableChangeListeners() {
    return vetoListeners.toArray(new VetoableChangeListener[0]);
  }

  /** Lists all registered {@link PropertyChangeListener}s.

       @return The listeners.
   */
  public PropertyViewedListener[] getPropertyViewedListeners() {
    return viewedListeners.toArray(new PropertyViewedListener[0]);
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////

  /** Fires a {@link PropertyChangeEvent} to all registered {@link PropertyChangeListener}s.

      @param propertyName The name of the property that was changed.
      @param oldValue     The old value of the property.
      @param newValue     The new value of the property.
   */
  public final void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
    firePropertyChange(new PropertyChangeEvent(source, propertyName, oldValue, newValue));
  }

  /** Fires a {@link PropertyChangeEvent} to all registered {@link PropertyChangeListener}s.

      @param evt The property change event.
   */
  public void firePropertyChange(PropertyChangeEvent evt) {
    for (PropertyChangeListener changeListener : changeListeners) {
      changeListener.propertyChange(evt);
    }
  }

  /** Fires a {@link PropertyChangeEvent} to all registered {@link PropertyChangeListener}s and
      all {@link VetoableChangeListener}s. If any of the {@link VetoableChangeListener}s vetos the
      change, any that had been informed of the change will be informed of a change back to the
      old value, and then a {@link PropertyVetoException} will be thrown.

      @param propertyName The name of the property that was changed.
      @param oldValue     The old value of the property.
      @param newValue     The new value of the property.
      @throws PropertyVetoException if change is vetoed
   */
  public final void fireVetoableChange(String propertyName, Object oldValue, Object newValue)
                                      throws PropertyVetoException {
    fireVetoableChange(new PropertyChangeEvent(source, propertyName, oldValue, newValue));
  }

  /** Fires a {@link PropertyChangeEvent} to all registered {@link PropertyChangeListener}s and
      all {@link VetoableChangeListener}s. If any of the {@link VetoableChangeListener}s vetos the
      change, any that had been informed of the change will be informed of a change back to the
      old value, and then a {@link PropertyVetoException} will be thrown.

      @param evt The property change event.

      @throws PropertyVetoException If one of the {@link VetoableChangeListener}s vetos the change.
   */
  public void fireVetoableChange (PropertyChangeEvent evt) throws PropertyVetoException {
    int i = 0;

    try {
      for (i = 0; i < changeListeners.size(); i++) {
        vetoListeners.get(i).vetoableChange(evt);
      }
    } catch (PropertyVetoException e) {
      /* Revert everyone back. */
      PropertyChangeEvent revert = new PropertyChangeEvent(evt.getSource(), evt.getPropertyName(),
                                                           null, evt.getOldValue());
      for (; i >= 0; i--) {
        try {
          vetoListeners.get(i).vetoableChange(revert);
        } catch(PropertyVetoException ex) {
          /* IGNORE */
        }
      }

      throw e;
    }

    firePropertyChange(evt);
  }

  /** Fires a {@link nxm.sys.inc.PropertyViewedListener.PropertyViewEvent} to all registered
      {@link PropertyViewedListener}s.

      @param propertyName The name of the property that was changed.
      @param oldValue     The old value of the property.
   */
  public final void firePropertyView(String propertyName, Object oldValue) {
    firePropertyView(new PropertyViewEvent(source, propertyName, oldValue));
  }

  /** Fires a {@link nxm.sys.inc.PropertyViewedListener.PropertyViewEvent} to all registered
      {@link PropertyViewedListener}s.

      @param evt The property change event.
   */
  public void firePropertyView(PropertyViewEvent evt) {
    for (PropertyViewedListener viewedListener : viewedListeners) {
      viewedListener.propertyView(evt);
    }
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // A class that can be extended by any class that needs listeners.
  //////////////////////////////////////////////////////////////////////////////////////////////////
  /** A class that can be extended by any class that needs listeners. */
  public static class HasPropertyListeners {
    /** The listeners. */
    protected final PropertyListeners listeners = new PropertyListeners(this);

    /** default constructor */
    public HasPropertyListeners () {}

    /** Registers a {@link PropertyChangeListener}.
        @param listener The listener.
     */
    public void addPropertyChangeListener(PropertyChangeListener listener) {
      listeners.addPropertyChangeListener(listener);
    }

    /** Registers a {@link PropertyChangeListener}.
        @param listener The listener.
     */
    public void addVetoableChangeListener(VetoableChangeListener listener) {
      listeners.addVetoableChangeListener(listener);
    }

    /** Registers a {@link PropertyChangeListener}.
        @param listener The listener.
     */
    public void addPropertyViewedListener(PropertyViewedListener listener) {
      listeners.addPropertyViewedListener(listener);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////

    /** De-registers a {@link PropertyChangeListener}.
        @param listener The listener.
     */
    public void removePropertyChangeListener(PropertyChangeListener listener) {
      listeners.removePropertyChangeListener(listener);
    }

    /** De-registers a {@link PropertyChangeListener}.
        @param listener The listener.
     */
    public void removeVetoableChangeListener(VetoableChangeListener listener) {
      listeners.removeVetoableChangeListener(listener);
    }

    /** De-registers a {@link PropertyChangeListener}.
        @param listener The listener.
     */
    public void removePropertyViewedListener(PropertyViewedListener listener) {
      listeners.removePropertyViewedListener(listener);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////

    /** Lists all registered {@link PropertyChangeListener}s.
        @return The listeners.
     */
    public PropertyChangeListener[] getPropertyChangeListeners() {
      return listeners.getPropertyChangeListeners();
    }

    /** Lists all registered {@link PropertyChangeListener}s.
        @return The listeners.
     */
    public VetoableChangeListener[] getVetoableChangeListeners() {
      return listeners.getVetoableChangeListeners();
    }

    /** Lists all registered {@link PropertyChangeListener}s.
        @return The listeners.
     */
    public PropertyViewedListener[] getPropertyViewedListeners() {
      return listeners.getPropertyViewedListeners();
    }
  }
}
