Fix CurrencyUnit (#4016)

Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
J-N-K 2024-01-05 18:20:43 +01:00 committed by GitHub
parent 8e7d5d880c
commit 36cafd765f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 51 additions and 108 deletions

View File

@ -12,8 +12,10 @@
*/
package org.openhab.core.internal.library.unit;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Map;
import java.util.Objects;
import javax.measure.UnitConverter;
@ -22,6 +24,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import tech.units.indriya.function.AbstractConverter;
import tech.units.indriya.function.Calculus;
/**
* The {@link CurrencyConverter} implements an {@link UnitConverter} for
@ -82,4 +85,28 @@ public class CurrencyConverter extends AbstractConverter {
public boolean isLinear() {
return true;
}
/**
* This is currently necessary because conversion of {@link tech.units.indriya.unit.ProductUnit}s requires a
* converter that is properly registered. This is currently not possible. We can't use the registered providers,
* because they only have package-private constructors.
*
* {@see https://github.com/unitsofmeasurement/indriya/issues/402}
*/
static {
// call to ensure map is initialized
Map<Class<? extends AbstractConverter>, Integer> normalFormOrder = (Map<Class<? extends AbstractConverter>, Integer>) Calculus
.getNormalFormOrder();
try {
Field field = Calculus.class.getDeclaredField("normalFormOrder");
field.setAccessible(true);
@SuppressWarnings("unchecked")
Map<Class<? extends AbstractConverter>, Integer> original = (Map<Class<? extends AbstractConverter>, Integer>) field
.get(null);
original.put(CurrencyConverter.class, 1000);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new IllegalStateException("Could not add currency converter", e);
}
}
}

View File

