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

[UoM] Add currency handling #3503

Merged
merged 4 commits into from
Dec 16, 2023
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
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.config.core;

import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.unit.CurrencyProvider;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;

/**
* The {@link CurrencyServiceConfigOptionProvider} is an implementation of {@link ConfigOptionProvider} for the
* available currency providers.
*
* @author Jan N. Klug - Initial contribution
*/
@Component(service = ConfigOptionProvider.class)
@NonNullByDefault
public class CurrencyServiceConfigOptionProvider implements ConfigOptionProvider {

private final List<CurrencyProvider> currencyProviders = new CopyOnWriteArrayList<>();

@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
public void addCurrencyProvider(CurrencyProvider currencyProvider) {
currencyProviders.add(currencyProvider);
}

public void removeCurrencyProvider(CurrencyProvider currencyProvider) {
currencyProviders.remove(currencyProvider);
}

@Override
public @Nullable Collection<ParameterOption> getParameterOptions(URI uri, String param, @Nullable String context,
@Nullable Locale locale) {
if ("system:units".equals(uri.toString()) && param.equals("currencyProvider")) {
return currencyProviders.stream().map(this::mapProvider).toList();
}
return null;
}

private ParameterOption mapProvider(CurrencyProvider currencyProvider) {
String providerName = currencyProvider.getName();
int lastDot = providerName.lastIndexOf(".");
String providerDescription = lastDot > -1 ? providerName.substring(lastDot + 1) : providerName;
return new ParameterOption(providerName, providerDescription);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import org.osgi.service.component.annotations.Component;

/**
* {@link ConfigOptionProvider} that provides a list of
* A {@link ConfigOptionProvider} that provides a list of config options for the i18n service
*
* @author Simon Kaufmann - Initial contribution
* @author Erdoan Hadzhiyusein - Added time zone
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,17 @@
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.i18n.UnitProvider;
import org.openhab.core.library.dimension.ArealDensity;
import org.openhab.core.library.dimension.Currency;
import org.openhab.core.library.dimension.DataAmount;
import org.openhab.core.library.dimension.DataTransferRate;
import org.openhab.core.library.dimension.Density;
import org.openhab.core.library.dimension.ElectricConductivity;
import org.openhab.core.library.dimension.EnergyPrice;
import org.openhab.core.library.dimension.Intensity;
import org.openhab.core.library.dimension.RadiationSpecificActivity;
import org.openhab.core.library.dimension.VolumetricFlowRate;
import org.openhab.core.library.types.PointType;
import org.openhab.core.library.unit.CurrencyUnits;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
Expand Down Expand Up @@ -130,6 +133,7 @@ public class I18nProviderImpl
public static final String REGION = "region";
public static final String VARIANT = "variant";
private @Nullable Locale locale;
private @Nullable String currencyCode;

// TranslationProvider
private final ResourceBundleTracker resourceBundleTracker;
Expand Down Expand Up @@ -395,6 +399,7 @@ public static Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extend
addDefaultUnit(dimensionMap, Area.class, SIUnits.SQUARE_METRE, ImperialUnits.SQUARE_FOOT);
addDefaultUnit(dimensionMap, ArealDensity.class, Units.DOBSON_UNIT);
addDefaultUnit(dimensionMap, CatalyticActivity.class, Units.KATAL);
addDefaultUnit(dimensionMap, Currency.class, CurrencyUnits.BASE_CURRENCY);
addDefaultUnit(dimensionMap, DataAmount.class, Units.BYTE);
addDefaultUnit(dimensionMap, DataTransferRate.class, Units.MEGABIT_PER_SECOND);
addDefaultUnit(dimensionMap, Density.class, Units.KILOGRAM_PER_CUBICMETRE);
Expand All @@ -420,6 +425,7 @@ public static Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extend
addDefaultUnit(dimensionMap, Mass.class, SIUnits.KILOGRAM, ImperialUnits.POUND);
addDefaultUnit(dimensionMap, Power.class, Units.WATT);
addDefaultUnit(dimensionMap, Pressure.class, HECTO(SIUnits.PASCAL), ImperialUnits.INCH_OF_MERCURY);
addDefaultUnit(dimensionMap, EnergyPrice.class, CurrencyUnits.BASE_ENERGY_PRICE);
addDefaultUnit(dimensionMap, RadiationDoseAbsorbed.class, Units.GRAY);
addDefaultUnit(dimensionMap, RadiationDoseEffective.class, Units.SIEVERT);
addDefaultUnit(dimensionMap, Radioactivity.class, Units.BECQUEREL);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.internal.library.unit;

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Objects;

import javax.measure.UnitConverter;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

import tech.units.indriya.function.AbstractConverter;

/**
* The {@link CurrencyConverter} implements an {@link UnitConverter} for
* {@link org.openhab.core.library.unit.CurrencyUnit}
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class CurrencyConverter extends AbstractConverter {

private final BigDecimal factor;

public CurrencyConverter(BigDecimal factor) {
this.factor = factor;
}

@Override
public boolean equals(@Nullable Object cvtr) {
return cvtr instanceof CurrencyConverter currencyConverter && factor.equals(currencyConverter.factor);
}

@Override
public int hashCode() {
return Objects.hashCode(factor);
}

@Override
protected @Nullable String transformationLiteral() {
return null;
}

@Override
protected AbstractConverter inverseWhenNotIdentity() {
return new CurrencyConverter(BigDecimal.ONE.divide(factor, MathContext.DECIMAL128));
}

@Override
protected boolean canReduceWith(@Nullable AbstractConverter that) {
return false;
}

@Override
protected Number convertWhenNotIdentity(@NonNullByDefault({}) Number value) {
return new BigDecimal(value.toString()).multiply(factor, MathContext.DECIMAL128);
}

@Override
public int compareTo(@Nullable UnitConverter o) {
return o instanceof CurrencyConverter currencyConverter ? factor.compareTo(currencyConverter.factor) : -1;
}

@Override
public boolean isIdentity() {
return false;
}

@Override
public boolean isLinear() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.internal.library.unit;

import static org.openhab.core.library.unit.CurrencyUnits.BASE_CURRENCY;

import java.math.BigDecimal;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import javax.measure.Unit;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.dimension.Currency;
import org.openhab.core.library.unit.CurrencyProvider;
import org.openhab.core.library.unit.CurrencyUnit;
import org.openhab.core.library.unit.CurrencyUnits;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import tech.units.indriya.format.SimpleUnitFormat;

/**
* The {@link CurrencyService} allows to register and switch {@link CurrencyProvider}s and provides exchange rates
* for currencies
*
* @author Jan N. Klug - Initial contribution
*/
@Component(service = CurrencyService.class, immediate = true, configurationPid = CurrencyService.CONFIGURATION_PID, property = {
Constants.SERVICE_PID + "=org.openhab.units", //
"service.config.label=Unit Settings", //
"service.config.category=system", //
"service.config.description.uri=system:units" })
@NonNullByDefault
public class CurrencyService {
public static final String CONFIGURATION_PID = "org.openhab.units";
public static final String CONFIG_OPTION_CURRENCY_PROVIDER = "currencyProvider";
private final Logger logger = LoggerFactory.getLogger(CurrencyService.class);

public static Function<Unit<Currency>, @Nullable BigDecimal> FACTOR_FCN = unit -> null;

private final Map<String, CurrencyProvider> currencyProviders = new ConcurrentHashMap<>();

private CurrencyProvider enabledCurrencyProvider = DefaultCurrencyProvider.getInstance();
private String configuredCurrencyProvider = DefaultCurrencyProvider.getInstance().getName();

@Activate
public CurrencyService(Map<String, Object> config) {
modified(config);
}

@Modified
public void modified(Map<String, Object> config) {
String configOption = (String) config.get(CONFIG_OPTION_CURRENCY_PROVIDER);
configuredCurrencyProvider = Objects.requireNonNullElse(configOption,
DefaultCurrencyProvider.getInstance().getName());
CurrencyProvider currencyProvider = currencyProviders.getOrDefault(configuredCurrencyProvider,
DefaultCurrencyProvider.getInstance());
enableProvider(currencyProvider);
}

@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
public void addCurrencyProvider(CurrencyProvider currencyProvider) {
currencyProviders.put(currencyProvider.getName(), currencyProvider);
if (configuredCurrencyProvider.equals(currencyProvider.getName())) {
enableProvider(currencyProvider);
}
}

public void removeCurrencyProvider(CurrencyProvider currencyProvider) {
if (currencyProvider.equals(enabledCurrencyProvider)) {
logger.warn("The currently activated currency provider is being removed. Enabling default.");
enableProvider(DefaultCurrencyProvider.getInstance());
}
currencyProviders.remove(currencyProvider.getName());
}

private synchronized void enableProvider(CurrencyProvider currencyProvider) {
SimpleUnitFormat unitFormatter = SimpleUnitFormat.getInstance();
// remove units from old provider
enabledCurrencyProvider.getAdditionalCurrencies().forEach(CurrencyUnits::removeUnit);
unitFormatter.removeLabel(enabledCurrencyProvider.getBaseCurrency());

// add new units
FACTOR_FCN = currencyProvider.getExchangeRateFunction();
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()));

currencyProvider.getAdditionalCurrencies().forEach(CurrencyUnits::addUnit);

this.enabledCurrencyProvider = currencyProvider;
}

private static class DefaultCurrencyProvider implements CurrencyProvider {
private static final CurrencyProvider INSTANCE = new DefaultCurrencyProvider();

@Override
public Unit<Currency> getBaseCurrency() {
return new CurrencyUnit("DEF", null);
}

@Override
public Collection<Unit<Currency>> getAdditionalCurrencies() {
return Set.of();
}

@Override
public Function<Unit<Currency>, @Nullable BigDecimal> getExchangeRateFunction() {
return unit -> null;
}

public static CurrencyProvider getInstance() {
return INSTANCE;
}
}
}
Loading