| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sis.measure; |
| |
| import java.util.Objects; |
| import javax.measure.Unit; |
| import javax.measure.UnitConverter; |
| import javax.measure.IncommensurableException; |
| import org.apache.sis.util.Numbers; |
| import org.apache.sis.util.resources.Errors; |
| |
| |
| /** |
| * A range of numbers associated with a unit of measurement. All operations performed by this |
| * class ({@linkplain #union union}, {@linkplain #intersect intersection}, <i>etc.</i>) are |
| * performed in the unit of measurement of {@code this} range object - values of the range |
| * object given in argument are converted if needed before an operation is applied. |
| * |
| * <p>Other methods defined in this class:</p> |
| * <ul> |
| * <li>Convenience {@code create(…)} static methods for every floating point primitive types. |
| * Usage of {@code MeasurementRange} with integer types is possible, but no convenience |
| * method is provided for integers because they are usually not representative of the |
| * nature of physical measurements.</li> |
| * <li>{@link #unit()} for getting the unit of measurement associated to this range.</li> |
| * <li>{@link #convertTo(Unit)} for converting the unit of measurement.</li> |
| * <li>{@link #castTo(Class)} for casting the range values to another type.</li> |
| * </ul> |
| * |
| * <h2>Null unit of measurement</h2> |
| * The unit of measurement should not be null, otherwise a {@link NumberRange} should be used |
| * instead of {@code MeasurementRange}. Nevertheless this class is tolerant to {@code null} |
| * units in order to support situations where a unit of measurement <em>should</em> be specified, |
| * but for some reason is unavailable. If the unit of measurement become known at a later stage, |
| * it can be specified by a call to {@link #convertTo(Unit)}. |
| * |
| * <h2>Immutability and thread safety</h2> |
| * This class is immutable and thus inherently thread-safe. |
| * Subclasses may or may not be immutable, at implementation choice. But implementers are |
| * encouraged to make sure that subclasses remain immutable for more predictable behavior. |
| * |
| * @author Martin Desruisseaux (IRD) |
| * @version 0.6 |
| * |
| * @param <E> the type of range elements as a subclass of {@link Number}. |
| * |
| * @see RangeFormat |
| * @see org.apache.sis.util.collection.RangeSet |
| * |
| * @since 0.3 |
| */ |
| public class MeasurementRange<E extends Number & Comparable<? super E>> extends NumberRange<E> { |
| /** |
| * Serial number for inter-operability with different versions. |
| */ |
| private static final long serialVersionUID = -3532903747339978756L; |
| |
| /** |
| * The unit of measurement, or {@code null} if unknown. |
| * |
| * @see #unit() |
| */ |
| @SuppressWarnings("serial") // Most SIS implementations are serializable. |
| private final Unit<?> unit; |
| |
| /** |
| * Constructs a range of {@code float} values. |
| * The minimum and maximum values cannot be NaN but can be infinite. |
| * If the minimum is greater than the maximum, then the range {@linkplain #isEmpty() is empty}. |
| * This method may return a shared instance, at implementation choice. |
| * |
| * @param minValue the minimal value, or {@link Float#NEGATIVE_INFINITY} if none. |
| * @param isMinIncluded {@code true} if the minimal value is inclusive, or {@code false} if exclusive. |
| * @param maxValue the maximal value, or {@link Float#POSITIVE_INFINITY} if none. |
| * @param isMaxIncluded {@code true} if the maximal value is inclusive, or {@code false} if exclusive. |
| * @param unit the unit of measurement, or {@code null} if unknown. |
| * @return the new range of numeric values for the given endpoints and unit of measurement. |
| * @throws IllegalArgumentException if {@link Float#isNaN(float)} is {@code true} for a given value. |
| */ |
| public static MeasurementRange<Float> create(float minValue, boolean isMinIncluded, |
| float maxValue, boolean isMaxIncluded, Unit<?> unit) |
| { |
| final Float min = valueOf("minValue", minValue, Float.NEGATIVE_INFINITY); |
| final Float max = valueOf("maxValue", maxValue, Float.POSITIVE_INFINITY); |
| // No need to test isCacheable(Number) because the type is known and valueOf(…) disallows NaN values. |
| return unique(new MeasurementRange<>(Float.class, min, isMinIncluded, Objects.equals(min, max) ? min : max, isMaxIncluded, unit)); |
| } |
| |
| /** |
| * Constructs a range of {@code double} values. |
| * The minimum and maximum values cannot be NaN but can be infinite. |
| * If the minimum is greater than the maximum, then the range {@linkplain #isEmpty() is empty}. |
| * This method may return a shared instance, at implementation choice. |
| * |
| * @param minValue the minimal value, or {@link Double#NEGATIVE_INFINITY} if none. |
| * @param isMinIncluded {@code true} if the minimal value is inclusive, or {@code false} if exclusive. |
| * @param maxValue the maximal value, or {@link Double#POSITIVE_INFINITY} if none. |
| * @param isMaxIncluded {@code true} if the maximal value is inclusive, or {@code false} if exclusive. |
| * @param unit the unit of measurement, or {@code null} if unknown. |
| * @return the new range of numeric values for the given endpoints and unit of measurement. |
| * @throws IllegalArgumentException if {@link Double#isNaN(double)} is {@code true} for a given value. |
| */ |
| public static MeasurementRange<Double> create(double minValue, boolean isMinIncluded, |
| double maxValue, boolean isMaxIncluded, Unit<?> unit) |
| { |
| final Double min = valueOf("minValue", minValue, Double.NEGATIVE_INFINITY); |
| final Double max = valueOf("maxValue", maxValue, Double.POSITIVE_INFINITY); |
| // No need to test isCacheable(Number) because the type is known and valueOf(…) disallows NaN values. |
| return unique(new MeasurementRange<>(Double.class, min, isMinIncluded, Objects.equals(min, max) ? min : max, isMaxIncluded, unit)); |
| } |
| |
| /** |
| * Constructs a range of {@code double} values greater than the given value. |
| * The {@code minValue} is often zero for creating a range of strictly positive values. |
| * This method may return a shared instance, at implementation choice. |
| * |
| * @param minValue the minimal value (exclusive), or {@link Double#NEGATIVE_INFINITY} if none. |
| * @param unit the unit of measurement, or {@code null} if unknown. |
| * @return the new range of numeric values greater than the given value. |
| * @throws IllegalArgumentException if {@link Double#isNaN(double)} is {@code true} for the given value. |
| * |
| * @since 0.6 |
| */ |
| public static MeasurementRange<Double> createGreaterThan(final double minValue, final Unit<?> unit) { |
| // No need to test isCacheable(Number) because the type is known and valueOf(…) disallows NaN values. |
| return unique(new MeasurementRange<>(Double.class, |
| valueOf("minValue", minValue, Double.NEGATIVE_INFINITY), false, null, false, unit)); |
| } |
| |
| /** |
| * Constructs a range using the smallest type of {@link Number} that can hold the given values. |
| * This method performs the same work as {@link NumberRange#createBestFit NumberRange.createBestFit(…)} |
| * with an additional {@code unit} argument. |
| * |
| * <p>This method may return a shared instance, at implementation choice.</p> |
| * |
| * @param minValue the minimal value, or {@code null} if none. |
| * @param isMinIncluded {@code true} if the minimal value is inclusive, or {@code false} if exclusive. |
| * @param maxValue the maximal value, or {@code null} if none. |
| * @param isMaxIncluded {@code true} if the maximal value is inclusive, or {@code false} if exclusive. |
| * @param unit the unit of measurement, or {@code null} if unknown. |
| * @return the new range, or {@code null} if both {@code minValue} and {@code maxValue} are {@code null}. |
| * |
| * @see NumberRange#createBestFit(Number, boolean, Number, boolean) |
| */ |
| @SuppressWarnings({"rawtypes","unchecked"}) |
| public static MeasurementRange<?> createBestFit(final Number minValue, final boolean isMinIncluded, |
| final Number maxValue, final boolean isMaxIncluded, final Unit<?> unit) |
| { |
| final Class<? extends Number> type; |
| type = Numbers.widestClass(Numbers.narrowestClass(minValue), |
| Numbers.narrowestClass(maxValue)); |
| if (type == null) { |
| return null; |
| } |
| Number min = Numbers.cast(minValue, type); |
| Number max = Numbers.cast(maxValue, type); |
| final boolean isCacheable = isCacheable(min) && isCacheable(max); |
| if (isCacheable && Objects.equals(min, max)) { |
| max = min; // Share the same instance. |
| } |
| MeasurementRange range = new MeasurementRange(type, min, isMinIncluded, max, isMaxIncluded, unit); |
| if (isCacheable) { |
| range = unique(range); |
| } |
| return range; |
| } |
| |
| /** |
| * Constructs a range with the same values as the specified range and the given unit. |
| * This is a copy constructor, with the addition of a unit of measurement. |
| * |
| * @param range the range to copy. The elements must be {@link Number} instances. |
| * @param unit the unit of measurement, or {@code null} if unknown. |
| */ |
| public MeasurementRange(final Range<E> range, final Unit<?> unit) { |
| super(range); |
| this.unit = unit; |
| } |
| |
| /** |
| * Constructs a range of the given type with values from the given annotation. |
| * This constructor does not verify if the given type is wide enough for the values of |
| * the given annotation, because those information are usually static. If nevertheless |
| * the given type is not wide enough, then the values are truncated in the same way |
| * than the Java language casts primitive types. |
| * |
| * @param type the element type, restricted to one of {@link Byte}, {@link Short}, |
| * {@link Integer}, {@link Long}, {@link Float} or {@link Double}. |
| * @param range the range of values. |
| * @param unit the unit of measurement, or {@code null} if unknown. |
| * @throws IllegalArgumentException if the given type is not one of the primitive wrappers for numeric types. |
| */ |
| public MeasurementRange(final Class<E> type, final ValueRange range, final Unit<?> unit) throws IllegalArgumentException { |
| super(type, range); |
| this.unit = unit; |
| } |
| |
| /** |
| * Constructs a range of {@link Number} objects. |
| * |
| * @param type the element type, usually one of {@link Float} or {@link Double}. |
| * @param minValue the minimal value, or {@code null} if none. |
| * @param isMinIncluded {@code true} if the minimal value is inclusive, or {@code false} if exclusive. |
| * @param maxValue the maximal value, or {@code null} if none. |
| * @param isMaxIncluded {@code true} if the maximal value is inclusive, or {@code false} if exclusive. |
| * @param unit the unit of measurement, or {@code null} if unknown. |
| */ |
| public MeasurementRange(final Class<E> type, |
| final E minValue, final boolean isMinIncluded, |
| final E maxValue, final boolean isMaxIncluded, |
| final Unit<?> unit) |
| { |
| super(type, minValue, isMinIncluded, maxValue, isMaxIncluded); |
| this.unit = unit; |
| } |
| |
| /** |
| * Constructs a range with the same values as the specified range, casted to the specified type. |
| * |
| * @param type the element type, 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. |
| * @param unit the unit of measurement, or {@code null} if unknown. |
| */ |
| private MeasurementRange(final Class<E> type, final Range<? extends Number> range, final Unit<?> unit) { |
| super(type, range); |
| this.unit = unit; |
| } |
| |
| /** |
| * Creates a new range using the same element type and the same unit as this range. |
| */ |
| @Override |
| Range<E> create(final E minValue, final boolean isMinIncluded, |
| final E maxValue, final boolean isMaxIncluded) |
| { |
| return new MeasurementRange<>(elementType, minValue, isMinIncluded, maxValue, isMaxIncluded, unit); |
| } |
| |
| /** |
| * Returns the unit of measurement, or {@code null} if unknown. |
| * In principle the unit should never be null, otherwise a {@link NumberRange} should have been used |
| * instead of {@code MeasurementRange}. Nevertheless this method may return {@code null} if a unit |
| * <em>should</em> exist but for some reason is unavailable. |
| * |
| * @return the unit of measurement, or {@code null}. |
| */ |
| @Override |
| public Unit<?> unit() { |
| return unit; |
| } |
| |
| /** |
| * Converts this range to the specified unit. If this measurement range has null unit, |
| * then the specified target unit are simply assigned to the returned range with no |
| * other changes. |
| * |
| * @param targetUnit the target unit, or {@code null} for keeping the unit unchanged. |
| * @return the converted range, or {@code this} if no conversion is needed. |
| * @throws IncommensurableException if the target unit are not compatible with this {@linkplain #unit() range unit}. |
| */ |
| public MeasurementRange<E> convertTo(final Unit<?> targetUnit) throws IncommensurableException { |
| return convertAndCast(elementType, targetUnit); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @return {@inheritDoc} |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| public <N extends Number & Comparable<? super N>> MeasurementRange<N> castTo(final Class<N> type) { |
| if (elementType == type) { |
| return (MeasurementRange<N>) this; |
| } else { |
| return new MeasurementRange<>(type, this, unit); |
| } |
| } |
| |
| /** |
| * If the given range is an instance of {@code MeasurementRange}, converts that |
| * range to the unit of this range. Otherwise returns the given range unchanged. |
| * |
| * @param range the range to convert. |
| * @return the converted range. |
| * @throws IllegalArgumentException if the given target unit is not compatible with the unit of this range. |
| */ |
| private <N extends E> Range<N> convert(final Range<N> range) throws IllegalArgumentException { |
| if (range instanceof MeasurementRange<?>) try { |
| return ((MeasurementRange<N>) range).convertAndCast(range.elementType, unit); |
| } catch (IncommensurableException e) { |
| throw new IllegalArgumentException(Errors.format(Errors.Keys.IncompatibleUnits_2, |
| ((MeasurementRange<?>) range).unit, unit), e); |
| } |
| return 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} unit to the same unit as this |
| * instance. |
| * |
| * @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. |
| */ |
| @Override |
| <N extends Number & Comparable<? super N>> |
| NumberRange<N> convertAndCast(final NumberRange<?> range, final Class<N> type) |
| throws IllegalArgumentException |
| { |
| if (range instanceof MeasurementRange<?>) try { |
| return ((MeasurementRange<?>) range).convertAndCast(type, unit); |
| } catch (IncommensurableException e) { |
| throw new IllegalArgumentException(Errors.format(Errors.Keys.IncompatibleUnits_2, |
| ((MeasurementRange<?>) range).unit, unit), e); |
| } |
| return new MeasurementRange<>(type, range, unit); |
| } |
| |
| /** |
| * Casts this range to the specified type and converts to the specified unit. |
| * This method is invoked on the {@code other} instance in expressions like |
| * {@code this.operation(other)}. |
| * |
| * @param type the class to cast to. Must be one of {@link Byte}, {@link Short}, |
| * {@link Integer}, {@link Long}, {@link Float} or {@link Double}. |
| * @param targetUnit the target unit, or {@code null} for no change. |
| * @return the casted range, or {@code this}. |
| * @throws IncommensurableException if the given target unit is not compatible with the unit of this range. |
| */ |
| @SuppressWarnings("unchecked") |
| private <N extends Number & Comparable<? super N>> MeasurementRange<N> |
| convertAndCast(final Class<N> type, Unit<?> targetUnit) throws IncommensurableException |
| { |
| if (targetUnit == null || targetUnit.equals(unit)) { |
| if (elementType == type) { |
| return (MeasurementRange<N>) this; |
| } |
| targetUnit = unit; |
| } else if (unit != null) { |
| final UnitConverter converter = unit.getConverterToAny(targetUnit); |
| if (!converter.isIdentity()) { |
| boolean minInc = isMinIncluded; |
| boolean maxInc = isMaxIncluded; |
| double minimum = converter.convert(getMinDouble()); |
| double maximum = converter.convert(getMaxDouble()); |
| if (minimum > maximum) { |
| final double td = minimum; minimum = maximum; maximum = td; |
| final boolean tb = minInc; minInc = maxInc; maxInc = tb; |
| } |
| if (Numbers.isInteger(type)) { |
| minInc &= (minimum == (minimum = Math.floor(minimum))); |
| maxInc &= (maximum == (maximum = Math.ceil (maximum))); |
| } |
| return new MeasurementRange<>(type, |
| Numbers.cast(minimum, type), minInc, |
| Numbers.cast(maximum, type), maxInc, targetUnit); |
| } |
| } |
| return new MeasurementRange<>(type, this, targetUnit); |
| } |
| |
| /** |
| * Returns an initially empty array of the given length. |
| */ |
| @Override |
| @SuppressWarnings({"unchecked","rawtypes"}) // Generic array creation. |
| final Range<E>[] newArray(final int length) { |
| return new MeasurementRange[length]; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * If the given range is an instance of {@code MeasurementRange}, then this method converts |
| * the value of the other range to the unit of measurement of this range before to perform |
| * the operation. |
| * |
| * @return {@inheritDoc} |
| * @throws IllegalArgumentException if the given range is an instance of |
| * {@code MeasurementRange} using incommensurable unit of measurement. |
| */ |
| @Override |
| public boolean contains(final Range<? extends E> range) throws IllegalArgumentException { |
| return super.contains(convert(range)); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * If the given range is an instance of {@code MeasurementRange}, then this method converts |
| * the value of the other range to the unit of measurement of this range before to perform |
| * the operation. |
| * |
| * @return {@inheritDoc} |
| * @throws IllegalArgumentException if the given range is an instance of |
| * {@code MeasurementRange} using incommensurable unit of measurement. |
| */ |
| @Override |
| public boolean intersects(final Range<? extends E> range) throws IllegalArgumentException { |
| return super.intersects(convert(range)); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * If the given range is an instance of {@code MeasurementRange}, then this method converts |
| * the value of the other range to the unit of measurement of this range before to perform |
| * the operation. |
| * |
| * @return {@inheritDoc} |
| * @throws IllegalArgumentException if the given range is an instance of |
| * {@code MeasurementRange} using incommensurable unit of measurement. |
| */ |
| @Override |
| public Range<E> intersect(final Range<E> range) throws IllegalArgumentException { |
| return super.intersect(convert(range)); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * If the given range is an instance of {@code MeasurementRange}, then this method converts |
| * the value of the other range to the unit of measurement of this range before to perform |
| * the operation. |
| * |
| * @return {@inheritDoc} |
| * @throws IllegalArgumentException if the given range is an instance of |
| * {@code MeasurementRange} using incommensurable unit of measurement. |
| */ |
| @Override |
| public Range<E> union(final Range<E> range) throws IllegalArgumentException { |
| return super.union(convert(range)); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * If the given range is an instance of {@code MeasurementRange}, then this method converts |
| * the value of the other range to the unit of measurement of this range before to perform |
| * the operation. |
| * |
| * @return {@inheritDoc} |
| * @throws IllegalArgumentException if the given range is an instance of |
| * {@code MeasurementRange} using incommensurable unit of measurement. |
| */ |
| @Override |
| public Range<E>[] subtract(final Range<E> range) throws IllegalArgumentException { |
| return super.subtract(convert(range)); |
| } |
| |
| /** |
| * Compares this measurement range with the specified object for equality. Two {@code MeasurementRange} instances |
| * are considered equal if they met all conditions {@linkplain Range#equals(Object) documented in the parent class} |
| * and their {@link #unit()} are equal in the sense of {@link Objects#equals(Object, Object)}. |
| * Note that this comparison does not distinguish the various {@link Float#NaN} or {@link Double#NaN} bit patterns. |
| * |
| * @return {@inheritDoc} |
| */ |
| @Override |
| public boolean equals(final Object object) { |
| return super.equals(object) && Objects.equals(unit, ((MeasurementRange<?>) object).unit); |
| } |
| |
| /** |
| * Returns a hash code value for this measurement range. |
| * |
| * @return {@inheritDoc} |
| */ |
| @Override |
| public int hashCode() { |
| return super.hashCode() + Objects.hashCode(unit); |
| } |
| } |