001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright 2005-2011 The ConQAT Project                                   |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016+-------------------------------------------------------------------------*/
017package org.conqat.lib.commons.math;
018
019import java.io.Serializable;
020import java.text.NumberFormat;
021
022import org.conqat.lib.commons.assertion.CCSMAssert;
023import org.conqat.lib.commons.string.StringUtils;
024
025import com.fasterxml.jackson.annotation.JsonCreator;
026import com.fasterxml.jackson.annotation.JsonProperty;
027
028/**
029 * A class that represents ranges that may include or exclude the upper and
030 * lower bounds. This class is immutable.
031 * <p>
032 * Note: If a range is constructed where the upper and lower bounds are equal
033 * and one of them is exclusive, this range is considered empty, i.e. no number
034 * can be contained in it.
035 */
036public class Range implements Comparable<Range>, Serializable {
037
038        /** Version for serialization. */
039        private static final long serialVersionUID = 1;
040
041        /** The name of the JSON property name for {@link #lower}. */
042        private static final String LOWER_PROPERTY = "lower";
043
044        /** The name of the JSON property name for {@link #upper}. */
045        private static final String UPPER_PROPERTY = "upper";
046
047        /** The lower bound. */
048        @JsonProperty(LOWER_PROPERTY)
049        private final double lower;
050
051        /** The upper bound. */
052        @JsonProperty(UPPER_PROPERTY)
053        private final double upper;
054
055        /** Flag that indicates if lower bound is inclusive or not. */
056        @JsonProperty("lowerIsInclusive")
057        private final boolean lowerIsInclusive;
058
059        /** Flag that indicates if upper bound is inclusive or not. */
060        @JsonProperty("upperIsInclusive")
061        private final boolean upperIsInclusive;
062
063        /** Create range where both bounds are inclusive. */
064        @JsonCreator
065        public Range(@JsonProperty(LOWER_PROPERTY) double lower, @JsonProperty(UPPER_PROPERTY) double upper) {
066                this(lower, true, upper, true);
067        }
068
069        /**
070         * Create range.
071         * 
072         * @param lowerIsInclusive
073         *            flag that indicates if lower bound is inclusive or not
074         * @param upperIsInclusive
075         *            flag that indicates if upper bound is inclusive or not
076         */
077        public Range(double lowerBound, boolean lowerIsInclusive, double upperBound, boolean upperIsInclusive) {
078                CCSMAssert.isFalse(Double.isNaN(lowerBound), "Lower bound must not be NaN");
079                CCSMAssert.isFalse(Double.isNaN(upperBound), "Upper bound must not be NaN");
080                lower = lowerBound;
081                this.lowerIsInclusive = lowerIsInclusive;
082                upper = upperBound;
083                this.upperIsInclusive = upperIsInclusive;
084        }
085
086        /** Checks is a number is contained in the range. */
087        public boolean contains(double number) {
088                if (lowerIsInclusive) {
089                        if (number < lower) {
090                                return false;
091                        }
092                } else {
093                        if (number <= lower) {
094                                return false;
095                        }
096                }
097
098                if (upperIsInclusive) {
099                        return !(number > upper);
100                } else {
101                        return !(number >= upper);
102                }
103
104        }
105
106        /**
107         * Hash code includes bound and the flags that indicate if the bounds are
108         * inclusive or not.
109         */
110        @Override
111        public int hashCode() {
112                if (isEmpty()) {
113                        return 0;
114                }
115
116                int result = hash(upper) + 37 * hash(lower);
117
118                // indicate bounds by bit flips; which bit is used is not really
119                // relevant
120                if (lowerIsInclusive) {
121                        result ^= 0x100000;
122                }
123                if (upperIsInclusive) {
124                        result ^= 0x4000;
125                }
126
127                return result;
128        }
129
130        /** Code for hashing a double is copied from {@link Double#hashCode()}. */
131        private static int hash(double number) {
132                long bits = Double.doubleToLongBits(number);
133                return (int) (bits ^ (bits >>> 32));
134        }
135
136        /**
137         * Two ranges are equal if their bounds are equal and the flags that indicate if
138         * the bounds are inclusive or not are equal, too. Empty ranges are considered
139         * equal regardless for there specific bounds.
140         */
141        @Override
142        public boolean equals(Object obj) {
143                if (obj == this) {
144                        return true;
145                }
146                if (!(obj instanceof Range)) {
147                        return false;
148                }
149
150                Range other = (Range) obj;
151
152                if (isEmpty() && other.isEmpty()) {
153                        return true;
154                }
155
156                return lowerIsInclusive == other.lowerIsInclusive && upperIsInclusive == other.upperIsInclusive
157                                && lower == other.lower && upper == other.upper;
158        }
159
160        /** Get lower bound. */
161        public double getLower() {
162                return lower;
163        }
164
165        /** Get upper bound. */
166        public double getUpper() {
167                return upper;
168        }
169
170        /** Get flag that indicates if the lower bound is inclusive. */
171        public boolean isLowerInclusive() {
172                return lowerIsInclusive;
173        }
174
175        /** Get flag that indicates if the upper bound is inclusive. */
176        public boolean isUpperInclusive() {
177                return upperIsInclusive;
178        }
179
180        /** Checks if a range is empty. */
181        public boolean isEmpty() {
182                if (lowerIsInclusive && upperIsInclusive) {
183                        return lower > upper;
184                }
185                return lower >= upper;
186        }
187
188        /**
189         * Returns whether this range is a singleton (i.e. contains of a single
190         * point/number).
191         */
192        public boolean isSingleton() {
193                return lower == upper && lowerIsInclusive && upperIsInclusive;
194        }
195
196        /**
197         * Returns the size of the range. In case of empty ranges this returns 0. Note
198         * that inclusiveness of bound is not relevant for this method.
199         */
200        public double size() {
201                return Math.max(0, upper - lower);
202        }
203
204        /** This forwards to <code>format(null);</code>. */
205        @Override
206        public String toString() {
207                return format(null);
208        }
209
210        /**
211         * String representation contains the bounds and brackets that indicate if the
212         * bounds are inclusive or exclusive.
213         * 
214         * @param numberFormat
215         *            number format used for formatting the numbers. If this is
216         *            <code>null</code>, no special formatting is applied.
217         */
218        public String format(NumberFormat numberFormat) {
219                StringBuilder result = new StringBuilder();
220                if (lowerIsInclusive) {
221                        result.append("[");
222                } else {
223                        result.append("]");
224                }
225                result.append(StringUtils.format(lower, numberFormat) + ";" + StringUtils.format(upper, numberFormat));
226                if (upperIsInclusive) {
227                        result.append("]");
228                } else {
229                        result.append("[");
230                }
231                return result.toString();
232        }
233
234        /**
235         * Returns whether this range has a non-empty intersection with another range.
236         */
237        public boolean overlaps(Range other) {
238                if (isEmpty() || other.isEmpty()) {
239                        return false;
240                }
241
242                if (lower == other.lower) {
243                        if (isSingleton()) {
244                                return other.lowerIsInclusive;
245                        }
246                        if (other.isSingleton()) {
247                                return lowerIsInclusive;
248                        }
249                        return true;
250                }
251
252                if (lower < other.lower) {
253                        if (upperIsInclusive && other.lowerIsInclusive) {
254                                return other.lower <= upper;
255                        }
256                        return other.lower < upper;
257                }
258
259                return other.overlaps(this);
260        }
261
262        /**
263         * {@inheritDoc}
264         * <p>
265         * Compares by lower values.
266         */
267        @Override
268        public int compareTo(Range other) {
269                int result = Double.compare(lower, other.lower);
270                if (result != 0) {
271                        return result;
272                }
273
274                if (lowerIsInclusive && !other.lowerIsInclusive) {
275                        return -1;
276                }
277                if (!lowerIsInclusive && other.lowerIsInclusive) {
278                        return 1;
279                }
280                return 0;
281        }
282
283        /** Determines whether the other range is fully contained in this range. */
284        public boolean contains(Range other) {
285                boolean sameLowerBound = isLowerInclusive() == other.isLowerInclusive() && getLower() == other.getLower();
286                boolean containsLower = contains(other.getLower()) || sameLowerBound;
287                boolean sameUpperBound = isUpperInclusive() == other.isUpperInclusive() && getUpper() == other.getUpper();
288                boolean containsUpper = contains(other.getUpper()) || sameUpperBound;
289
290                return containsLower && containsUpper;
291        }
292
293        /**
294         * Calculates the union of this range with another range. The union is defined
295         * by the smaller lower bound and the higher upper bound of the two ranges.
296         */
297        public Range union(Range other) {
298                double lowerBoundOfOtherRange = other.getLower();
299                double minLower;
300                boolean lowerInclusive;
301
302                if (lower == lowerBoundOfOtherRange) {
303                        minLower = lower;
304                        lowerInclusive = isLowerInclusive() || other.isLowerInclusive();
305                } else if (lower < lowerBoundOfOtherRange) {
306                        minLower = lower;
307                        lowerInclusive = isLowerInclusive();
308                } else {
309                        minLower = lowerBoundOfOtherRange;
310                        lowerInclusive = other.isLowerInclusive();
311                }
312
313                double upperBoundOfOtherRange = other.getUpper();
314                double maxUpper;
315                boolean upperInclusive;
316
317                if (upper == upperBoundOfOtherRange) {
318                        maxUpper = upper;
319                        upperInclusive = isUpperInclusive() || other.isUpperInclusive();
320                } else if (upper < upperBoundOfOtherRange) {
321                        maxUpper = upperBoundOfOtherRange;
322                        upperInclusive = other.isUpperInclusive();
323                } else {
324                        maxUpper = upper;
325                        upperInclusive = isUpperInclusive();
326                }
327
328                return new Range(minLower, lowerInclusive, maxUpper, upperInclusive);
329        }
330}