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}