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);
}
}