/*
 * @(#)RangeFilter.java    1.0  9 February 2001
 *
 * Copyright 2004
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.filter;

import edu.neu.ccs.*;
import java.beans.*;
import java.math.*;

/**
 * <P>Filter that enforces inclusive or exclusive
 * numeric upper and lower bounds 
 * by throwing an exception
 * when the data to be filtered violates either bound.
 *
 * Inner classes provide the functionality of both
 * integer and real number bounds,
 * of primitive or arbitrary precision.</P>
 *
 * @author  Jeff Raab
 * @version 2.2
 * @since   1.0
 */
public class RangeFilter 
    extends NumericFilter 
    implements PropertyChangeListener
{
    /** Bound property name for the minimum property. */
    public static final String MINIMUM = 
        MinimumBoundFilter.MINIMUM;

    /** Bound property name for the maximum property. */
    public static final String MAXIMUM =
        MaximumBoundFilter.MAXIMUM;

    /** 
     * Bound property name for 
     * the minimum exclusivity property. 
     */
    public static final String MINIMUM_INCLUSIVE =
         "minimum.inclusive";

    /** 
     * Bound property name for 
     * the minimum exclusivity property. 
     */
    public static final String MAXIMUM_INCLUSIVE = 
        "maximum.inclusive";
        
    /**
     * Minimum bound filter used to enforce
     * the lower bound of this range.
     */
    protected MinimumBoundFilter minimum = null;

    /**
     * Maximum bound filter used to enforce
     * the upper bound of this range.
     */
    protected MaximumBoundFilter maximum = null;

    ////////////////////////////
    // PropertyChangeListener //
    ////////////////////////////
    
    /**
     * Passes on events generated by property changes
     * imposed on the minimum and maximum bounds.
     *
     * @param evt the property change event to handle
     */
    public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getSource() == minimum) {
            if (evt.getPropertyName() == 
                MinimumBoundFilter.MINIMUM) 
            {
                changeAdapter.firePropertyChange(
                    MINIMUM,
                    evt.getOldValue(),
                    evt.getNewValue());
            }
            else if (evt.getPropertyName() == 
                     MinimumBoundFilter.INCLUSIVE)
            {
                changeAdapter.firePropertyChange(
                    MINIMUM_INCLUSIVE,
                    evt.getOldValue(),
                    evt.getNewValue());
            }
        }
        else if (evt.getSource() == maximum) {
            if (evt.getPropertyName() ==
                     MaximumBoundFilter.MAXIMUM)
            {
                changeAdapter.firePropertyChange(
                    MAXIMUM,
                    evt.getOldValue(),
                    evt.getNewValue());
            }
            else if (evt.getPropertyName() ==
                     MinimumBoundFilter.INCLUSIVE)
            {
                changeAdapter.firePropertyChange(
                    MAXIMUM_INCLUSIVE,
                    evt.getOldValue(),
                    evt.getNewValue());
            }
        }
    }

    ///////////////////
    // Inner classes //
    ///////////////////
    
    /**
     * <P>Range filter 
     * representing integer upper and lower bounds 
     * of primitive precision.</P>
     *
     * @author  Jeff Raab
     * @version 2.1
     * @since   1.0
     */
    public static class Long extends RangeFilter {

        //////////////////
        // Constructors //
        //////////////////
    
        /**
         * Constructs an inclusive range 
         * bounded by the given values.
         *
         * @param min the minimum value for the range
         * @param max the maximum value for the range
         */
        public Long(long min, long max) {
            this(min, true, max, true);
        }

        /**
         * Constructs a range 
         * bounded by the given values
         * with the given individual exclusivity.
         *
         * @param min the minimum value for the range
         * @param minInclusive whether or not the minimum value
         *      is inclusive
         * @param max the maximum value for the range
         * @param maxaInclusive whether or not the maximum value
         *      is inclusive
         */
        public Long(
            long min, 
            boolean minInclusive, 
            long max,
            boolean maxInclusive) 
        {
            minimum = new MinimumBoundFilter.Long(
                min, minInclusive);
            minimum.addPropertyChangeListener(this);

            maximum = new MaximumBoundFilter.Long
                (max, maxInclusive);
            maximum.addPropertyChangeListener(this);
        }

        //////////////////////
        // StringableFilter //
        //////////////////////
        
        /**
         * Returns the given object 
         * if it satisfies the bound for this range.
         *
         * @param obj the object to be filtered
         * @throws FilterException if the object 
         *      is out of bounds
         */
        public Stringable filterStringable(Stringable obj) 
            throws FilterException 
        {
            // superclass filters out objects not
            // assignable from the Number type
            obj = super.filterStringable(obj);
                
            // ensure the value satisfies the bounds
            try {
                obj = minimum.filterStringable(obj);
                obj = maximum.filterStringable(obj);
            }
        
            // if not, build an appropriate exception message
            catch (FilterException ex) {
                String message = "Value not within the range ";
                message += (isMinimumInclusive() ? "[" : "(");
                message += getMinimum() + ",";
                message += getMaximum();
                message += (isMaximumInclusive() ? "]" : ")");

                // and throw an exception
                throw new FilterException(obj, message);
            }
        
            // otherwise return the object
            return obj;
        }

        ////////////////
        // Public API //
        ////////////////

        /**
         * Sets the minimum bound for the range
         * to the given value.
         *
         * @param min the desired minimum bound
         * @see #getMinimum()
         */
        public void setMinimum(long min) {
            getMinimumFilter().setMinimum(min);
        }

        /**
         * Returns the minimum bound for this range.
         *
         * @see #setMinimum(long)
         */
        public long getMinimum() {
            return getMinimumFilter().getMinimum();
        }

        /**
         * Sets the maximum bound for the range
         * to the given value.
         *
         * @param max the desired maximum bound
         * @see #getMaximum()
         */
        public void setMaximum(long max) {
            getMaximumFilter().setMaximum(max);
        }

        /**
         * Returns the maximum bound for this range.
         *
         * @see #setMaximum(long)
         */
        public long getMaximum() {
            return getMaximumFilter().getMaximum();
        }   

        /**
         * Sets whether or not the minimum bound is inclusive
         * to the given value.
         *  
         * @param inclusive whether or not the bound is inclusive
         * @see #isMinimumInclusive()
         */
        public void setMinimumInclusive(boolean inclusive) {
            minimum.setInclusive(inclusive);
        }

        /**
         * Returns whether or not the minimum bound is inclusive.
         *
         * @see #setMinimumInclusive(boolean)
         */
        public boolean isMinimumInclusive() {
            return minimum.isInclusive();
        }

        /**
         * Sets whether or not the maximum bound is inclusive
         * to the given value.
         *
         * @param inclusive whether or not the bound is inclusive
         * @see #isMaximumInclusive()
         */
        public void setMaximumInclusive(boolean inclusive) {
            maximum.setInclusive(inclusive);
        }

        /**
         * Returns whether or not the maximum bound is inclusive.
         *
         * @see #setMaximumInclusive(boolean)
         */
        public boolean isMaximumInclusive() {
            return maximum.isInclusive();
        }
        
        ///////////////////////
        // Protected methods //
        ///////////////////////
        
        /**
         * Returns the minimum bound filter
         * used by this range filter.
         */
        protected MinimumBoundFilter.Long getMinimumFilter() {
            return (MinimumBoundFilter.Long)minimum;
        }

        /**
         * Returns the maximum bound filter
         * used by this range filter.
         */
        protected MaximumBoundFilter.Long getMaximumFilter() {
            return (MaximumBoundFilter.Long)maximum;
        }
    }

    /**
     * <P>Range filter 
     * representing real number upper and lower bounds 
     * of primitive precision.</P>
     *
     * @author  Jeff Raab
     * @version 2.1
     * @since   1.0
     */
    public static class Double extends RangeFilter {

        //////////////////
        // Constructors //
        //////////////////
    
        /**
         * Constructs an inclusive range 
         * bounded by the given values.
         *
         * @param min the minimum value for the range
         * @param max the maximum value for the range
         */
        public Double(double min, double max) {
            this(min, true, max, true);
        }

        /**
         * Constructs a range 
         * bounded by the given values
         * with the given individual exclusivity.
         *
         * @param min the minimum value for the range
         * @param minInclusive whether or not the minimum value
         *      is inclusive
         * @param max the maximum value for the range
         * @param maxaInclusive whether or not the maximum value
         *      is inclusive
         */
        public Double(
            double min, 
            boolean minInclusive, 
            double max,
            boolean maxInclusive) 
        {
            minimum = new MinimumBoundFilter.Double(
                min, minInclusive);
            minimum.addPropertyChangeListener(this);

            maximum = new MaximumBoundFilter.Double(
                max, maxInclusive);
            maximum.addPropertyChangeListener(this);
        }

        //////////////////////
        // StringableFilter //
        //////////////////////
        
        /**
         * Returns the given object 
         * if it satisfies the bound for this range.
         *
         * @param obj the object to be filtered
         * @throws FilterException if the object 
         *      is out of bounds
         */
        public Stringable filterStringable(Stringable obj) 
            throws FilterException 
        {
            // superclass filters out objects not
            // assignable from the Number type
            obj = super.filterStringable(obj);
                
            // ensure the value satisfies the bounds
            try {
                obj = minimum.filterStringable(obj);
                obj = maximum.filterStringable(obj);
            }
        
            // if not, build an appropriate exception message
            catch (FilterException ex) {
                String message = "Value not within the range ";
                message += (isMinimumInclusive() ? "[" : "(");
                message += getMinimum() + ",";
                message += getMaximum();
                message += (isMaximumInclusive() ? "]" : ")");

                // and throw an exception
                throw new FilterException(obj, message);
            }
        
            // otherwise return the object
            return obj;
        }

        ////////////////
        // Public API //
        ////////////////

        /**
         * Sets the minimum bound for the range
         * to the given value.
         *
         * @param min the desired minimum bound
         * @see #getMinimum()
         */
        public void setMinimum(double min) {
            getMinimumFilter().setMinimum(min);
        }

        /**
         * Returns the minimum bound for this range.
         *
         * @see #setMinimum(double)
         */
        public double getMinimum() {
            return getMinimumFilter().getMinimum();
        }

        /**
         * Sets the maximum bound for the range
         * to the given value.
         *
         * @param max the desired maximum bound
         * @see #getMaximum()
         */
        public void setMaximum(double max) {
            getMaximumFilter().setMaximum(max);
        }

        /**
         * Returns the maximum bound for this range.
         *
         * @see #setMaximum(double)
         */
        public double getMaximum() {
            return getMaximumFilter().getMaximum();
        }   

        /**
         * Sets whether or not the minimum bound is inclusive
         * to the given value.
         *  
         * @param inclusive whether or not the bound is inclusive
         * @see #isMinimumInclusive()
         */
        public void setMinimumInclusive(boolean inclusive) {
            minimum.setInclusive(inclusive);
        }

        /**
         * Returns whether or not the minimum bound is inclusive.
         *
         * @see #setMinimumInclusive(boolean)
         */
        public boolean isMinimumInclusive() {
            return minimum.isInclusive();
        }

        /**
         * Sets whether or not the maximum bound is inclusive
         * to the given value.
         *
         * @param inclusive whether or not the bound is inclusive
         * @see #isMaximumInclusive()
         */
        public void setMaximumInclusive(boolean inclusive) {
            maximum.setInclusive(inclusive);
        }

        /**
         * Returns whether or not the maximum bound is inclusive.
         *
         * @see #setMaximumInclusive(boolean)
         */
        public boolean isMaximumInclusive() {
            return maximum.isInclusive();
        }

        ///////////////////////
        // Protected methods //
        ///////////////////////
        
        /**
         * Returns the minimum bound filter
         * used by this range filter.
         */
        protected MinimumBoundFilter.Double getMinimumFilter() {
            return (MinimumBoundFilter.Double)minimum;
        }

        /**
         * Returns the maximum bound filter
         * used by this range filter.
         */
        protected MaximumBoundFilter.Double getMaximumFilter() {
            return (MaximumBoundFilter.Double)maximum;
        }
    }

    /**
     * <P>Range filter 
     * representing integer upper and lower bounds 
     * of arbitrary precision.</P>
     *
     * @author  Jeff Raab
     * @version 2.1
     * @since   1.0
     */
    public static class BigInteger extends RangeFilter {

        //////////////////
        // Constructors //
        //////////////////
    
        /**
         * Constructs an inclusive range 
         * bounded by the given values.
         *
         * @param min the minimum value for the range
         * @param max the maximum value for the range
         */
        public BigInteger(
            java.math.BigInteger min, 
            java.math.BigInteger max) 
        {
            this(min, true, max, true);
        }

        /**
         * Constructs a range 
         * bounded by the given values
         * with the given individual exclusivity.
         *
         * @param min the minimum value for the range
         * @param minInclusive whether or not the minimum value
         *      is inclusive
         * @param max the maximum value for the range
         * @param maxaInclusive whether or not the maximum value
         *      is inclusive
         */
        public BigInteger(
            java.math.BigInteger min, 
            boolean minInclusive, 
            java.math.BigInteger max,
            boolean maxInclusive) 
        {
            minimum = new MinimumBoundFilter.BigInteger(
                min, minInclusive);
            minimum.addPropertyChangeListener(this);

            maximum = new MaximumBoundFilter.BigInteger(
                max, maxInclusive);
            maximum.addPropertyChangeListener(this);
        }

        //////////////////////
        // StringableFilter //
        //////////////////////
        
        /**
         * Returns the given object 
         * if it satisfies the bound for this range.
         *
         * @param obj the object to be filtered
         * @throws FilterException if the object 
         *      is out of bounds
         */
        public Stringable filterStringable(Stringable obj) 
            throws FilterException 
        {
            // superclass filters out objects not
            // assignable from the Number type
            obj = super.filterStringable(obj);
                
            // ensure the value satisfies the bounds
            try {
                obj = minimum.filterStringable(obj);
                obj = maximum.filterStringable(obj);
            }
        
            // if not, build an appropriate exception message
            catch (FilterException ex) {
                String message = "Value not within the range ";
                message += (isMinimumInclusive() ? "[" : "(");
                message += getMinimum() + ",";
                message += getMaximum();
                message += (isMaximumInclusive() ? "]" : ")");

                // and throw an exception
                throw new FilterException(obj, message);
            }
        
            // otherwise return the object
            return obj;
        }

        ////////////////
        // Public API //
        ////////////////

        /**
         * Sets the minimum bound for the range
         * to the given value.
         *
         * If <CODE>null</CODE>, the value is not changed.
         *
         * @param min the desired minimum bound
         * @see #getMinimum()
         */
        public void setMinimum(java.math.BigInteger min) {
            getMinimumFilter().setMinimum(min);
        }

        /**
         * Returns the minimum bound for this range.
         *
         * @see #setMinimum(java.math.BigInteger)
         */
        public java.math.BigInteger getMinimum() {
            return getMinimumFilter().getMinimum();
        }

        /**
         * Sets the maximum bound for the range
         * to the given value.
         *
         * If <CODE>null</CODE>, the value is not changed.
         *
         * @param max the desired maximum bound
         * @see #getMaximum()
         */
        public void setMaximum(java.math.BigInteger max) {
            getMaximumFilter().setMaximum(max);
        }

        /**
         * Returns the maximum bound for this range.
         *
         * @see #setMaximum(java.math.BigInteger)
         */
        public java.math.BigInteger getMaximum() {
            return getMaximumFilter().getMaximum();
        }   

        /**
         * Sets whether or not the minimum bound is inclusive
         * to the given value.
         *  
         * @param inclusive whether or not the bound is inclusive
         * @see #isMinimumInclusive()
         */
        public void setMinimumInclusive(boolean inclusive) {
            minimum.setInclusive(inclusive);
        }

        /**
         * Returns whether or not the minimum bound is inclusive.
         *
         * @see #setMinimumInclusive(boolean)
         */
        public boolean isMinimumInclusive() {
            return minimum.isInclusive();
        }

        /**
         * Sets whether or not the maximum bound is inclusive
         * to the given value.
         *
         * @param inclusive whether or not the bound is inclusive
         * @see #isMaximumInclusive()
         */
        public void setMaximumInclusive(boolean inclusive) {
            maximum.setInclusive(inclusive);
        }

        /**
         * Returns whether or not the maximum bound is inclusive.
         *
         * @see #setMaximumInclusive(boolean)
         */
        public boolean isMaximumInclusive() {
            return maximum.isInclusive();
        }
        
        ///////////////////////
        // Protected methods //
        ///////////////////////
        
        /**
         * Returns the minimum bound filter
         * used by this range filter.
         */
        protected MinimumBoundFilter.BigInteger getMinimumFilter() {
            return (MinimumBoundFilter.BigInteger)minimum;
        }

        /**
         * Returns the maximum bound filter
         * used by this range filter.
         */
        protected MaximumBoundFilter.BigInteger getMaximumFilter() {
            return (MaximumBoundFilter.BigInteger)maximum;
        }
    }

    /**
     * <P>Range filter 
     * representing real number upper and lower bounds 
     * of arbitrary precision.</P>
     *
     * @author  Jeff Raab
     * @version 2.1
     * @since   1.0
     */
    public static class BigDecimal extends RangeFilter {

        /** The minimum range bound. */
        protected MinimumBoundFilter.BigDecimal minimum = null;

        /** The maximum range bound. */
        protected MaximumBoundFilter.BigDecimal maximum = null;

        //////////////////
        // Constructors //
        //////////////////
    
        /**
         * Constructs an inclusive range 
         * bounded by the given values.
         *
         * @param min the minimum value for the range
         * @param max the maximum value for the range
         */
        public BigDecimal(
            java.math.BigDecimal min, 
            java.math.BigDecimal max) 
        {
            this(min, true, max, true);
        }

        /**
         * Constructs a range 
         * bounded by the given values
         * with the given individual exclusivity.
         *
         * @param min the minimum value for the range
         * @param minInclusive whether or not the minimum value
         *      is inclusive
         * @param max the maximum value for the range
         * @param maxaInclusive whether or not the maximum value
         *      is inclusive
         */
        public BigDecimal(
            java.math.BigDecimal min, 
            boolean minInclusive, 
            java.math.BigDecimal max,
            boolean maxInclusive) 
        {
            minimum = new MinimumBoundFilter.BigDecimal(
                min, minInclusive);
            minimum.addPropertyChangeListener(this);

            maximum = new MaximumBoundFilter.BigDecimal(
                max, maxInclusive);
            maximum.addPropertyChangeListener(this);
        }

        //////////////////////
        // StringableFilter //
        //////////////////////
        
        /**
         * Returns the given object 
         * if it satisfies the bound for this range.
         *
         * @param obj the object to be filtered
         * @throws FilterException if the object 
         *      is out of bounds
         */
        public Stringable filterStringable(Stringable obj) 
            throws FilterException 
        {
            // superclass filters out objects not
            // assignable from the Number type
            obj = super.filterStringable(obj);
                
            // ensure the value satisfies the bounds
            try {
                obj = minimum.filterStringable(obj);
                obj = maximum.filterStringable(obj);
            }
        
            // if not, build an appropriate exception message
            catch (FilterException ex) {
                String message = "Value not within the range ";
                message += (isMinimumInclusive() ? "[" : "(");
                message += getMinimum() + ",";
                message += getMaximum();
                message += (isMaximumInclusive() ? "]" : ")");

                // and throw an exception
                throw new FilterException(obj, message);
            }
        
            // otherwise return the object
            return obj;
        }

        ////////////////
        // Public API //
        ////////////////

        /**
         * Sets the minimum bound for the range
         * to the given value.
         *
         * If <CODE>null</CODE>, the value is not changed.
         *
         * @param min the desired minimum bound
         * @see #getMinimum()
         */
        public void setMinimum(java.math.BigDecimal min) {
            getMinimumFilter().setMinimum(min);
        }

        /**
         * Returns the minimum bound for this range.
         *
         * @see #setMinimum(java.math.BigDecimal)
         */
        public java.math.BigDecimal getMinimum() {
            return getMinimumFilter().getMinimum();
        }

        /**
         * Sets the maximum bound for the range
         * to the given value.
         *
         * If <CODE>null, the value is not changed.
         *
         * @param max the desired maximum bound
         * @see #getMaximum()
         */
        public void setMaximum(java.math.BigDecimal max) {
            getMaximumFilter().setMaximum(max);
        }

        /**
         * Returns the maximum bound for this range.
         *
         * @see #setMaximum(java.math.BigDecimal)
         */
        public java.math.BigDecimal getMaximum() {
            return getMaximumFilter().getMaximum();
        }   

        /**
         * Sets whether or not the minimum bound is inclusive
         * to the given value.
         *  
         * @param inclusive whether or not the bound is inclusive
         * @see #isMinimumInclusive()
         */
        public void setMinimumInclusive(boolean inclusive) {
            minimum.setInclusive(inclusive);
        }

        /**
         * Returns whether or not the minimum bound is inclusive.
         *
         * @see #setMinimumInclusive(boolean)
         */
        public boolean isMinimumInclusive() {
            return minimum.isInclusive();
        }

        /**
         * Sets whether or not the maximum bound is inclusive
         * to the given value.
         *
         * @param inclusive whether or not the bound is inclusive
         * @see #isMaximumInclusive()
         */
        public void setMaximumInclusive(boolean inclusive) {
            maximum.setInclusive(inclusive);
        }

        /**
         * Returns whether or not the maximum bound is inclusive.
         *
         * @see #setMaximumInclusive(boolean)
         */
        public boolean isMaximumInclusive() {
            return maximum.isInclusive();
        }
        
        ///////////////////////
        // Protected methods //
        ///////////////////////
        
        /**
         * Returns the minimum bound filter
         * used by this range filter.
         */
        protected MinimumBoundFilter.BigDecimal getMinimumFilter() {
            return (MinimumBoundFilter.BigDecimal)minimum;
        }

        /**
         * Returns the maximum bound filter
         * used by this range filter.
         */
        protected MaximumBoundFilter.BigDecimal getMaximumFilter() {
            return (MaximumBoundFilter.BigDecimal)maximum;
        }
    }
}
