/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.util.money;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.joda.beans.Bean;
import org.joda.beans.BeanBuilder;
import org.joda.beans.BeanDefinition;
import org.joda.beans.ImmutableBean;
import org.joda.beans.JodaBeanUtils;
import org.joda.beans.MetaProperty;
import org.joda.beans.Property;
import org.joda.beans.PropertyDefinition;
import org.joda.beans.impl.direct.DirectFieldsBeanBuilder;
import org.joda.beans.impl.direct.DirectMetaBean;
import org.joda.beans.impl.direct.DirectMetaProperty;
import org.joda.beans.impl.direct.DirectMetaPropertyMap;
import com.google.common.collect.ImmutableSortedMap;
import com.opengamma.util.ArgumentChecker;
/**
* A map of currency amounts keyed by currency.
* <p>
* This is a container holding multiple {@link CurrencyAmount} instances.
* The amounts do not necessarily the same worth or value in each currency.
* <p>
* This class behaves as a set - if an amount is added with the same currency as one of the
* elements, the amounts are added. For example, adding EUR 100 to the container
* (EUR 200, CAD 100) would give (EUR 300, CAD 100).
* <p>
* This class is immutable and thread-safe.
*/
@BeanDefinition(builderScope = "private")
public final class MultipleCurrencyAmount implements ImmutableBean,
Iterable<CurrencyAmount>, Serializable {
/** Serialization version. */
private static final long serialVersionUID = 1L;
/**
* The map of {@code CurrencyAmount} keyed by currency.
*/
@PropertyDefinition(validate = "notNull")
private final ImmutableSortedMap<Currency, CurrencyAmount> _currencyAmountMap;
/**
* Obtains a {@code MultipleCurrencyAmount} from a currency and amount.
*
* @param currency the currency, not null
* @param amount the amount
* @return the amount, not null
*/
public static MultipleCurrencyAmount of(final Currency currency, final double amount) {
return new MultipleCurrencyAmount(ImmutableSortedMap.of(currency, CurrencyAmount.of(currency, amount)));
}
/**
* Obtains a {@code MultipleCurrencyAmount} from a paired array of currencies and amounts.
*
* @param currencies the currencies, not null
* @param amounts the amounts, not null
* @return the amount, not null
*/
public static MultipleCurrencyAmount of(final Currency[] currencies, final double[] amounts) {
ArgumentChecker.noNulls(currencies, "currencies");
ArgumentChecker.notNull(amounts, "amounts");
final int length = currencies.length;
ArgumentChecker.isTrue(length == amounts.length, "Currency array and amount array must be the same length");
List<CurrencyAmount> list = new ArrayList<CurrencyAmount>(length);
for (int i = 0; i < length; i++) {
list.add(CurrencyAmount.of(currencies[i], amounts[i]));
}
return of(list);
}
/**
* Obtains a {@code MultipleCurrencyAmount} from a paired list of currencies and amounts.
*
* @param currencies the currencies, not null
* @param amounts the amounts, not null
* @return the amount, not null
*/
public static MultipleCurrencyAmount of(final List<Currency> currencies, final List<Double> amounts) {
ArgumentChecker.noNulls(currencies, "currencies");
ArgumentChecker.noNulls(amounts, "amounts");
final int length = currencies.size();
ArgumentChecker.isTrue(length == amounts.size(), "Currency array and amount array must be the same length");
List<CurrencyAmount> list = new ArrayList<CurrencyAmount>(length);
for (int i = 0; i < length; i++) {
list.add(CurrencyAmount.of(currencies.get(i), amounts.get(i)));
}
return of(list);
}
/**
* Obtains a {@code MultipleCurrencyAmount} from a map of currency to amount.
*
* @param amountMap the amounts, not null
* @return the amount, not null
*/
public static MultipleCurrencyAmount of(final Map<Currency, Double> amountMap) {
ArgumentChecker.notNull(amountMap, "amountMap");
TreeMap<Currency, CurrencyAmount> map = new TreeMap<Currency, CurrencyAmount>();
for (Entry<Currency, Double> entry : amountMap.entrySet()) {
ArgumentChecker.notNull(entry.getValue(), "amount");
map.put(entry.getKey(), CurrencyAmount.of(entry.getKey(), entry.getValue()));
}
return new MultipleCurrencyAmount(map);
}
/**
* Obtains a {@code MultipleCurrencyAmount} from a list of {@code CurrencyAmount}.
*
* @param currencyAmounts the amounts, not null
* @return the amount, not null
*/
public static MultipleCurrencyAmount of(final CurrencyAmount... currencyAmounts) {
ArgumentChecker.notNull(currencyAmounts, "currencyAmounts");
return of(Arrays.asList(currencyAmounts));
}
/**
* Obtains a {@code MultipleCurrencyAmount} from a list of {@code CurrencyAmount}.
*
* @param currencyAmounts the amounts, not null
* @return the amount, not null
*/
public static MultipleCurrencyAmount of(final Iterable<CurrencyAmount> currencyAmounts) {
ArgumentChecker.notNull(currencyAmounts, "currencyAmounts");
TreeMap<Currency, CurrencyAmount> map = new TreeMap<Currency, CurrencyAmount>();
for (CurrencyAmount currencyAmount : currencyAmounts) {
ArgumentChecker.notNull(currencyAmount, "currencyAmount");
CurrencyAmount existing = map.get(currencyAmount.getCurrency());
if (existing != null) {
map.put(currencyAmount.getCurrency(), existing.plus(currencyAmount));
} else {
map.put(currencyAmount.getCurrency(), currencyAmount);
}
}
return new MultipleCurrencyAmount(map);
}
//-------------------------------------------------------------------------
/**
* Gets the number of stored amounts.
*
* @return the number of amounts
*/
public int size() {
return _currencyAmountMap.size();
}
/**
* Iterates though the currency-amounts.
*
* @return the iterator, not null
*/
@Override
public Iterator<CurrencyAmount> iterator() {
return _currencyAmountMap.values().iterator();
}
/**
* Gets the currency amounts as an array.
*
* @return the independent, modifiable currency amount array, not null
*/
public CurrencyAmount[] getCurrencyAmounts() {
return _currencyAmountMap.values().toArray(new CurrencyAmount[_currencyAmountMap.size()]);
}
/**
* Gets the amount for the specified currency.
*
* @param currency the currency to find an amount for, not null
* @return the amount
* @throws IllegalArgumentException if the currency is not present
*/
public double getAmount(final Currency currency) {
CurrencyAmount currencyAmount = getCurrencyAmount(currency);
if (currencyAmount == null) {
throw new IllegalArgumentException("Do not have an amount with currency " + currency);
}
return currencyAmount.getAmount();
}
/**
* Gets the {@code CurrencyAmount} for the specified currency.
*
* @param currency the currency to find an amount for, not null
* @return the amount, null if no amount for the currency
*/
public CurrencyAmount getCurrencyAmount(final Currency currency) {
ArgumentChecker.notNull(currency, "currency");
return _currencyAmountMap.get(currency);
}
//-------------------------------------------------------------------------
/**
* Returns a copy of this {@code MultipleCurrencyAmount} with the specified amount added.
* <p>
* This adds the specified amount to this monetary amount, returning a new object.
* If the currency is already present, the amount is added to the existing amount.
* If the currency is not yet present, the currency-amount is added to the map.
* The addition simply uses standard {@code double} arithmetic.
* <p>
* This instance is immutable and unaffected by this method.
*
* @param currencyAmountToAdd the amount to add, in the same currency, not null
* @return an amount based on this with the specified amount added, not null
*/
public MultipleCurrencyAmount plus(final CurrencyAmount currencyAmountToAdd) {
ArgumentChecker.notNull(currencyAmountToAdd, "currencyAmountToAdd");
ImmutableSortedMap.Builder<Currency, CurrencyAmount> copy = ImmutableSortedMap.naturalOrder();
CurrencyAmount previous = getCurrencyAmount(currencyAmountToAdd.getCurrency());
for (CurrencyAmount amount : _currencyAmountMap.values()) {
if (amount.getCurrency().equals(currencyAmountToAdd.getCurrency())) {
copy.put(amount.getCurrency(), previous.plus(currencyAmountToAdd));
} else {
copy.put(amount.getCurrency(), amount);
}
}
if (previous == null) {
copy.put(currencyAmountToAdd.getCurrency(), currencyAmountToAdd);
}
return new MultipleCurrencyAmount(copy.build());
}
/**
* Returns a copy of this {@code MultipleCurrencyAmount} with the specified amount added.
* <p>
* This adds the specified amount to this monetary amount, returning a new object.
* If the currency is already present, the amount is added to the existing amount.
* If the currency is not yet present, the currency-amount is added to the map.
* The addition simply uses standard {@code double} arithmetic.
* <p>
* This instance is immutable and unaffected by this method.
*
* @param currency the currency to add to, not null
* @param amountToAdd the amount to add
* @return an amount based on this with the specified amount added, not null
*/
public MultipleCurrencyAmount plus(final Currency currency, final double amountToAdd) {
ArgumentChecker.notNull(currency, "currency");
ImmutableSortedMap.Builder<Currency, CurrencyAmount> copy = ImmutableSortedMap.naturalOrder();
CurrencyAmount previous = getCurrencyAmount(currency);
for (CurrencyAmount amount : _currencyAmountMap.values()) {
if (amount.getCurrency().equals(currency)) {
copy.put(amount.getCurrency(), previous.plus(amountToAdd));
} else {
copy.put(amount.getCurrency(), amount);
}
}
if (previous == null) {
copy.put(currency, CurrencyAmount.of(currency, amountToAdd));
}
return new MultipleCurrencyAmount(copy.build());
}
/**
* Returns a copy of this {@code MultipleCurrencyAmount} with the specified amount added.
* <p>
* This adds the specified amount to this monetary amount, returning a new object.
* If the currency is already present, the amount is added to the existing amount.
* If the currency is not yet present, the currency-amount is added to the map.
* The addition simply uses standard {@code double} arithmetic.
* <p>
* This instance is immutable and unaffected by this method.
*
* @param multipleCurrencyAmountToAdd the currency to add to, not null
* @return an amount based on this with the specified amount added, not null
*/
public MultipleCurrencyAmount plus(final MultipleCurrencyAmount multipleCurrencyAmountToAdd) {
ArgumentChecker.notNull(multipleCurrencyAmountToAdd, "multipleCurrencyAmountToAdd");
MultipleCurrencyAmount result = this;
for (CurrencyAmount currencyAmount : multipleCurrencyAmountToAdd) {
result = result.plus(currencyAmount);
}
return result;
}
/**
* Returns a copy of this {@code MultipleCurrencyAmount} with all the amounts multiplied by the factor.
* <p>
* This instance is immutable and unaffected by this method.
*
* @param factor The multiplicative factor.
* @return An amount based on this with all the amounts multiplied by the factor. Not null
*/
public MultipleCurrencyAmount multipliedBy(final double factor) {
TreeMap<Currency, Double> map = new TreeMap<Currency, Double>();
for (CurrencyAmount currencyAmount : this) {
map.put(currencyAmount.getCurrency(), currencyAmount.getAmount() * factor);
}
return MultipleCurrencyAmount.of(map);
}
//-------------------------------------------------------------------------
/**
* Returns a copy of this {@code MultipleCurrencyAmount} with the specified currency.
* <p>
* This adds the specified amount to this monetary amount, returning a new object.
* Any previous amount for the specified currency is replaced.
* <p>
* This instance is immutable and unaffected by this method.
*
* @param currency the currency to replace, not null
* @param amount the new amount
* @return an amount based on this with the specified currency replaced, not null
*/
public MultipleCurrencyAmount with(final Currency currency, final double amount) {
ArgumentChecker.notNull(currency, "currency");
TreeMap<Currency, CurrencyAmount> copy = new TreeMap<Currency, CurrencyAmount>(_currencyAmountMap);
copy.put(currency, CurrencyAmount.of(currency, amount));
return new MultipleCurrencyAmount(copy);
}
/**
* Returns a copy of this {@code MultipleCurrencyAmount} without the specified currency.
* <p>
* This removes the specified currency from this monetary amount, returning a new object.
* <p>
* This instance is immutable and unaffected by this method.
*
* @param currency the currency to replace, not null
* @return an amount based on this with the specified currency removed, not null
*/
public MultipleCurrencyAmount without(final Currency currency) {
ArgumentChecker.notNull(currency, "currency");
TreeMap<Currency, CurrencyAmount> copy = new TreeMap<Currency, CurrencyAmount>(_currencyAmountMap);
if (copy.remove(currency) == null) {
return this;
}
return new MultipleCurrencyAmount(copy);
}
//-----------------------------------------------------------------------
/**
* Gets the amount as a string.
* <p>
* The format includes each currency-amount.
*
* @return the currency amount, not null
*/
@Override
public String toString() {
return _currencyAmountMap.values().toString();
}
private MultipleCurrencyAmount(ImmutableSortedMap<Currency, CurrencyAmount> currencyAmountMap) {
JodaBeanUtils.notNull(currencyAmountMap, "currencyAmountMap");
this._currencyAmountMap = currencyAmountMap;
}
//------------------------- AUTOGENERATED START -------------------------
///CLOVER:OFF
/**
* The meta-bean for {@code MultipleCurrencyAmount}.
* @return the meta-bean, not null
*/
public static MultipleCurrencyAmount.Meta meta() {
return MultipleCurrencyAmount.Meta.INSTANCE;
}
static {
JodaBeanUtils.registerMetaBean(MultipleCurrencyAmount.Meta.INSTANCE);
}
private MultipleCurrencyAmount(
SortedMap<Currency, CurrencyAmount> currencyAmountMap) {
JodaBeanUtils.notNull(currencyAmountMap, "currencyAmountMap");
this._currencyAmountMap = ImmutableSortedMap.copyOfSorted(currencyAmountMap);
}
@Override
public MultipleCurrencyAmount.Meta metaBean() {
return MultipleCurrencyAmount.Meta.INSTANCE;
}
@Override
public <R> Property<R> property(String propertyName) {
return metaBean().<R>metaProperty(propertyName).createProperty(this);
}
@Override
public Set<String> propertyNames() {
return metaBean().metaPropertyMap().keySet();
}
//-----------------------------------------------------------------------
/**
* Gets the map of {@code CurrencyAmount} keyed by currency.
* @return the value of the property, not null
*/
public ImmutableSortedMap<Currency, CurrencyAmount> getCurrencyAmountMap() {
return _currencyAmountMap;
}
//-----------------------------------------------------------------------
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj != null && obj.getClass() == this.getClass()) {
MultipleCurrencyAmount other = (MultipleCurrencyAmount) obj;
return JodaBeanUtils.equal(getCurrencyAmountMap(), other.getCurrencyAmountMap());
}
return false;
}
@Override
public int hashCode() {
int hash = getClass().hashCode();
hash = hash * 31 + JodaBeanUtils.hashCode(getCurrencyAmountMap());
return hash;
}
//-----------------------------------------------------------------------
/**
* The meta-bean for {@code MultipleCurrencyAmount}.
*/
public static final class Meta extends DirectMetaBean {
/**
* The singleton instance of the meta-bean.
*/
static final Meta INSTANCE = new Meta();
/**
* The meta-property for the {@code currencyAmountMap} property.
*/
@SuppressWarnings({"unchecked", "rawtypes" })
private final MetaProperty<ImmutableSortedMap<Currency, CurrencyAmount>> _currencyAmountMap = DirectMetaProperty.ofImmutable(
this, "currencyAmountMap", MultipleCurrencyAmount.class, (Class) ImmutableSortedMap.class);
/**
* The meta-properties.
*/
private final Map<String, MetaProperty<?>> _metaPropertyMap$ = new DirectMetaPropertyMap(
this, null,
"currencyAmountMap");
/**
* Restricted constructor.
*/
private Meta() {
}
@Override
protected MetaProperty<?> metaPropertyGet(String propertyName) {
switch (propertyName.hashCode()) {
case -218001197: // currencyAmountMap
return _currencyAmountMap;
}
return super.metaPropertyGet(propertyName);
}
@Override
public BeanBuilder<? extends MultipleCurrencyAmount> builder() {
return new MultipleCurrencyAmount.Builder();
}
@Override
public Class<? extends MultipleCurrencyAmount> beanType() {
return MultipleCurrencyAmount.class;
}
@Override
public Map<String, MetaProperty<?>> metaPropertyMap() {
return _metaPropertyMap$;
}
//-----------------------------------------------------------------------
/**
* The meta-property for the {@code currencyAmountMap} property.
* @return the meta-property, not null
*/
public MetaProperty<ImmutableSortedMap<Currency, CurrencyAmount>> currencyAmountMap() {
return _currencyAmountMap;
}
//-----------------------------------------------------------------------
@Override
protected Object propertyGet(Bean bean, String propertyName, boolean quiet) {
switch (propertyName.hashCode()) {
case -218001197: // currencyAmountMap
return ((MultipleCurrencyAmount) bean).getCurrencyAmountMap();
}
return super.propertyGet(bean, propertyName, quiet);
}
@Override
protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) {
metaProperty(propertyName);
if (quiet) {
return;
}
throw new UnsupportedOperationException("Property cannot be written: " + propertyName);
}
}
//-----------------------------------------------------------------------
/**
* The bean-builder for {@code MultipleCurrencyAmount}.
*/
private static final class Builder extends DirectFieldsBeanBuilder<MultipleCurrencyAmount> {
private SortedMap<Currency, CurrencyAmount> _currencyAmountMap = new TreeMap<Currency, CurrencyAmount>();
/**
* Restricted constructor.
*/
private Builder() {
}
//-----------------------------------------------------------------------
@Override
public Object get(String propertyName) {
switch (propertyName.hashCode()) {
case -218001197: // currencyAmountMap
return _currencyAmountMap;
default:
throw new NoSuchElementException("Unknown property: " + propertyName);
}
}
@SuppressWarnings("unchecked")
@Override
public Builder set(String propertyName, Object newValue) {
switch (propertyName.hashCode()) {
case -218001197: // currencyAmountMap
this._currencyAmountMap = (SortedMap<Currency, CurrencyAmount>) newValue;
break;
default:
throw new NoSuchElementException("Unknown property: " + propertyName);
}
return this;
}
@Override
public Builder set(MetaProperty<?> property, Object value) {
super.set(property, value);
return this;
}
@Override
public Builder setString(String propertyName, String value) {
setString(meta().metaProperty(propertyName), value);
return this;
}
@Override
public Builder setString(MetaProperty<?> property, String value) {
super.setString(property, value);
return this;
}
@Override
public Builder setAll(Map<String, ? extends Object> propertyValueMap) {
super.setAll(propertyValueMap);
return this;
}
@Override
public MultipleCurrencyAmount build() {
return new MultipleCurrencyAmount(
_currencyAmountMap);
}
//-----------------------------------------------------------------------
@Override
public String toString() {
StringBuilder buf = new StringBuilder(64);
buf.append("MultipleCurrencyAmount.Builder{");
buf.append("currencyAmountMap").append('=').append(JodaBeanUtils.toString(_currencyAmountMap));
buf.append('}');
return buf.toString();
}
}
///CLOVER:ON
//-------------------------- AUTOGENERATED END --------------------------
}