Skip to content

Commit

Permalink
Fix CurrencyUnit (#4016)
Browse files Browse the repository at this point in the history
Signed-off-by: Jan N. Klug <github@klug.nrw>
  • Loading branch information
J-N-K authored Jan 5, 2024
1 parent 8e7d5d8 commit 36cafd7
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,6 +24,7 @@
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
Expand Down Expand Up @@ -82,4 +85,28 @@ public boolean isIdentity() {
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);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,10 @@ private synchronized void enableProvider(CurrencyProvider currencyProvider) {
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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,13 @@
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;
Expand All @@ -39,27 +36,19 @@
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.
*
* @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('$');
Expand All @@ -82,32 +71,17 @@ public CurrencyUnit(String name, @Nullable String symbol) throws IllegalArgument
}

public UnitConverter getSystemConverter() {
return AbstractConverter.IDENTITY;
return internalGetConverterTo(getSystemUnit());
}

@Override
public String toString() {
return getName();
protected Unit<Currency> toSystemUnit() {
return BASE_CURRENCY;
}

@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;
public String toString() {
return getName();
}

@Override
Expand All @@ -120,7 +94,7 @@ public Dimension getDimension() {
return DIMENSION;
}

public void setName(String name) {
public void setName(@NonNullByDefault({}) String name) {
this.name = name;
}

Expand All @@ -138,41 +112,6 @@ public void setSymbol(@Nullable String s) {
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));
Expand Down Expand Up @@ -214,42 +153,6 @@ private UnitConverter internalGetConverterTo(Unit<Currency> that) throws Unconve
"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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
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
Expand Down Expand Up @@ -97,6 +98,16 @@ public void testPriceCalculation() {
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);
Expand Down

0 comments on commit 36cafd7

Please sign in to comment.