/* XXL: The eXtensible and fleXible Library for data processing
Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger
Head of the Database Research Group
Department of Mathematics and Computer Science
University of Marburg
Germany
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 3 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, see <http://www.gnu.org/licenses/>.
http://code.google.com/p/xxl/
*/
package xxl.core.util;
import java.util.Comparator;
import xxl.core.comparators.ComparableComparator;
import xxl.core.indexStructures.Descriptor;
import xxl.core.math.Maths;
/**
* Class to implement one-dimensional intervals of any ordered data type. <p>
*
* The order of the basic data type is represented by means of comparators. If
* no comparator is specified in a constructor a ComparableComparator.DEFAULT_INSTANCE
* is used instead. <br>
* The borders of an interval are represented in an Object-array called
* <tt>border</tt> with <tt>length == 2</tt>.
* That means the left border is contained in <tt>border[0]</tt> and the right
* border is contained <tt>border[1]</tt>.
* The borders of an interval may or may not belong to the interval, as desired.
* This information is described in the boolean array <code>inclusive</code>.
* If <tt>inclusive[0] == true</tt> the left border is contained in this interval. If it
* is <tt>false</tt> the left border is not included. <tt>inclusive[1]</tt> contains
* the same information for the right border of an interval.
* This class provides methods to compare, union and intersect intervals, even to check
* if an interval is contained in an interval.
* Further it overrides the methods <tt>equals</tt>, <tt>clone</tt> and <tt>toString</tt>
* of the superclass <tt>Object</tt>. <p>
*
* <b>Note:</b>
* If an instance of this class is created in any constructor the condition if an interval
* contains a minimum of one point is checked. An <tt>IllegalArgumentException</tt> is
* thrown in the case when the interval does not contain any point.
* <p>
* <b>Example usage:</b>
* <br><br>
* <code><pre>
* Interval1D interval1 = new Interval1D(new Integer(-20), true, new Integer(20), false);
* Interval1D interval2 = new Interval1D(new Integer(1), new Integer(30));
*
* System.out.println("Interval1:");
* System.out.println("\tleft border: " +interval1.border(false));
* System.out.println("\tleft border included? " +interval1.includes(false));
* System.out.println("\tright border: " +interval1.border(true));
* System.out.println("\tright border included? " +interval1.includes(true));
* System.out.println("Printed directly to output stream: " +interval1 +"\n");
* System.out.println("Interval2:");
* System.out.println("\tleft border: " +interval2.border(false));
* System.out.println("\tleft border included? " +interval2.includes(true));
* System.out.println("\tright border: " +interval2.border(true));
* System.out.println("\tright border included? " +interval2.includes(true));
* System.out.println("Printed directly to output stream: " +interval2 +"\n");
* System.out.println("Are the intervals equal? " +interval1.equals(interval2));
* System.out.println("Do the intervals overlap? " +interval1.overlaps(interval2));
* System.out.println("Does interval1 contain interval2? " +interval1.contains(interval2));
* System.out.println("Interval3 gets a clone of interval1.");
* Interval1D interval3 = (Interval1D)interval1.clone();
* System.out.println("Union of interval1 and interval2: " +interval1.union(interval2));
* System.out.println("Intersection of interval3 and interval2: " +interval3.intersect(interval2));
* </code></pre>
* <p>
* The following output was created:
* <pre>
* Interval1:
* left border: -20
* left border included? true
* right border: 20
* right border included? false
* Printed directly to output stream: [-20,20[
*
* Interval2:
* left border: 1
* left border included? true
* right border: 30
* right border included? true
* Printed directly to output stream: [1,30]
*
* Are the intervals equal? false
* Do the intervals overlap? 0
* Does interval1 contain interval2? false
* Interval3 gets a clone of interval1.
* Union of interval1 and interval2: [-20,30]
* Intersection of interval3 and interval2: [1,20[
* </pre>
*
* @see java.util.Comparator
* @see xxl.core.comparators.ComparableComparator
* @see xxl.core.indexStructures.Descriptor
* @see java.lang.Cloneable
*/
public class Interval1D implements Descriptor {
/**
* Object-array containing the left and right border.
* <b>Note:</b> border.length = 2
* border[0]: left border of the interval
* border[1]: right border of the interval
*/
protected Object [] border;
/**
* The boolean array containing the information if the borders of an interval
* may or may not belong to this interval.
* <b>Note:</b> border.length = 2
* If inclusive[0] is <tt>true</tt> then the left border belongs to the interval,
* otherwise this border does not belong to this interval.
* If inclusive[1] is <tt>true</tt> then the right border belongs to the interval,
* otherwise this border does not belong to this interval.
*/
protected boolean [] inclusive;
/**
* The comparator defining the order of the basic data type.
* This comparator is used to compare two borders.
*/
public final Comparator comparator;
/**
* Constructs a new interval by providing the left and right borders and a comparator.
*
* @param leftBorder The left border of the interval.
* @param leftInclusive <tt>True</tt> iff the left border belongs to the interval.
* @param rightBorder The right border of the interval.
* @param rightInclusive <tt>True</tt> iff the right border belongs to the interval.
* @param comparator The comparator defining the order of the basic data type.
* @throws IllegalArgumentException if the interval does not contain any point.
*/
public Interval1D (Object leftBorder, boolean leftInclusive, Object rightBorder, boolean rightInclusive, Comparator comparator) throws IllegalArgumentException {
int comparison = comparator.compare(leftBorder, rightBorder);
if (comparison>0 || comparison==0 && !(leftInclusive && rightInclusive))
throw new IllegalArgumentException("Interval does not contain any point.");
this.border = new Object [] {leftBorder, rightBorder};
this.inclusive = new boolean [] {leftInclusive, rightInclusive};
this.comparator = comparator;
}
/**
* Constructs a new interval by providing the left and right borders based on a Comparable data type.
* A {@link xxl.core.comparators.ComparableComparator comparable comparator} is used to compare two borders.
*
* @param leftBorder The left border of the interval.
* @param leftInclusive <tt>True</tt> iff the left border belongs to the interval.
* @param rightBorder The right border of the interval.
* @param rightInclusive <tt>True</tt> iff the right border belongs to the interval.
* @throws IllegalArgumentException if the interval does not contain any point.
*/
public Interval1D (Object leftBorder, boolean leftInclusive, Object rightBorder, boolean rightInclusive) throws IllegalArgumentException {
this(leftBorder, leftInclusive, rightBorder, rightInclusive, new ComparableComparator());
}
/**
* Constructs a new closed interval by providing the left and right borders and a comparator.
*
* @param leftBorder The left border of the interval.
* @param rightBorder the right border of the interval.
* @param comparator The comparator defining the order on the basic data type.
* @throws IllegalArgumentException if the interval does not contain any point.
*/
public Interval1D (Object leftBorder, Object rightBorder, Comparator comparator) throws IllegalArgumentException {
this(leftBorder, true, rightBorder, true, comparator);
}
/**
* Constructs a new closed interval by providing the left and right borders based on a Comparable data type.
* A {@link xxl.core.comparators.ComparableComparator comparable comparator} is used to compare two borders.
*
* @param leftBorder The left border of the interval.
* @param rightBorder The right border of the interval.
* @throws IllegalArgumentException in case if the interval does not contain any point.
*/
public Interval1D (Object leftBorder, Object rightBorder) throws IllegalArgumentException {
this(leftBorder, true, rightBorder, true);
}
/**
* Constructs a new closed interval by providing a single point and a comparator.
* That means the interval is defined as follows: [point, point]
*
* @param point The only point the interval will contain.
* @param comparator The comparator defining the order on the basic data type.
*/
public Interval1D (Object point, Comparator comparator) {
this(point, true, point, true, comparator);
}
/**
* Constructs a new interval by providing a single point based on a Comparable data type.
* A {@link xxl.core.comparators.ComparableComparator comparable comparator} is used to compare two borders.
*
* @param point The only point the interval will contain.
*/
public Interval1D (Object point) {
this(point, new ComparableComparator());
}
/**
* Copy-constructor.
* The created new instance of <tt>Interval1D</tt> will be equal to the given interval.
* The clone of the interval is another interval that has exactly the
* same border properties and the same comparator as the current interval.
*
* @param interval The interval to be cloned.
*/
public Interval1D (Interval1D interval) {
this(interval.border[0], interval.inclusive[0], interval.border[1], interval.inclusive[1], interval.comparator);
}
/**
* Constructs a new interval given a descriptor.
* The descriptor is casted to an Interval1D and then
* the copy-constructor is called.
* @param descriptor the given descriptor
*/
public Interval1D (Descriptor descriptor) {
this((Interval1D)descriptor);
}
/** Returns the Comparator used by this interval to compare two
* borders.
* @return returns Comparator used by this interval
*
*/
public Comparator comparator(){
return comparator;
}
/**
* Clones this interval.
* The produced new <tt>Interval1D</tt> will be equal to this interval.
* The clone of the interval is another interval that has exactly the
* same border properties and the same comparator as the current interval.
* The copy-constructor is called.
* <p>Overrides the <code>clone</code> method of <code>Object</code>.
*
* @return a clone of this interval.
* @see #Interval1D (Interval1D interval)
*/
public Object clone () {
return new Interval1D(this);
}
/**
* Returns a String representation of this interval.
* <p>Overrides the <code>toString</code> method of <code>Object</code>.
*
* @return the String representation of this interval.
*/
public String toString () {
return (inclusive[0]?"[":"]")+border[0]+","+border[1]+((inclusive[1]?"]":"["));
}
/**
* Returns <tt>true</tt> iff the given object is an interval having the same
* border properties and comparator as this interval.
* Otherwise <tt>false</tt> is returned.
* <p>Overrides the <code>equals</code> method of <code>Object</code>.
*
* @param object The object, an interval, to be compared with this interval.
* @return Returns <tt>true</tt> if the given object is an interval having the same
* border properties and comparator as this interval.
* Returns <tt>false</tt> if the given object is not an interval, the borders
* differ in any kind or the used comparators are not equal.
*/
public boolean equals (Object object) {
try {
Interval1D interval = (Interval1D)object;
for (int i=0; i<2; i++)
if (inclusive[i]!=interval.inclusive[i] ||
border[i]!=interval.border[i] && !border[i].equals(interval.border[i]) && comparator.compare(border[i], interval.border[i])!=0)
return false;
return true;
}
catch (ClassCastException cce) {
return false;
}
catch (NullPointerException npe) {
return false;
}
}
/**
* Returns the desired border of this interval.
*
* @param rightBorder Returns the right border if <tt>true</tt>.
* @return Returns the interval's right border is the specified parameter is <tt>true</tt>,
* otherwise (<tt>false</tt>) the interval's left border.
*/
public Object border (boolean rightBorder) {
return border[rightBorder? 1: 0];
}
/**
* Checks if the specified border is included in this interval.
* Returns <tt>true</tt> if the desired border belongs to this interval
* otherwise <tt>false</tt>.
*
* @param rightBorder Examines the right border if <tt>true</tt>. If this parameter
* is <tt>false</tt> the left border is examined.
* @return Returns <tt>true</tt> if the specified border belongs to this interval,
* otherwise <tt>false</tt>.
*/
public boolean includes (boolean rightBorder) {
return inclusive[rightBorder? 1: 0];
}
/**
* Checks whether a point is contained by this interval.
*
* @param point The point to be tested.
* @return Returns 0 if the point is contained by this interval,
* returns -1 if the point is located to the right of this interval, else 1.
* @throws IllegalArgumentException if the point can not be tested properly.
*/
public int contains (Object point) throws IllegalArgumentException {
try {
int result = 0;
for(int i=0; i<2; i++) {
int comparison = comparator.compare(border[i], point);
result += (comparison!=0 || inclusive[i])? Maths.signum(comparison): 1-2*i;
}
return result/2;
}
catch (Exception e) {
throw new IllegalArgumentException();
}
}
/**
* Checks whether an interval is contained by this interval.
* The implementation is as follows:
* <br><br>
* <code><pre>
* for(int i=0; i<2; i++) {
* int comparison = Math.signum(comparator.compare(border[i], interval.border[i]));
*
* if (comparison==0? !inclusive[i] && interval.inclusive[i]: comparison==1-2*i)
* return false;
* }
* return true;
* </code></pre>
* At first the left borders of the intervals are tested to be equal using this
* interval's comparator. If this is the case (<code>comparison == 0</code>)
* the inclusion of the left borders of intervals has to be checked.
* If <code>comparison == 1</code> <tt>false</tt> is returned, because the left
* border of the specified interval is less than the left border of this interval and
* so the specified interval is larger.
* If the left border properties are exactly equal, then the right border properties are
* checked in the same way.
*
* @param interval The interval to be tested.
* @return Returns <tt>true</tt> if this intervals contains the given interval,
* otherwise <tt>false</tt>.
* @throws IllegalArgumentException if the interval can not be tested properly.
*/
public boolean contains (Interval1D interval) throws IllegalArgumentException {
try {
for(int i=0; i<2; i++) {
int comparison = Maths.signum(comparator.compare(border[i], interval.border[i]));
if (comparison==0? !inclusive[i] && interval.inclusive[i]: comparison==1-2*i)
return false;
}
return true;
}
catch (Exception e) {
throw new IllegalArgumentException();
}
}
/**
* Checks whether an descriptor is contained by this interval.
* <b>Note:</b The descriptor is casted to an Interval1D and
* the method {@link #contains(Interval1D)} is called.
*
* @param descriptor The descriptor to be tested.
* @return Returns <tt>true</tt> if this intervals contains the given descriptor,
* otherwise <tt>false</tt>.
* @throws IllegalArgumentException if the descriptor can not be tested properly.
* @see #contains(Interval1D)
*/
public boolean contains (Descriptor descriptor) throws IllegalArgumentException {
return contains((Interval1D)descriptor);
}
/**
* Checks whether an interval and this interval do overlap.
* The implementation is as follows:
* <br><br>
* <code><pre>
* int result = 0;
*
* for(int i=0; i<2; i++) {
* int comparison = comparator.compare(border[i], interval.border[1-i]);
* result += (comparison!=0 || inclusive[i] && interval.inclusive[1-i])? Math.signum(comparison): 1-2*i;
* }
* return result/2;
* </code></pre>
*
* @param interval The interval to be tested.
* @return Returns 0 if the interval and this interval do overlap,
* returns -1 if the interval is located to the right of this interval, else 1.
* @throws IllegalArgumentException if the interval can not be tested properly.
*/
public int overlaps (Interval1D interval) throws IllegalArgumentException {
try {
int result = 0;
for(int i=0; i<2; i++) {
int comparison = comparator.compare(border[i], interval.border[1-i]);
result += (comparison!=0 || inclusive[i] && interval.inclusive[1-i])? Maths.signum(comparison): 1-2*i;
}
return result/2;
}
catch (Exception e) {
throw new IllegalArgumentException();
}
}
/**
* Checks whether a descriptor and this interval do overlap.
* <b>Note:</b The descriptor is casted to an Interval1D and
* the method {@link #overlaps(Interval1D)} is called.
*
* @param descriptor The descriptor to be tested.
* @return Returns 0 if the descriptor and this interval do overlap,
* returns -1 if the descriptor is located to the right of this interval, else 1.
* @throws IllegalArgumentException if the descriptor can not be tested properly.
* @see #overlaps(Interval1D)
*/
public boolean overlaps (Descriptor descriptor) throws IllegalArgumentException {
return overlaps((Interval1D)descriptor)==0;
}
/**
* Extends this interval to contain a given interval, too.
* The borders of this interval are changed in following way:
* <br><br>
* <code><pre>
* for (int i=0; i<2; i++) {
* int comparison = Math.signum(comparator.compare(border[i], interval.border[i]));
*
* if (comparison==0)
* inclusive[i] |= interval.inclusive[i];
* else if (comparison==1-2*i) {
* inclusive[i] = interval.inclusive[i];
* border[i] = interval.border[i];
* }
* }
* return this;
* </code></pre>
* If the given interval is larger than this one, <code>border[i]</code> is set to
* <code>interval.border[i]</code> and <code>inclusive[i]</code> is set to
* <code>interval.inclusive[i]</code>. If the intervals are equal concerning their
* borders, i.e. <code>comparision == 0</code>, then only the inclusion of this
* interval's borders will possibly be set.
*
* @param interval The interval which defines the extension of this interval.
* @return Returns this interval, now containg the specified interval, too.
* @throws IllegalArgumentException if the union can not be performed properly.
*/
public Interval1D union (Interval1D interval) throws IllegalArgumentException {
try {
for (int i=0; i<2; i++) {
int comparison = Maths.signum(comparator.compare(border[i], interval.border[i]));
if (comparison==0)
inclusive[i] |= interval.inclusive[i];
else if (comparison==1-2*i) {
inclusive[i] = interval.inclusive[i];
border[i] = interval.border[i];
}
}
return this;
}
catch (Exception e) {
throw new IllegalArgumentException();
}
}
/**
* Extends this interval to contain a given descriptor, too.
* <b>Note:</b The descriptor is casted to an Interval1D and
* the method {@link #union(Interval1D)} is called.
*
* @param descriptor The descriptor which defines the extension of this interval.
* @throws IllegalArgumentException if the union can not be performed properly.
* @see #union(Interval1D)
*/
public void union (Descriptor descriptor) throws IllegalArgumentException {
union((Interval1D)descriptor);
}
/**
* Shrinks this interval to reflect the intersection with a given interval.
* An intersection can only be computed if the interval overlaps with the given interval,
* therefore an <tt>IllegalArgumentException</tt> is thrown, if the intervals
* do not overlap.
* If the intervals overlap, this interval is shrinked as follows:
* <br><br>
* <code><pre>
* for (int i=0; i<2; i++) {
* int comparison = Math.signum(comparator.compare(border[i], interval.border[i]));
*
* if (comparison==0)
* inclusive[i] &= interval.inclusive[i];
* else if (comparison==i*2-1) {
* inclusive[i] = interval.inclusive[i];
* border[i] = interval.border[i];
* }
* }
* return this;
* </code></pre>
* If <code>comparsion == 0</code> the intervals have the same left (right) border and
* therefore only the inclusion of this interval's border have to be set.
* If this interval's left border (<tt>border[0]</tt>) is less than the given interval's
* left border (<tt>interval.border[0]</tt>), i.e. <code>comparsion == -1</code> the given
* interval's border properties are assumed by this interval.
* In the other case when this interval's right border (<tt>border[1]</tt>) is
* taller than the given interval's right border (<tt>interval.border[1]</tt>),
* i.e <code>comparsion == -1</code>, the given interval's borders are also
* assumed by this interval.
*
* @param interval The interval to be intersected with.
* @return This interval (shrinked).
* @throws IllegalArgumentException if the intersection can not be performed properly.
*/
public Interval1D intersect (Interval1D interval) throws IllegalArgumentException {
if (overlaps(interval)!=0)
throw new IllegalArgumentException("Intervals do not overlap");
try {
for (int i=0; i<2; i++) {
int comparison = Maths.signum(comparator.compare(border[i], interval.border[i]));
if (comparison==0)
inclusive[i] &= interval.inclusive[i];
else if (comparison==i*2-1) {
inclusive[i] = interval.inclusive[i];
border[i] = interval.border[i];
}
}
return this;
}
catch (Exception e) {
throw new IllegalArgumentException();
}
}
/**
* Shrinks this interval to reflect the intersection with a given interval.
* An intersection can only be computed if the interval overlaps with the given interval,
* therefore an <tt>IllegalArgumentException</tt> is thrown, if the intervals
* do not overlap. <br>
* <b>Note:</b> The descriptor is casted to an Interval1D and
* the method {@link #intersect(Interval1D)} is called.
*
* @param descriptor The descriptor to be intersected with.
* @return This descriptor (shrinked).
* @throws IllegalArgumentException if the intersection cannot be performed properly.
* @see #intersect(Interval1D)
*/
public Descriptor intersect (Descriptor descriptor) throws IllegalArgumentException {
return intersect((Interval1D)descriptor);
}
}