/*
 This file is part of the software framework JStatCom, 
 a library for building applications for data based analysis.

 Copyright (C) 2000-2005  Markus Kraetzig

 This library is free software; you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as
 published by the Free Software Foundation; either version 2.1 of the
 License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 USA
 */

package com.jstatcom.model;

import java.text.NumberFormat;

import com.jstatcom.parser.NumberRangeParser;
import com.jstatcom.util.UMath;

/**
 * This class specifies an interval defined by two numbers and a type and
 * provides methods to validate numbers and other NumberRange
 * instances against it.
 * 

* The class is immutable by design, see (Joshua Bloch, "Effective Java", * Item 13). * * @author Markus Kraetzig */ public final class NumberRange { /** * The lower bound of this range. */ public final double lowerBound; /** * The upper bound of this range. */ public final double upperBound; /** * The type of this range, which is either * OPEN, CLOSED, LEFT_OPEN or RIGHT_OPEN. */ public final NumberRangeTypes rangeType; /** * Creates an open range with (-Infinity,+Infinity). */ public NumberRange() { this.lowerBound = Double.NEGATIVE_INFINITY; this.upperBound = Double.POSITIVE_INFINITY; this.rangeType = NumberRangeTypes.OPEN; } /** * Creates a range according to the given bounds and the type. * * @param lowerBoundNew * the lower bound of the interval * @param upperBoundNew * the upper bound of the interval * @param rangeType * the interval type to be created * @throws IllegalArgumentException * if: *

*/ public NumberRange(double lowerBoundNew, double upperBoundNew, NumberRangeTypes rangeType) { checkArguments(lowerBoundNew, upperBoundNew, rangeType); this.lowerBound = lowerBoundNew; this.upperBound = upperBoundNew; this.rangeType = rangeType; } /** * See NumberRange constructor. */ private void checkArguments(double lowerBoundNew, double upperBoundNew, NumberRangeTypes rangeType) { if (rangeType == null) throw new IllegalArgumentException("Range type is null."); Double lowerB = new Double(lowerBoundNew); Double upperB = new Double(upperBoundNew); if (Double.isNaN(lowerB) || Double.isNaN(upperB)) throw new IllegalArgumentException( "NaN are not allowed for bounds."); if (lowerBoundNew == Double.POSITIVE_INFINITY || upperBoundNew == Double.NEGATIVE_INFINITY) throw new IllegalArgumentException("Wrong +/-INFINITY bounds."); if (lowerBoundNew == Double.NEGATIVE_INFINITY && !rangeType.leftOpen) throw new IllegalArgumentException( "Lower bound cannot be closed and -INFINITY."); if (upperBoundNew == Double.POSITIVE_INFINITY && !rangeType.rightOpen) throw new IllegalArgumentException( "Upper bound cannot be closed and +INFINITY."); if (upperBoundNew == lowerBoundNew && (rangeType.leftOpen || rangeType.rightOpen)) throw new IllegalArgumentException( "If bounds are equal, interval type must be CLOSED."); if (upperBoundNew < lowerBoundNew) throw new IllegalArgumentException( "Lower bound is greater than upper bound."); } /** * Finds a number that is valid for this range. * * @return a valid number within this range */ public double defaultNumber() { if (upperBound == Double.POSITIVE_INFINITY && lowerBound == Double.NEGATIVE_INFINITY) { return 0; } if (lowerBound > Double.NEGATIVE_INFINITY && !rangeType.leftOpen) { // lower bound is possible return lowerBound; } if (upperBound < Double.POSITIVE_INFINITY && !rangeType.rightOpen) { // upper bound is possible return upperBound; } if (upperBound < Double.POSITIVE_INFINITY && lowerBound > Double.NEGATIVE_INFINITY) { if (upperBound - lowerBound > 1) return lowerBound + 1; return (upperBound - lowerBound) / 2 + lowerBound; } if (lowerBound > Double.NEGATIVE_INFINITY) { return lowerBound + 1; // upper must be +inf } if (upperBound < Double.POSITIVE_INFINITY) { return upperBound - 1; // lower must be -inf } throw new RuntimeException("Code should never be reached."); } /** * Finds a number range that is enclosed by this range. The range type of * the returned range is always CLOSED. * * @return a valid number within this range */ public NumberRange defaultNumberRange() { if (upperBound == Double.POSITIVE_INFINITY && lowerBound == Double.NEGATIVE_INFINITY) { return NumberRange.valueOf("[0,100]"); // just a default } if (rangeType == NumberRangeTypes.CLOSED) return this; if (upperBound < Double.POSITIVE_INFINITY && lowerBound > Double.NEGATIVE_INFINITY) { double lBound = 0; double uBound = 0; if (rangeType == NumberRangeTypes.LEFT_OPEN) { uBound = upperBound; if (upperBound - lowerBound > 1) lBound = lowerBound + 1; else lBound = upperBound; return new NumberRange(lBound, uBound, NumberRangeTypes.CLOSED); } if (rangeType == NumberRangeTypes.RIGHT_OPEN) { lBound = lowerBound; if (upperBound - lowerBound > 1) uBound = upperBound - 1; else uBound = lowerBound; return new NumberRange(lBound, uBound, NumberRangeTypes.CLOSED); } if (rangeType == NumberRangeTypes.OPEN) { uBound = upperBound; if (upperBound - lowerBound > 1) { lBound = lowerBound + 1; uBound = upperBound - 1; return new NumberRange(lBound, uBound, NumberRangeTypes.CLOSED); } double val = (upperBound - lowerBound) / 2 + lowerBound; return new NumberRange(val, val, NumberRangeTypes.CLOSED); } } // In any other case just create a one number range. double defaultNumber = defaultNumber(); return new NumberRange(defaultNumber, defaultNumber, NumberRangeTypes.CLOSED); } /** * Tests whether a given number is enclosed in this range and returns a * formatted message if not. If the number is either POS_INF, NEG_INF or * NaN, it is never enclosed in the range. * * @param number * the value to check * @return null if number is contained, message if not */ public String encloses(double number) { boolean leftCheck = lowerBound < number; if (!rangeType.leftOpen && !leftCheck) leftCheck = lowerBound == number; boolean rightCheck = upperBound > number; if (!rangeType.rightOpen && !rightCheck) rightCheck = upperBound == number; if (leftCheck && rightCheck) return null; String msg = number + " is not contained in " + toString() + "."; return msg; } /** * Tests whether a given number is enclosed in this range and returns a * formatted message if not. This version also takes the given precision * into account and rounds the number before checking. If the * number is either POS_INF, NEG_INF or NaN, it is never enclosed in the * range. * * @param number * the value to check * @param precision * the number of digits right to the decimal point to take into * account for checking * @param formatter * a formatter for number in the possible error * message, can be null * * @return null if number is contained, message if not * @throws IllegalArgumentException * if if (precision < 0) */ public String encloses(double number, int precision, NumberFormat formatter) { if (precision < 0) throw new IllegalArgumentException("Precision < 0."); double orig = number; String rounded = ""; boolean isNaN = Double.isNaN(number); if (!isNaN && number != Double.NEGATIVE_INFINITY && number != Double.POSITIVE_INFINITY) { number = UMath.round(number, precision); if (number - orig != 0) rounded = " (rounded)"; } boolean leftCheck = lowerBound < number; if (!rangeType.leftOpen && !leftCheck) leftCheck = lowerBound == number; boolean rightCheck = upperBound > number; if (!rangeType.rightOpen && !rightCheck) rightCheck = upperBound == number; if (leftCheck && rightCheck) return null; String num = number + ""; if (formatter != null) { formatter.setMaximumFractionDigits(precision); formatter.setMinimumFractionDigits(precision); num = formatter.format(number); formatter.setMaximumFractionDigits(100); } String msg = num + rounded + " is not contained in " + formattedString(formatter) + "."; return msg; } /** * Tests whether a given range is completely enclosed within this range and * returns a formatted message if not. A range cannot be enclosed, if one of * its bounds is POS_INF or NEG_INF. * * @param numberRange * the range to check * @return null if numberRange is contained, message if not */ public String encloses(NumberRange numberRange) { boolean leftCheck = lowerBound < numberRange.lowerBound; if (!rangeType.leftOpen && !leftCheck) leftCheck = lowerBound == numberRange.lowerBound; boolean rightCheck = upperBound > numberRange.upperBound; if (!rangeType.rightOpen && !rightCheck) rightCheck = upperBound == numberRange.upperBound; if (leftCheck && rightCheck) return null; String msg = numberRange + " is not contained in " + toString() + "."; return msg; } /** * Provides logical equals check. * * @param o * another range to compare with * @return true if lower bound, upper bound and range type * are all equal */ public boolean equals(Object o) { if (!(o instanceof NumberRange)) return false; NumberRange other = (NumberRange) o; return lowerBound == other.lowerBound && upperBound == other.upperBound && rangeType == other.rangeType; } /** * Gets a string representation of this range with a special formatter. If * formatter == null, this is identical to * toString. * * @param formatter * a number formatter for the bounds, can be null * @return a string in mathematical notation */ public String formattedString(NumberFormat formatter) { StringBuffer ret = new StringBuffer(); ret.append(rangeType.leftBracket); if (formatter == null) { if ((Math.rint(lowerBound) - lowerBound) == 0) ret.append(new Double(lowerBound).intValue()); else ret.append(lowerBound); } else ret.append(formatter.format(lowerBound)); ret.append(", "); if (formatter == null) { { if ((Math.rint(upperBound) - upperBound) == 0) ret.append(new Double(upperBound).intValue()); else ret.append(upperBound); } } else ret.append(formatter.format(upperBound)); ret.append(rangeType.rightBracket); return ret.toString(); } /** * Gets the lower bound for this range. * * @return lower bound */ public double lowerBound() { return lowerBound; } /** * Gets the interval type for this range. * * @return range type */ public NumberRangeTypes type() { return rangeType; } /** * Gets the upper bound for this range. * * @return upper bound */ public double upperBound() { return upperBound; } /** * Overrides hashCode, because equals was overwritten. The * algorithm was taken from (Joshua Bloch, "Effective Java", Item 8). * * @return the hash code */ public int hashCode() { int result = 17; long f = Double.doubleToLongBits(lowerBound); result = 37 * result + (int) (f ^ (f >>> 32)); f = Double.doubleToLongBits(upperBound); result = 37 * result + (int) (f ^ (f >>> 32)); result = 37 * result + rangeType.hashCode(); return result; } /** * Gets string representation of this range. * * @return a string in mathematical notation */ public String toString() { StringBuffer ret = new StringBuffer(); ret.append(rangeType.leftBracket); if ((Math.rint(lowerBound) - lowerBound) == 0) ret.append(new Double(lowerBound).intValue()); else ret.append(lowerBound); ret.append(", "); if ((Math.rint(upperBound) - upperBound) == 0) ret.append(new Double(upperBound).intValue()); else ret.append(upperBound); ret.append(rangeType.rightBracket); return ret.toString(); } /** * Static initialized that creates a NumberRange from a * string representation. Valid expressions are: *
    *
  • [1,100] *
  • (-Infinity,100.3] *
  • (-Infinity,+Infinity) *
  • (-12E-2,23] *
  • [100,100] *
*
* * @param exp * string containing a valid representation for a number range * @return a number range instance created from exp * @throws IllegalArgumentException * if exp could not be parsed to a valid number * range * @see #NumberRange(double lowerBoundNew,double * upperBoundNew,NumberRangeTypes rangeType) */ public static NumberRange valueOf(String exp) { NumberRange range = null; try { range = new NumberRangeParser().parseStringToRange(exp); } catch (Throwable ex) { throw new IllegalArgumentException( "NumberRange construction from \"" + exp + "\" failed.\n" + ex); } return range; } }

Ads help maintain this website.