001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ----------
028 * Range.java
029 * ----------
030 * (C) Copyright 2002-2013, 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 * 08-Jan-2012 : New method combineIgnoringNaN() (DG);
060 *
061 */
062
063package org.jfree.data;
064
065import java.io.Serializable;
066import org.jfree.chart.util.ParamChecks;
067
068/**
069 * Represents an immutable range of values.
070 */
071public strictfp class Range implements Serializable {
072
073    /** For serialization. */
074    private static final long serialVersionUID = -906333695431863380L;
075
076    /** The lower bound of the range. */
077    private double lower;
078
079    /** The upper bound of the range. */
080    private double upper;
081
082    /**
083     * Creates a new range.
084     *
085     * @param lower  the lower bound (must be <= upper bound).
086     * @param upper  the upper bound (must be >= lower bound).
087     */
088    public Range(double lower, double upper) {
089        if (lower > upper) {
090            String msg = "Range(double, double): require lower (" + lower
091                + ") <= upper (" + upper + ").";
092            throw new IllegalArgumentException(msg);
093        }
094        this.lower = lower;
095        this.upper = upper;
096    }
097
098    /**
099     * Returns the lower bound for the range.
100     *
101     * @return The lower bound.
102     */
103    public double getLowerBound() {
104        return this.lower;
105    }
106
107    /**
108     * Returns the upper bound for the range.
109     *
110     * @return The upper bound.
111     */
112    public double getUpperBound() {
113        return this.upper;
114    }
115
116    /**
117     * Returns the length of the range.
118     *
119     * @return The length.
120     */
121    public double getLength() {
122        return this.upper - this.lower;
123    }
124
125    /**
126     * Returns the central value for the range.
127     *
128     * @return The central value.
129     */
130    public double getCentralValue() {
131        return this.lower / 2.0 + this.upper / 2.0;
132    }
133
134    /**
135     * Returns <code>true</code> if the range contains the specified value and
136     * <code>false</code> otherwise.
137     *
138     * @param value  the value to lookup.
139     *
140     * @return <code>true</code> if the range contains the specified value.
141     */
142    public boolean contains(double value) {
143        return (value >= this.lower && value <= this.upper);
144    }
145
146    /**
147     * Returns <code>true</code> if the range intersects with the specified
148     * range, and <code>false</code> otherwise.
149     *
150     * @param b0  the lower bound (should be <= b1).
151     * @param b1  the upper bound (should be >= b0).
152     *
153     * @return A boolean.
154     */
155    public boolean intersects(double b0, double b1) {
156        if (b0 <= this.lower) {
157            return (b1 > this.lower);
158        }
159        else {
160            return (b0 < this.upper && b1 >= b0);
161        }
162    }
163
164    /**
165     * Returns <code>true</code> if the range intersects with the specified
166     * range, and <code>false</code> otherwise.
167     *
168     * @param range  another range (<code>null</code> not permitted).
169     *
170     * @return A boolean.
171     *
172     * @since 1.0.9
173     */
174    public boolean intersects(Range range) {
175        return intersects(range.getLowerBound(), range.getUpperBound());
176    }
177
178    /**
179     * Returns the value within the range that is closest to the specified
180     * value.
181     *
182     * @param value  the value.
183     *
184     * @return The constrained value.
185     */
186    public double constrain(double value) {
187        double result = value;
188        if (!contains(value)) {
189            if (value > this.upper) {
190                result = this.upper;
191            }
192            else if (value < this.lower) {
193                result = this.lower;
194            }
195        }
196        return result;
197    }
198
199    /**
200     * Creates a new range by combining two existing ranges.
201     * <P>
202     * Note that:
203     * <ul>
204     *   <li>either range can be <code>null</code>, in which case the other
205     *       range is returned;</li>
206     *   <li>if both ranges are <code>null</code> the return value is
207     *       <code>null</code>.</li>
208     * </ul>
209     *
210     * @param range1  the first range (<code>null</code> permitted).
211     * @param range2  the second range (<code>null</code> permitted).
212     *
213     * @return A new range (possibly <code>null</code>).
214     */
215    public static Range combine(Range range1, Range range2) {
216        if (range1 == null) {
217            return range2;
218        }
219        if (range2 == null) {
220            return range1;
221        }
222        double l = Math.min(range1.getLowerBound(), range2.getLowerBound());
223        double u = Math.max(range1.getUpperBound(), range2.getUpperBound());
224        return new Range(l, u);
225    }
226
227    /**
228     * Combines two ranges.  This method has a special handling for Double.NaN.
229     *
230     * @param range1  the first range (<code>null</code> permitted).
231     * @param range2  the second range (<code>null</code> permitted).
232     *
233     * @return A new range (possibly <code>null</code>).
234     *
235     * @since 1.0.15
236     */
237    public static Range combineIgnoringNaN(Range range1, Range range2) {
238        if (range1 == null) {
239            return range2;
240        }
241        if (range2 == null) {
242            return range1;
243        }
244        double l = min(range1.getLowerBound(), range2.getLowerBound());
245        double u = max(range1.getUpperBound(), range2.getUpperBound());
246        return new Range(l, u);
247    }
248
249    private static double min(double d1, double d2) {
250        if (Double.isNaN(d1)) {
251            return d2;
252        }
253        if (Double.isNaN(d2)) {
254            return d1;
255        }
256        return Math.min(d1, d2);
257    }
258
259    private static double max(double d1, double d2) {
260        if (Double.isNaN(d1)) {
261            return d2;
262        }
263        if (Double.isNaN(d2)) {
264            return d1;
265        }
266        return Math.max(d1, d2);
267    }
268
269    /**
270     * Returns a range that includes all the values in the specified
271     * <code>range</code> AND the specified <code>value</code>.
272     *
273     * @param range  the range (<code>null</code> permitted).
274     * @param value  the value that must be included.
275     *
276     * @return A range.
277     *
278     * @since 1.0.1
279     */
280    public static Range expandToInclude(Range range, double value) {
281        if (range == null) {
282            return new Range(value, value);
283        }
284        if (value < range.getLowerBound()) {
285            return new Range(value, range.getUpperBound());
286        }
287        else if (value > range.getUpperBound()) {
288            return new Range(range.getLowerBound(), value);
289        }
290        else {
291            return range;
292        }
293    }
294
295    /**
296     * Creates a new range by adding margins to an existing range.
297     *
298     * @param range  the range (<code>null</code> not permitted).
299     * @param lowerMargin  the lower margin (expressed as a percentage of the
300     *                     range length).
301     * @param upperMargin  the upper margin (expressed as a percentage of the
302     *                     range length).
303     *
304     * @return The expanded range.
305     */
306    public static Range expand(Range range,
307                               double lowerMargin, double upperMargin) {
308        ParamChecks.nullNotPermitted(range, "range");
309        double length = range.getLength();
310        double lower = range.getLowerBound() - length * lowerMargin;
311        double upper = range.getUpperBound() + length * upperMargin;
312        if (lower > upper) {
313            lower = lower / 2.0 + upper / 2.0;
314            upper = lower;
315        }
316        return new Range(lower, upper);
317    }
318
319    /**
320     * Shifts the range by the specified amount.
321     *
322     * @param base  the base range (<code>null</code> not permitted).
323     * @param delta  the shift amount.
324     *
325     * @return A new range.
326     */
327    public static Range shift(Range base, double delta) {
328        return shift(base, delta, false);
329    }
330
331    /**
332     * Shifts the range by the specified amount.
333     *
334     * @param base  the base range (<code>null</code> not permitted).
335     * @param delta  the shift amount.
336     * @param allowZeroCrossing  a flag that determines whether or not the
337     *                           bounds of the range are allowed to cross
338     *                           zero after adjustment.
339     *
340     * @return A new range.
341     */
342    public static Range shift(Range base, double delta,
343                              boolean allowZeroCrossing) {
344        ParamChecks.nullNotPermitted(base, "base");
345        if (allowZeroCrossing) {
346            return new Range(base.getLowerBound() + delta,
347                    base.getUpperBound() + delta);
348        }
349        else {
350            return new Range(shiftWithNoZeroCrossing(base.getLowerBound(),
351                    delta), shiftWithNoZeroCrossing(base.getUpperBound(),
352                    delta));
353        }
354    }
355
356    /**
357     * Returns the given <code>value</code> adjusted by <code>delta</code> but
358     * with a check to prevent the result from crossing <code>0.0</code>.
359     *
360     * @param value  the value.
361     * @param delta  the adjustment.
362     *
363     * @return The adjusted value.
364     */
365    private static double shiftWithNoZeroCrossing(double value, double delta) {
366        if (value > 0.0) {
367            return Math.max(value + delta, 0.0);
368        }
369        else if (value < 0.0) {
370            return Math.min(value + delta, 0.0);
371        }
372        else {
373            return value + delta;
374        }
375    }
376
377    /**
378     * Scales the range by the specified factor.
379     *
380     * @param base the base range (<code>null</code> not permitted).
381     * @param factor the scaling factor (must be non-negative).
382     *
383     * @return A new range.
384     *
385     * @since 1.0.9
386     */
387    public static Range scale(Range base, double factor) {
388        ParamChecks.nullNotPermitted(base, "base");
389        if (factor < 0) {
390            throw new IllegalArgumentException("Negative 'factor' argument.");
391        }
392        return new Range(base.getLowerBound() * factor,
393                base.getUpperBound() * factor);
394    }
395
396    /**
397     * Tests this object for equality with an arbitrary object.
398     *
399     * @param obj  the object to test against (<code>null</code> permitted).
400     *
401     * @return A boolean.
402     */
403    @Override
404    public boolean equals(Object obj) {
405        if (!(obj instanceof Range)) {
406            return false;
407        }
408        Range range = (Range) obj;
409        if (!(this.lower == range.lower)) {
410            return false;
411        }
412        if (!(this.upper == range.upper)) {
413            return false;
414        }
415        return true;
416    }
417
418    /**
419     * Returns a hash code.
420     *
421     * @return A hash code.
422     */
423    @Override
424    public int hashCode() {
425        int result;
426        long temp;
427        temp = Double.doubleToLongBits(this.lower);
428        result = (int) (temp ^ (temp >>> 32));
429        temp = Double.doubleToLongBits(this.upper);
430        result = 29 * result + (int) (temp ^ (temp >>> 32));
431        return result;
432    }
433
434    /**
435     * Returns a string representation of this Range.
436     *
437     * @return A String "Range[lower,upper]" where lower=lower range and
438     *         upper=upper range.
439     */
440    @Override
441    public String toString() {
442        return ("Range[" + this.lower + "," + this.upper + "]");
443    }
444
445}