/*
 * Copyright (c) 2002-2011 JGoodies Karsten Lentzsch. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  o Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  o Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 *  o Neither the name of JGoodies Karsten Lentzsch nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jgoodies.binding.value;

import static com.jgoodies.common.base.Preconditions.checkArgument;

/**
 * A ValueModel implementation that is intended to be used as trigger channel
 * for instances of BufferedValueModel. API users shall trigger commit and flush
 * events using {@code #triggerCommit} and {@code #triggerFlush}.<p>
 *
 * This Trigger class works around an inconvenient situation when using
 * a general ValueHolder as trigger channel of a BufferedValueModel.
 * BufferedValueHolder performs commit and flush events only if the trigger
 * channel value reports a change. And a ValueHolder doesn't report a change
 * if {@code #setValue} tries to set the current value. For example
 * if you set {@code Boolean.TRUE} twice, the latter doesn't fire
 * a property change event. The methods {@code #triggerCommit} and
 * {@code #triggerFlush} check for the current state and guarantee
 * that the appropriate {@code PropertyChangeEvent} is fired.
 * On the other hand, the implementation minimizes the number of events
 * necessary to commit or flush buffered values.<p>
 *
 * <strong>Constraints:</strong> The value is of type {@code Boolean}.
 * <p>
 * The following example delays the commit of a buffered value:
 * <pre>
 * ValueModel subject = new ValueHolder();
 * Trigger trigger = new Trigger();
 * BufferedValueModel buffer = new BufferedValueModel(subject, trigger);
 *
 * buffer.setValue("value");
 * ...
 * trigger.triggerCommit();
 * </pre>
 *
 * @author  Karsten Lentzsch
 * @version $Revision: 1.10 $
 *
 * @see     BufferedValueModel
 */
public final class Trigger extends AbstractValueModel {

    private static final Boolean COMMIT  = Boolean.TRUE;
    private static final Boolean FLUSH   = Boolean.FALSE;
    private static final Boolean NEUTRAL = null;

    /**
     * Holds the current trigger state.
     */
    private Boolean value;


    // Instance Creation ******************************************************

    /**
     * Constructs a Trigger set to neutral.
     */
    public Trigger() {
        value = NEUTRAL;
    }


    // ValueModel Implementation **********************************************

    /**
     * Returns a Boolean that indicates the current trigger state.
     *
     * @return a Boolean that indicates the current trigger state
     */
    public Object getValue() {
        return value;
    }

    /**
     * Sets a new Boolean value and rejects all non-Boolean values.
     * Fires no change event if the new value is equal to the
     * previously set value.<p>
     *
     * This method is not intended to be used by API users.
     * Instead you should trigger commit and flush events by invoking
     * {@code #triggerCommit} or {@code #triggerFlush}.
     *
     * @param newValue  the Boolean value to be set
     * @throws IllegalArgumentException   if the newValue is not a Boolean
     */
    public void setValue(Object newValue) {
        checkArgument(newValue == null || newValue instanceof Boolean,
                "Trigger values must be of type Boolean.");
        Object oldValue = value;
        value = (Boolean) newValue;
        fireValueChange(oldValue, newValue);
    }


    // Triggering *************************************************************

    /**
     * Triggers a commit event in BufferedValueModels that share this Trigger.
     * Sets the value to {@code Boolean.TRUE} and ensures that listeners
     * are notified about a value change to this new value. If necessary
     * the value is temporarily set to {@code null}. This way it minimizes
     * the number of PropertyChangeEvents fired by this Trigger.
     */
    public void triggerCommit() {
        if (COMMIT.equals(getValue())) {
            setValue(NEUTRAL);
        }
        setValue(COMMIT);
    }

    /**
     * Triggers a flush event in BufferedValueModels that share this Trigger.
     * Sets the value to {@code Boolean.FALSE} and ensures that listeners
     * are notified about a value change to the new value. If necessary
     * the value is temporarily set to {@code null}.  This way it minimizes
     * the number of PropertyChangeEvents fired by this Trigger.
     */
    public void triggerFlush() {
        if (FLUSH.equals(getValue())) {
            setValue(NEUTRAL);
        }
        setValue(FLUSH);
    }


}
