| /* |
| * 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.Map; |
| import java.util.Objects; |
| import java.io.Serializable; |
| import java.io.ObjectStreamException; |
| import javax.measure.Unit; |
| import javax.measure.Quantity; |
| import javax.measure.Dimension; |
| import javax.measure.UnitConverter; |
| import javax.measure.UnconvertibleException; |
| import javax.measure.IncommensurableException; |
| import javax.measure.spi.QuantityFactory; |
| import org.apache.sis.util.Characters; |
| import org.apache.sis.util.ArgumentChecks; |
| import org.apache.sis.util.ObjectConverters; |
| import org.apache.sis.util.ComparisonMode; |
| import org.apache.sis.util.resources.Errors; |
| import org.apache.sis.internal.converter.SurjectiveConverter; |
| import org.apache.sis.math.Fraction; |
| |
| |
| /** |
| * Implementation of base, alternate and derived units (see {@link AbstractUnit} for a description of unit kinds). |
| * A {@code SystemUnit} is a base or alternate unit if associated to a base {@link UnitDimension}, or is a derived |
| * units otherwise. No other type is allowed since {@code SystemUnit} is always a combination of fundamental units |
| * without scale factor or offset. |
| * |
| * @author Martin Desruisseaux (MPO, Geomatys) |
| * @version 1.1 |
| * |
| * @param <Q> the kind of quantity to be measured using this units. |
| * |
| * @since 0.8 |
| * @module |
| */ |
| final class SystemUnit<Q extends Quantity<Q>> extends AbstractUnit<Q> implements QuantityFactory<Q> { |
| /** |
| * For cross-version compatibility. |
| */ |
| private static final long serialVersionUID = 4097466138698631914L; |
| |
| /** |
| * The non-empty symbol for {@link Units#UNITY}. |
| */ |
| static final String ONE = "1"; |
| |
| /** |
| * The type of quantity that uses this unit, or {@code null} if unknown. |
| * This field should be null only when this unit is the result of an arithmetic |
| * operation and that result can not be mapped to a known {@link Quantity} subtype. |
| */ |
| final Class<Q> quantity; |
| |
| /** |
| * The dimension of this unit of measurement. Can not be null. |
| */ |
| final UnitDimension dimension; |
| |
| /** |
| * The factory to use for creating quantities, or {@code null} if none. |
| * This field does not need to be serialized because {@link AbstractUnit#readResolve()} |
| * replaces deserialized instances by corresponding {@link Units} hard-coded instances. |
| */ |
| final transient ScalarFactory<Q> factory; |
| |
| /** |
| * Units for the same quantity but with scale factors that are not the SI one. |
| * This is initialized by {@link Units} only and shall not change anymore after. |
| * All units in this array shall use an instance of {@link LinearConverter}. |
| * |
| * @see #related(int) |
| */ |
| private transient ConventionalUnit<Q>[] related; |
| |
| /** |
| * Creates a new unit having the given symbol and EPSG code. |
| * |
| * @param quantity the type of quantity that uses this unit, or {@code null} if unknown. |
| * @param dimension the unit dimension. |
| * @param symbol the unit symbol, or {@code null} if this unit has no specific symbol. |
| * @param scope {@link UnitRegistry#SI}, {@link UnitRegistry#ACCEPTED}, other constants or 0 if unknown. |
| * @param epsg the EPSG code, or 0 if this unit has no EPSG code. |
| * @param factory the factory to use for creating quantities, or {@code null} if none. |
| */ |
| SystemUnit(final Class<Q> quantity, final UnitDimension dimension, final String symbol, |
| final byte scope, final short epsg, final ScalarFactory<Q> factory) |
| { |
| super(symbol, scope, epsg); |
| this.quantity = quantity; |
| this.dimension = dimension; |
| this.factory = factory; |
| } |
| |
| /** |
| * Returns a unit of the given dimension with default name and symbol. |
| * This method is invoked for creating the result of arithmetic operations. |
| * If there is no predefined unit for the given dimension, then the new unit may be allocated a symbol derived from |
| * this unit's symbol. A new symbol is created only if this unit symbol and the {@code other} unit symbol are simple |
| * (for example "m" but not "m²", or "N" but not "N/m"). |
| * |
| * @param operation symbol to write after the symbol of this unit for generating the new unit symbol, or 0 |
| * for not inferring new symbol. Ignored if the condition documented in javadoc does not hold. |
| * @param other other units to append after the operation symbol, or {@code null} if none or should be ignored. |
| * Ignored if the condition documented in javadoc does not hold. |
| */ |
| private SystemUnit<?> create(final UnitDimension newDimension, final char operation, final Unit<?> other) { |
| /* |
| * Check if we are computing the inverse of the other unit. We are computing inverse |
| * if this unit is unity ("1") and is used as the numerator of a division. The unity |
| * symbol is an empty string but we will format it as "1". |
| */ |
| String ts = getSymbol(); |
| final boolean inverse = (ts != null && ts.isEmpty() && operation == DIVIDE); |
| /* |
| * Check if the SystemUnit to create is known to Units, provided that no dimensionless units |
| * is involved. If a dimensionless unit is involved, we will try to build a symbol before to |
| * check if the unit is known to Units. The reason is that there is many dimensionless units |
| * with different symbols (rad, sr, psu, …). |
| */ |
| final boolean deferred = newDimension.isDimensionless() || (!inverse && dimension.isDimensionless()) || |
| (other != null && UnitDimension.isDimensionless(other.getDimension())); |
| if (!deferred) { |
| final SystemUnit<?> result = Units.get(newDimension); |
| if (result != null) return result; |
| } |
| /* |
| * Try to create a unit symbol as the concatenation of the symbols of the two units, |
| * with the operation symbol between them. If we can not, `symbol` will stay null. |
| */ |
| String symbol = null; |
| if (operation != 0) { |
| symbol = inferSymbol(operation, other); |
| } |
| /* |
| * Performs now the check that we did not performed at the |
| * beginning of this method if any component was unitless. |
| * The difference is that we take unit symbol in account. |
| */ |
| if (deferred) { |
| final SystemUnit<?> result = Units.get(newDimension); |
| if (result != null && result.sameSymbol(symbol)) { |
| return result; |
| } |
| } |
| if (newDimension == dimension && sameSymbol(symbol)) { |
| return this; |
| } |
| return new SystemUnit<>(null, newDimension, symbol, (byte) 0, (short) 0, null); |
| } |
| |
| /** |
| * Returns {@code true} if the given symbol is null or equals to the symbol of this unit. |
| */ |
| private boolean sameSymbol(final String symbol) { |
| return (symbol == null) || symbol.equals(getSymbol()); |
| } |
| |
| /** |
| * Returns the dimension of this unit. |
| * Two units {@code u1} and {@code u2} are {@linkplain #isCompatible(Unit) compatible} |
| * if and only if {@code u1.getDimension().equals(u2.getDimension())}. |
| * |
| * @return the dimension of this unit. |
| * |
| * @see #isCompatible(Unit) |
| */ |
| @Override |
| public Dimension getDimension() { |
| return dimension; |
| } |
| |
| /** |
| * Returns the unscaled system unit from which this unit is derived. |
| * Since this unit is already a base, alternate or derived unit, this method returns {@code true}. |
| * |
| * @return {@code this} |
| */ |
| @Override |
| public SystemUnit<Q> getSystemUnit() { |
| return this; |
| } |
| |
| /** |
| * Returns the base units and their exponent whose product is this unit, |
| * or {@code null} if this unit is a base unit (not a product of existing units). |
| * |
| * @return the base units and their exponent making up this unit. |
| */ |
| @Override |
| public Map<SystemUnit<?>, Integer> getBaseUnits() { |
| final Map<UnitDimension,Integer> dim = dimension.getBaseDimensions(); |
| if (dim == null) { |
| return null; // This unit is associated to a base dimension. |
| } |
| return ObjectConverters.derivedKeys(dim, DimToUnit.INSTANCE, Integer.class); |
| } |
| |
| /** |
| * Returns the base units used by Apache SIS implementations. |
| * Contrarily to {@link #getBaseUnits()}, this method never returns {@code null}. |
| */ |
| @Override |
| final Map<SystemUnit<?>, Fraction> getBaseSystemUnits() { |
| return ObjectConverters.derivedKeys(dimension.components, DimToUnit.INSTANCE, Fraction.class); |
| } |
| |
| /** |
| * The converter for replacing the keys in the {@link SystemUnit#getBaseUnits()} map from {@link UnitDimension} |
| * instances to {@link SystemUnit} instances. We apply conversions on the fly instead of extracting the data in |
| * a new map once for all because the copy may fail if an entry contains a rational instead of an integer power. |
| * With on-the-fly conversions, the operation will not fail if the user never ask for that particular value. |
| */ |
| private static final class DimToUnit extends SurjectiveConverter<UnitDimension, SystemUnit<?>> implements Serializable { |
| /** |
| * For cross-version compatibility. |
| */ |
| private static final long serialVersionUID = 7545067577687885675L; |
| |
| /** |
| * The unique instance used by {@link SystemUnit#getBaseUnits()}. |
| */ |
| static final DimToUnit INSTANCE = new DimToUnit(); |
| |
| /** |
| * Constructor for the singleton {@link #INSTANCE}. |
| */ |
| private DimToUnit() { |
| } |
| |
| /** |
| * Returns the type of key values in the map returned by {@link UnitDimension#getBaseDimensions()}. |
| */ |
| @Override |
| public Class<UnitDimension> getSourceClass() { |
| return UnitDimension.class; |
| } |
| |
| /** |
| * Returns the type of key values in the map to be returned by {@link SystemUnit#getBaseUnits()}. |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| public Class<SystemUnit<?>> getTargetClass() { |
| return (Class) SystemUnit.class; |
| } |
| |
| /** |
| * Returns the unit associated to the given dimension, or {@code null} if none. |
| */ |
| @Override |
| public SystemUnit<?> apply(final UnitDimension dim) { |
| return Units.get(dim); |
| } |
| |
| /** |
| * Invoked on deserialization for replacing the deserialized instance by the unique instance. |
| */ |
| Object readResolve() throws ObjectStreamException { |
| return INSTANCE; |
| } |
| } |
| |
| /** |
| * Casts this unit to a parameterized unit of specified nature or throw a {@code ClassCastException} |
| * if the dimension of the specified quantity and this unit's dimension do not match. |
| * |
| * @param <T> the type of the quantity measured by the unit. |
| * @param type the quantity class identifying the nature of the unit. |
| * @return this unit parameterized with the specified type. |
| * @throws ClassCastException if the dimension of this unit is different from the specified quantity dimension. |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| public <T extends Quantity<T>> Unit<T> asType(final Class<T> type) throws ClassCastException { |
| ArgumentChecks.ensureNonNull("type", type); |
| if (type == quantity) { |
| if (getSymbol() == null) { |
| // If this unit has no symbol, opportunistically supply a symbol if we find it. |
| final SystemUnit<T> unit = Units.get(type); |
| if (unit != null) { |
| return unit; |
| } |
| } |
| return (Unit<T>) this; |
| } |
| /* |
| * Verifies what are the expected dimensions of the given type by searching for the corresponding unit. |
| * If we find that unit, returns it on the assumption that its symbol is right while the symbol of this |
| * unit may no longer be right for the given type. If we can not find a pre-defined units, then create |
| * a new unit with the requested type but no symbol since we do not know yet what the symbol should be |
| * for the new quantity. |
| */ |
| SystemUnit<T> unit = Units.get(type); |
| if (unit == null) { |
| unit = new SystemUnit<>(type, dimension, null, (byte) 0, (short) 0, null); // Intentionally no symbol. |
| } |
| if (!dimension.equals(unit.dimension)) { |
| throw new ClassCastException(Errors.format(Errors.Keys.IncompatibleUnitDimension_5, new Object[] { |
| this, (quantity != null) ? quantity.getSimpleName() : "?", dimension, |
| type.getSimpleName(), unit.dimension})); |
| } |
| return unit; |
| } |
| |
| /** |
| * Returns {@code true} if this unit is equals to the given unit ignoring name, symbol and EPSG code. |
| * This method should always returns {@code true} if parameterized type has not been compromised with |
| * raw types or unchecked casts. |
| * |
| * @param other the other unit, which must be a system unit. |
| */ |
| final boolean equalsIgnoreMetadata(final Unit<Q> other) { |
| if (quantity != null && other instanceof SystemUnit<?>) { |
| /* |
| * For SIS implementation, we just need to compare the quantity class, if known. |
| * Two units for the same quantity implies that they are also for the same dimension. |
| */ |
| final Class<?> c = ((SystemUnit<Q>) other).quantity; |
| if (c != null) { |
| return (quantity == c); |
| } |
| } |
| /* |
| * For foreigner implementations, comparing the units dimension is better than nothing. |
| * But this check is not as reliable as comparing the quantity classes. |
| */ |
| assert other == other.getSystemUnit() : other; |
| return dimension.equals(other.getDimension()); |
| } |
| |
| /** |
| * Returns a converter of numeric values from this unit to another unit of same type. |
| * |
| * @param unit the unit of same type to which to convert the numeric values. |
| * @return the converter from this unit to {@code that} unit. |
| * @throws UnconvertibleException if the converter can not be constructed. |
| */ |
| @Override |
| public UnitConverter getConverterTo(final Unit<Q> unit) throws UnconvertibleException { |
| ArgumentChecks.ensureNonNull("unit", unit); |
| final Unit<Q> step = unit.getSystemUnit(); |
| if (step != this && !equalsIgnoreMetadata(step)) { |
| // Should never occur unless parameterized type has been compromised. |
| throw new UnconvertibleException(incompatible(unit)); |
| } |
| if (step == unit) { |
| return IdentityConverter.INSTANCE; |
| } |
| /* |
| * At this point we know that the given units is not a system unit. Ask the conversion |
| * FROM the given units (before to inverse it) instead of TO the given units because |
| * in Apache SIS implementation, the former returns directly ConventionalUnit.toTarget |
| * while the later implies a recursive call to this method. |
| */ |
| return unit.getConverterTo(step).inverse(); |
| } |
| |
| /** |
| * Returns a converter from this unit to the specified unit of unknown type. |
| * This method can be used when the quantity type of the specified unit is unknown at compile-time |
| * or when dimensional analysis allows for conversion between units of different type. |
| * |
| * @param unit the unit to which to convert the numeric values. |
| * @return the converter from this unit to {@code that} unit. |
| * @throws IncommensurableException if this unit is not {@linkplain #isCompatible(Unit) compatible} with {@code that} unit. |
| * |
| * @see #isCompatible(Unit) |
| */ |
| @Override |
| public UnitConverter getConverterToAny(final Unit<?> unit) throws IncommensurableException { |
| ArgumentChecks.ensureNonNull("unit", unit); |
| final Unit<?> step = unit.getSystemUnit(); |
| if (step != this && !isCompatible(step)) { |
| throw new IncommensurableException(incompatible(unit)); |
| } |
| if (step == unit) { |
| return IdentityConverter.INSTANCE; |
| } |
| // Same remark than in getConverterTo(Unit). |
| return unit.getConverterToAny(step).inverse(); |
| } |
| |
| /** |
| * Returns a system unit equivalent to this unscaled standard unit but used in expressions |
| * to distinguish between quantities of a different nature but of the same dimensions. |
| * |
| * <p>The most important alternate unit in Apache SIS is {@link Units#RADIAN}, defined as below:</p> |
| * |
| * {@preformat java |
| * Unit<Angle> RADIAN = ONE.alternate("rad").asType(Angle.class); |
| * } |
| * |
| * @param symbol the new symbol for the alternate unit. |
| * @return the alternate unit. |
| * @throws IllegalArgumentException if the specified symbol is already associated to a different unit. |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| public Unit<Q> alternate(final String symbol) { |
| ArgumentChecks.ensureNonEmpty("symbol", symbol); |
| final int c = invalidCharForSymbol(symbol, 0, false); |
| if (c >= 0) { |
| throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalCharacter_2, |
| "symbol", String.valueOf(Character.toChars(c)))); |
| } |
| if (symbol.equals(getSymbol())) { |
| return this; |
| } |
| final SystemUnit<Q> alt = new SystemUnit<>(quantity, dimension, symbol, (byte) 0, (short) 0, factory); |
| if (quantity != null) { |
| /* |
| * Use the cache only if this unit has a non-null quantity type. Do not use the cache even |
| * in read-only mode when `quantity` is null because we would be unable to guarantee that |
| * the parameterized type <Q> is correct. |
| */ |
| final Object existing = UnitRegistry.putIfAbsent(symbol, alt); |
| if (existing != null) { |
| if (existing instanceof SystemUnit<?>) { |
| final SystemUnit<?> unit = (SystemUnit<?>) existing; |
| if (quantity.equals(unit.quantity) && dimension.equals(unit.dimension)) { |
| return (SystemUnit<Q>) unit; |
| } |
| } |
| throw new IllegalArgumentException(Errors.format(Errors.Keys.ElementAlreadyPresent_1, symbol)); |
| } |
| /* |
| * This method may be invoked for a new quantity, after a call to `asType(Class)`. |
| * Try to register the new unit for that Quantity. But if another unit is already |
| * registered for that Quantity, this is not necessarily an error. |
| */ |
| UnitRegistry.putIfAbsent(quantity, alt); |
| } |
| return alt; |
| } |
| |
| /** |
| * Returns the product of this unit with the one specified. |
| * |
| * @param multiplier the unit multiplier. |
| * @return {@code this} × {@code multiplier} |
| */ |
| @Override |
| public Unit<?> multiply(final Unit<?> multiplier) { |
| ArgumentChecks.ensureNonNull("multiplier", multiplier); |
| if (multiplier == this) return pow(2); // For formating e.g. "K²" instead of "K⋅K". |
| return product(multiplier, false); |
| } |
| |
| /** |
| * Returns the quotient of this unit with the one specified. |
| * |
| * @param divisor the unit divisor. |
| * @return {@code this} ∕ {@code divisor} |
| */ |
| @Override |
| public Unit<?> divide(final Unit<?> divisor) { |
| ArgumentChecks.ensureNonNull("divisor", divisor); |
| return product(divisor, true); |
| } |
| |
| /** |
| * Implementation of {@link #multiply(Unit)} and {@link #divide(Unit)} methods. |
| * |
| * @param inverse wether to use the inverse of {@code other}. |
| */ |
| private <T extends Quantity<T>> Unit<?> product(final Unit<T> other, final boolean inverse) { |
| final Unit<T> intermediate = other.getSystemUnit(); |
| final Dimension dim = intermediate.getDimension(); |
| final UnitDimension newDimension; |
| final char operation; |
| if (inverse) { |
| operation = DIVIDE; |
| newDimension = dimension.divide(dim); |
| } else { |
| operation = MULTIPLY; |
| newDimension = dimension.multiply(dim); |
| } |
| final boolean transformed = (intermediate != other); |
| Unit<?> result = create(newDimension, operation, transformed ? null : other); |
| if (transformed) { |
| UnitConverter c = other.getConverterTo(intermediate); |
| if (!c.isLinear()) { |
| throw new IllegalArgumentException(Errors.format(Errors.Keys.NonRatioUnit_1, other)); |
| } |
| if (!c.isIdentity()) { |
| if (inverse) c = c.inverse(); |
| result = result.transform(c); |
| /* |
| * If the system unit product is an Apache SIS implementation, try to infer a unit symbol |
| * to be given to our customized `transform` method. Otherwise fallback on standard API. |
| */ |
| result = inferSymbol(result, operation, other); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * 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 Unit<?> pow(final int n) { |
| switch (n) { |
| case 0: return Units.UNITY; |
| case 1: return this; |
| default: { |
| final char p = (n >= 0 && n <= 9) ? Characters.toSuperScript((char) ('0' + n)) : 0; |
| return create(dimension.pow(n), p, null); |
| } |
| } |
| } |
| |
| /** |
| * 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 {@code n == 0}. |
| */ |
| @Override |
| public Unit<?> root(final int n) { |
| return create(dimension.root(n), (char) 0, null); |
| } |
| |
| /** |
| * Returns the unit derived from this unit using the specified converter. |
| * |
| * @param operation the converter from the transformed unit to this unit. |
| * @return the unit after the specified transformation. |
| */ |
| @Override |
| public Unit<Q> transform(UnitConverter operation) { |
| ArgumentChecks.ensureNonNull("operation", operation); |
| AbstractUnit<Q> base = this; |
| final ConventionalUnit<Q> pseudo = Prefixes.pseudoSystemUnit(this); |
| if (pseudo != null) { |
| /* |
| * Special case for Units.KILOGRAM, to be replaced by Units.GRAM so a prefix can be computed. |
| * The kilogram may appear in an expression like "kg/m", which we want to replace by "g/m". |
| * |
| * Note: we could argue that this block should be UnitFormat's work rather than SystemUnit. |
| * We perform this work here because this unit symbol may be different than what UnitFormat |
| * would infer, for example because of symbols recorded by sequence of Unit.multiply(Unit) |
| * and Unit.divide(Unit) operations. |
| */ |
| operation = operation.concatenate(pseudo.toTarget.inverse()); |
| base = pseudo; |
| } |
| return ConventionalUnit.create(base, operation); |
| } |
| |
| /** |
| * Invoked by {@link Units} initializer before to fill the {@link #related} array. |
| * We define this method only for isolating the generic array creation. |
| */ |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| final void related(final int n) { |
| if (related != null) { |
| throw new IllegalStateException(); |
| } |
| related = new ConventionalUnit[n]; |
| } |
| |
| /** |
| * Returns units for the same quantity but with scale factors that are not the SI one, or {@code null} if none. |
| * This method returns a direct reference to the internal field; caller shall not modify. |
| */ |
| @Override |
| @SuppressWarnings("ReturnOfCollectionOrArrayField") |
| final ConventionalUnit<Q>[] related() { |
| return related; |
| } |
| |
| /** |
| * Compares this unit with the given object for equality, |
| * optionally ignoring metadata and rounding errors. |
| * |
| * @param other the other object to compare with this unit, or {@code null}. |
| * @return {@code true} if the given object is equal to this unit. |
| */ |
| @Override |
| public boolean equals(final Object other, final ComparisonMode mode) { |
| if (other == this) { |
| return true; |
| } |
| if (super.equals(other, mode)) { |
| final SystemUnit<?> that = (SystemUnit<?>) other; |
| return Objects.equals(quantity, that.quantity) && dimension.equals(that.dimension); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns a hash code value for this unit. |
| */ |
| @Override |
| public int hashCode() { |
| return super.hashCode() + 37 * dimension.hashCode(); |
| } |
| |
| /** |
| * Creates a quantity for the given value and unit of measurement. |
| */ |
| @Override |
| public Quantity<Q> create(final Number value, final Unit<Q> unit) { |
| ArgumentChecks.ensureNonNull("value", value); |
| ArgumentChecks.ensureNonNull("unit", unit); |
| final double v = AbstractConverter.doubleValue(value); |
| if (factory != null) { |
| return factory.create(v, unit); |
| } else if (quantity != null) { |
| return ScalarFallback.factory(v, unit, quantity); |
| } else { |
| return new Scalar<>(v, unit); |
| } |
| } |
| } |