package com.taobao.tddl.optimizer.utils.range; import java.io.Serializable; import com.taobao.tddl.optimizer.core.datatype.DataType; /** * A class to represent ranges of values. A range is defined to contain all the * values between the minimum and maximum values, where the minimum/maximum * value can be considered either included or excluded from the range. * <p> * This example creates a range of <code>Integer</code>s whose minimum value is * 1 and the maximum value is 5. The range is inclusive at both ends: * <p> * <code> * Range intRange = new Range(Integer.class, new Integer(1), new Integer(5)); * </code> * <p> * A <code>Range</code> can be unbounded at either or both of its ends. An * unbounded end is specified by passing null for the value of that end. A * <code>Range</code> unbounded at both of its ends represents a range of all * possible values for the <code>Class</code> of elements in that * <code>Range</code>. The <code>isMinIncluded()</code> method will always * return true for a <code>Range</code> unbounded on the minimum side and * correspondingly the <code>isMaxIncluded()</code> method will always return * true for a <code>Range</code> unbounded on the maximum side. * <p> * An empty range is defined as a <code>Range</code> whose minimum value is * greater than it's maximum value if the ends are included, or as a * <code>Range</code> whose minimum value is greater than or equal to it's * maximum value, if the minimum or the maximum value is excluded. * * @since JAI 1.1 */ public class Range implements Serializable { private static final long serialVersionUID = 2457011772528137531L; // The class of the elements in this Range. private Class elementClass; // The minimum and maximum values of the range. private final Comparable minValue, maxValue; // The the minimum/maximum value is included in the range.i // The default value is true, that is, included. private boolean isMinIncluded = true, isMaxIncluded = true; private final DataType type; /** * Constructs a <code>Range</code> object given the <code>Class</code> of * the elements in the <code>Range</code>, the minimum value and the maximum * value. The minimum and the maximum value are considered inclusive. * <p> * An unbounded range can be specified by passing in a null for either of * the two values, in which case the <code>Range</code> is unbounded on one * side, or for both, in which case the <code>Range</code> represents an all * inclusive set. * * @param elementClass The <code>Class</code> of the <code>Range</code> * elements. * @param minValue The lowest value included in the <code>Range</code>. * @param maxValue The highest value included in the <code>Range</code>. * @throws IllegalArgumentException if minValue and maxValue are both null, * and elementClass is not one of the subclasses of <code>Comparable</code>. * @throws IllegalArgumentException if minValue is not the same * <code>Class</code> as elementClass. * @throws IllegalArgumentException if maxValue is not the same * <code>Class</code> as elementClass. */ public Range(Class elementClassNoUsed, DataType type, Comparable minValue, Comparable maxValue){ // If both minValue and maxValue are null, check whether elementClass // is an instanceof Comparable. if ((minValue == null) && (maxValue == null)) { Class c = null; try { c = Class.forName("java.lang.Comparable"); } catch (ClassNotFoundException e) { } // // if (!c.isAssignableFrom(elementClass)) // throw new IllegalArgumentException(("Range0")); this.elementClass = c; } else this.elementClass = minValue == null ? maxValue.getClass() : minValue.getClass(); if (minValue != null && minValue.getClass() != this.elementClass) { throw new IllegalArgumentException(("Range1")); } this.minValue = minValue; if (maxValue != null && maxValue.getClass() != this.elementClass) { throw new IllegalArgumentException(("Range2")); } this.type = type; this.maxValue = maxValue; } /** * Constructs a <code>Range</code> object given the <code>Class</code> of * the elements in the <code>Range</code>, the minimum value and the maximum * value. Whether the minimum value and the maximum value are considered * inclusive is specified via the <code>isMinIncluded</code> and * <code>isMaxIncluded</code> variables. * <p> * An unbounded range can be specified by passing in a null for either of * the two values, in which case the <code>Range</code> is unbounded at one * end, or for both, in which case the <code>Range</code> represents an all * inclusive set. If null is passed in for either variable, the * <code>boolean</code> variables have no effect. * * @param elementClass The <code>Class</code> of the <code>Range</code> * elements. * @param minValue The lowest value for the <code>Range</code>. * @param isMinIncluded A boolean that defines whether the minimum value is * included in the <code>Range</code>. * @param maxValue The highest value for the <code>Range</code>. * @param isMaxIncluded A boolean that defines whether the maximum value is * included in the <code>Range</code>. * @throws IllegalArgumentException if minValue and maxValue are both null, * and elementClass is not one of the subclasses of <code>Comparable</code>. * @throws IllegalArgumentException if minValue is not the same * <code>Class</code> as elementClass. * @throws IllegalArgumentException if maxValue is not the same * <code>Class</code> as elementClass. */ public Range(Class elementClass, DataType type, Comparable minValue, boolean isMinIncluded, Comparable maxValue, boolean isMaxIncluded){ this(elementClass, type, minValue, maxValue); this.isMinIncluded = isMinIncluded; this.isMaxIncluded = isMaxIncluded; } /** * Returns true if the minimum value is included within this * <code>Range</code>. If the range is unbounded at this end, this method * will return true. */ public boolean isMinIncluded() { if (this.minValue == null) return true; return isMinIncluded; } /** * Returns true if the maximum value is included within this * <code>Range</code>. If the range is unbounded at this end, this method * will return true. */ public boolean isMaxIncluded() { if (this.maxValue == null) return true; return isMaxIncluded; } /** * Returns the <code>Class</code> of the elements of this <code>Range</code> * . */ public Class getElementClass() { return elementClass; } /** * Returns the minimum value of this <code>Range</code>. Returns null if the * <code>Range</code> is unbounded at this end. */ public Comparable getMinValue() { return minValue; } /** * Returns the maximum value of this <code>Range</code>. Returns null if the * <code>Range</code> is unbounded at this end. */ public Comparable getMaxValue() { return maxValue; } /** * Returns true if the specified value is within this <code>Range</code>, * i.e. is either equal to or greater than the minimum value of this * <code>Range</code> and is either lesser than or equal to the maximum * value of this <code>Range</code>. * * @param value The value to be checked for being within this * <code>Range</code>. * @throws IllegalArgumentException if the <code>Class</code> of the value * parameter is not the same as the elementClass of this <code>Range</code>. */ public boolean contains(Comparable value) { if (value != null && value.getClass() != elementClass) { throw new IllegalArgumentException(("Range3")); } // First check if the Range is empty if (isEmpty() == true) return false; // check both bounds. return isUnderUpperBound(value) && isOverLowerBound(value); } /** * Return true if the specific value is smaller than the maximum of this * range. If this range is unbounded at the maximum end, return true; if the * specific value is null and the maximum end is bounded, return false, that * is, suppose this null is the "positive infinite". */ private boolean isUnderUpperBound(Comparable value) { // if the maximum side is unbounded, return true if (this.maxValue == null) return true; // if the object passed in is null, return false: suppose it is // the "positive infinite". So be care when use this method. if (value == null) return false; if (isMaxIncluded) { return type.compare(maxValue, value) >= 0; } // if (isMaxIncluded) return maxValue.compareTo(value) >= 0; return type.compare(maxValue, value) > 0; // return maxValue.compareTo(value) > 0; } /** * Return true if the specific value is larger than the minimum of this * range. If this range is unbounded at the minimum end, return true; if the * specific value is null, return false; */ private boolean isOverLowerBound(Comparable value) { // if the minimum side is unbounded, return true if (this.minValue == null) return true; // if the object passed in is null, return false: suppose it is // the "negative infinite". So be care when use this method. if (value == null) return false; if (isMinIncluded) { return type.compare(minValue, value) <= 0; } // if (isMinIncluded) return minValue.compareTo(value) <= 0; return type.compare(minValue, value) < 0; // else return minValue.compareTo(value) < 0; } /** * Returns true if the supplied <code>Range</code> is fully contained within * this <code>Range</code>. Fully contained is defined as having the minimum * and maximum values of the fully contained range lie within the range of * values of the containing <code>Range</code>. * * @throws IllegalArgumentException if the <code>Class</code> of the * elements of the given <code>Range</code> is not the same as the * <code>Class</code> of the elements of this <code>Range</code>. * @throws IllegalArgumentException if the given <code>Range</code> is null */ public boolean contains(Range range) { if (range == null) throw new IllegalArgumentException(("Range5")); if (elementClass != range.getElementClass()) throw new IllegalArgumentException(("Range4")); if (range.isEmpty()) return true; Comparable min = range.getMinValue(); Comparable max = range.getMaxValue(); boolean maxSide, minSide; // // if (max == null) maxSide = (maxValue == null); // else maxSide = isUnderUpperBound(max) || (isMaxIncluded == // range.isMaxIncluded() && max.equals(maxValue)); if (max == null) maxSide = (maxValue == null); else maxSide = isUnderUpperBound(max) || (isMaxIncluded == range.isMaxIncluded() && type.compare(max, maxValue) == 0); if (min == null) minSide = (minValue == null); else minSide = isOverLowerBound(min) || (isMinIncluded == range.isMinIncluded() && type.compare(min, minValue) == 0); // if (min == null) minSide = (minValue == null); // else minSide = isOverLowerBound(min) || (isMinIncluded == // range.isMinIncluded() && min.equals(minValue)); return minSide && maxSide; } /** * Returns true if this <code>Range</code> intersects the given * <code>Range</code>. * * @throws IllegalArgumentException if the <code>Class</code> of the * elements of the given <code>Range</code> is not the same as the * <code>Class</code> of the elements of this <code>Range</code>. * @throws IllegalArgumentException if the given <code>Range</code> is null */ public boolean intersects(Range range) { if (range == null) throw new IllegalArgumentException(("Range5")); if (elementClass != range.getElementClass()) throw new IllegalArgumentException(("Range4")); return !intersect(range).isEmpty(); } /** * Returns the union of this <code>Range</code> with the given * <code>Range</code>. If this <code>Range</code> and the given * <code>Range</code> are disjoint, the <code>Range</code> returned as a * result of the union will have a minimum value set to the minimum of the * two disjoint range's minimum values, and the maximum set to the maximum * of the two disjoint range's maximum values, thus including the disjoint * range within it. * * @throws IllegalArgumentException if the <code>Class</code> of the * elements of the given <code>Range</code> is not the same as the * <code>Class</code> of the elements of this <code>Range</code>. * @throws IllegalArgumentException if the given <code>Range</code> is null */ public Range union(Range range) { if (range == null) throw new IllegalArgumentException(("Range5")); if (elementClass != range.getElementClass()) throw new IllegalArgumentException(("Range4")); if (this.isEmpty()) return new Range(elementClass, type, range.getMinValue(), range.isMinIncluded(), range.getMaxValue(), range.isMaxIncluded()); if (range.isEmpty()) return new Range(elementClass, type, this.minValue, this.isMinIncluded, this.maxValue, this.isMaxIncluded); boolean containMin = !isOverLowerBound(range.getMinValue()); boolean containMax = !isUnderUpperBound(range.getMaxValue()); // If the minimum of this range is contained in the given range, the // minimum of the union is the minimum of the given range; otherwise // it is the minimum of this range. So does the boolean isMinIncluded. // Similar for the maximum end Comparable minValue = containMin ? range.getMinValue() : this.minValue; Comparable maxValue = containMax ? range.getMaxValue() : this.maxValue; boolean isMinIncluded = containMin ? range.isMinIncluded() : this.isMinIncluded; boolean isMaxIncluded = containMax ? range.isMaxIncluded() : this.isMaxIncluded; return new Range(elementClass, type, minValue, isMinIncluded, maxValue, isMaxIncluded); } /** * Returns the intersection of this <code>Range</code> with the given * <code>Range</code>. * * @throws IllegalArgumentException if the <code>Class</code> of the * elements of the given <code>Range</code> is not the same as the * <code>Class</code> of the elements of this <code>Range</code>. * @throws IllegalArgumentException if the given <code>Range</code> is null */ public Range intersect(Range range) { if (range == null) throw new IllegalArgumentException(("Range5")); if (elementClass != range.getElementClass()) throw new IllegalArgumentException(("Range4")); if (this.isEmpty()) { Comparable temp = this.minValue; if (temp == null) temp = this.maxValue; // get a non-null object to create an empty Range // because the range is empty, so temp will not be // null return new Range(elementClass, type, temp, false, temp, false); } if (range.isEmpty()) { Comparable temp = range.getMinValue(); if (temp == null) temp = range.getMaxValue(); // get a non-null object to create an empty Range // because the range is empty, so temp will not be // null return new Range(elementClass, type, temp, false, temp, false); } boolean containMin = !isOverLowerBound(range.getMinValue()); boolean containMax = !isUnderUpperBound(range.getMaxValue()); // If the minimum of this range is contained in the given range, the // minimum of the intersect range should be the one of this range; // similarly, we can get the maximum and the booleans. Comparable minValue = containMin ? this.minValue : range.getMinValue(); Comparable maxValue = containMax ? this.maxValue : range.getMaxValue(); boolean isMinIncluded = containMin ? this.isMinIncluded : range.isMinIncluded(); boolean isMaxIncluded = containMax ? this.isMaxIncluded : range.isMaxIncluded(); return new Range(elementClass, type, minValue, isMinIncluded, maxValue, isMaxIncluded); } /** * Returns the <code>Range</code> of values that are in this * <code>Range</code> but not in the given <code>Range</code>. If the * subtraction results in two disjoint <code>Range</code>s, they will be * returned as two elements of a <code>Range</code> array, otherwise the * resultant <code>Range</code> will be returned as the first element of a * one element array. When this <code>Range</code> and the given * <code>Range</code> are both unbounded at both the ends (i.e both the * <code>Range</code>s are all inclusive), this method will return null as * the first element of one element array, as a result of the subtraction. * When this <code>Range</code> is completely contained in the given * <code>Range</code>, an empty <code>Range</code> is returned. * * @throws IllegalArgumentException if the <code>Class</code> of the * elements of the given <code>Range</code> is not the same as the * <code>Class</code> of the elements of this <code>Range</code>. */ public Range[] subtract(Range range) { if (range == null) throw new IllegalArgumentException(("Range5")); if (elementClass != range.getElementClass()) throw new IllegalArgumentException(("Range4")); // if this range is empty, return an empty range by copying this range; // if the given range is empty, return this range if (this.isEmpty() || range.isEmpty()) { Range[] ra = { new Range(elementClass, type, this.minValue, this.isMinIncluded, this.maxValue, this.isMaxIncluded) }; return ra; } Comparable min = range.getMinValue(); Comparable max = range.getMaxValue(); boolean minIn = range.isMinIncluded(); boolean maxIn = range.isMaxIncluded(); if (this.minValue == null && this.maxValue == null && min == null && max == null) { Range[] ra = { null }; return ra; } boolean containMin = this.contains(min); boolean containMax = this.contains(max); // this range may be a full range [null, null] if (containMin && containMax) { Range r1 = new Range(elementClass, type, this.minValue, this.isMinIncluded, min, !minIn); Range r2 = new Range(elementClass, type, max, !maxIn, this.maxValue, this.isMaxIncluded); // if r1 is empty , return the second section only; // or if this range and the given range are all unbounded // at the minimum end, r1 is [null, null] but // should be empty. so we need to treat it as a special case; // otherwise, a full range is returned if (r1.isEmpty() || (this.minValue == null && min == null)) { Range[] ra = { r2 }; return ra; } // similar to above if (r2.isEmpty() || (this.maxValue == null && max == null)) { Range[] ra = { r1 }; return ra; } Range[] ra = { r1, r2 }; return ra; } // if the max of the given range is in this range, return the range // from max of given range to the max of this range else if (containMax) { Range[] ra = { new Range(elementClass, type, max, !maxIn, this.maxValue, this.isMaxIncluded) }; return ra; } // if the min of the given range is in this range, return the range // from the min of this range to the min of the given range else if (containMin) { Range[] ra = { new Range(elementClass, type, this.minValue, this.isMinIncluded, min, !minIn) }; return ra; } // no overlap, just copy this range if ((min != null && !isUnderUpperBound(min)) || (max != null && !isOverLowerBound(max))) { Range[] ra = { new Range(elementClass, type, this.minValue, this.isMinIncluded, this.maxValue, this.isMaxIncluded) }; return ra; } // this range is contained in the given range, return an empty range min = (this.minValue == null) ? this.maxValue : this.minValue; Range[] ra = { new Range(elementClass, type, min, false, min, false) }; return ra; } /** * Compute a hash code value for this <code>Range</code> object. * * @return a hash code value for this <code>Range</code> object. */ @Override public int hashCode() { int code = this.elementClass.hashCode(); if (isEmpty()) return code; code ^= Integer.MAX_VALUE; if (this.minValue != null) { code ^= this.minValue.hashCode(); if (this.isMinIncluded) code ^= 0xFFFF0000; } if (this.maxValue != null) { code ^= this.maxValue.hashCode() * 31; if (this.isMaxIncluded) code ^= 0xFFFF; } return code; } /** * Returns true if this <code>Range</code> and the given <code>Range</code> * both have elements of the same <code>Class</code>, their minimum and * maximum values are the same, and their isMinIncluded() and * isMaxIncluded() methods return the same values. If this * <code>Range</code> and the given <code>Range</code> are both empty and * the <code>Class</code> of their elements is the same, they will be found * to be equal and true will be returned. */ @Override public boolean equals(Object other) { // this range is not null, so if the given object is null, // return false if (other == null) return false; // if the given object is not a range, return false if (!(other instanceof Range)) return false; Range r = (Range) other; // if the element class is not same, return false if (this.elementClass != r.getElementClass()) return false; // two empty ranges are equal if (this.isEmpty() && r.isEmpty()) return true; Comparable min = r.getMinValue(); // if the minimum is not null, compare both minValue and the boolean if (this.minValue != null) { if (!(this.type.compare(minValue, min) == 0)) return false; // if (!this.minValue.equals(min)) return false; if (this.isMinIncluded != r.isMinIncluded()) return false; } // if the minimum is unbounded, just check the given range is bounded // or not else if (min != null) return false; Comparable max = r.getMaxValue(); // if the maximum is not null, compare both maxValue and the boolean if (this.maxValue != null) { if (!(this.type.compare(maxValue, max) == 0)) return false; // if (!this.maxValue.equals(max)) return false; if (this.isMaxIncluded != r.isMaxIncluded()) return false; } // if the maximum is unbounded, just check the given range is bounded // or not else if (max != null) return false; return true; } /** * Returns true if this <code>Range</code> is empty, i.e. if the minimum * value is greater than the maximum value, if both are included, or if the * minimum value is greater than equal to the maximum value if either the * minimum or maximum value is excluded. */ public boolean isEmpty() { // an unbounded range is not empty if (minValue == null || maxValue == null) return false; int cmp = this.type.compare(minValue, maxValue); // int cmp = this.minValue.compareTo(this.maxValue); // if the minimum is larger than the maximum, this range is empty if (cmp > 0) return true; // if the minimum equals to the maximum and one side is not // inclusive, then it is empty if (cmp == 0) return !(isMinIncluded & isMaxIncluded); // otherwise, not empty return false; } public boolean isSingleValue() { if (this.maxValue != null && this.minValue != null && this.isMaxIncluded && this.isMinIncluded && this.type.compare(maxValue, minValue) == 0) return true; // if (this.maxValue != null && this.minValue != null && // this.isMaxIncluded && this.isMinIncluded // && this.maxValue.equals(minValue)) return true; return false; } /** * Returns a <code>String</code> representation of this <code>Range</code>. */ @Override public String toString() { // if inclusive, display '[' otherwise display '(' char c1 = isMinIncluded ? '[' : '('; char c2 = isMaxIncluded ? ']' : ')'; // if both ends are bounded if (minValue != null && maxValue != null) return new String(c1 + this.minValue.toString() + ", " + this.maxValue.toString() + c2); // if the maximum end is bounded if (maxValue != null) return new String(c1 + "-∞, " + this.maxValue.toString() + c2); // if the minimum end is bounded if (minValue != null) return new String(c1 + this.minValue.toString() + ", " + "+∞" + c2); // both ends are unbounded return new String(c1 + "-∞, +∞" + c2); } }