/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2003-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 static org.geotools.resources.ClassChanger.getFinestClass; import static org.geotools.resources.ClassChanger.getWidestClass; import org.geotools.resources.ClassChanger; import org.geotools.resources.XMath; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; /** * A range of numbers. {@linkplain #union Union} and {@linkplain #intersect intersection} * are computed as usual, except that widening conversions will be applied as needed. * * @since 2.0 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) */ public class NumberRange<T extends Number & Comparable<? super T>> extends Range<T> { // // IMPLEMENTATION NOTE: This class is full of @SuppressWarnings("unchecked") annotations. // Nevertheless we should never get ClassCastException - if we get some this is a bug in // this implementation. Users may get IllegalArgumentException however. // /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -818167965963008231L; /** * Constructs an inclusive range of {@code byte} values. * * @param minimum The minimum value, inclusive. * @param maximum The maximum value, <strong>inclusive</strong>. * * @since 2.5 */ public static NumberRange<Byte> create(final byte minimum, final byte maximum) { return create(minimum, true, maximum, true); } /** * Constructs a range of {@code byte} values. * * @param minimum The minimum value. * @param isMinIncluded Defines whether the minimum value is included in the range. * @param maximum The maximum value. * @param isMaxIncluded Defines whether the maximum value is included in the range. * * @since 2.5 */ public static NumberRange<Byte> create(final byte minimum, final boolean isMinIncluded, final byte maximum, final boolean isMaxIncluded) { return new NumberRange<Byte>(Byte.class, Byte.valueOf(minimum), isMinIncluded, Byte.valueOf(maximum), isMaxIncluded); } /** * Constructs an inclusive range of {@code short} values. * * @param minimum The minimum value, inclusive. * @param maximum The maximum value, <strong>inclusive</strong>. * * @since 2.5 */ public static NumberRange<Short> create(final short minimum, final short maximum) { return create(minimum, true, maximum, true); } /** * Constructs a range of {@code short} values. * * @param minimum The minimum value. * @param isMinIncluded Defines whether the minimum value is included in the range. * @param maximum The maximum value. * @param isMaxIncluded Defines whether the maximum value is included in the range. * * @since 2.5 */ public static NumberRange<Short> create(final short minimum, final boolean isMinIncluded, final short maximum, final boolean isMaxIncluded) { return new NumberRange<Short>(Short.class, Short.valueOf(minimum), isMinIncluded, Short.valueOf(maximum), isMaxIncluded); } /** * Constructs an inclusive range of {@code int} values. * * @param minimum The minimum value, inclusive. * @param maximum The maximum value, <strong>inclusive</strong>. * * @since 2.5 */ public static NumberRange<Integer> create(final int minimum, final int maximum) { return create(minimum, true, maximum, true); } /** * Constructs a range of {@code int} values. * * @param minimum The minimum value. * @param isMinIncluded Defines whether the minimum value is included in the range. * @param maximum The maximum value. * @param isMaxIncluded Defines whether the maximum value is included in the range. * * @since 2.5 */ public static NumberRange<Integer> create(final int minimum, final boolean isMinIncluded, final int maximum, final boolean isMaxIncluded) { return new NumberRange<Integer>(Integer.class, Integer.valueOf(minimum), isMinIncluded, Integer.valueOf(maximum), isMaxIncluded); } /** * Constructs an inclusive range of {@code long} values. * * @param minimum The minimum value, inclusive. * @param maximum The maximum value, <strong>inclusive</strong>. * * @since 2.5 */ public static NumberRange<Long> create(final long minimum, final long maximum) { return create(minimum, true, maximum, true); } /** * Constructs a range of {@code long} values. * * @param minimum The minimum value. * @param isMinIncluded Defines whether the minimum value is included in the range. * @param maximum The maximum value. * @param isMaxIncluded Defines whether the maximum value is included in the range. * * @since 2.5 */ public static NumberRange<Long> create(final long minimum, final boolean isMinIncluded, final long maximum, final boolean isMaxIncluded) { return new NumberRange<Long>(Long.class, Long.valueOf(minimum), isMinIncluded, Long.valueOf(maximum), isMaxIncluded); } /** * Constructs an inclusive range of {@code float} values. * * @param minimum The minimum value, inclusive. * @param maximum The maximum value, <strong>inclusive</strong>. * * @since 2.5 */ public static NumberRange<Float> create(final float minimum, final float maximum) { return create(minimum, true, maximum, true); } /** * Constructs a range of {@code float} values. * * @param minimum The minimum value. * @param isMinIncluded Defines whether the minimum value is included in the range. * @param maximum The maximum value. * @param isMaxIncluded Defines whether the maximum value is included in the range. * * @since 2.5 */ public static NumberRange<Float> create(final float minimum, final boolean isMinIncluded, final float maximum, final boolean isMaxIncluded) { return new NumberRange<Float>(Float.class, Float.valueOf(minimum), isMinIncluded, Float.valueOf(maximum), isMaxIncluded); } /** * Constructs an inclusive range of {@code double} values. * * @param minimum The minimum value, inclusive. * @param maximum The maximum value, <strong>inclusive</strong>. * * @since 2.5 */ public static NumberRange<Double> create(final double minimum, final double maximum) { return create(minimum, true, maximum, true); } /** * Constructs a range of {@code double} values. * * @param minimum The minimum value. * @param isMinIncluded Defines whether the minimum value is included in the range. * @param maximum The maximum value. * @param isMaxIncluded Defines whether the maximum value is included in the range. * * @since 2.5 */ public static NumberRange<Double> create(final double minimum, final boolean isMinIncluded, final double maximum, final boolean isMaxIncluded) { return new NumberRange<Double>(Double.class, Double.valueOf(minimum), isMinIncluded, Double.valueOf(maximum), isMaxIncluded); } /** * Constructs an inclusive range of {@link Comparable} objects. * This constructor is used by {@link RangeSet#newRange} only. * * @param type The element class, usually one of {@link Byte}, {@link Short}, * {@link Integer}, {@link Long}, {@link Float} or {@link Double}. * @param minimum The minimum value, inclusive. * @param maximum The maximum value, <strong>inclusive</strong>. * @throws IllegalArgumentException if at least one argument is not of the expected type. */ @SuppressWarnings("unchecked") NumberRange(Class<T> type, Comparable<T> minimum, Comparable<T> maximum) throws IllegalArgumentException { super(type, (T) minimum, (T) maximum); } /** * Constructs an inclusive range of {@link Number} objects. * * @param type The element class, usually one of {@link Byte}, {@link Short}, * {@link Integer}, {@link Long}, {@link Float} or {@link Double}. * @param minimum The minimum value, inclusive. * @param maximum The maximum value, <strong>inclusive</strong>. */ public NumberRange(final Class<T> type, final T minimum, final T maximum) { super(type, minimum, maximum); } /** * Constructs a range of {@link Number} objects. * * @param type The element class, usually one of {@link Byte}, {@link Short}, * {@link Integer}, {@link Long}, {@link Float} or {@link Double}. * @param minimum The minimum value. * @param isMinIncluded Defines whether the minimum value is included in the range. * @param maximum The maximum value. * @param isMaxIncluded Defines whether the maximum value is included in the range. */ public NumberRange(final Class<T> type, final T minimum, final boolean isMinIncluded, final T maximum, final boolean isMaxIncluded) { super(type, minimum, isMinIncluded, maximum, isMaxIncluded); } /** * Constructs a range with the same values than the specified range, * casted to the specified type. * * @param type The element class, usually one of {@link Byte}, {@link Short}, * {@link Integer}, {@link Long}, {@link Float} or {@link Double}. * @param range The range to copy. The elements must be {@link Number} instances. * @throws IllegalArgumentException if the values are not convertible to the specified class. */ NumberRange(final Class<T> type, final Range<? extends Number> range) throws IllegalArgumentException { // TODO: remove the (Number) casts when we will be allowed to compile for Java 6. this(type, ClassChanger.cast((Number) range.getMinValue(), type), range.isMinIncluded(), ClassChanger.cast((Number) range.getMaxValue(), type), range.isMaxIncluded()); } /** * Constructs a range with the same type and the same values than the * specified range. This is a copy constructor. * * @param range The range to copy. The elements must be {@link Number} instances. * * @since 2.4 */ public NumberRange(final Range<T> range) { super(range.getElementClass(), range.getMinValue(), range.isMinIncluded(), range.getMaxValue(), range.isMaxIncluded()); } /** * Creates a new range using the same element class than this range. This method will * be overriden by subclasses in order to create a range of a more specific type. */ @Override NumberRange<T> create(final T minValue, final boolean isMinIncluded, final T maxValue, final boolean isMaxIncluded) { return new NumberRange<T>(elementClass, minValue, isMinIncluded, maxValue, isMaxIncluded); } /** * Ensures that {@link #elementClass} is compatible with the type expected by this range class. * Invoked for argument checking by the super-class constructor. */ @Override void checkElementClass() throws IllegalArgumentException { ensureNumberClass(elementClass); } /** * Returns the type of minimum and maximum values. */ private static Class<? extends Number> getElementClass(final Range<?> range) throws IllegalArgumentException { ensureNonNull("range", range); final Class<?> type = range.elementClass; ensureNumberClass(type); /* * Safe because we checked in the above line. We could have used Class.asSubclass(Class) * instead but we want an IllegalArgumentException in case of failure rather than a * ClassCastException. */ @SuppressWarnings("unchecked") final Class<? extends Number> result = (Class<? extends Number>) type; return result; } /** * Ensures that the given class is {@link Number} or a subclass. */ private static void ensureNumberClass(final Class<?> type) throws IllegalArgumentException { if (!Number.class.isAssignableFrom(type)) { throw new IllegalArgumentException(Errors.format( ErrorKeys.ILLEGAL_CLASS_$2, type, Number.class)); } } /** * Wraps the specified {@link Range} in a {@code NumberRange} object. If the specified * range is already an instance of {@code NumberRange}, then it is returned unchanged. * * @param <N> The type of elements in the given range. * @param range The range to wrap. * @return The same range than {@code range} as a {@code NumberRange} object. */ public static <N extends Number & Comparable<? super N>> NumberRange<N> wrap(final Range<N> range) { if (range instanceof NumberRange) { @SuppressWarnings("unchecked") // Method signature should have enforced the right type. final NumberRange<N> cast = (NumberRange<N>) range; return cast; } // The constructor will ensure that the range element class is a subclass of Number. return new NumberRange<N>(range); } /** * Casts the specified range to the specified type. If this class is associated to a unit of * measurement, then this method convert the {@code range} units to the same units than this * instance. This method is overriden by {@link MeasurementRange} only in the way described * above. * * @param type The class to cast to. Must be one of {@link Byte}, {@link Short}, * {@link Integer}, {@link Long}, {@link Float} or {@link Double}. * @return The casted range, or {@code range} if no cast is needed. * @throws IllegalArgumentException if the values are not convertible to the specified class. */ <N extends Number & Comparable<? super N>> NumberRange<N> convertAndCast(final Range<? extends Number> range, final Class<N> type) throws IllegalArgumentException { if (type.equals(range.getElementClass())) { @SuppressWarnings("unchecked") // Safe because we checked in the line just above. final NumberRange<N> cast = (NumberRange<N>) wrap((Range) range); return cast; } // TODO: Remove the (Range) cast when we will be allowed to compile for Java 6. return new NumberRange<N>(type, (Range) range); } /** * Work around a Java 5 compiler bug which is fixed in Java 6. * * @todo Delete when we will be allowed to target Java 6. */ @Deprecated final NumberRange damnJava5(final Range range, final Class type) { return convertAndCast(range, type); } /** * Casts this range to the specified type. * * @param <N> The class to cast to. * @param type The class to cast to. Must be one of {@link Byte}, {@link Short}, * {@link Integer}, {@link Long}, {@link Float} or {@link Double}. * @return The casted range, or {@code this} if this range already uses the specified type. * @throws IllegalArgumentException if the values are not convertible to the specified class. */ public <N extends Number & Comparable<? super N>> NumberRange<N> castTo(final Class<N> type) throws IllegalArgumentException { return convertAndCast(this, type); } /** * Returns an initially empty array of the given length. */ @Override @SuppressWarnings("unchecked") // Generic array creation. NumberRange<T>[] newArray(final int length) { return new NumberRange[length]; } /** * Returns {@code true} if the specified value is within this range. * * @param value The value to check for inclusion. * @return {@code true} if the given value is withing this range. * @throws IllegalArgumentException if the given value is not comparable. */ public boolean contains(final Number value) throws IllegalArgumentException { if (value != null && !(value instanceof Comparable)) { throw new IllegalArgumentException(Errors.format( ErrorKeys.NOT_COMPARABLE_CLASS_$1, value.getClass())); } return contains((Comparable<?>) value); } /** * Returns {@code true} if the specified value is within this range. * The given value must be a subclass of {@link Number}. * * @throws IllegalArgumentException if the given value is not a subclass of {@link Number}. */ @Override public boolean contains(Comparable<?> value) throws IllegalArgumentException { if (value == null) { return false; } ensureNumberClass(value.getClass()); /* * Suppress warning because we checked the class in the line just above, so we are safe. * We could have used Class.cast(Object) but we want an IllegalArgumentException with a * localized message. */ @SuppressWarnings("unchecked") Number number = (Number) value; final Class<? extends Number> type = getWidestClass(elementClass, number.getClass()); number = ClassChanger.cast(number, type); /* * The 'type' bounds should actually be <? extends Number & Comparable> since the method * signature expect a Comparable and we have additionnaly casted to a Number. However I * have not found a way to express that safely in a local variable with Java 6. */ @SuppressWarnings("unchecked") final boolean contains = castTo((Class) type).containsNC((Comparable) number); return contains; } /** * Returns {@code true} if the supplied range is fully contained within this range. */ @Override @SuppressWarnings("unchecked") public boolean contains(Range<?> range) { final Class<? extends Number> type = getWidestClass(elementClass, getElementClass(range)); /* * The type bounds is actually <? extends Number & Comparable> but I'm unable to express * it it local variable as of Java 6. So we have to bypass the compiler check, but those * casts are actually safes, including Range because getElementClass(range) would have * throw an exception otherwise. */ range = convertAndCast((Range) range, (Class) type); return castTo((Class) type).containsNC(range); } /** * Returns {@code true} if this range intersects the given range. */ @Override @SuppressWarnings("unchecked") public boolean intersects(Range<?> range) { final Class<? extends Number> type = getWidestClass(elementClass, getElementClass(range)); /* * The type bounds is actually <? extends Number & Comparable> but I'm unable to express * it it local variable as of Java 6. So we have to bypass the compiler check, but those * casts are actually safes, including Range because getElementClass(range) would have * throw an exception otherwise. */ range = convertAndCast((Range) range, (Class) type); return castTo((Class) type).intersectsNC(range); } /** * Returns the union of this range with the given range. * Widening conversions will be applied as needed. */ @Override @SuppressWarnings("unchecked") public NumberRange<?> union(Range<?> range) { final Class<? extends Number> type = getWidestClass(elementClass, getElementClass(range)); range = convertAndCast((Range) range, (Class) type); return (NumberRange) castTo((Class) type).unionNC(range); } /** * Returns the intersection of this range with the given range. Widening * conversions will be applied as needed. */ @Override @SuppressWarnings("unchecked") public NumberRange<?> intersect(Range<?> range) { final Class<? extends Number> rangeType = getElementClass(range); Class<? extends Number> type = getWidestClass(elementClass, rangeType); range = castTo((Class) type).intersectNC(convertAndCast((Range) range, (Class) type)); /* * Use a finer type capable to holds the result (since the intersection * may have reduced the range), but not finer than the finest type of * the ranges used in the intersection calculation. */ type = getFinestClass(elementClass, rangeType); if (range.minValue != null) { type = getWidestClass(type, getFinestClass(((Number) range.minValue).doubleValue())); } if (range.maxValue != null) { type = getWidestClass(type, getFinestClass(((Number) range.maxValue).doubleValue())); } return convertAndCast((Range) range, (Class) type); } /** * Returns the range of values that are in this range but not in the given range. */ @Override @SuppressWarnings("unchecked") public NumberRange<?>[] subtract(Range<?> range) { Class<? extends Number> type = getWidestClass(elementClass, getElementClass(range)); return (NumberRange[]) castTo((Class) type) .subtractNC(convertAndCast((Range) range, (Class) type)); } /** * Returns the {@linkplain #getMinValue minimum value} as a {@code double}. * If this range is unbounded, then {@link Double#NEGATIVE_INFINITY} is returned. * * @return The minimum value. */ @SuppressWarnings("unchecked") public double getMinimum() { final Number value = (Number) getMinValue(); return (value != null) ? value.doubleValue() : Double.NEGATIVE_INFINITY; } /** * Returns the {@linkplain #getMinimum() minimum value} with the specified inclusive or * exclusive state. If this range is unbounded, then {@link Double#NEGATIVE_INFINITY} is * returned. * * @param inclusive * {@code true} for the minimum value inclusive, or {@code false} * for the minimum value exclusive. * @return The minimum value, inclusive or exclusive as requested. */ public double getMinimum(final boolean inclusive) { double value = getMinimum(); if (inclusive != isMinIncluded()) { value = XMath.rool(getElementClass(), value, inclusive ? +1 : -1); } return value; } /** * Returns the {@linkplain #getMaxValue maximum value} as a {@code double}. * If this range is unbounded, then {@link Double#POSITIVE_INFINITY} is returned. * * @return The maximum value. */ @SuppressWarnings("unchecked") public double getMaximum() { final Number value = (Number) getMaxValue(); return (value != null) ? value.doubleValue() : Double.POSITIVE_INFINITY; } /** * Returns the {@linkplain #getMaximum() maximum value} with the specified inclusive or * exclusive state. If this range is unbounded, then {@link Double#POSITIVE_INFINITY} is * returned. * * @param inclusive * {@code true} for the maximum value inclusive, or {@code false} * for the maximum value exclusive. * @return The maximum value, inclusive or exclusive as requested. */ public double getMaximum(final boolean inclusive) { double value = getMaximum(); if (inclusive != isMaxIncluded()) { value = XMath.rool(getElementClass(), value, inclusive ? -1 : +1); } return value; } }