001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2005, 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-2005, 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.1 2005/10/25 21:29:13 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     *
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(
206                        range1.getLowerBound(), range2.getLowerBound()
207                    );
208                    double u = Math.max(
209                        range1.getUpperBound(), range2.getUpperBound()
210                    );
211                    return new Range(l, u);
212                }
213            }
214        }
215        
216        /**
217         * Creates a new range by adding margins to an existing range.
218         * 
219         * @param range  the range (<code>null</code> not permitted).
220         * @param lowerMargin  the lower margin (expressed as a percentage of the 
221         *                     range length).
222         * @param upperMargin  the upper margin (expressed as a percentage of the 
223         *                     range length).
224         * 
225         * @return The expanded range.
226         */
227        public static Range expand(Range range, 
228                                   double lowerMargin, double upperMargin) {
229            if (range == null) {
230                throw new IllegalArgumentException("Null 'range' argument.");   
231            }
232            double length = range.getLength();
233            double lower = length * lowerMargin;
234            double upper = length * upperMargin;
235            return new Range(
236                range.getLowerBound() - lower, range.getUpperBound() + upper
237            );
238        }
239    
240        /**
241         * Shifts the range by the specified amount.
242         * 
243         * @param base  the base range.
244         * @param delta  the shift amount.
245         * 
246         * @return A new range.
247         */
248        public static Range shift(Range base, double delta) {
249            return shift(base, delta, false);
250        }
251        
252        /**
253         * Shifts the range by the specified amount.
254         * 
255         * @param base  the base range.
256         * @param delta  the shift amount.
257         * @param allowZeroCrossing  a flag that determines whether or not the 
258         *                           bounds of the range are allowed to cross
259         *                           zero after adjustment.
260         * 
261         * @return A new range.
262         */
263        public static Range shift(Range base, double delta, 
264                                  boolean allowZeroCrossing) {
265            if (allowZeroCrossing) {
266                return new Range(
267                    base.getLowerBound() + delta, base.getUpperBound() + delta
268                );   
269            }
270            else {
271                return new Range(
272                    shiftWithNoZeroCrossing(base.getLowerBound(), delta), 
273                    shiftWithNoZeroCrossing(base.getUpperBound(), delta)
274                );
275            }
276        }
277        
278        private static double shiftWithNoZeroCrossing(double value, double delta) {
279            if (value > 0.0) {
280                return Math.max(value + delta, 0.0);  
281            }
282            else if (value < 0.0) {
283                return Math.min(value + delta, 0.0);
284            }
285            else {
286                return value + delta;   
287            }
288        }
289        
290        /**
291         * Tests this object for equality with an arbitrary object.
292         *
293         * @param obj  the object to test against (<code>null</code> permitted).
294         *
295         * @return A boolean.
296         */
297        public boolean equals(Object obj) {
298            if (!(obj instanceof Range)) {
299                return false;
300            }
301            Range range = (Range) obj;
302            if (!(this.lower == range.lower)) {
303                return false;
304            }
305            if (!(this.upper == range.upper)) {
306                return false;
307            }
308            return true;
309        }
310    
311        /**
312         * Returns a hash code.
313         * 
314         * @return A hash code.
315         */
316        public int hashCode() {
317            int result;
318            long temp;
319            temp = Double.doubleToLongBits(this.lower);
320            result = (int) (temp ^ (temp >>> 32));
321            temp = Double.doubleToLongBits(this.upper);
322            result = 29 * result + (int) (temp ^ (temp >>> 32));
323            return result;
324        }
325    
326        /**
327         * Returns a string representation of this Range.
328         *
329         * @return A String "Range[lower,upper]" where lower=lower range and 
330         *         upper=upper range.
331         */
332        public String toString() {
333            return ("Range[" + this.lower + "," + this.upper + "]");
334        }
335    
336    }