001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, 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-2008, 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 *                   Sergei Ivanov;
037 *
038 * Changes (from 23-Jun-2001)
039 * --------------------------
040 * 22-Apr-2002 : Version 1, loosely based by code by Bill Kelemen (DG);
041 * 30-Apr-2002 : Added getLength() and getCentralValue() methods.  Changed
042 *               argument check in constructor (DG);
043 * 13-Jun-2002 : Added contains(double) method (DG);
044 * 22-Aug-2002 : Added fix to combine method where both ranges are null, thanks
045 *               to Chuanhao Chiu for reporting and fixing this (DG);
046 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
047 * 26-Mar-2003 : Implemented Serializable (DG);
048 * 14-Aug-2003 : Added equals() method (DG);
049 * 27-Aug-2003 : Added toString() method (BK);
050 * 11-Sep-2003 : Added Clone Support (NB);
051 * 23-Sep-2003 : Fixed Checkstyle issues (DG);
052 * 25-Sep-2003 : Oops, Range immutable, clone not necessary (NB);
053 * 05-May-2004 : Added constrain() and intersects() methods (DG);
054 * 18-May-2004 : Added expand() method (DG);
055 * ------------- JFreeChart 1.0.x ---------------------------------------------
056 * 11-Jan-2006 : Added new method expandToInclude(Range, double) (DG);
057 * 18-Dec-2007 : New methods intersects(Range) and scale(...) thanks to Sergei
058 *               Ivanov (DG);
059 *
060 */
061
062package org.jfree.data;
063
064import java.io.Serializable;
065
066/**
067 * Represents an immutable range of values.
068 */
069public strictfp class Range implements Serializable {
070
071    /** For serialization. */
072    private static final long serialVersionUID = -906333695431863380L;
073
074    /** The lower bound of the range. */
075    private double lower;
076
077    /** The upper bound of the range. */
078    private double upper;
079
080    /**
081     * Creates a new range.
082     *
083     * @param lower  the lower bound (must be <= upper bound).
084     * @param upper  the upper bound (must be >= lower bound).
085     */
086    public Range(double lower, double upper) {
087        if (lower > upper) {
088            String msg = "Range(double, double): require lower (" + lower
089                + ") <= upper (" + upper + ").";
090            throw new IllegalArgumentException(msg);
091        }
092        this.lower = lower;
093        this.upper = upper;
094    }
095
096    /**
097     * Returns the lower bound for the range.
098     *
099     * @return The lower bound.
100     */
101    public double getLowerBound() {
102        return this.lower;
103    }
104
105    /**
106     * Returns the upper bound for the range.
107     *
108     * @return The upper bound.
109     */
110    public double getUpperBound() {
111        return this.upper;
112    }
113
114    /**
115     * Returns the length of the range.
116     *
117     * @return The length.
118     */
119    public double getLength() {
120        return this.upper - this.lower;
121    }
122
123    /**
124     * Returns the central value for the range.
125     *
126     * @return The central value.
127     */
128    public double getCentralValue() {
129        return this.lower / 2.0 + this.upper / 2.0;
130    }
131
132    /**
133     * Returns <code>true</code> if the range contains the specified value and
134     * <code>false</code> otherwise.
135     *
136     * @param value  the value to lookup.
137     *
138     * @return <code>true</code> if the range contains the specified value.
139     */
140    public boolean contains(double value) {
141        return (value >= this.lower && value <= this.upper);
142    }
143
144    /**
145     * Returns <code>true</code> if the range intersects with the specified
146     * range, and <code>false</code> otherwise.
147     *
148     * @param b0  the lower bound (should be <= b1).
149     * @param b1  the upper bound (should be >= b0).
150     *
151     * @return A boolean.
152     */
153    public boolean intersects(double b0, double b1) {
154        if (b0 <= this.lower) {
155            return (b1 > this.lower);
156        }
157        else {
158            return (b0 < this.upper && b1 >= b0);
159        }
160    }
161
162    /**
163     * Returns <code>true</code> if the range intersects with the specified
164     * range, and <code>false</code> otherwise.
165     *
166     * @param range  another range (<code>null</code> not permitted).
167     *
168     * @return A boolean.
169     *
170     * @since 1.0.9
171     */
172    public boolean intersects(Range range) {
173        return intersects(range.getLowerBound(), range.getUpperBound());
174    }
175
176    /**
177     * Returns the value within the range that is closest to the specified
178     * value.
179     *
180     * @param value  the value.
181     *
182     * @return The constrained value.
183     */
184    public double constrain(double value) {
185        double result = value;
186        if (!contains(value)) {
187            if (value > this.upper) {
188                result = this.upper;
189            }
190            else if (value < this.lower) {
191                result = this.lower;
192            }
193        }
194        return result;
195    }
196
197    /**
198     * Creates a new range by combining two existing ranges.
199     * <P>
200     * Note that:
201     * <ul>
202     *   <li>either range can be <code>null</code>, in which case the other
203     *       range is returned;</li>
204     *   <li>if both ranges are <code>null</code> the return value is
205     *       <code>null</code>.</li>
206     * </ul>
207     *
208     * @param range1  the first range (<code>null</code> permitted).
209     * @param range2  the second range (<code>null</code> permitted).
210     *
211     * @return A new range (possibly <code>null</code>).
212     */
213    public static Range combine(Range range1, Range range2) {
214        if (range1 == null) {
215            return range2;
216        }
217        else {
218            if (range2 == null) {
219                return range1;
220            }
221            else {
222                double l = Math.min(range1.getLowerBound(),
223                        range2.getLowerBound());
224                double u = Math.max(range1.getUpperBound(),
225                        range2.getUpperBound());
226                return new Range(l, u);
227            }
228        }
229    }
230
231    /**
232     * Returns a range that includes all the values in the specified
233     * <code>range</code> AND the specified <code>value</code>.
234     *
235     * @param range  the range (<code>null</code> permitted).
236     * @param value  the value that must be included.
237     *
238     * @return A range.
239     *
240     * @since 1.0.1
241     */
242    public static Range expandToInclude(Range range, double value) {
243        if (range == null) {
244            return new Range(value, value);
245        }
246        if (value < range.getLowerBound()) {
247            return new Range(value, range.getUpperBound());
248        }
249        else if (value > range.getUpperBound()) {
250            return new Range(range.getLowerBound(), value);
251        }
252        else {
253            return range;
254        }
255    }
256
257    /**
258     * Creates a new range by adding margins to an existing range.
259     *
260     * @param range  the range (<code>null</code> not permitted).
261     * @param lowerMargin  the lower margin (expressed as a percentage of the
262     *                     range length).
263     * @param upperMargin  the upper margin (expressed as a percentage of the
264     *                     range length).
265     *
266     * @return The expanded range.
267     */
268    public static Range expand(Range range,
269                               double lowerMargin, double upperMargin) {
270        if (range == null) {
271            throw new IllegalArgumentException("Null 'range' argument.");
272        }
273        double length = range.getLength();
274        double lower = range.getLowerBound() - length * lowerMargin;
275        double upper = range.getUpperBound() + length * upperMargin;
276        if (lower > upper) {
277            lower = lower / 2.0 + upper / 2.0;
278            upper = lower;
279        }
280        return new Range(lower, upper);
281    }
282
283    /**
284     * Shifts the range by the specified amount.
285     *
286     * @param base  the base range (<code>null</code> not permitted).
287     * @param delta  the shift amount.
288     *
289     * @return A new range.
290     */
291    public static Range shift(Range base, double delta) {
292        return shift(base, delta, false);
293    }
294
295    /**
296     * Shifts the range by the specified amount.
297     *
298     * @param base  the base range (<code>null</code> not permitted).
299     * @param delta  the shift amount.
300     * @param allowZeroCrossing  a flag that determines whether or not the
301     *                           bounds of the range are allowed to cross
302     *                           zero after adjustment.
303     *
304     * @return A new range.
305     */
306    public static Range shift(Range base, double delta,
307                              boolean allowZeroCrossing) {
308        if (base == null) {
309            throw new IllegalArgumentException("Null 'base' argument.");
310        }
311        if (allowZeroCrossing) {
312            return new Range(base.getLowerBound() + delta,
313                    base.getUpperBound() + delta);
314        }
315        else {
316            return new Range(shiftWithNoZeroCrossing(base.getLowerBound(),
317                    delta), shiftWithNoZeroCrossing(base.getUpperBound(),
318                    delta));
319        }
320    }
321
322    /**
323     * Returns the given <code>value</code> adjusted by <code>delta</code> but
324     * with a check to prevent the result from crossing <code>0.0</code>.
325     *
326     * @param value  the value.
327     * @param delta  the adjustment.
328     *
329     * @return The adjusted value.
330     */
331    private static double shiftWithNoZeroCrossing(double value, double delta) {
332        if (value > 0.0) {
333            return Math.max(value + delta, 0.0);
334        }
335        else if (value < 0.0) {
336            return Math.min(value + delta, 0.0);
337        }
338        else {
339            return value + delta;
340        }
341    }
342
343    /**
344     * Scales the range by the specified factor.
345     *
346     * @param base the base range (<code>null</code> not permitted).
347     * @param factor the scaling factor (must be non-negative).
348     *
349     * @return A new range.
350     *
351     * @since 1.0.9
352     */
353    public static Range scale(Range base, double factor) {
354        if (base == null) {
355            throw new IllegalArgumentException("Null 'base' argument.");
356        }
357        if (factor < 0) {
358            throw new IllegalArgumentException("Negative 'factor' argument.");
359        }
360        return new Range(base.getLowerBound() * factor,
361                base.getUpperBound() * factor);
362    }
363
364    /**
365     * Tests this object for equality with an arbitrary object.
366     *
367     * @param obj  the object to test against (<code>null</code> permitted).
368     *
369     * @return A boolean.
370     */
371    public boolean equals(Object obj) {
372        if (!(obj instanceof Range)) {
373            return false;
374        }
375        Range range = (Range) obj;
376        if (!(this.lower == range.lower)) {
377            return false;
378        }
379        if (!(this.upper == range.upper)) {
380            return false;
381        }
382        return true;
383    }
384
385    /**
386     * Returns a hash code.
387     *
388     * @return A hash code.
389     */
390    public int hashCode() {
391        int result;
392        long temp;
393        temp = Double.doubleToLongBits(this.lower);
394        result = (int) (temp ^ (temp >>> 32));
395        temp = Double.doubleToLongBits(this.upper);
396        result = 29 * result + (int) (temp ^ (temp >>> 32));
397        return result;
398    }
399
400    /**
401     * Returns a string representation of this Range.
402     *
403     * @return A String "Range[lower,upper]" where lower=lower range and
404     *         upper=upper range.
405     */
406    public String toString() {
407        return ("Range[" + this.lower + "," + this.upper + "]");
408    }
409
410}