/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
*
* 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;
* version 2.1 of the License.
*
* 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.
*/
package org.geotools.util;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.NoSuchElementException;
import java.util.SortedSet;
import org.opengis.util.Cloneable;
import org.geotools.resources.ClassChanger;
import org.geotools.resources.Classes;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
import static org.geotools.resources.Classes.*;
/**
* An ordered set of ranges. {@code RangeSet} objects store an arbitrary number of
* {@linkplain Range ranges} in any Java's primitives ({@code int}, {@code float},
* etc.) or any {@linkplain Comparable comparable} objects. Ranges may be added in any order.
* When a range is added, {@code RangeSet} first looks for an existing range overlapping the
* specified range. If an overlapping range is found, ranges are merged as of {@link Range#union}.
* Consequently, ranges returned by {@link #iterator} may not be the same than added ranges.
* <p>
* All entries in this set can be seen as {@link Range} objects.
* This class is <strong>not</strong> thread-safe.
*
* @param <T> The type of range elements.
*
* @since 2.0
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
* @author Andrea Aime
*/
public class RangeSet<T extends Comparable<? super T>> extends AbstractSet<Range<T>>
implements SortedSet<Range<T>>, Cloneable, Serializable
{
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = 2439002271813328080L;
/**
* The comparator for ranges. Defined only in order to comply to {@link #comparator}
* contract, but not used for internal working in this class.
*/
private static final Comparator<Range> COMPARATOR = new Comparator<Range>() {
@SuppressWarnings("unchecked")
public int compare(final Range r1, final Range r2) {
int cmin = r1.getMinValue().compareTo(r2.getMinValue());
int cmax = r1.getMaxValue().compareTo(r2.getMaxValue());
if (cmin == 0) cmin = (r1.isMinIncluded() ? -1 : 0) - (r2.isMinIncluded() ? -1 : 0);
if (cmax == 0) cmax = (r1.isMaxIncluded() ? +1 : 0) - (r2.isMaxIncluded() ? +1 : 0);
if (cmin == cmax) return cmax; // Easy case: min and max are both greater, smaller or eq.
if (cmin == 0) return cmax; // Easy case: only max value differ.
if (cmax == 0) return cmin; // Easy case: only min value differ.
// One range is included in the other.
throw new IllegalArgumentException("Unordered ranges");
}
};
/**
* The {@linkplain #getElementClass element class} of ranges.
*/
private final Class<T> elementClass;
/**
* Identical to {@code elementClass} except if the later is a {@link Number} subclass.
* In the later case, this field is set to <code>{@link Number}.class</code>.
*/
private final Class<?> relaxedClass;
/**
* Identical to {@code elementClass} except if the later is the wrapper of some
* primitive type. In the later case this field is set to that primitive type.
* This is the type to be used in arrays.
*/
private final Class<?> arrayElementClass;
/**
* The primitive type, as one of {@code DOUBLE}, {@code FLOAT}, {@code LONG},
* {@code INTEGER}, {@code SHORT}, {@code BYTE}, {@code CHARACTER} or
* {@code OTHER} enumeration.
*/
private final byte arrayElementCode;
/**
* Tableau d'intervalles. Il peut s'agir d'un tableau d'un des types primitifs
* du Java (par exemple {@code int[]} ou {@code float[]}), ou d'un
* tableau de type {@code Comparable[]}. Les éléments de ce tableau doivent
* obligatoirement être en ordre strictement croissant et sans doublon.
* <p>
* La longueur de ce tableau est le double du nombre d'intervalles. Il aurait
* été plus efficace d'utiliser une variable séparée (pour ne pas être obligé
* d'agrandir ce tableau à chaque ajout d'un intervalle), mais malheureusement
* le J2SE 1.4 ne nous fournit pas de méthode {@code Arrays.binarySearch}
* qui nous permettent de spécifier les limites du tableau (voir RFE #4306897
* à http://developer.java.sun.com/developer/bugParade/bugs/4306897.html).
*
* @todo Revisit when we will be allowed to compile for Java 6.
*/
private Object array;
/**
* Compte le nombre de modifications apportées au tableau des intervalles.
* Ce comptage sert à vérifier si une modification survient pendant qu'un
* itérateur balayait les intervalles.
*/
private int modCount;
/**
* {@code true} if and only if the element class represents a primitive type.
* This is equivalents to {@code primitiveType.isPrimitive()} and is computed
* once for ever for performance reason.
*/
private final boolean isPrimitive;
/**
* {@code true} if we should invoke {@link ClassChanger#toNumber}
* before to store a value into the array. It will be the case if the
* array {@code array} contains primitive elements and the type
* {@code type} is not the corresponding wrapper.
*/
private final boolean useClassChanger;
/**
* {@code true} if instances of {@link NumberRange} should be created instead
* of {@link Range}.
*/
private final boolean isNumeric;
/**
* Constructs an empty set of range.
*
* @param type The class of the range elements. It must be a primitive
* type or a class implementing {@link Comparable}.
* @throws IllegalArgumentException if {@code type} is not a
* primitive type or a class implementing {@link Comparable}.
*/
public RangeSet(final Class<T> type) throws IllegalArgumentException {
if (!Comparable.class.isAssignableFrom(type)) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.NOT_COMPARABLE_CLASS_$1, type));
}
Class<?> elementType = ClassChanger.getTransformedClass(type); // e.g. change Date --> Long
useClassChanger = (elementType != type);
elementClass = type;
arrayElementClass = wrapperToPrimitive(elementType);
arrayElementCode = getEnumConstant(arrayElementClass);
isPrimitive = arrayElementClass.isPrimitive();
isNumeric = Number.class.isAssignableFrom(type);
relaxedClass = isNumeric ? Number.class : type;
}
/**
* Converts a value from an arbitrary type to the wrapper of {@link #arrayElementClass}.
*/
private Comparable<?> toArrayElement(Comparable<?> value) {
if (!relaxedClass.isInstance(value)) {
throw new IllegalArgumentException(value == null ?
Errors.format(ErrorKeys.NULL_ARGUMENT_$1, "value") :
Errors.format(ErrorKeys.ILLEGAL_CLASS_$2, value.getClass(), elementClass));
}
if (useClassChanger) try {
value = (Comparable) ClassChanger.toNumber(value);
} catch (ClassNotFoundException cause) {
/*
* Should not happen since the constructor should have make sure
* that this operation is legal for value of class 'type'.
*/
final ClassCastException exception = new ClassCastException(Errors.format(
ErrorKeys.ILLEGAL_CLASS_$2, value.getClass(), elementClass));
exception.initCause(cause);
throw exception;
}
return value;
}
/**
* Returns the comparator associated with this sorted set.
*/
@SuppressWarnings("unchecked") // Because we share the same static COMPARATOR instance.
public Comparator<Range<T>> comparator() {
return (Comparator) COMPARATOR;
}
/**
* Remove all elements from this set of ranges.
*/
@Override
public void clear() {
array = null;
modCount++;
}
/**
* Returns the number of ranges in this set.
*/
public int size() {
return (array != null) ? Array.getLength(array)/2 : 0;
}
/**
* Add a range to this set. Range may be added in any order. If the specified range
* overlap an existing range, the two range will be merged as of {@link Range#union}.
* <p>
* Note: current version do not support open interval (i.e. {@code Range.is[Min/Max]Included()}
* must return {@code true}).
*
* @param range The range to add.
* @return {@code true} if this set changed as a result of the call.
*
* @todo support open intervals.
*/
@Override
public boolean add(final Range<T> range) {
if (!range.isMinIncluded() || !range.isMaxIncluded()) {
throw new UnsupportedOperationException("Open interval not yet supported");
}
return add((Comparable) range.getMinValue(), (Comparable) range.getMaxValue());
}
/**
* Adds a range of values to this set. Range may be added in any order.
* If the specified range overlap an existing range, the two ranges will be merged.
*
* @param min The lower value, inclusive.
* @param max The upper value, inclusive.
* @return {@code true} if this set changed as a result of the call.
* @throws IllegalArgumentException if {@code lower} is greater than {@code upper}.
*/
public <N> boolean add(final Comparable<? super N> min, final Comparable<? super N> max)
throws IllegalArgumentException
{
Comparable lower = toArrayElement(min);
Comparable upper = toArrayElement(max);
if (lower.compareTo(upper) > 0) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.BAD_RANGE_$2, min, max));
}
if (array == null) {
modCount++;
array = Array.newInstance(arrayElementClass, 2);
Array.set(array, 0, lower);
Array.set(array, 1, upper);
return true;
}
final int modCountChk = modCount;
int i0 = binarySearch(lower);
int i1;
if (i0 < 0) {
/*
* Si le début de la plage ne correspond pas à une des dates en
* mémoire, il faudra l'insérer à quelque part dans le tableau.
* Si la date tombe dans une des plages déjà existantes (si son
* index est impair), on étend la date de début pour prendre le
* début de la plage. Visuellement, on fait:
*
* 0 1 2 3 4 5 6 7
* ##### ######## ##### #######
* <---^ ^
* lower(i=3) upper(i=5)
*/
if (((i0 = ~i0) & 1) != 0) { // Attention: c'est ~ et non -
lower = (Comparable) Array.get(array, --i0);
i1 = binarySearch(upper);
} else {
/*
* Si la date de début ne tombe pas dans une plage déjà
* existante, il faut étendre la valeur de début qui se
* trouve dans le tableau. Visuellement, on fait:
*
* 0 1 2 3 4 5 6 7
* ##### ***######## ##### #######
* ^ ^
* lower(i=2) upper(i=5)
*/
if (i0 != Array.getLength(array) && (i1 = binarySearch(upper)) != ~i0) {
modCount++;
Array.set(array, i0, lower);
} else {
/*
* Un cas particulier se produit si la nouvelle plage
* est à insérer à la fin du tableau. Dans ce cas, on
* n'a qu'à agrandir le tableau et écrire les valeurs
* directement à la fin. Ce traitement est nécessaire
* pour eviter les 'ArrayIndexOutOfBoundsException'.
* Un autre cas particulier se produit si la nouvelle
* plage est entièrement comprise entre deux plages
* déjà existantes. Le même code ci-dessous insèrera
* la nouvelle plage à l'index 'i0'.
*/
modCount++;
final Object old = array;
final int length = Array.getLength(array);
array = Array.newInstance(arrayElementClass, length+2);
System.arraycopy(old, 0, array, 0, i0);
System.arraycopy(old, i0, array, i0+2, length-i0);
Array.set(array, i0+0, lower);
Array.set(array, i0+1, upper);
return true;
}
}
} else {
i0 &= ~1;
i1 = binarySearch(upper);
}
/*
* A ce stade, on est certain que 'i0' est pair et pointe vers le début
* de la plage dans le tableau. Fait maintenant le traitement pour 'i1'.
*/
if (i1 < 0) {
/*
* Si la date de fin tombe dans une des plages déjà existantes
* (si son index est impair), on l'étend pour pendre la fin de
* la plage trouvée dans le tableau. Visuellement, on fait:
*
* 0 1 2 3 4 5 6 7
* ##### ######## ##### #######
* ^ ^-->
* lower(i=2) upper(i=5)
*/
if (((i1 = ~i1) & 1) != 0) { // Attention: c'est ~ et non -
upper = (Comparable) Array.get(array, i1);
} else {
/*
* Si la date de fin ne tombe pas dans une plage déjà
* existante, il faut étendre la valeur de fin qui se
* trouve dans le tableau. Visuellement, on fait:
*
* 0 1 2 3 4 5 6 7
* ##### ######## #####** #######
* ^ ^
* lower(i=2) upper(i=6)
*/
modCount++;
Array.set(array, --i1, upper);
}
} else {
i1 |= 1;
}
/*
* A ce stade, on est certain que 'i1' est impair et pointe vers la fin
* de la plage dans le tableau. On va maintenant supprimer tout ce qui
* se trouve entre 'i0' et 'i1', à l'exclusion de 'i0' et 'i1'.
*/
assert (i0 & 1)==0 : i0;
assert (i1 & 1)!=0 : i1;
final int n = i1 - (++i0);
if (n > 0) {
modCount++;
final Object old = array;
final int length = Array.getLength(array);
array = Array.newInstance(arrayElementClass, length-n);
System.arraycopy(old, 0, array, 0, i0);
System.arraycopy(old, i1, array, i0, length-i1);
}
assert (Array.getLength(array) & 1) == 0;
return modCountChk != modCount;
}
/**
* Add a range of values to this set. Range may be added in any order.
* If the specified range overlap an existing range, the two ranges
* will be merged.
*
* @param lower The lower value, inclusive.
* @param upper The upper value, inclusive.
* @return {@code true} if this set changed as a result of the call.
* @throws IllegalArgumentException if {@code lower} is greater than {@code upper}.
*/
public boolean add(byte lower, byte upper) throws IllegalArgumentException {
return add(Byte.valueOf(lower), Byte.valueOf(upper));
}
/**
* Add a range of values to this set. Range may be added in any order.
* If the specified range overlap an existing range, the two ranges
* will be merged.
*
* @param lower The lower value, inclusive.
* @param upper The upper value, inclusive.
* @return {@code true} if this set changed as a result of the call.
* @throws IllegalArgumentException if {@code lower} is greater than {@code upper}.
*/
public boolean add(short lower, short upper) throws IllegalArgumentException {
return add(Short.valueOf(lower), Short.valueOf(upper));
}
/**
* Add a range of values to this set. Range may be added in any order.
* If the specified range overlap an existing range, the two ranges
* will be merged.
*
* @param lower The lower value, inclusive.
* @param upper The upper value, inclusive.
* @return {@code true} if this set changed as a result of the call.
* @throws IllegalArgumentException if {@code lower} is greater than {@code upper}.
*/
public boolean add(int lower, int upper) throws IllegalArgumentException {
return add(Integer.valueOf(lower), Integer.valueOf(upper));
}
/**
* Add a range of values to this set. Range may be added in any order.
* If the specified range overlap an existing range, the two ranges
* will be merged.
*
* @param lower The lower value, inclusive.
* @param upper The upper value, inclusive.
* @return {@code true} if this set changed as a result of the call.
* @throws IllegalArgumentException if {@code lower} is greater than {@code upper}.
*/
public boolean add(long lower, long upper) throws IllegalArgumentException {
return add(Long.valueOf(lower), Long.valueOf(upper));
}
/**
* Add a range of values to this set. Range may be added in any order.
* If the specified range overlap an existing range, the two ranges
* will be merged.
*
* @param lower The lower value, inclusive.
* @param upper The upper value, inclusive.
* @return {@code true} if this set changed as a result of the call.
* @throws IllegalArgumentException if {@code lower} is greater than {@code upper}.
*/
public boolean add(float lower, float upper) throws IllegalArgumentException {
return add(Float.valueOf(lower), Float.valueOf(upper));
}
/**
* Add a range of values to this set. Range may be added in any order.
* If the specified range overlap an existing range, the two ranges
* will be merged.
*
* @param lower The lower value, inclusive.
* @param upper The upper value, inclusive.
* @return {@code true} if this set changed as a result of the call.
* @throws IllegalArgumentException if {@code lower} is greater than {@code upper}.
*/
public boolean add(double lower, double upper) throws IllegalArgumentException {
return add(Double.valueOf(lower), Double.valueOf(upper));
}
/**
* Remove a range of values from this set. Range may be removed in any order.
*
* @param min The lower value to remove, exclusive.
* @param max The upper value to remove, exclusive.
* @return {@code true} if this set changed as a result of the call.
* @throws IllegalArgumentException if {@code lower} is greater than {@code upper}.
*/
public <N> boolean remove(final Comparable<? super N> min, final Comparable<? super N> max)
throws IllegalArgumentException
{
Comparable lower = toArrayElement(min);
Comparable upper = toArrayElement(max);
if (lower.compareTo(upper) >= 0) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.BAD_RANGE_$2, min, max));
}
// if already empty, or range outside the current set, nothing to change
if (array == null) {
return false;
}
final int modCountChk = modCount;
int i0 = binarySearch(lower);
int i1 = binarySearch(upper);
if (i0 < 0) {
if (((i0 = ~i0) & 1) != 0) { // Attention: c'est ~ et non -
/*
* Si le début de la plage ne correspond pas à une des dates en mémoire,
* il faudra faire un trou à quelque part dans le tableau. Si la date tombe
* dans une des plages déjà existantes (si son index est impair), on change
* la date de fin de la plage existante. Visuellement, on fait:
*
* 0 1 2 3 4 5 6 7
* ##### #####--- --### #######
* ^ ^
* lower(i=3) upper(i=5)
*/
modCount++;
if (i1 != ~i0) {
Array.set(array, i0, lower);
} else {
/*
* Special case if the upper index is inside the same range than the lower one:
*
* 0 1 2 3 4 5
* ##### ####---------##### #####
* ^ ^
* lower(i=3) upper(i=3)
*/
final Object old = array;
final int length = Array.getLength(array);
array = Array.newInstance(arrayElementClass, length + 2);
System.arraycopy(old, 0, array, 0, i0);
System.arraycopy(old, i0, array, i0 + 2, length - i0);
Array.set(array, i0 + 0, lower);
Array.set(array, i0 + 1, upper);
return true;
}
} else {
/*
* Si la date de début ne tombe pas dans une plage déjà
* existante, il faut prendre la date de fin de la plage
* précédente. Visuellement, on fait:
*
* 0 1 2 3 4 5 6 7
* ##### ######## ##### #######
* <---^ ^
* lower(i=2) upper(i=5)
*/
i0--;
}
} else {
if ((i0 & 1) == 0) {
i0--;
}
}
/*
* A ce stade, on est certain que 'i0' est impair et pointe vers la fin
* d'une plage dans le tableau. Fait maintenant le traitement pour 'i1'.
*/
if (i1 < 0) {
/*
* Si la date de fin tombe dans une des plages déjà existantes
* (si son index est impair), on change la date de début de la
* plage existante. Visuellement, on fait:
*
* 0 1 2 3 4 5 6 7
* ##### ######## --### #######
* ^ ^
* lower(i=3) upper(i=5)
*/
if (((i1 = ~i1) & 1) != 0) { // Attention: c'est ~ et non -
modCount++;
Array.set(array, --i1, upper);
} else {
/*
* Si la date de fin ne tombe pas dans une plage déjà existante, il
* faudra (plus tard) supprimer les éventuelles plages qui le précède.
*
* 0 1 2 3 4 5 6 7
* ##### ######## ####### ###########
* ^ ^
* lower(i=3) upper(i=6)
*/
// nothing to do
}
} else {
i1 &= ~1;
}
/*
* A ce stade, on est certain que 'i1' est pair et pointe vers la début
* de la plage dans le tableau. On va maintenant supprimer tout ce qui
* se trouve entre 'i0' et 'i1', à l'exclusion de 'i0' et 'i1'.
*/
assert (i0 & 1) != 0 : i0;
assert (i1 & 1) == 0 : i1;
final int n = i1 - (++i0);
if (n > 0) {
modCount++;
final Object old = array;
final int length = Array.getLength(array);
array = Array.newInstance(arrayElementClass, length - n);
System.arraycopy(old, 0, array, 0, i0);
System.arraycopy(old, i1, array, i0, length - i1);
}
assert (Array.getLength(array) & 1) == 0;
return modCountChk != modCount;
}
/**
* Remove a range of values from this set. Range may be removed in any order.
*
* @param lower The lower value to remove, exclusive.
* @param upper The upper value to remove, exclusive.
* @return {@code true} if this set changed as a result of the call.
* @throws IllegalArgumentException if {@code lower} is greater than {@code upper}.
*/
public boolean remove(byte lower, byte upper) throws IllegalArgumentException {
return remove(Byte.valueOf(lower), Byte.valueOf(upper));
}
/**
* Remove a range of values from this set. Range may be removed in any order.
*
* @param lower The lower value to remove, exclusive.
* @param upper The upper value to remove, exclusive.
* @return {@code true} if this set changed as a result of the call.
* @throws IllegalArgumentException if {@code lower} is greater than {@code upper}.
*/
public boolean remove(short lower, short upper) throws IllegalArgumentException {
return remove(Short.valueOf(lower), Short.valueOf(upper));
}
/**
* Remove a range of values from this set. Range may be removed in any order.
*
* @param lower The lower value to remove, exclusive.
* @param upper The upper value to remove, exclusive.
* @return {@code true} if this set changed as a result of the call.
* @throws IllegalArgumentException if {@code lower} is greater than {@code upper}.
*/
public boolean remove(int lower, int upper) throws IllegalArgumentException {
return remove(Integer.valueOf(lower), Integer.valueOf(upper));
}
/**
* Remove a range of values from this set. Range may be removed in any order.
*
* @param lower The lower value to remove, exclusive.
* @param upper The upper value to remove, exclusive.
* @return {@code true} if this set changed as a result of the call.
* @throws IllegalArgumentException if {@code lower} is greater than {@code upper}.
*/
public boolean remove(long lower, long upper) throws IllegalArgumentException {
return remove(Long.valueOf(lower), Long.valueOf(upper));
}
/**
* Remove a range of values from this set. Range may be removed in any order.
*
* @param lower The lower value to remove, exclusive.
* @param upper The upper value to remove, exclusive.
* @return {@code true} if this set changed as a result of the call.
* @throws IllegalArgumentException if {@code lower} is greater than {@code upper}.
*/
public boolean remove(float lower, float upper) throws IllegalArgumentException {
return remove(Float.valueOf(lower), Float.valueOf(upper));
}
/**
* Remove a range of values from this set. Range may be removed in any order.
*
* @param lower The lower value to remove, exclusive.
* @param upper The upper value to remove, exclusive.
* @return {@code true} if this set changed as a result of the call.
* @throws IllegalArgumentException if {@code lower} is greater than {@code upper}.
*/
public boolean remove(double lower, double upper) throws IllegalArgumentException {
return remove(Double.valueOf(lower), Double.valueOf(upper));
}
/**
* Retourne l'index de l'élément {@code value} dans le tableau {@code array}.
* Cette méthode interprète le tableau {@code array} comme un tableau d'un des types
* intrinsèques du Java, et appelle la méthode {@code Arrays.binarySearch} appropriée.
*
* @param value The value to search. This value must have been converted with
* {@link #toNumber} prior to call this method.
*/
private int binarySearch(final Comparable value) {
switch (arrayElementCode) {
case DOUBLE: return Arrays.binarySearch((double[]) array, ((Number) value).doubleValue());
case FLOAT: return Arrays.binarySearch((float []) array, ((Number) value).floatValue ());
case LONG: return Arrays.binarySearch((long []) array, ((Number) value).longValue ());
case INTEGER: return Arrays.binarySearch((int []) array, ((Number) value).intValue ());
case SHORT: return Arrays.binarySearch((short []) array, ((Number) value).shortValue ());
case BYTE: return Arrays.binarySearch((byte []) array, ((Number) value).byteValue ());
case CHARACTER:return Arrays.binarySearch((char []) array, ((Character) value).charValue ());
default: return Arrays.binarySearch((Object[]) array, value);
}
}
/**
* Returns a new {@link Range} object initialized with the given values.
*
* @param lower The lower value, inclusive.
* @param upper The upper value, inclusive.
*/
private Range<T> newRange(final T lower, final T upper) {
if (isNumeric) {
return new NumberRange(elementClass, lower, upper);
} else {
return new Range<T>(elementClass, lower, upper);
}
}
/**
* Returns the value at the specified index.
* Even index are lower bounds, while odd index are upper bounds.
*/
private T get(final int index) {
Comparable value = (Comparable) Array.get(array, index);
if (useClassChanger) try {
value = ClassChanger.toComparable((Number) value, elementClass);
} catch (ClassNotFoundException exception) {
// Should not happen, since class type should
// have been checked by all 'add(...)' methods
throw new IllegalStateException(exception);
}
return elementClass.cast(value);
}
/**
* Returns a {@linkplain Range#getMinValue range's minimum value} as a {@code double}.
* The {@code index} can be any value from 0 inclusive to the set's {@link #size size}
* exclusive. The returned values always increase with {@code index}.
*
* @param index The range index, from 0 inclusive to {@link #size size} exclusive.
* @return The minimum value for the range at the specified index.
* @throws IndexOutOfBoundsException if {@code index} is out of bounds.
* @throws ClassCastException if range elements are not convertible to numbers.
*/
public final double getMinValueAsDouble(int index)
throws IndexOutOfBoundsException, ClassCastException
{
index *= 2;
return (isPrimitive) ? Array.getDouble(array, index) :
((Number) Array.get(array, index)).doubleValue();
}
/**
* Returns a {@linkplain Range#getMaxValue range's maximum value} as a {@code double}.
* The {@code index} can be any value from 0 inclusive to the set's {@link #size size}
* exclusive. The returned values always increase with {@code index}.
*
* @param index The range index, from 0 inclusive to {@link #size size} exclusive.
* @return The maximum value for the range at the specified index.
* @throws IndexOutOfBoundsException if {@code index} is out of bounds.
* @throws ClassCastException if range elements are not convertible to numbers.
*/
public final double getMaxValueAsDouble(int index)
throws IndexOutOfBoundsException, ClassCastException
{
index = 2*index + 1;
return (isPrimitive) ? Array.getDouble(array, index) :
((Number) Array.get(array, index)).doubleValue();
}
/**
* If the specified value is inside a range, returns the index of this range.
* Otherwise, returns {@code -1}.
*
* @param value The value to search.
* @return The index of the range which contains this value, or -1 if there is no such range.
*/
public int indexOfRange(final Comparable value) {
int index = binarySearch(toArrayElement(value));
if (index < 0) {
// Found an insertion point. Make sure that the insertion
// point is inside a range (i.e. before the maximum value).
index = ~index; // Tild sign, not minus.
if ((index & 1) == 0) {
return -1;
}
}
index /= 2; // Round toward 0 (odd index are maximum values).
assert newRange(get(2*index), get(2*index+1)).contains(value) : value;
return index;
}
/**
* Returns {@code true} if this set contains the specified element.
*
* @param object The object to compare to this set.
* @return {@code true} if the given object is equals to this set.
*/
@Override
public boolean contains(final Object object) {
@SuppressWarnings("unchecked") // We are going to check just the line after.
final Range<T> range = (Range<T>) object;
if (elementClass.equals(range.elementClass)) {
if (range.isMinIncluded() && range.isMaxIncluded()) {
final int index = binarySearch(toArrayElement(range.getMinValue()));
if (index >= 0 && (index & 1)==0) {
@SuppressWarnings("unchecked")
final int c = get(index+1).compareTo(range.getMaxValue());
return c == 0;
}
}
}
return false;
}
/**
* Returns the first (lowest) range currently in this sorted set.
*
* @throws NoSuchElementException if the set is empty.
*/
public Range<T> first() throws NoSuchElementException {
if (array!=null && Array.getLength(array)!=0) {
return newRange(get(0), get(1));
}
throw new NoSuchElementException();
}
/**
* Returns the last (highest) range currently in this sorted set.
*
* @throws NoSuchElementException if the set is empty.
*/
public Range<T> last() throws NoSuchElementException {
if (array != null) {
final int length = Array.getLength(array);
if (length != 0) {
return newRange(get(length-2), get(length-1));
}
}
throw new NoSuchElementException();
}
/**
* Returns a view of the portion of this sorted set whose elements range
* from {@code lower}, inclusive, to {@code upper}, exclusive.
*
* @param lower Low endpoint (inclusive) of the sub set.
* @param upper High endpoint (exclusive) of the sub set.
* @return A view of the specified range within this sorted set.
*/
public SortedSet<Range<T>> subSet(final Range<T> lower, final Range<T> upper) {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Returns a view of the portion of this sorted set whose elements are
* strictly less than {@code upper}.
*
* @param upper High endpoint (exclusive) of the headSet.
* @return A view of the specified initial range of this sorted set.
*/
public SortedSet<Range<T>> headSet(final Range<T> upper) {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Returns a view of the portion of this sorted set whose elements are
* greater than or equal to {@code lower}.
*
* @param lower Low endpoint (inclusive) of the tailSet.
* @return A view of the specified final range of this sorted set.
*/
public SortedSet<Range<T>> tailSet(final Range<T> lower) {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Returns an iterator over the elements in this set of ranges.
* All elements are {@link Range} objects.
*/
@Override
public java.util.Iterator<Range<T>> iterator() {
return new Iterator();
}
/**
* An iterator for iterating through ranges in a {@link RangeSet}.
* All elements are {@link Range} objects.
*
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
private final class Iterator implements java.util.Iterator<Range<T>> {
/**
* Modification count at construction time.
*/
private int modCount = RangeSet.this.modCount;
/**
* The array length.
*/
private int length = (array != null) ? Array.getLength(array) : 0;
/**
* Current position in {@link RangeSet#array}.
*/
private int position;
/**
* Returns {@code true} if the iteration has more elements.
*/
public boolean hasNext() {
return position < length;
}
/**
* Returns the next element in the iteration.
*/
public Range<T> next() {
if (hasNext()) {
final T lower = get(position++);
final T upper = get(position++);
if (RangeSet.this.modCount != modCount) {
// Check it last, in case a change occured
// while we was constructing the element.
throw new ConcurrentModificationException();
}
return newRange(lower, upper);
}
throw new NoSuchElementException();
}
/**
* Removes from the underlying collection the
* last element returned by the iterator.
*/
public void remove() {
if (position != 0) {
if (RangeSet.this.modCount == modCount) {
final Object newArray = Array.newInstance(arrayElementClass, length-=2);
System.arraycopy(array, position, newArray, position-=2, length-position);
System.arraycopy(array, 0, newArray, 0, position);
array = newArray;
modCount = ++RangeSet.this.modCount;
} else {
throw new ConcurrentModificationException();
}
} else {
throw new IllegalStateException();
}
}
}
/**
* Returns a hash value for this set of ranges.
* This value need not remain consistent between
* different implementations of the same class.
*/
@Override
public int hashCode() {
int code = elementClass.hashCode();
if (array != null) {
for (int i=Array.getLength(array); (i -= 8) >= 0;) {
code = code*37 + Array.get(array, i).hashCode();
}
}
return code;
}
/**
* Compares the specified object with this set of ranges for equality.
*
* @param object The object to compare with this range.
* @return {@code true} if the given object is equals to this range.
*/
@Override
public boolean equals(final Object object) {
if (object!=null && object.getClass().equals(getClass())) {
final RangeSet that = (RangeSet) object;
if (Utilities.equals(this.elementClass, that.elementClass)) {
switch (arrayElementCode) {
case DOUBLE: return Arrays.equals((double[])this.array, (double[])that.array);
case FLOAT: return Arrays.equals((float [])this.array, ( float[])that.array);
case LONG: return Arrays.equals((long [])this.array, ( long[])that.array);
case INTEGER: return Arrays.equals((int [])this.array, ( int[])that.array);
case SHORT: return Arrays.equals((short [])this.array, ( short[])that.array);
case BYTE: return Arrays.equals((byte [])this.array, ( byte[])that.array);
case CHARACTER:return Arrays.equals((char [])this.array, ( char[])that.array);
default: return Arrays.equals((Object[])this.array, (Object[])that.array);
}
}
}
return false;
}
/**
* Returns a clone of this range set.
*
* @return A clone of this range set.
*/
@Override
public RangeSet clone() {
final RangeSet set;
try {
set = (RangeSet) super.clone();
} catch (CloneNotSupportedException exception) {
// Should not happen, since we are cloneable.
throw new AssertionError(exception);
}
switch (set.arrayElementCode) {
case DOUBLE: set.array = ((double[])set.array).clone(); break;
case FLOAT: set.array = ((float [])set.array).clone(); break;
case LONG: set.array = ((long [])set.array).clone(); break;
case INTEGER: set.array = ((int [])set.array).clone(); break;
case SHORT: set.array = ((short [])set.array).clone(); break;
case BYTE: set.array = ((byte [])set.array).clone(); break;
case CHARACTER:set.array = ((char [])set.array).clone(); break;
default: set.array = ((Object[])set.array).clone(); break;
}
return set;
}
/**
* Returns a string representation of this set of ranges.
* The returned string is implementation dependent.
* It is usually provided for debugging purposes.
*/
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder(Classes.getShortClassName(this));
buffer.append('[');
boolean first = true;
for (final Range range : this) {
if (!first) {
buffer.append(',');
}
buffer.append('{') .append(range.getMinValue())
.append("..").append(range.getMaxValue()).append('}');
first = false;
}
return buffer.append(']').toString();
}
}