tec.uom.se.AbstractUnit 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 tec.uom.se.format.SimpleUnitFormat;
import tec.uom.se.function.AddConverter;
import tec.uom.se.function.MultiplyConverter;
import tec.uom.se.function.RationalConverter;
import tec.uom.se.quantity.QuantityDimension;
import tec.uom.se.spi.DimensionalModel;
import tec.uom.se.unit.AlternateUnit;
import tec.uom.se.unit.AnnotatedUnit;
import tec.uom.se.unit.ProductUnit;
import tec.uom.se.unit.TransformedUnit;
import javax.measure.*;
import javax.measure.quantity.Dimensionless;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
*
* The class represents units founded on the seven SI base units for seven base quantities assumed to be mutually independent.
*
*
*
* For all physics units, unit conversions are symmetrical: u1.getConverterTo(u2).equals(u2.getConverterTo(u1).inverse())
. Non-physical
* units (e.g. currency units) for which conversion is not symmetrical should have their own separate class hierarchy and are considered distinct
* (e.g. financial units), although they can always be combined with physics units (e.g. "€/Kg", "$/h").
*
*
* @see Wikipedia: International System of Units
* @author Jean-Marie Dautelle
* @author Werner Keil
* @version 1.0.10, March 4, 2018
* @since 1.0
*/
public abstract class AbstractUnit> implements ComparableUnit {
/**
*
*/
private static final long serialVersionUID = -4344589505537030204L;
/**
* Holds the dimensionless unit ONE
.
*
* @see Wikipedia: Natural Units - Choosing constants to
* normalize
* @see Units of Dimension One
*/
public static final AbstractUnit ONE = new ProductUnit<>();
/**
* Holds the name.
*/
protected String name;
/**
* Holds the symbol.
*/
private String symbol;
/**
* Holds the unique symbols collection (base units or alternate units).
*/
protected static final Map> SYMBOL_TO_UNIT = new HashMap<>();
/**
* DefaultQuantityFactory constructor.
*/
protected AbstractUnit() {
}
protected Type getActualType() {
ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
return parameterizedType.getActualTypeArguments()[0].getClass().getGenericInterfaces()[0];
}
/**
* Indicates if this unit belongs to the set of coherent SI units (unscaled SI units).
*
* The base and coherent derived units of the SI form a coherent set, designated the set of coherent SI units. The word coherent is used here in the
* following sense: when coherent units are used, equations between the numerical values of quantities take exactly the same form as the equations
* between the quantities themselves. Thus if only units from a coherent set are used, conversion factors between units are never required.
*
* @return equals(toSystemUnit())
*/
public boolean isSystemUnit() {
Unit si = this.toSystemUnit();
return (this == si) || this.equals(si);
}
/**
* Returns the unscaled {@link SI} unit from which this unit is derived.
*
* The SI unit can be be used to identify a quantity given the unit. For example: static boolean isAngularVelocity(AbstractUnit unit) {
* return unit.toSystemUnit().equals(RADIAN.divide(SECOND)); } assert(REVOLUTION.divide(MINUTE).isAngularVelocity()); // Returns true.
*
* @return the unscaled metric unit from which this unit is derived.
*/
protected abstract Unit toSystemUnit();
/**
* Returns the converter from this unit to its unscaled {@link #toSysemUnit System Unit} unit.
*
* @return getConverterTo(this.toSystemUnit())
* @see #toSI
*/
public abstract UnitConverter getSystemConverter();
/**
* Annotates the specified unit. Annotation does not change the unit semantic. Annotations are often written between curly braces behind units. For
* example:
* AbstractUnit PERCENT_VOL = Units.PERCENT.annotate("vol"); // "%{vol}" AbstractUnit KG_TOTAL =
* Units.KILOGRAM.annotate("total"); // "kg{total}" AbstractUnit RED_BLOOD_CELLS = Units.ONE.annotate("RBC"); // "{RBC}"
*
* Note: Annotation of system units are not considered themselves as system units.
*
* @param annotation
* the unit annotation.
* @return the annotated unit.
*/
public AnnotatedUnit annotate(String annotation) {
return new AnnotatedUnit<>(this, annotation);
}
/**
* Returns the abstract unit represented by the specified characters as per default format.
*
* Locale-sensitive unit parsing could be handled using {@link LocalUnitFormat} in subclasses of AbstractUnit.
*
*
* Note: The standard format supports dimensionless units. AbstractUnit PERCENT =
* AbstractUnit.parse("100").inverse().asType(Dimensionless.class);
*
*
* @param charSequence
* the character sequence to parse.
* @return SimpleUnitFormat.getInstance().parse(csq, new ParsePosition(0))
* @throws ParserException
* if the specified character sequence cannot be correctly parsed (e.g. not UCUM compliant).
*/
public static AbstractUnit parse(CharSequence charSequence) {
return SimpleUnitFormat.getInstance().parse(charSequence);
}
/**
* Returns the standard representation of this physics unit. The string produced for a given unit is always the same; it is not affected by the
* locale. It can be used as a canonical string representation for exchanging units, or as a key for a Hashtable, etc.
*
* Locale-sensitive unit parsing could be handled using {@link LocalUnitFormat} in subclasses of AbstractUnit.
*
* @return SimpleUnitFormat.getInstance().format(this)
*/
@Override
public String toString() {
return SimpleUnitFormat.getInstance().format(this);
}
// ///////////////////////////////////////////////////////
// Implements javax.measure.Unit interface //
// ///////////////////////////////////////////////////////
/**
* Returns the system unit (unscaled SI unit) from which this unit is derived. They can be be used to identify a quantity given the unit. For
* example:
* static boolean isAngularVelocity(AbstractUnit unit) {
return unit.getSystemUnit().equals(RADIAN.divide(SECOND));
}
*
assert(REVOLUTION.divide(MINUTE).isAngularVelocity()); // Returns true.
*
* @return the unscaled metric unit from which this unit is derived.
*/
@Override
public final Unit getSystemUnit() {
return toSystemUnit();
}
/**
* Indicates if this unit is compatible with the unit specified. To be compatible both units must be physics units having the same fundamental
* dimension.
*
* @param that
* the other unit.
* @return true
if this unit and that unit have equals fundamental dimension according to the current physics model; false
* otherwise.
*/
@Override
public final boolean isCompatible(Unit that) {
if ((this == that) || this.equals(that))
return true;
if (!(that instanceof AbstractUnit))
return false;
Dimension thisDimension = this.getDimension();
Dimension thatDimension = that.getDimension();
if (thisDimension.equals(thatDimension))
return true;
DimensionalModel model = DimensionalModel.current(); // Use
// dimensional
// analysis
// model.
return model.getFundamentalDimension(thisDimension).equals(model.getFundamentalDimension(thatDimension));
}
public boolean isEquivalentTo(Unit that) {
if (this.compareTo(that) == 0)
return true;
return this.getConverterTo(that).equals(that.getConverterTo(this));
}
public boolean isEquivalentOf(Unit that) {
return isEquivalentTo(that);
}
/**
* Casts this unit to a parameterized unit of specified nature or throw a ClassCastException if the dimension of the specified quantity and this
* unit's dimension do not match (regardless whether or not the dimensions are independent or not).
*
* @param type
* the quantity class identifying the nature of the unit.
* @throws ClassCastException
* if the dimension of this unit is different from the SI dimension of the specified type.
* @see Units#getUnit(Class)
*/
@SuppressWarnings("unchecked")
@Override
public final > AbstractUnit asType(Class type) {
Dimension typeDimension = QuantityDimension.of(type);
if ((typeDimension != null) && (!typeDimension.equals(this.getDimension())))
throw new ClassCastException("The unit: " + this + " is not compatible with quantities of type " + type);
return (AbstractUnit) this;
}
@Override
public abstract Map, Integer> getBaseUnits();
@Override
public abstract Dimension getDimension();
protected void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public String getSymbol() {
return symbol;
}
protected void setSymbol(String s) {
this.symbol = s;
}
@Override
public final UnitConverter getConverterTo(Unit that) throws UnconvertibleException {
if ((this == that) || this.equals(that))
return AbstractConverter.IDENTITY; // Shortcut.
Unit thisSystemUnit = this.getSystemUnit();
Unit thatSystemUnit = that.getSystemUnit();
if (!thisSystemUnit.equals(thatSystemUnit))
try {
return getConverterToAny(that);
} catch (IncommensurableException e) {
throw new UnconvertibleException(e);
}
UnitConverter thisToSI = this.getSystemConverter();
UnitConverter thatToSI = that.getConverterTo(thatSystemUnit);
return thatToSI.inverse().concatenate(thisToSI);
}
@SuppressWarnings("rawtypes")
@Override
public final UnitConverter getConverterToAny(Unit that) throws IncommensurableException, UnconvertibleException {
if (!isCompatible(that))
throw new IncommensurableException(this + " is not compatible with " + that);
AbstractUnit thatAbstr = (AbstractUnit) that; // Since both units are
// compatible they must
// be both physics
// units.
DimensionalModel model = DimensionalModel.current();
Unit thisSystemUnit = this.getSystemUnit();
UnitConverter thisToDimension = model.getDimensionalTransform(thisSystemUnit.getDimension()).concatenate(this.getSystemConverter());
Unit thatSystemUnit = thatAbstr.getSystemUnit();
UnitConverter thatToDimension = model.getDimensionalTransform(thatSystemUnit.getDimension()).concatenate(thatAbstr.getSystemConverter());
return thatToDimension.inverse().concatenate(thisToDimension);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public final AbstractUnit alternate(String symbol) {
return new AlternateUnit(this, symbol);
}
@Override
public final AbstractUnit transform(UnitConverter operation) {
Unit systemUnit = this.getSystemUnit();
UnitConverter cvtr;
if (this.isSystemUnit()) {
cvtr = this.getSystemConverter().concatenate(operation);
} else {
cvtr = operation;
}
if (cvtr.equals(AbstractConverter.IDENTITY) && systemUnit instanceof AbstractUnit) {
return (AbstractUnit) systemUnit;
} else {
return new TransformedUnit<>(null, this, systemUnit, cvtr);
}
}
@Override
public final AbstractUnit shift(double offset) {
if (offset == 0)
return this;
return transform(new AddConverter(offset));
}
@Override
public final AbstractUnit multiply(double factor) {
if (factor == 1)
return this;
if (isLongValue(factor))
return transform(new RationalConverter(BigInteger.valueOf((long) factor), BigInteger.ONE));
return transform(new MultiplyConverter(factor));
}
private static boolean isLongValue(double value) {
return !((value < Long.MIN_VALUE) || (value > Long.MAX_VALUE)) && Math.floor(value) == value;
}
/**
* Returns the product of this unit with the one specified.
*
*
* Note: If the specified unit (that) is not a physical unit, then that.multiply(this)
is returned.
*
*
* @param that
* the unit multiplicand.
* @return this * that
*/
@Override
public final AbstractUnit multiply(Unit that) {
if (that instanceof AbstractUnit)
return multiply((AbstractUnit) that);
// return that.multiply(this); // Commutatif.
return ProductUnit.getProductInstance(this, that);
}
/**
* Returns the product of this physical unit with the one specified.
*
* @param that
* the physical unit multiplicand.
* @return this * that
*/
protected final AbstractUnit multiply(AbstractUnit that) {
if (this.equals(ONE))
return that;
if (that.equals(ONE))
return this;
return ProductUnit.getProductInstance(this, that);
}
/**
* Returns the inverse of this physical unit.
*
* @return 1 / this
*/
@Override
public final AbstractUnit inverse() {
if (this.equals(ONE))
return this;
return ProductUnit.getQuotientInstance(ONE, this);
}
/**
* Returns the result of dividing this unit by the specifified divisor. If the factor is an integer value, the division is exact. For example:
*
*
*
* QUART = GALLON_LIQUID_US.divide(4); // Exact definition.
*
*
*
* @param divisor
* the divisor value.
* @return this unit divided by the specified divisor.
*/
@Override
public final AbstractUnit divide(double divisor) {
if (divisor == 1)
return this;
if (isLongValue(divisor))
return transform(new RationalConverter(BigInteger.ONE, BigInteger.valueOf((long) divisor)));
return transform(new MultiplyConverter(1.0 / divisor));
}
/**
* Returns the quotient of this unit with the one specified.
*
* @param that
* the unit divisor.
* @return this.multiply(that.inverse())
*/
@Override
public final AbstractUnit divide(Unit that) {
return this.multiply(that.inverse());
}
/**
* Returns the quotient of this physical unit with the one specified.
*
* @param that
* the physical unit divisor.
* @return this.multiply(that.inverse())
*/
protected final AbstractUnit divide(AbstractUnit that) {
return this.multiply(that.inverse());
}
/**
* Returns a unit equals to the given root of this unit.
*
* @param n
* the root's order.
* @return the result of taking the given root of this unit.
* @throws ArithmeticException
* if n == 0
or if this operation would result in an unit with a fractional exponent.
*/
@Override
public final AbstractUnit root(int n) {
if (n > 0)
return ProductUnit.getRootInstance(this, n);
else if (n == 0)
throw new ArithmeticException("Root's order of zero");
else
// n < 0
return ONE.divide(this.root(-n));
}
/**
* Returns a unit equals to this unit raised to an exponent.
*
* @param n
* the exponent.
* @return the result of raising this unit to the exponent.
*/
@Override
public final AbstractUnit pow(int n) {
if (n > 0)
return this.multiply(this.pow(n - 1));
else if (n == 0)
return ONE;
else
// n < 0
return ONE.divide(this.pow(-n));
}
/**
* Compares this unit to the specified unit. The default implementation compares the name and symbol of both this unit and the specified unit.
*
* @return a negative integer, zero, or a positive integer as this unit is less than, equal to, or greater than the specified unit.
*/
public int compareTo(Unit that) {
if (name != null && getSymbol() != null) {
return name.compareTo(that.getName()) + getSymbol().compareTo(that.getSymbol());
} else if (name == null) {
if (getSymbol() != null && that.getSymbol() != null) {
return getSymbol().compareTo(that.getSymbol());
} else {
return -1;
}
} else if (getSymbol() == null) {
if (name != null) {
return name.compareTo(that.getName());
} else {
return -1;
}
} else {
return -1;
}
}
// //////////////////////////////////////////////////////////////
// Ensures that sub-classes implements hashCode/equals method.
// //////////////////////////////////////////////////////////////
@Override
public abstract int hashCode();
@Override
public abstract boolean equals(Object that);
}