001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it 
010 * under the terms of the GNU Lesser General Public License as published by 
011 * the Free Software Foundation; either version 2.1 of the License, or 
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but 
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
022 * USA.  
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025 * in the United States and other countries.]
026 *
027 * ----------
028 * Range.java
029 * ----------
030 * (C) Copyright 2002-2007, by Object Refinery Limited and Contributors.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   Chuanhao Chiu;
034 *                   Bill Kelemen; 
035 *                   Nicolas Brodu;
036 *
037 * Changes (from 23-Jun-2001)
038 * --------------------------
039 * 22-Apr-2002 : Version 1, loosely based by code by Bill Kelemen (DG);
040 * 30-Apr-2002 : Added getLength() and getCentralValue() methods.  Changed
041 *               argument check in constructor (DG);
042 * 13-Jun-2002 : Added contains(double) method (DG);
043 * 22-Aug-2002 : Added fix to combine method where both ranges are null, thanks
044 *               to Chuanhao Chiu for reporting and fixing this (DG);
045 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
046 * 26-Mar-2003 : Implemented Serializable (DG);
047 * 14-Aug-2003 : Added equals() method (DG);
048 * 27-Aug-2003 : Added toString() method (BK);
049 * 11-Sep-2003 : Added Clone Support (NB);
050 * 23-Sep-2003 : Fixed Checkstyle issues (DG);
051 * 25-Sep-2003 : Oops, Range immutable, clone not necessary (NB);
052 * 05-May-2004 : Added constrain() and intersects() methods (DG);
053 * 18-May-2004 : Added expand() method (DG);
054 * ------------- JFreeChart 1.0.x ---------------------------------------------
055 * 11-Jan-2006 : Added new method expandToInclude(Range, double) (DG);
056 * 
057 */
058
059package org.jfree.data;
060
061import java.io.Serializable;
062
063/**
064 * Represents an immutable range of values.
065 */
066public strictfp class Range implements Serializable {
067
068    /** For serialization. */
069    private static final long serialVersionUID = -906333695431863380L;
070    
071    /** The lower bound of the range. */
072    private double lower;
073
074    /** The upper bound of the range. */
075    private double upper;
076
077    /**
078     * Creates a new range.
079     *
080     * @param lower  the lower bound (must be <= upper bound).
081     * @param upper  the upper bound (must be >= lower bound).
082     */
083    public Range(double lower, double upper) {
084        if (lower > upper) {
085            String msg = "Range(double, double): require lower (" + lower 
086                + ") <= upper (" + upper + ").";
087            throw new IllegalArgumentException(msg);
088        }
089        this.lower = lower;
090        this.upper = upper;
091    }
092
093    /**
094     * Returns the lower bound for the range.
095     *
096     * @return The lower bound.
097     */
098    public double getLowerBound() {
099        return this.lower;
100    }
101
102    /**
103     * Returns the upper bound for the range.
104     *
105     * @return The upper bound.
106     */
107    public double getUpperBound() {
108        return this.upper;
109    }
110
111    /**
112     * Returns the length of the range.
113     *
114     * @return The length.
115     */
116    public double getLength() {
117        return this.upper - this.lower;
118    }
119
120    /**
121     * Returns the central value for the range.
122     *
123     * @return The central value.
124     */
125    public double getCentralValue() {
126        return this.lower / 2.0 + this.upper / 2.0;
127    }
128
129    /**
130     * Returns <code>true</code> if the range contains the specified value and 
131     * <code>false</code> otherwise.
132     *
133     * @param value  the value to lookup.
134     *
135     * @return <code>true</code> if the range contains the specified value.
136     */
137    public boolean contains(double value) {
138        return (value >= this.lower && value <= this.upper);
139    }
140    
141    /**
142     * Returns <code>true</code> if the range intersects with the specified 
143     * range, and <code>false</code> otherwise.
144     * 
145     * @param b0  the lower bound (should be <= b1).
146     * @param b1  the upper bound (should be >= b0).
147     * 
148     * @return A boolean.
149     */
150    public boolean intersects(double b0, double b1) {
151        if (b0 <= this.lower) {
152            return (b1 > this.lower);
153        }
154        else {
155            return (b0 < this.upper && b1 >= b0);
156        }
157    }
158
159    /**
160     * Returns the value within the range that is closest to the specified 
161     * value.
162     * 
163     * @param value  the value.
164     * 
165     * @return The constrained value.
166     */
167    public double constrain(double value) {
168        double result = value;
169        if (!contains(value)) {
170            if (value > this.upper) {
171                result = this.upper;   
172            }
173            else if (value < this.lower) {
174                result = this.lower;   
175            }
176        }
177        return result;
178    }
179    
180    /**
181     * Creates a new range by combining two existing ranges.
182     * <P>
183     * Note that:
184     * <ul>
185     *   <li>either range can be <code>null</code>, in which case the other 
186     *       range is returned;</li>
187     *   <li>if both ranges are <code>null</code> the return value is 
188     *       <code>null</code>.</li>
189     * </ul>
190     *
191     * @param range1  the first range (<code>null</code> permitted).
192     * @param range2  the second range (<code>null</code> permitted).
193     *
194     * @return A new range (possibly <code>null</code>).
195     */
196    public static Range combine(Range range1, Range range2) {
197        if (range1 == null) {
198            return range2;
199        }
200        else {
201            if (range2 == null) {
202                return range1;
203            }
204            else {
205                double l = Math.min(range1.getLowerBound(), 
206                        range2.getLowerBound());
207                double u = Math.max(range1.getUpperBound(), 
208                        range2.getUpperBound());
209                return new Range(l, u);
210            }
211        }
212    }
213    
214    /**
215     * Returns a range that includes all the values in the specified 
216     * <code>range</code> AND the specified <code>value</code>.
217     * 
218     * @param range  the range (<code>null</code> permitted).
219     * @param value  the value that must be included.
220     * 
221     * @return A range.
222     * 
223     * @since 1.0.1
224     */
225    public static Range expandToInclude(Range range, double value) {
226        if (range == null) {
227            return new Range(value, value);
228        }
229        if (value < range.getLowerBound()) {
230            return new Range(value, range.getUpperBound());
231        }
232        else if (value > range.getUpperBound()) {
233            return new Range(range.getLowerBound(), value);
234        }
235        else {
236            return range;
237        }
238    }
239    
240    /**
241     * Creates a new range by adding margins to an existing range.
242     * 
243     * @param range  the range (<code>null</code> not permitted).
244     * @param lowerMargin  the lower margin (expressed as a percentage of the 
245     *                     range length).
246     * @param upperMargin  the upper margin (expressed as a percentage of the 
247     *                     range length).
248     * 
249     * @return The expanded range.
250     */
251    public static Range expand(Range range, 
252                               double lowerMargin, double upperMargin) {
253        if (range == null) {
254            throw new IllegalArgumentException("Null 'range' argument.");   
255        }
256        double length = range.getLength();
257        double lower = length * lowerMargin;
258        double upper = length * upperMargin;
259        return new Range(range.getLowerBound() - lower, 
260                range.getUpperBound() + upper);
261    }
262
263    /**
264     * Shifts the range by the specified amount.
265     * 
266     * @param base  the base range.
267     * @param delta  the shift amount.
268     * 
269     * @return A new range.
270     */
271    public static Range shift(Range base, double delta) {
272        return shift(base, delta, false);
273    }
274    
275    /**
276     * Shifts the range by the specified amount.
277     * 
278     * @param base  the base range.
279     * @param delta  the shift amount.
280     * @param allowZeroCrossing  a flag that determines whether or not the 
281     *                           bounds of the range are allowed to cross
282     *                           zero after adjustment.
283     * 
284     * @return A new range.
285     */
286    public static Range shift(Range base, double delta, 
287                              boolean allowZeroCrossing) {
288        if (allowZeroCrossing) {
289            return new Range(base.getLowerBound() + delta, 
290                    base.getUpperBound() + delta);
291        }
292        else {
293            return new Range(shiftWithNoZeroCrossing(base.getLowerBound(), 
294                    delta), shiftWithNoZeroCrossing(base.getUpperBound(), 
295                    delta));
296        }
297    }
298
299    /**
300     * Returns the given <code>value</code> adjusted by <code>delta</code> but
301     * with a check to prevent the result from crossing <code>0.0</code>.
302     * 
303     * @param value  the value.
304     * @param delta  the adjustment.
305     * 
306     * @return The adjusted value.
307     */
308    private static double shiftWithNoZeroCrossing(double value, double delta) {
309        if (value > 0.0) {
310            return Math.max(value + delta, 0.0);  
311        }
312        else if (value < 0.0) {
313            return Math.min(value + delta, 0.0);
314        }
315        else {
316            return value + delta;   
317        }
318    }
319    
320    /**
321     * Tests this object for equality with an arbitrary object.
322     *
323     * @param obj  the object to test against (<code>null</code> permitted).
324     *
325     * @return A boolean.
326     */
327    public boolean equals(Object obj) {
328        if (!(obj instanceof Range)) {
329            return false;
330        }
331        Range range = (Range) obj;
332        if (!(this.lower == range.lower)) {
333            return false;
334        }
335        if (!(this.upper == range.upper)) {
336            return false;
337        }
338        return true;
339    }
340
341    /**
342     * Returns a hash code.
343     * 
344     * @return A hash code.
345     */
346    public int hashCode() {
347        int result;
348        long temp;
349        temp = Double.doubleToLongBits(this.lower);
350        result = (int) (temp ^ (temp >>> 32));
351        temp = Double.doubleToLongBits(this.upper);
352        result = 29 * result + (int) (temp ^ (temp >>> 32));
353        return result;
354    }
355
356    /**
357     * Returns a string representation of this Range.
358     *
359     * @return A String "Range[lower,upper]" where lower=lower range and 
360     *         upper=upper range.
361     */
362    public String toString() {
363        return ("Range[" + this.lower + "," + this.upper + "]");
364    }
365
366}