de.tsl2.nano.currency.CurrencyUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tsl2.nano.descriptor Show documentation
Show all versions of tsl2.nano.descriptor Show documentation
TSL2 Framework Descriptor (currency-handling, generic formatter, descriptors for beans, collections, actions and values)
/*
* File: $HeadURL$
* Id : $Id$
*
* created by: Thomas Schneider
* created on: Apr 16, 2012
*
* Copyright: (c) Thomas Schneider, all rights reserved
*/
package de.tsl2.nano.currency;
import java.io.File;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.Format;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Currency;
import java.util.Date;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.logging.Log;
import de.tsl2.nano.bean.BeanUtil;
import de.tsl2.nano.collection.CollectionUtil;
import de.tsl2.nano.core.IPredicate;
import de.tsl2.nano.core.log.LogFactory;
import de.tsl2.nano.core.util.FileUtil;
import de.tsl2.nano.core.util.MapUtil;
import de.tsl2.nano.core.util.Period;
/**
* The jdk implementation (until 1.6) {@link Currency} does not provide needed features like giving historized currency
* codes. This is done by ICU - International Components for Unicode - Project. But, no factor for calculating the new
* currency value is given, the implementation is heavy (7MB) and has to initialize a lot of overhead (several seconds).
*
* This class encapsulates and simplifies the use of historic currencies through {@link CurrencyUnit}s. The
* {@link CurrencyUnit} wraps the standard {@link Currency} extending historizing and calculating aspects. The file
* 'historical-currencies.csv' should define all historic currencies - the list is not complete! For a complete list, see
* http://de.wikipedia.org/wiki/ISO_4217.
*
* @author ts
* @version $Revision$
*/
public class CurrencyUtil {
private static final String HISTORICAL_CURRENCIES_FILE_NAME = "historical-currencies.csv";
/** historic Currency-Units read from csv file */
static final Collection historicCurrencyUnits = new ArrayList();
/** cache of already loaded currencies and their units */
static final Map currencyUnits = new Hashtable();
private static final Log LOG = LogFactory.getLog(CurrencyUtil.class);
// public static final Currency getCurrency(Locale loc, Date date) {
// ULocale uLocale = new ULocale(loc.getISO3Country(), loc.getISO3Language());
// String[] currencyCodes = com.ibm.icu.util.Currency.getAvailableCurrencyCodes(uLocale, date);
// LOG.debug("Currencies for locale " + loc + " and date " + date + ": " + StringUtil.toString(currencyCodes, 80));
// return Currency.getInstance(currencyCodes[0]);
//}
/**
* getCurrency
*
* @return default currency unit
*/
public static final CurrencyUnit getCurrency() {
return getCurrency(Locale.getDefault());
}
/**
* getCurrency
*
* @param loc locale to get the currency for
* @return currency unit for given locale
*/
public static final CurrencyUnit getCurrency(Locale loc) {
Currency c = Currency.getInstance(loc);
CurrencyUnit unit = currencyUnits.get(c);
if (unit == null) {
unit = new CurrencyUnit(loc.getCountry(), c.getCurrencyCode(), null, null, 1.0);
currencyUnits.put(c, unit);
}
return unit;
}
/**
* getCurrency
*
* @param date validation date for the default currency
* @return default currency, valid for the given date
*/
public static final CurrencyUnit getCurrency(Date date) {
return getCurrency(Locale.getDefault(), date);
}
public static final CurrencyUnit getCurrencyUnit(final Currency c) {
if (c == null) {
return null;
}
if (historicCurrencyUnits.isEmpty()) {
initializeCurrencyUnits();
}
Collection currencies = CollectionUtil.getFilteredCollection(historicCurrencyUnits,
new IPredicate() {
@Override
public boolean eval(CurrencyUnit arg0) {
CurrencyUnit cu = arg0;
return cu.getCurrencyCode().equals(c.getCurrencyCode());
}
});
return currencies.iterator().next();
}
/**
* getCurrency
*
* @param loc locale to get the currency for
* @param date validation date for the default currency
* @return default currency, valid for the given date
*/
public static final CurrencyUnit getCurrency(final Locale loc, final Date date) {
if (historicCurrencyUnits.isEmpty()) {
initializeCurrencyUnits();
}
Collection currencies = CollectionUtil.getFilteredCollection(historicCurrencyUnits,
new IPredicate() {
@Override
public boolean eval(CurrencyUnit arg0) {
CurrencyUnit cu = arg0;
return cu.getCountryCode().equals(loc.getCountry()) && new Period(cu.getValidFrom(),
cu.getValidUntil()).contains(new Period(date, date));
}
});
if (currencies.size() == 0) {//no historical entry found - use standard currency
LOG.warn("no historical entry found for " + date + " -> using standard currency!");
return getCurrency(loc);
} else if (currencies.size() != 1) {
LOG.warn("not exactly one currency found for " + loc + ", " + date);
}
return currencies.iterator().next();
}
/**
* convenience to get the value for the actual default currency (depends on current locale).
*
* @param value historic value
* @param currencyDate date of value and its currency
* @return calculated and rounded value for the actual currency
*/
public static BigDecimal getActualValue(float value, Date currencyDate) {
return getActualValue(value, getCurrency(currencyDate));
}
/**
* calculates the value for the given (perhaps historic) unit to actual default unit (depends on current locale).
* The result is round to the currencies default fraction digits. Please see hints of
* {@link BigDecimal#BigDecimal(double)} to see problems on construction with a double value.
*
* @param value historic value
* @param unit currency unit
* @return actual rounded value
*/
public static BigDecimal getActualValue(float value, CurrencyUnit unit) {
BigDecimal bd = BigDecimal.valueOf(value / unit.getFactor());
return bd.setScale(unit.getCurrency().getDefaultFractionDigits(), BigDecimal.ROUND_HALF_UP);
}
/**
* convenience to get the factor from historic currency to actual currency (depends on current locale).
*
* @param currencyDate date of value and its currency
* @return historic factor or null
*/
public static final Double getFactor(Date historicCurrencyDate) {
return getFactor(Locale.getDefault(), historicCurrencyDate);
}
/**
* convenience to get the factor from historic currency to actual currency.
*
* @param loc locale of currency
* @param currencyDate date of value and its currency
* @return historic factor or null
*/
public static final Double getFactor(Locale loc, Date historicCurrencyDate) {
return getCurrency(loc, historicCurrencyDate).factor;
}
/**
* evaluates the currency symbol of the given date and formats the value plus symbol. see
* {@link #getFormattedValue(Number, String)}.
*/
public static String getFormattedValue(Number value, Date dateOfCurrency) {
return getFormattedValue(value, getCurrency(dateOfCurrency).getCurrency().getSymbol());
}
/**
* see {@link #getFormattedValue(Number, String)}.
*/
public static String getFormattedValue(Number value, CurrencyUnit unit) {
return getFormattedValue(value, unit.getCurrency().getSymbol());
}
/**
* formats the given value to fulfill the current locale - appending the given symbol
*
* @param value value to format
* @param currencySymbol symbol to append
* @return string representation
*/
public static String getFormattedValue(Number value, String currencySymbol) {
return value != null ? MessageFormat.format("{0} {1}", value, currencySymbol) : "";
}
/**
* creates a NumberFormat for BigDecimals with currency code and fractionDigits
*
* @param currencyCode (optional) currency code (see {@link Currency#getInstance(String)} and {@link http
* ://de.wikipedia.org/wiki/ISO_4217} - or null.
* @param fractionDigits number of fraction digits (precision)
* @return new numberformat instance
*/
public static final NumberFormat getFormat(String currencyCode, int fractionDigits) {
final DecimalFormat numberFormat = (DecimalFormat) (currencyCode != null ? NumberFormat.getCurrencyInstance()
: NumberFormat.getInstance());
if (currencyCode != null) {
numberFormat.setCurrency(Currency.getInstance(currencyCode));
}
numberFormat.setMinimumFractionDigits(fractionDigits);
numberFormat.setMaximumFractionDigits(fractionDigits);
numberFormat.setGroupingUsed(true);
numberFormat.setParseBigDecimal(true);
return numberFormat;
}
/**
* delegates to {@link #getFormat(Locale, Date)} with default locale
*/
public static NumberFormat getFormat(Date historicCurrencyDate) {
return getFormat(Locale.getDefault(), historicCurrencyDate);
}
/**
* formatter for given perhaps historic currency
* @param loc locale
* @param historicCurrencyDate date of currency
* @return formatter
*/
public static NumberFormat getFormat(Locale loc, Date historicCurrencyDate) {
CurrencyUnit currency = getCurrency(loc, historicCurrencyDate);
return getFormat(currency.getCurrencyCode(), currency.getCurrency().getDefaultFractionDigits());
}
/**
* initializeCurrencyUnits
*/
private static void initializeCurrencyUnits() {
String filePath = null;
InputStreamReader reader = null;
final File file = new File(System.getProperty("user.dir") + File.separator + HISTORICAL_CURRENCIES_FILE_NAME);
if (file.exists() && file.canRead()) {
filePath = file.getPath();
reader = new InputStreamReader(FileUtil.getFile(filePath));
}
if (filePath == null) {
//loading from resource in a jar needs a path separator '/'
final String ppath = CurrencyUnit.class.getPackage().getName().replace('.', '/');
filePath = ppath + "/" + HISTORICAL_CURRENCIES_FILE_NAME;
reader = new InputStreamReader(FileUtil.getResource(filePath));
}
Format df = new SimpleDateFormat("yyyy-MM-dd");
Map formats = MapUtil.asMap("validFrom",
df,
"validUntil",
df,
"factor",
NumberFormat.getInstance(Locale.US));
Collection c = BeanUtil.fromFlatFile(reader,
"\t",
CurrencyUnit.class,
formats,
"countryCode",
"currencyCode",
"validFrom",
"validUntil",
"factor");
for (CurrencyUnit cu : c) {
historicCurrencyUnits.add(cu);
}
}
}