/*
Copyright 2008-2010 Gephi
Authors : Cezary Bartosiak
Website : http://www.gephi.org
This file is part of Gephi.
Gephi is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Gephi 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Gephi. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gephi.data.attributes.type;
import org.gephi.data.attributes.api.AttributeUtils;
/**
* This class represents an interval with some value.
*
* @author Cezary Bartosiak
*
* @param <T> type of data
*/
public final class Interval<T> implements Comparable<Interval> {
private double low; // the left endpoint
private double high; // the right endpoint
private boolean lopen; // indicates if the left endpoint is excluded
private boolean ropen; // indicates if the right endpoint is excluded
private T value; // the value stored in this interval
/**
* Constructs a new interval instance.
*
* <p>Note that {@code value} cannot be null if you want use this
* {@code interval} as a value storage. If it is null some estimators
* could not work and generate exceptions.
*
* @param low the left endpoint
* @param high the right endpoint
* @param lopen indicates if the left endpoint is excluded (true in this case)
* @param ropen indicates if the right endpoint is excluded (true in this case)
* @param value the value stored in this interval
*
* @throws IllegalArgumentException if {@code low} > {@code high}.
*/
public Interval(double low, double high, boolean lopen, boolean ropen, T value) {
if (low > high)
throw new IllegalArgumentException(
"The left endpoint of the interval must be less than " +
"the right endpoint.");
this.low = low;
this.high = high;
this.lopen = lopen;
this.ropen = ropen;
this.value = value;
}
/**
* Constructs a new interval instance with no value.
*
* @param low the left endpoint
* @param high the right endpoint
* @param lopen indicates if the left endpoint is excluded (true in this case)
* @param ropen indicates if the right endpoint is excluded (true in this case)
*
* @throws IllegalArgumentException if {@code low} > {@code high}.
*/
public Interval(double low, double high, boolean lopen, boolean ropen) {
this(low, high, lopen, ropen, null);
}
/**
* Constructs a new interval instance with left and right endpoints included
* by default.
*
* <p>Note that {@code value} cannot be null if you want use this
* {@code interval} as a value storage. If it is null some estimators
* could not work and generate exceptions.
*
* @param low the left endpoint
* @param high the right endpoint
* @param value the value stored in this interval
*
* @throws IllegalArgumentException if {@code low} > {@code high}.
*/
public Interval(double low, double high, T value) {
this(low, high, false, false, value);
}
/**
* Constructs a new interval instance with no value and left and right
* endpoints included by default.
*
* @param low the left endpoint
* @param high the right endpoint
*
* @throws IllegalArgumentException if {@code low} > {@code high}.
*/
public Interval(double low, double high) {
this(low, high, false, false, null);
}
/**
* Compares this interval with the specified interval for order.
*
* <p>Any two intervals <i>i</i> and <i>i'</i> satisfy the {@code interval
* trichotomy}; that is, exactly one of the following three properties
* holds:
* <ol>
* <li>
* <i>i</i> and <i>i'</i> overlap;
*
* <li>
* <i>i</i> is to the left of <i>i'</i> (<i>i.high < i'.low</i>);
*
* <li>
* <i>i</i> is to the right of <i>i'</i> (<i>i'.high < i.low</i>).
* </ol>
*
* <p>Note that if two intervals are equal ({@code i.low = i'.low} and
* {@code i.high = i'.high}), they overlap as well. But if they simply
* overlap (for instance {@code i.low < i'.low} and {@code i.high >
* i'.high}) they aren't equal. Remember that if two intervals are equal,
* they have got the same bounds excluded or included.
*
* @param interval the interval to be compared
*
* @return a negative integer, zero, or a positive integer as this interval
* is to the left of, overlaps with, or is to the right of the
* specified interval.
*
* @throws NullPointerException if {@code interval} is null.
*/
public int compareTo(Interval interval) {
if (interval == null)
throw new NullPointerException("Interval cannot be null.");
if (high < interval.low || high <= interval.low && (ropen || interval.lopen))
return -1;
if (interval.high < low || interval.high <= low && (interval.ropen || lopen))
return 1;
return 0;
}
/**
* Returns the left endpoint.
*
* @return the left endpoint.
*/
public double getLow() {
return low;
}
/**
* Returns the right endpoint.
*
* @return the right endpoint.
*/
public double getHigh() {
return high;
}
/**
* Indicates if the left endpoint is excluded.
*
* @return {@code true} if the left endpoint is excluded,
* {@code false} otherwise.
*/
public boolean isLowExcluded() {
return lopen;
}
/**
* Indicates if the right endpoint is excluded.
*
* @return {@code true} if the right endpoint is excluded,
* {@code false} otherwise.
*/
public boolean isHighExcluded() {
return ropen;
}
/**
* Returns the value stored in this interval.
*
* @return the value stored in this interval.
*/
public T getValue() {
return value;
}
/**
* Compares this interval with the specified object for equality.
*
* <p>Note that two intervals are equal if {@code i.low = i'.low} and
* {@code i.high = i'.high} and they have got the bounds excluded/included.
*
* @param obj object to which this interval is to be compared
*
* @return {@code true} if and only if the specified {@code Object} is a
* {@code Interval} whose low and high are equal to this
* {@code Interval's}.
*
* @see #compareTo(org.gephi.data.attributes.type.Interval)
* @see #hashCode
*/
@Override
public boolean equals(Object obj) {
if (obj != null && obj.getClass().equals(this.getClass())) {
Interval<T> interval = (Interval<T>)obj;
if (low == interval.low && high == interval.high &&
lopen == interval.lopen && ropen == interval.ropen)
return true;
}
return false;
}
@Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + (int) (Double.doubleToLongBits(this.low) ^ (Double.doubleToLongBits(this.low) >>> 32));
hash = 97 * hash + (int) (Double.doubleToLongBits(this.high) ^ (Double.doubleToLongBits(this.high) >>> 32));
hash = 97 * hash + (this.lopen ? 1 : 0);
hash = 97 * hash + (this.ropen ? 1 : 0);
return hash;
}
/**
* Creates a string representation of the interval with its value.
*
* @param timesAsDoubles indicates if times should be shown as doubles or dates
*
* @return a string representation with times as doubles or dates.
*/
public String toString(boolean timesAsDoubles) {
if (timesAsDoubles)
return (lopen ? "(" : "[") + low + ", " + high + ", " + value + (ropen ? ")" : "]");
else return (lopen ? "(" : "[") + AttributeUtils.getXMLDateStringFromDouble(low) + ", " +
AttributeUtils.getXMLDateStringFromDouble(high) + ", " + value + (ropen ? ")" : "]");
}
/**
* Returns a string representation of this interval in one of the formats:
* <ol>
* <li>
* {@code [low, high, value]}
* <li>
* {@code (low, high, value]}
* <li>
* {@code [low, high, value)}
* <li>
* {@code (low, high, value)}
* </ol>
*
* <p>Times are always shown as doubles</p>
*
* @return a string representation of this interval.
*/
@Override
public String toString() {
return toString(true);
}
}