package org.apache.solr.schema;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.SortField;
import org.apache.lucene.analysis.util.ResourceLoader;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.response.TextResponseWriter;
import org.apache.solr.response.XMLWriter;
import org.apache.solr.search.QParser;
import org.apache.solr.search.SolrConstantScoreQuery;
import org.apache.solr.search.function.ValueSourceRangeFilter;
import org.apache.lucene.analysis.util.ResourceLoaderAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Currency;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Field type for support of monetary values.
* <p>
* See <a href="http://wiki.apache.org/solr/CurrencyField">http://wiki.apache.org/solr/CurrencyField</a>
*/
public class CurrencyField extends FieldType implements SchemaAware, ResourceLoaderAware {
protected static final String PARAM_DEFAULT_CURRENCY = "defaultCurrency";
protected static final String PARAM_RATE_PROVIDER_CLASS = "providerClass";
protected static final Object PARAM_PRECISION_STEP = "precisionStep";
protected static final String DEFAULT_RATE_PROVIDER_CLASS = "solr.FileExchangeRateProvider";
protected static final String DEFAULT_DEFAULT_CURRENCY = "USD";
protected static final String DEFAULT_PRECISION_STEP = "0";
protected static final String FIELD_SUFFIX_AMOUNT_RAW = "_amount_raw";
protected static final String FIELD_SUFFIX_CURRENCY = "_currency";
private IndexSchema schema;
protected FieldType fieldTypeCurrency;
protected FieldType fieldTypeAmountRaw;
private String exchangeRateProviderClass;
private String defaultCurrency;
private ExchangeRateProvider provider;
public static Logger log = LoggerFactory.getLogger(CurrencyField.class);
@Override
protected void init(IndexSchema schema, Map<String, String> args) {
super.init(schema, args);
if (this.isMultiValued()) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"CurrencyField types can not be multiValued: " +
this.typeName);
}
this.schema = schema;
this.exchangeRateProviderClass = args.get(PARAM_RATE_PROVIDER_CLASS);
this.defaultCurrency = args.get(PARAM_DEFAULT_CURRENCY);
if (this.defaultCurrency == null) {
this.defaultCurrency = DEFAULT_DEFAULT_CURRENCY;
}
if (this.exchangeRateProviderClass == null) {
this.exchangeRateProviderClass = DEFAULT_RATE_PROVIDER_CLASS;
}
if (java.util.Currency.getInstance(this.defaultCurrency) == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid currency code " + this.defaultCurrency);
}
String precisionStepString = args.get(PARAM_PRECISION_STEP);
if (precisionStepString == null) {
precisionStepString = DEFAULT_PRECISION_STEP;
}
// Initialize field type for amount
fieldTypeAmountRaw = new TrieLongField();
fieldTypeAmountRaw.setTypeName("amount_raw_type_tlong");
Map<String,String> map = new HashMap<String,String>(1);
map.put("precisionStep", precisionStepString);
fieldTypeAmountRaw.init(schema, map);
// Initialize field type for currency string
fieldTypeCurrency = new StrField();
fieldTypeCurrency.setTypeName("currency_type_string");
fieldTypeCurrency.init(schema, new HashMap<String,String>());
args.remove(PARAM_RATE_PROVIDER_CLASS);
args.remove(PARAM_DEFAULT_CURRENCY);
args.remove(PARAM_PRECISION_STEP);
try {
Class<? extends ExchangeRateProvider> c = schema.getResourceLoader().findClass(exchangeRateProviderClass, ExchangeRateProvider.class);
provider = c.newInstance();
provider.init(args);
} catch (Exception e) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Error instansiating exhange rate provider "+exchangeRateProviderClass+". Please check your FieldType configuration", e);
}
}
@Override
public boolean isPolyField() {
return true;
}
@Override
public void checkSchemaField(final SchemaField field) throws SolrException {
super.checkSchemaField(field);
if (field.multiValued()) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"CurrencyFields can not be multiValued: " +
field.getName());
}
}
@Override
public IndexableField[] createFields(SchemaField field, Object externalVal, float boost) {
CurrencyValue value = CurrencyValue.parse(externalVal.toString(), defaultCurrency);
IndexableField[] f = new IndexableField[field.stored() ? 3 : 2];
SchemaField amountField = getAmountField(field);
f[0] = amountField.createField(String.valueOf(value.getAmount()), amountField.indexed() && !amountField.omitNorms() ? boost : 1F);
SchemaField currencyField = getCurrencyField(field);
f[1] = currencyField.createField(value.getCurrencyCode(), currencyField.indexed() && !currencyField.omitNorms() ? boost : 1F);
if (field.stored()) {
org.apache.lucene.document.FieldType customType = new org.apache.lucene.document.FieldType();
assert !customType.omitNorms();
customType.setStored(true);
String storedValue = externalVal.toString().trim();
if (storedValue.indexOf(",") < 0) {
storedValue += "," + defaultCurrency;
}
f[2] = createField(field.getName(), storedValue, customType, 1F);
}
return f;
}
private SchemaField getAmountField(SchemaField field) {
return schema.getField(field.getName() + POLY_FIELD_SEPARATOR + FIELD_SUFFIX_AMOUNT_RAW);
}
private SchemaField getCurrencyField(SchemaField field) {
return schema.getField(field.getName() + POLY_FIELD_SEPARATOR + FIELD_SUFFIX_CURRENCY);
}
private void createDynamicCurrencyField(String suffix, FieldType type) {
String name = "*" + POLY_FIELD_SEPARATOR + suffix;
Map<String, String> props = new HashMap<String, String>();
props.put("indexed", "true");
props.put("stored", "false");
props.put("multiValued", "false");
props.put("omitNorms", "true");
int p = SchemaField.calcProps(name, type, props);
schema.registerDynamicField(SchemaField.create(name, type, p, null));
}
/**
* When index schema is informed, add dynamic fields.
*
* @param indexSchema The index schema.
*/
public void inform(IndexSchema indexSchema) {
createDynamicCurrencyField(FIELD_SUFFIX_CURRENCY, fieldTypeCurrency);
createDynamicCurrencyField(FIELD_SUFFIX_AMOUNT_RAW, fieldTypeAmountRaw);
}
/**
* Load the currency config when resource loader initialized.
*
* @param resourceLoader The resource loader.
*/
public void inform(ResourceLoader resourceLoader) {
provider.inform(resourceLoader);
boolean reloaded = provider.reload();
if(!reloaded) {
log.warn("Failed reloading currencies");
}
}
@Override
public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
CurrencyValue value = CurrencyValue.parse(externalVal, defaultCurrency);
CurrencyValue valueDefault;
valueDefault = value.convertTo(provider, defaultCurrency);
return getRangeQuery(parser, field, valueDefault, valueDefault, true, true);
}
@Override
public Query getRangeQuery(QParser parser, SchemaField field, String part1, String part2, final boolean minInclusive, final boolean maxInclusive) {
final CurrencyValue p1 = CurrencyValue.parse(part1, defaultCurrency);
final CurrencyValue p2 = CurrencyValue.parse(part2, defaultCurrency);
if (p1 != null && p2 != null && !p1.getCurrencyCode().equals(p2.getCurrencyCode())) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Cannot parse range query " + part1 + " to " + part2 +
": range queries only supported when upper and lower bound have same currency.");
}
return getRangeQuery(parser, field, p1, p2, minInclusive, maxInclusive);
}
public Query getRangeQuery(QParser parser, SchemaField field, final CurrencyValue p1, final CurrencyValue p2, final boolean minInclusive, final boolean maxInclusive) {
String currencyCode = (p1 != null) ? p1.getCurrencyCode() :
(p2 != null) ? p2.getCurrencyCode() : defaultCurrency;
final CurrencyValueSource vs = new CurrencyValueSource(field, currencyCode, parser);
return new SolrConstantScoreQuery(new ValueSourceRangeFilter(vs,
p1 == null ? null : p1.getAmount() + "" , p2 == null ? null : p2.getAmount() + "", minInclusive, maxInclusive));
}
@Override
public SortField getSortField(SchemaField field, boolean reverse) {
try {
// Convert all values to default currency for sorting.
return (new CurrencyValueSource(field, defaultCurrency, null)).getSortField(reverse);
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
}
}
public void write(XMLWriter xmlWriter, String name, IndexableField field) throws IOException {
xmlWriter.writeStr(name, field.stringValue(), false);
}
@Override
public void write(TextResponseWriter writer, String name, IndexableField field) throws IOException {
writer.writeStr(name, field.stringValue(), false);
}
public ExchangeRateProvider getProvider() {
return provider;
}
class CurrencyValueSource extends ValueSource {
private static final long serialVersionUID = 1L;
private String targetCurrencyCode;
private ValueSource currencyValues;
private ValueSource amountValues;
private final SchemaField sf;
public CurrencyValueSource(SchemaField sfield, String targetCurrencyCode, QParser parser) {
this.sf = sfield;
this.targetCurrencyCode = targetCurrencyCode;
SchemaField amountField = schema.getField(sf.getName() + POLY_FIELD_SEPARATOR + FIELD_SUFFIX_AMOUNT_RAW);
SchemaField currencyField = schema.getField(sf.getName() + POLY_FIELD_SEPARATOR + FIELD_SUFFIX_CURRENCY);
currencyValues = currencyField.getType().getValueSource(currencyField, parser);
amountValues = amountField.getType().getValueSource(amountField, parser);
}
public FunctionValues getValues(Map context, AtomicReaderContext reader) throws IOException {
final FunctionValues amounts = amountValues.getValues(context, reader);
final FunctionValues currencies = currencyValues.getValues(context, reader);
return new FunctionValues() {
private final int MAX_CURRENCIES_TO_CACHE = 256;
private final int[] fractionDigitCache = new int[MAX_CURRENCIES_TO_CACHE];
private final String[] currencyOrdToCurrencyCache = new String[MAX_CURRENCIES_TO_CACHE];
private final double[] exchangeRateCache = new double[MAX_CURRENCIES_TO_CACHE];
private int targetFractionDigits = -1;
private int targetCurrencyOrd = -1;
private boolean initializedCache;
private String getDocCurrencyCode(int doc, int currencyOrd) {
if (currencyOrd < MAX_CURRENCIES_TO_CACHE) {
String currency = currencyOrdToCurrencyCache[currencyOrd];
if (currency == null) {
currencyOrdToCurrencyCache[currencyOrd] = currency = currencies.strVal(doc);
}
if (currency == null) {
currency = defaultCurrency;
}
if (targetCurrencyOrd == -1 && currency.equals(targetCurrencyCode)) {
targetCurrencyOrd = currencyOrd;
}
return currency;
} else {
return currencies.strVal(doc);
}
}
public long longVal(int doc) {
if (!initializedCache) {
for (int i = 0; i < fractionDigitCache.length; i++) {
fractionDigitCache[i] = -1;
}
initializedCache = true;
}
long amount = amounts.longVal(doc);
int currencyOrd = currencies.ordVal(doc);
if (currencyOrd == targetCurrencyOrd) {
return amount;
}
double exchangeRate;
int sourceFractionDigits;
if (targetFractionDigits == -1) {
targetFractionDigits = Currency.getInstance(targetCurrencyCode).getDefaultFractionDigits();
}
if (currencyOrd < MAX_CURRENCIES_TO_CACHE) {
exchangeRate = exchangeRateCache[currencyOrd];
if (exchangeRate <= 0.0) {
String sourceCurrencyCode = getDocCurrencyCode(doc, currencyOrd);
exchangeRate = exchangeRateCache[currencyOrd] = provider.getExchangeRate(sourceCurrencyCode, targetCurrencyCode);
}
sourceFractionDigits = fractionDigitCache[currencyOrd];
if (sourceFractionDigits == -1) {
String sourceCurrencyCode = getDocCurrencyCode(doc, currencyOrd);
sourceFractionDigits = fractionDigitCache[currencyOrd] = Currency.getInstance(sourceCurrencyCode).getDefaultFractionDigits();
}
} else {
String sourceCurrencyCode = getDocCurrencyCode(doc, currencyOrd);
exchangeRate = provider.getExchangeRate(sourceCurrencyCode, targetCurrencyCode);
sourceFractionDigits = Currency.getInstance(sourceCurrencyCode).getDefaultFractionDigits();
}
return CurrencyValue.convertAmount(exchangeRate, sourceFractionDigits, amount, targetFractionDigits);
}
public int intVal(int doc) {
return (int) longVal(doc);
}
public double doubleVal(int doc) {
return (double) longVal(doc);
}
public float floatVal(int doc) {
return (float) longVal(doc);
}
public String strVal(int doc) {
return Long.toString(longVal(doc));
}
public String toString(int doc) {
return name() + '(' + amounts.toString(doc) + ',' + currencies.toString(doc) + ')';
}
};
}
public String name() {
return "currency";
}
@Override
public String description() {
return name() + "(" + sf.getName() + ")";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CurrencyValueSource that = (CurrencyValueSource) o;
return !(amountValues != null ? !amountValues.equals(that.amountValues) : that.amountValues != null) &&
!(currencyValues != null ? !currencyValues.equals(that.currencyValues) : that.currencyValues != null) &&
!(targetCurrencyCode != null ? !targetCurrencyCode.equals(that.targetCurrencyCode) : that.targetCurrencyCode != null);
}
@Override
public int hashCode() {
int result = targetCurrencyCode != null ? targetCurrencyCode.hashCode() : 0;
result = 31 * result + (currencyValues != null ? currencyValues.hashCode() : 0);
result = 31 * result + (amountValues != null ? amountValues.hashCode() : 0);
return result;
}
}
}
/**
* Configuration for currency. Provides currency exchange rates.
*/
class FileExchangeRateProvider implements ExchangeRateProvider {
public static Logger log = LoggerFactory.getLogger(FileExchangeRateProvider.class);
protected static final String PARAM_CURRENCY_CONFIG = "currencyConfig";
// Exchange rate map, maps Currency Code -> Currency Code -> Rate
private Map<String, Map<String, Double>> rates = new HashMap<String, Map<String, Double>>();
private String currencyConfigFile;
private ResourceLoader loader;
/**
* Returns the currently known exchange rate between two currencies. If a direct rate has been loaded,
* it is used. Otherwise, if a rate is known to convert the target currency to the source, the inverse
* exchange rate is computed.
*
* @param sourceCurrencyCode The source currency being converted from.
* @param targetCurrencyCode The target currency being converted to.
* @return The exchange rate.
* @throws SolrException if the requested currency pair cannot be found
*/
public double getExchangeRate(String sourceCurrencyCode, String targetCurrencyCode) {
if (sourceCurrencyCode == null || targetCurrencyCode == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Cannot get exchange rate; currency was null.");
}
if (sourceCurrencyCode.equals(targetCurrencyCode)) {
return 1.0;
}
Double directRate = lookupRate(sourceCurrencyCode, targetCurrencyCode);
if (directRate != null) {
return directRate;
}
Double symmetricRate = lookupRate(targetCurrencyCode, sourceCurrencyCode);
if (symmetricRate != null) {
return 1.0 / symmetricRate;
}
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No available conversion rate between " + sourceCurrencyCode + " to " + targetCurrencyCode);
}
/**
* Looks up the current known rate, if any, between the source and target currencies.
*
* @param sourceCurrencyCode The source currency being converted from.
* @param targetCurrencyCode The target currency being converted to.
* @return The exchange rate, or null if no rate has been registered.
*/
private Double lookupRate(String sourceCurrencyCode, String targetCurrencyCode) {
Map<String, Double> rhs = rates.get(sourceCurrencyCode);
if (rhs != null) {
return rhs.get(targetCurrencyCode);
}
return null;
}
/**
* Registers the specified exchange rate.
*
* @param ratesMap The map to add rate to
* @param sourceCurrencyCode The source currency.
* @param targetCurrencyCode The target currency.
* @param rate The known exchange rate.
*/
private void addRate(Map<String, Map<String, Double>> ratesMap, String sourceCurrencyCode, String targetCurrencyCode, double rate) {
Map<String, Double> rhs = ratesMap.get(sourceCurrencyCode);
if (rhs == null) {
rhs = new HashMap<String, Double>();
ratesMap.put(sourceCurrencyCode, rhs);
}
rhs.put(targetCurrencyCode, rate);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FileExchangeRateProvider that = (FileExchangeRateProvider) o;
return !(rates != null ? !rates.equals(that.rates) : that.rates != null);
}
@Override
public int hashCode() {
return rates != null ? rates.hashCode() : 0;
}
public String toString() {
return "["+this.getClass().getName()+" : " + rates.size() + " rates.]";
}
@Override
public Set<String> listAvailableCurrencies() {
Set<String> currencies = new HashSet<String>();
for(String from : rates.keySet()) {
currencies.add(from);
for(String to : rates.get(from).keySet()) {
currencies.add(to);
}
}
return currencies;
}
@Override
public boolean reload() throws SolrException {
InputStream is = null;
Map<String, Map<String, Double>> tmpRates = new HashMap<String, Map<String, Double>>();
try {
log.info("Reloading exchange rates from file "+this.currencyConfigFile);
is = loader.openResource(currencyConfigFile);
javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
dbf.setXIncludeAware(true);
dbf.setNamespaceAware(true);
} catch (UnsupportedOperationException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "XML parser doesn't support XInclude option", e);
}
try {
Document doc = dbf.newDocumentBuilder().parse(is);
XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
// Parse exchange rates.
NodeList nodes = (NodeList) xpath.evaluate("/currencyConfig/rates/rate", doc, XPathConstants.NODESET);
for (int i = 0; i < nodes.getLength(); i++) {
Node rateNode = nodes.item(i);
NamedNodeMap attributes = rateNode.getAttributes();
Node from = attributes.getNamedItem("from");
Node to = attributes.getNamedItem("to");
Node rate = attributes.getNamedItem("rate");
if (from == null || to == null || rate == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Exchange rate missing attributes (required: from, to, rate) " + rateNode);
}
String fromCurrency = from.getNodeValue();
String toCurrency = to.getNodeValue();
Double exchangeRate;
if (java.util.Currency.getInstance(fromCurrency) == null ||
java.util.Currency.getInstance(toCurrency) == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Could not find from currency specified in exchange rate: " + rateNode);
}
try {
exchangeRate = Double.parseDouble(rate.getNodeValue());
} catch (NumberFormatException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Could not parse exchange rate: " + rateNode, e);
}
addRate(tmpRates, fromCurrency, toCurrency, exchangeRate);
}
} catch (SAXException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error parsing currency config.", e);
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error parsing currency config.", e);
} catch (ParserConfigurationException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error parsing currency config.", e);
} catch (XPathExpressionException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error parsing currency config.", e);
}
} catch (IOException e) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Error while opening Currency configuration file "+currencyConfigFile, e);
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// Atomically swap in the new rates map, if it loaded successfully
this.rates = tmpRates;
return true;
}
@Override
public void init(Map<String,String> params) throws SolrException {
this.currencyConfigFile = params.get(PARAM_CURRENCY_CONFIG);
if(currencyConfigFile == null) {
throw new SolrException(ErrorCode.NOT_FOUND, "Missing required configuration "+PARAM_CURRENCY_CONFIG);
}
// Removing config params custom to us
params.remove(PARAM_CURRENCY_CONFIG);
}
@Override
public void inform(ResourceLoader loader) throws SolrException {
if(loader == null) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Needs ResourceLoader in order to load config file");
}
this.loader = loader;
reload();
}
}
/**
* Represents a Currency field value, which includes a long amount and ISO currency code.
*/
class CurrencyValue {
private long amount;
private String currencyCode;
/**
* Constructs a new currency value.
*
* @param amount The amount.
* @param currencyCode The currency code.
*/
public CurrencyValue(long amount, String currencyCode) {
this.amount = amount;
this.currencyCode = currencyCode;
}
/**
* Constructs a new currency value by parsing the specific input.
* <p/>
* Currency values are expected to be in the format <amount>,<currency code>,
* for example, "500,USD" would represent 5 U.S. Dollars.
* <p/>
* If no currency code is specified, the default is assumed.
*
* @param externalVal The value to parse.
* @param defaultCurrency The default currency.
* @return The parsed CurrencyValue.
*/
public static CurrencyValue parse(String externalVal, String defaultCurrency) {
if (externalVal == null) {
return null;
}
String amount = externalVal;
String code = defaultCurrency;
if (externalVal.contains(",")) {
String[] amountAndCode = externalVal.split(",");
amount = amountAndCode[0];
code = amountAndCode[1];
}
if (amount.equals("*")) {
return null;
}
Currency currency = java.util.Currency.getInstance(code);
if (currency == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid currency code " + code);
}
try {
double value = Double.parseDouble(amount);
long currencyValue = Math.round(value * Math.pow(10.0, currency.getDefaultFractionDigits()));
return new CurrencyValue(currencyValue, code);
} catch (NumberFormatException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
}
}
/**
* The amount of the CurrencyValue.
*
* @return The amount.
*/
public long getAmount() {
return amount;
}
/**
* The ISO currency code of the CurrencyValue.
*
* @return The currency code.
*/
public String getCurrencyCode() {
return currencyCode;
}
/**
* Performs a currency conversion & unit conversion.
*
* @param exchangeRates Exchange rates to apply.
* @param sourceCurrencyCode The source currency code.
* @param sourceAmount The source amount.
* @param targetCurrencyCode The target currency code.
* @return The converted indexable units after the exchange rate and currency fraction digits are applied.
*/
public static long convertAmount(ExchangeRateProvider exchangeRates, String sourceCurrencyCode, long sourceAmount, String targetCurrencyCode) {
double exchangeRate = exchangeRates.getExchangeRate(sourceCurrencyCode, targetCurrencyCode);
return convertAmount(exchangeRate, sourceCurrencyCode, sourceAmount, targetCurrencyCode);
}
/**
* Performs a currency conversion & unit conversion.
*
* @param exchangeRate Exchange rate to apply.
* @param sourceFractionDigits The fraction digits of the source.
* @param sourceAmount The source amount.
* @param targetFractionDigits The fraction digits of the target.
* @return The converted indexable units after the exchange rate and currency fraction digits are applied.
*/
public static long convertAmount(final double exchangeRate, final int sourceFractionDigits, final long sourceAmount, final int targetFractionDigits) {
int digitDelta = targetFractionDigits - sourceFractionDigits;
double value = ((double) sourceAmount * exchangeRate);
if (digitDelta != 0) {
if (digitDelta < 0) {
for (int i = 0; i < -digitDelta; i++) {
value *= 0.1;
}
} else {
for (int i = 0; i < digitDelta; i++) {
value *= 10.0;
}
}
}
return (long) value;
}
/**
* Performs a currency conversion & unit conversion.
*
* @param exchangeRate Exchange rate to apply.
* @param sourceCurrencyCode The source currency code.
* @param sourceAmount The source amount.
* @param targetCurrencyCode The target currency code.
* @return The converted indexable units after the exchange rate and currency fraction digits are applied.
*/
public static long convertAmount(double exchangeRate, String sourceCurrencyCode, long sourceAmount, String targetCurrencyCode) {
if (targetCurrencyCode.equals(sourceCurrencyCode)) {
return sourceAmount;
}
int sourceFractionDigits = Currency.getInstance(sourceCurrencyCode).getDefaultFractionDigits();
Currency targetCurrency = Currency.getInstance(targetCurrencyCode);
int targetFractionDigits = targetCurrency.getDefaultFractionDigits();
return convertAmount(exchangeRate, sourceFractionDigits, sourceAmount, targetFractionDigits);
}
/**
* Returns a new CurrencyValue that is the conversion of this CurrencyValue to the specified currency.
*
* @param exchangeRates The exchange rate provider.
* @param targetCurrencyCode The target currency code to convert this CurrencyValue to.
* @return The converted CurrencyValue.
*/
public CurrencyValue convertTo(ExchangeRateProvider exchangeRates, String targetCurrencyCode) {
return new CurrencyValue(convertAmount(exchangeRates, this.getCurrencyCode(), this.getAmount(), targetCurrencyCode), targetCurrencyCode);
}
public String toString() {
return String.valueOf(amount) + "," + currencyCode;
}
}