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    
059    package org.jfree.data;
060    
061    import java.io.Serializable;
062    
063    /**
064     * Represents an immutable range of values.
065     */
066    public 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    }