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