@ -108,8 +108,10 @@ public class CurrencyService {
Unit<Currency> baseCurrency = currencyProvider.getBaseCurrency();
((CurrencyUnit) BASE_CURRENCY).setSymbol(baseCurrency.getSymbol());
((CurrencyUnit) BASE_CURRENCY).setName(baseCurrency.getName());
unitFormatter.label(BASE_CURRENCY,
Objects.requireNonNullElse(baseCurrency.getSymbol(), baseCurrency.getName()));
unitFormatter.label(BASE_CURRENCY, baseCurrency.getName());
if (baseCurrency.getSymbol() != null) {
unitFormatter.alias(BASE_CURRENCY, baseCurrency.getSymbol());
}
currencyProvider.getAdditionalCurrencies().forEach(CurrencyUnits::addUnit);

View File

@ -19,16 +19,13 @@ import static org.eclipse.jdt.annotation.DefaultLocation.TYPE_BOUND;
import static org.openhab.core.library.unit.CurrencyUnits.BASE_CURRENCY;
import static tech.units.indriya.AbstractUnit.ONE;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Map;
import java.util.Objects;
import javax.measure.Dimension;
import javax.measure.IncommensurableException;
import javax.measure.Prefix;
import javax.measure.Quantity;
import javax.measure.UnconvertibleException;
import javax.measure.Unit;
import javax.measure.UnitConverter;
@ -39,18 +36,11 @@ import org.openhab.core.internal.library.unit.CurrencyConverter;
import org.openhab.core.internal.library.unit.CurrencyService;
import org.openhab.core.library.dimension.Currency;
import tech.units.indriya.AbstractUnit;
import tech.units.indriya.function.AbstractConverter;
import tech.units.indriya.function.AddConverter;
import tech.units.indriya.function.Calculus;
import tech.units.indriya.function.MultiplyConverter;
import tech.units.indriya.function.RationalNumber;
import tech.units.indriya.unit.AlternateUnit;
import tech.units.indriya.unit.ProductUnit;
import tech.units.indriya.unit.TransformedUnit;
import tech.units.indriya.unit.UnitDimension;
import tech.uom.lib.common.function.Nameable;
import tech.uom.lib.common.function.PrefixOperator;
import tech.uom.lib.common.function.SymbolSupplier;
/**
* The {@link CurrencyUnit} is a UoM compatible unit for currencies.
@ -58,8 +48,7 @@ import tech.uom.lib.common.function.SymbolSupplier;
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault({ PARAMETER, RETURN_TYPE, FIELD, TYPE_BOUND })
public final class CurrencyUnit implements Unit<Currency>, Comparable<Unit<Currency>>, PrefixOperator<Currency>,
Nameable, Serializable, SymbolSupplier {
public final class CurrencyUnit extends AbstractUnit<Currency> {
private static final long serialVersionUID = -1L;
private static final Dimension DIMENSION = UnitDimension.parse('$');
@ -82,7 +71,12 @@ public final class CurrencyUnit implements Unit<Currency>, Comparable<Unit<Curre
}
public UnitConverter getSystemConverter() {
return AbstractConverter.IDENTITY;
return internalGetConverterTo(getSystemUnit());
}
@Override
protected Unit<Currency> toSystemUnit() {
return BASE_CURRENCY;
}
@Override
@ -90,26 +84,6 @@ public final class CurrencyUnit implements Unit<Currency>, Comparable<Unit<Curre
return getName();
}
@Override
public Unit<Currency> getSystemUnit() {
return this;
}
@Override
public boolean isCompatible(@NonNullByDefault({}) Unit<?> that) {
return DIMENSION.equals(that.getDimension());
}
@SuppressWarnings("unchecked")
@Override
public @NonNullByDefault({}) <T extends Quantity<T>> Unit<T> asType(@NonNullByDefault({}) Class<T> type) {
Dimension typeDimension = UnitDimension.of(type);
if (typeDimension != null && !typeDimension.equals(this.getDimension())) {
throw new ClassCastException("The unit: " + this + " is not compatible with quantities of type " + type);
}
return (Unit<T>) this;
}
@Override
public @NonNullByDefault({}) Map<? extends Unit<?>, Integer> getBaseUnits() {
return Map.of();
@ -120,7 +94,7 @@ public final class CurrencyUnit implements Unit<Currency>, Comparable<Unit<Curre
return DIMENSION;
}
public void setName(String name) {
public void setName(@NonNullByDefault({}) String name) {
this.name = name;
}
@ -138,41 +112,6 @@ public final class CurrencyUnit implements Unit<Currency>, Comparable<Unit<Curre
this.symbol = s;
}
@Override
public final UnitConverter getConverterTo(@NonNullByDefault({}) Unit<Currency> that) throws UnconvertibleException {
return internalGetConverterTo(that);
}
@SuppressWarnings("unchecked")
@Override
public final @NonNullByDefault({}) UnitConverter getConverterToAny(@NonNullByDefault({}) Unit<?> that)
throws IncommensurableException, UnconvertibleException {
if (!isCompatible(that)) {
throw new IncommensurableException(this + " is not compatible with " + that);
}
return internalGetConverterTo((Unit<Currency>) that);
}
@Override
public final Unit<Currency> alternate(@NonNullByDefault({}) String newSymbol) {
return new AlternateUnit<>(this, newSymbol);
}
@Override
public final Unit<Currency> transform(@NonNullByDefault({}) UnitConverter operation) {
return operation.isIdentity() ? this : new TransformedUnit<>(null, this, this, operation);
}
@Override
public Unit<Currency> shift(@NonNullByDefault({}) Number offset) {
return Calculus.currentNumberSystem().isZero(offset) ? this : transform(new AddConverter(offset));
}
@Override
public Unit<Currency> multiply(@NonNullByDefault({}) Number factor) {
return Calculus.currentNumberSystem().isOne(factor) ? this : transform(MultiplyConverter.of(factor));
}
@Override
public Unit<Currency> shift(double offset) {
return shift(RationalNumber.of(offset));
@ -214,42 +153,6 @@ public final class CurrencyUnit implements Unit<Currency>, Comparable<Unit<Curre
"Could not get factor for converting " + this.getName() + " to " + that.getName());
}
@Override
public final Unit<?> multiply(@NonNullByDefault({}) Unit<?> that) {
return that.equals(ONE) ? this : ProductUnit.ofProduct(this, that);
}
@Override
public final Unit<?> inverse() {
return ProductUnit.ofQuotient(ONE, this);
}
@Override
public final Unit<Currency> divide(@NonNullByDefault({}) Number divisor) {
if (Calculus.currentNumberSystem().isOne(divisor)) {
return this;
}
BigDecimal factor = BigDecimal.ONE.divide(new BigDecimal(divisor.toString()), MathContext.DECIMAL128);
return transform(MultiplyConverter.of(factor));
}
@Override
public final Unit<?> divide(@NonNullByDefault({}) Unit<?> that) {
return this.multiply(that.inverse());
}
@Override
public final Unit<?> root(int n) {
if (n > 0) {
return ProductUnit.ofRoot(this, n);
} else if (n == 0) {
throw new ArithmeticException("Root's order of zero");
} else {
// n < 0
return ONE.divide(this.root(-n));
}
}
@Override
public Unit<?> pow(int n) {
if (n > 0) {

View File

@ -33,6 +33,7 @@ import org.openhab.core.internal.library.unit.CurrencyService;
import org.openhab.core.library.dimension.Currency;
import org.openhab.core.library.dimension.EnergyPrice;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.types.util.UnitUtils;
/**
* The {@link CurrencyUnitTest} contains tests for the currency units
@ -97,6 +98,16 @@ public class CurrencyUnitTest {
assertThat(price.doubleValue(), closeTo(1.25, 1E-4));
}
@Test
public void testEnergyPriceConversion() {
QuantityType<EnergyPrice> price = new QuantityType<>("0.25 EUR/kWh");
QuantityType<EnergyPrice> convertedPrice = price.toUnit("DKK/kWh");
assertThat(convertedPrice, is(notNullValue()));
assertThat(convertedPrice.getUnit(), is(UnitUtils.parseUnit("DKK/kWh")));
assertThat(convertedPrice.doubleValue(), closeTo(1.8625, 1e-4));
}
private static class TestCurrencyProvider implements CurrencyProvider {
public static final Unit<Currency> EUR = new CurrencyUnit("EUR", "");
public static final Unit<Currency> DKK = new CurrencyUnit("DKK", null);