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