tec.uom.se.AbstractQuantity Maven / Gradle / Ivy
/*
* Units of Measurement Implementation for Java SE
* Copyright (c) 2005-2018, Jean-Marie Dautelle, Werner Keil, Otavio Santana.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
* and the following disclaimer in the documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of JSR-363 nor the names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tec.uom.se;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.Comparator;
import java.util.Objects;
import javax.measure.Quantity;
import javax.measure.Unit;
import javax.measure.UnitConverter;
import javax.measure.quantity.Dimensionless;
import tec.uom.lib.common.function.UnitSupplier;
import tec.uom.lib.common.function.ValueSupplier;
import tec.uom.se.format.QuantityFormat;
import tec.uom.se.function.NaturalOrder;
import tec.uom.se.quantity.Quantities;
/**
*
* This class represents the immutable result of a scalar measurement stated in a known unit.
*
*
*
* To avoid any loss of precision, known exact quantities (e.g. physical constants) should not be created from double
constants but from
* their decimal representation.
*
* public static final Quantity<Velocity> C = NumberQuantity.parse("299792458 m/s").asType(Velocity.class);
* // Speed of Light (exact).
*
*
*
*
* Quantities can be converted to different units.
*
* Quantity<Velocity> milesPerHour = C.to(MILES_PER_HOUR); // Use double implementation (fast).
* System.out.println(milesPerHour);
*
* > 670616629.3843951 m/h
*
*
*
*
* Applications may sub-class {@link AbstractQuantity} for particular quantity types.
*
* // Quantity of type Mass based on double primitive types.
* public class MassAmount extends AbstractQuantity<Mass> {
* private final double kilograms; // Internal SI representation.
* private Mass(double kg) { kilograms = kg; }
* public static Mass of(double value, Unit<Mass> unit) {
* return new Mass(unit.getConverterTo(SI.KILOGRAM).convert(value));
* }
* public Unit<Mass> getUnit() { return SI.KILOGRAM; }
* public Double getValue() { return kilograms; }
* ...
* }
*
* // Complex numbers measurements.
* public class ComplexQuantity
* <Q extends Quantity>extends AbstractQuantity
* <Q>{
* public Complex getValue() { ... } // Assuming Complex is a Number.
* ...
* }
*
* // Specializations of complex numbers measurements.
* public final class Current extends ComplexQuantity<ElectricCurrent> {...}
* public final class Tension extends ComplexQuantity<ElectricPotential> {...}
*
*
*
*
* All instances of this class shall be immutable.
*
*
* @author Werner Keil
* @version 1.0.6, August 7, 2017
* @since 1.0
*/
@SuppressWarnings("unchecked")
public abstract class AbstractQuantity> implements ComparableQuantity, UnitSupplier, ValueSupplier {
/**
*
*/
private static final long serialVersionUID = 293852425369811882L;
private final Unit unit;
/**
* Holds a dimensionless quantity of none (exact).
*/
public static final Quantity NONE = Quantities.getQuantity(0, AbstractUnit.ONE);
/**
* Holds a dimensionless quantity of one (exact).
*/
public static final Quantity ONE = Quantities.getQuantity(1, AbstractUnit.ONE);
/**
* constructor.
*/
protected AbstractQuantity(Unit unit) {
this.unit = unit;
}
/**
* Returns the numeric value of the quantity.
*
* @return the quantity value.
*/
@Override
public abstract Number getValue();
/**
* Returns the measurement unit.
*
* @return the measurement unit.
*/
@Override
public Unit getUnit() {
return unit;
}
/**
* Convenient method equivalent to {@link #to(javax.measure.unit.Unit) to(this.getUnit().toSI())}.
*
* @return this measure or a new measure equivalent to this measure but stated in SI units.
* @throws ArithmeticException
* if the result is inexact and the quotient has a non-terminating decimal expansion.
*/
public Quantity toSI() {
return to(this.getUnit().getSystemUnit());
}
/**
* Returns this measure after conversion to specified unit. The default implementation returns Measure.valueOf(doubleValue(unit), unit)
* . If this measure is already stated in the specified unit, then this measure is returned and no conversion is performed.
*
* @param unit
* the unit in which the returned measure is stated.
* @return this measure or a new measure equivalent to this measure but stated in the specified unit.
* @throws ArithmeticException
* if the result is inexact and the quotient has a non-terminating decimal expansion.
*/
@Override
public ComparableQuantity to(Unit unit) {
if (unit.equals(this.getUnit())) {
return this;
}
UnitConverter t = getUnit().getConverterTo(unit);
Number convertedValue = t.convert(getValue());
return Quantities.getQuantity(convertedValue, unit);
}
/**
* Returns this measure after conversion to specified unit. The default implementation returns
* Measure.valueOf(decimalValue(unit, ctx), unit)
. If this measure is already stated in the specified unit, then this measure is
* returned and no conversion is performed.
*
* @param unit
* the unit in which the returned measure is stated.
* @param ctx
* the math context to use for conversion.
* @return this measure or a new measure equivalent to this measure but stated in the specified unit.
* @throws ArithmeticException
* if the result is inexact but the rounding mode is UNNECESSARY
or mathContext.precision == 0
and the quotient
* has a non-terminating decimal expansion.
*/
public Quantity to(Unit unit, MathContext ctx) {
if (unit.equals(this.getUnit())) {
return this;
}
return Quantities.getQuantity(decimalValue(unit, ctx), unit);
}
@Override
public boolean isGreaterThan(Quantity that) {
return this.compareTo(that) > 0;
}
@Override
public boolean isGreaterThanOrEqualTo(Quantity that) {
return this.compareTo(that) >= 0;
}
@Override
public boolean isLessThan(Quantity that) {
return this.compareTo(that) < 0;
}
@Override
public boolean isLessThanOrEqualTo(Quantity that) {
return this.compareTo(that) <= 0;
}
@Override
public boolean isEquivalentOf(Quantity that) {
return this.compareTo(that) == 0;
}
@Override
public boolean isEquivalentTo(Quantity that) {
return isEquivalentOf(that);
}
/**
* Compares this measure to the specified Measurement quantity. The default implementation compares the {@link AbstractQuantity#doubleValue(Unit)}
* of both this measure and the specified Measurement stated in the same unit (this measure's {@link #getUnit() unit}).
*
* @return a negative integer, zero, or a positive integer as this measure is less than, equal to, or greater than the specified Measurement
* quantity.
* @see {@link NaturalOrder}
*/
@Override
public int compareTo(Quantity that) {
final Comparator> comparator = new NaturalOrder<>();
return comparator.compare(this, that);
}
/**
* Compares this quantity against the specified object for strict equality (same unit and same amount).
*
*
* Similarly to the {@link BigDecimal#equals} method which consider 2.0 and 2.00 as different objects because of different internal scales,
* quantities such as Quantities.getQuantity(3.0, KILOGRAM)
Quantities.getQuantity(3, KILOGRAM)
and
* Quantities.getQuantity("3 kg")
might not be considered equals because of possible differences in their implementations.
*
*
*
* To compare quantities stated using different units or using different amount implementations the {@link #compareTo compareTo} or
* {@link #equals(javax.measure.Quantity, double, javax.measure.unit.Unit) equals(Quantity, epsilon, epsilonUnit)} methods should be used.
*
*
* @param obj
* the object to compare with.
* @return this.getUnit.equals(obj.getUnit())
* && this.getValue().equals(obj.getValue())
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof AbstractQuantity) {
AbstractQuantity that = (AbstractQuantity) obj;
return Objects.equals(getUnit(), that.getUnit()) && Objects.equals(getValue(), that.getValue());
}
return false;
}
/**
* Compares this quantity and the specified quantity to the given accuracy. Quantities are considered approximately equals if their absolute
* differences when stated in the same specified unit is less than the specified epsilon.
*
* @param that
* the quantity to compare with.
* @param epsilon
* the absolute error stated in epsilonUnit.
* @param epsilonUnit
* the epsilon unit.
* @return abs(this.doubleValue(epsilonUnit) - that.doubleValue(epsilonUnit)) <= epsilon
*/
public boolean equals(AbstractQuantity that, double epsilon, Unit epsilonUnit) {
return Math.abs(this.doubleValue(epsilonUnit) - that.doubleValue(epsilonUnit)) <= epsilon;
}
/**
* Returns the hash code for this quantity.
*
* @return the hash code value.
*/
@Override
public int hashCode() {
return Objects.hash(getUnit(), getValue());
}
public abstract boolean isBig();
/**
* Returns the String
representation of this quantity. The string produced for a given quantity is always the same; it is not affected
* by locale. This means that it can be used as a canonical string representation for exchanging quantity, or as a key for a Hashtable, etc.
* Locale-sensitive quantity formatting and parsing is handled by the {@link MeasurementFormat} class and its subclasses.
*
* @return UnitFormat.getInternational().format(this)
*/
@Override
public String toString() {
return String.valueOf(getValue()) + " " + String.valueOf(getUnit());
}
public abstract BigDecimal decimalValue(Unit unit, MathContext ctx) throws ArithmeticException;
public abstract double doubleValue(Unit unit) throws ArithmeticException;
public final int intValue(Unit unit) throws ArithmeticException {
long longValue = longValue(unit);
if ((longValue < Integer.MIN_VALUE) || (longValue > Integer.MAX_VALUE)) {
throw new ArithmeticException("Cannot convert " + longValue + " to int (overflow)");
}
return (int) longValue;
}
protected long longValue(Unit unit) throws ArithmeticException {
double result = doubleValue(unit);
if ((result < Long.MIN_VALUE) || (result > Long.MAX_VALUE)) {
throw new ArithmeticException("Overflow (" + result + ")");
}
return (long) result;
}
protected final float floatValue(Unit unit) {
return (float) doubleValue(unit);
}
@Override
public , E extends Quantity> ComparableQuantity divide(Quantity that, Class asTypeQuantity) {
return divide(Objects.requireNonNull(that)).asType(Objects.requireNonNull(asTypeQuantity));
}
@Override
public , E extends Quantity> ComparableQuantity multiply(Quantity that, Class asTypeQuantity) {
return multiply(Objects.requireNonNull(that)).asType(Objects.requireNonNull(asTypeQuantity));
}
@Override
public > ComparableQuantity inverse(Class quantityClass) {
return inverse().asType(quantityClass);
}
/**
* Casts this quantity to a parameterized quantity of specified nature or throw a ClassCastException
if the dimension of the specified
* quantity and its unit's dimension do not match. For example:
*
* Quantity length = AbstractQuantity.parse("2 km").asType(Length.class);
*
*
* @param type
* the quantity class identifying the nature of the quantity.
* @return this quantity parameterized with the specified type.
* @throws ClassCastException
* if the dimension of this unit is different from the specified quantity dimension.
* @throws UnsupportedOperationException
* if the specified quantity class does not have a public static field named "UNIT" holding the SI unit for the quantity.
* @see Unit#asType(Class)
*/
public final > ComparableQuantity asType(Class type) throws ClassCastException {
this.getUnit().asType(type); // Raises ClassCastException if dimension
// mismatches.
return (ComparableQuantity) this;
}
/**
* Returns the quantity of unknown type corresponding to the specified representation. This method can be used to parse dimensionless quantities.
*
* Quatity proportion = AbstractQuantity.parse("0.234").asType(Dimensionless.class);
*
*
*
* Note: This method handles only {@link SimpleUnitFormat#getStandard standard} unit format. Locale-sensitive quantity parsing is currently not
* supported.
*
*
* @param csq
* the decimal value and its unit (if any) separated by space(s).
* @return QuantityFormat.getInstance().parse(csq)
*/
public static Quantity parse(CharSequence csq) {
return QuantityFormat.getInstance().parse(csq);
}
/**
* Utility class for number comparison and equality
*/
protected static final class Equalizer {
/**
* Converts a number to {@link BigDecimal}
*
* @param value
* the value to be converted
* @return the value converted
*/
public static BigDecimal toBigDecimal(Number value) {
if (BigDecimal.class.isInstance(value)) {
return BigDecimal.class.cast(value);
} else if (BigInteger.class.isInstance(value)) {
return new BigDecimal(BigInteger.class.cast(value));
}
return BigDecimal.valueOf(value.doubleValue());
}
/**
* Check if the both value has equality number, in other words, 1 is equals to 1.0000 and 1.0.
*
* If the first value is a Number of either Double , Float , Integer , Long ,
* Short or Byte it is compared using the respective *value()
method of Number . Otherwise it
* is checked, if {@link BigDecimal#compareTo(Object)} is equal to zero.
*
* @param valueA
* the value a
* @param valueB
* the value B
* @return {@link BigDecimal#compareTo(Object)} == zero
*/
public static boolean hasEquality(Number valueA, Number valueB) {
Objects.requireNonNull(valueA);
Objects.requireNonNull(valueB);
if (valueA instanceof Double && valueB instanceof Double) {
return valueA.doubleValue() == valueB.doubleValue();
} else if (valueA instanceof Float && valueB instanceof Float) {
return valueA.floatValue() == valueB.floatValue();
} else if (valueA instanceof Integer && valueB instanceof Integer) {
return valueA.intValue() == valueB.intValue();
} else if (valueA instanceof Long && valueB instanceof Long) {
return valueA.longValue() == valueB.longValue();
} else if (valueA instanceof Short && valueB instanceof Short) {
return valueA.shortValue() == valueB.shortValue();
} else if (valueA instanceof Byte && valueB instanceof Byte) {
return valueA.byteValue() == valueB.byteValue();
}
return toBigDecimal(valueA).compareTo(toBigDecimal(valueB)) == 0;
}
}
}