/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.basics.currency; import java.io.Serializable; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.joda.convert.FromString; import org.joda.convert.ToString; import com.opengamma.collect.ArgChecker; /** * An ordered pair of currencies, such as 'EUR/USD'. * <p> * This could be used to identify a pair of currencies for quoting rates in FX deals. * See {@link FxRate} for the representation that contains a rate. * <p> * This class is immutable and thread-safe. */ public final class CurrencyPair implements Serializable { /** Serialization version. */ private static final long serialVersionUID = 1L; /** * Regular expression to parse the textual format. */ private static final Pattern REGEX_FORMAT = Pattern.compile("([A-Z]{3})[/]([A-Z]{3})"); /** * The base currency of the pair. * In the pair 'AAA/BBB' the base is 'AAA'. */ private final Currency base; /** * The counter currency of the pair. * In the pair 'AAA/BBB' the counter is 'BBB'. */ private final Currency counter; //------------------------------------------------------------------------- /** * Obtains a currency pair from two currencies. * <p> * The first currency is the base and the second is the counter. * The two currencies may be the same. * * @param base the base currency * @param counter the counter currency * @return the currency pair */ public static CurrencyPair of(Currency base, Currency counter) { ArgChecker.notNull(base, "base"); ArgChecker.notNull(counter, "counter"); return new CurrencyPair(base, counter); } /** * Parses a currency pair from a string with format AAA/BBB. * <p> * The parsed format is '${baseCurrency}/${counterCurrency}'. * Currency parsing is case insensitive. * * @param pairStr the currency pair as a string AAA/BBB * @return the currency pair * @throws IllegalArgumentException if the pair cannot be parsed */ @FromString public static CurrencyPair parse(String pairStr) { ArgChecker.notNull(pairStr, "pairStr"); Matcher matcher = REGEX_FORMAT.matcher(pairStr.toUpperCase(Locale.ENGLISH)); if (matcher.matches() == false) { throw new IllegalArgumentException("Invalid currency pair: " + pairStr); } Currency base = Currency.parse(matcher.group(1)); Currency counter = Currency.parse(matcher.group(2)); return new CurrencyPair(base, counter); } //------------------------------------------------------------------------- /** * Creates a currency pair. * * @param base the base currency, validated not null * @param counter the counter currency, validated not null */ private CurrencyPair(Currency base, Currency counter) { this.base = base; this.counter = counter; } //------------------------------------------------------------------------- /** * Gets the inverse currency pair. * <p> * The inverse pair has the same currencies but in reverse order. * * @return the inverse pair */ public CurrencyPair inverse() { return new CurrencyPair(counter, base); } /** * Checks if the currency pair contains the supplied currency as either its base or counter. * * @param currency the currency to check against the pair * @return true if the currency is either the base or counter currency in the pair */ public boolean contains(Currency currency) { ArgChecker.notNull(currency, "currency"); return base.equals(currency) || counter.equals(currency); } /** * Checks if this currency pair is the inverse of the specified pair. * <p> * This could be used to check if an FX rate specified in one currency pair needs inverting. * * @param other the other currency pair * @return true if the currency is the inverse of the specified pair */ public boolean isInverse(CurrencyPair other) { ArgChecker.notNull(other, "currencyPair"); return base.equals(other.counter) && counter.equals(other.base); } /** * Checks if this currency pair is related to the specified pair. * <p> * Two pairs are related if they have at least one currency in common. * This could be used to check if two FX rates can be combined into a single rate. * * @param other the other currency pair * @return true if this pair contains either the base or counter currency of the specified pair */ public boolean isRelated(CurrencyPair other) { ArgChecker.notNull(other, "currencyPair"); return contains(other.base) || contains(other.counter); } //------------------------------------------------------------------------- /** * Gets the base currency of the pair. * <p> * In the pair 'AAA/BBB' the base is 'AAA'. * * @return the base currency */ public Currency getBase() { return base; } //------------------------------------------------------------------------- /** * Gets the counter currency of the pair. * <p> * In the pair 'AAA/BBB' the counter is 'BBB'. * <p> * The counter currency is also known as the <i>quote currency</i> or the <i>variable currency</i>. * * @return the counter currency */ public Currency getCounter() { return counter; } //------------------------------------------------------------------------- /** * Checks if this currency pair equals another. * <p> * The comparison checks the two currencies. * * @param obj the other currency pair, null returns false * @return true if equal */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj.getClass() == this.getClass()) { CurrencyPair other = (CurrencyPair) obj; return base.equals(other.base) && counter.equals(other.counter); } return false; } /** * Returns a suitable hash code for the currency. * * @return the hash code */ @Override public int hashCode() { return base.hashCode() ^ counter.hashCode(); } //------------------------------------------------------------------------- /** * Returns the formatted string version of the currency pair. * <p> * The format is '${baseCurrency}/${counterCurrency}'. * * @return the formatted string */ @Override @ToString public String toString() { return base.getCode() + "/" + counter.getCode(); } }