Package org.apache.sis.measure

Source Code of org.apache.sis.measure.MeasurementRange

/*
* 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 javax.measure.unit.Unit;
import javax.measure.converter.UnitConverter;
import javax.measure.converter.ConversionException;
import org.apache.sis.util.Numbers;
import org.apache.sis.util.resources.Errors;

// Related to JDK7
import org.apache.sis.internal.jdk7.Objects;


/**
* 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 #convertTo(Unit)} for converting the unit of measurement.</li>
*   <li>{@link #castTo(Class)} for casting the range values to an other type.</li>
* </ul>
*
* {@section Immutability and thread safety}
* This class is immutable and thus inherently thread-safe.
* Subclasses may or may not be immutable, at implementation choice. But implementors are
* encouraged to make sure that subclasses remain immutable for more predictable behavior.
*
* @param <E> The type of range elements as a subclass of {@link Number}.
*
* @author  Martin Desruisseaux (IRD)
* @since   0.3 (derived from geotk-2.4)
* @version 0.3
* @module
*
* @see RangeFormat
* @see org.apache.sis.util.collection.RangeSet
*/
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()
     */
    private final Unit<?> unit;

    /**
     * Constructs a range of {@code float} values.
     *
     * @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.
     */
    public static MeasurementRange<Float> create(float minValue, boolean isMinIncluded,
                                                 float maxValue, boolean isMaxIncluded, Unit<?> unit)
    {
        return new MeasurementRange<Float>(Float.class,
                valueOf("minValue", minValue, Float.NEGATIVE_INFINITY), isMinIncluded,
                valueOf("maxValue", maxValue, Float.POSITIVE_INFINITY), isMaxIncluded, unit);
    }

    /**
     * Constructs a range of {@code double} values.
     *
     * @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.
     */
    public static MeasurementRange<Double> create(double minValue, boolean isMinIncluded,
                                                  double maxValue, boolean isMaxIncluded, Unit<?> unit)
    {
        return new MeasurementRange<Double>(Double.class,
                valueOf("minValue", minValue, Double.NEGATIVE_INFINITY), isMinIncluded,
                valueOf("maxValue", maxValue, Double.POSITIVE_INFINITY), isMaxIncluded, unit);
    }

    /**
     * Constructs a range using the smallest type of {@link Number} that can hold the given values.
     * This method performs the same work than {@link NumberRange#createBestFit
     * NumberRange.createBestFit(…)} with an additional {@code unit} argument.
     *
     * @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 = Numbers.widestClass(
                Numbers.narrowestClass(minValue), Numbers.narrowestClass(maxValue));
        return (type == null) ? null :
            new MeasurementRange(type, Numbers.cast(minValue, type), isMinIncluded,
                                       Numbers.cast(maxValue, type), isMaxIncluded, unit);
    }

    /**
     * Constructs a range with the same values than 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 than 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 than this range.
     */
    @Override
    Range<E> create(final E minValue, final boolean isMinIncluded,
                    final E maxValue, final boolean isMaxIncluded)
    {
        return new MeasurementRange<E>(elementType, minValue, isMinIncluded, maxValue, isMaxIncluded, unit);
    }

    /**
     * Returns the unit of measurement, or {@code null} if unknown.
     *
     * @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 ConversionException if the target unit are not compatible with
     *         this {@linkplain #unit() range unit}.
     */
    public MeasurementRange<E> convertTo(final Unit<?> targetUnit) throws ConversionException {
        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<N>(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 (ConversionException 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 than 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 (ConversionException e) {
            throw new IllegalArgumentException(Errors.format(Errors.Keys.IncompatibleUnits_2,
                    ((MeasurementRange<?>) range).unit, unit), e);
        }
        return new MeasurementRange<N>(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 ConversionException 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 ConversionException
    {
        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.equals(UnitConverter.IDENTITY)) {
                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<N>(type,
                        Numbers.cast(minimum, type), minInc,
                        Numbers.cast(maximum, type), maxInc, targetUnit);
            }
        }
        return new MeasurementRange<N>(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 is 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 is 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 is 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 is 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 is 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 range with the specified object for equality.
     *
     * @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);
    }
}
TOP

Related Classes of org.apache.sis.measure.MeasurementRange

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.