Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix CurrencyUnit #4016

Merged
merged 1 commit into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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