001/*
002 *  Copyright 2009-2011 Stephen Colebourne
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.joda.money;
017
018import java.io.InvalidObjectException;
019import java.io.ObjectInputStream;
020import java.io.Serializable;
021import java.math.BigDecimal;
022import java.math.RoundingMode;
023import java.util.regex.Matcher;
024import java.util.regex.Pattern;
025
026/**
027 * Represents an exchange rate that allows {@link BigMoneyProvider} instances to be converted between amounts in
028 * different {@link CurrencyUnit}s. Exchange rate is an object that holds data about two {@link CurrencyUnit}s and a
029 * conversion rate, and basically mirrors the following equation:
030 * 
031 * <p>
032 * 1 major unit of <strong><code>target currency</code></strong> = <strong><code>rate</code></strong> major units of
033 * <strong><code>source currency</code></strong>.
034 * </p>
035 * 
036 * Exchange rates provide a few simple operations that can be performed with them through {@link ExchangeRateOperations}
037 * . A default implementation can be acquired using {@link #operations(int, RoundingMode)}.
038 * 
039 * <p>
040 * Instances of this class are immutable.
041 * </p>
042 * 
043 * <p>
044 * Two instances of this class are considered equal when both target and source currencies are the same and the rates
045 * compare equal according to {@link BigDecimal#compareTo(BigDecimal)}.
046 * </p>
047 * 
048 * Please be aware that creating an exchange rate less or equal to 0 is considered an error.
049 * 
050 * @author Tom Pasierb
051 */
052public final class ExchangeRate implements Serializable {
053
054    private static final long serialVersionUID = 1L;
055
056    private static final Pattern PARSE_REGEX = Pattern.compile("^1\\s+([A-Z]{3})\\s+=\\s([-+]?\\d+(.\\d+)?)\\s+([A-Z]{3})$");
057
058    /**
059     * Conversion rate between source and target currency.
060     */
061    private final BigDecimal rate;
062
063    /**
064     * The source currency.
065     */
066    private final CurrencyUnit source;
067
068    /**
069     * The target currency.
070     */
071    private final CurrencyUnit target;
072
073    public static ExchangeRate parse(String exchangeRate) {
074        Utils.notNull(exchangeRate, "Exchange rate cannot be null");
075
076        String exr = exchangeRate.trim();
077        Matcher matcher = PARSE_REGEX.matcher(exr);
078        if (!matcher.matches()) {
079            throw new IllegalArgumentException(String.format("Exchange rate '%s' cannot be parsed", exchangeRate));
080        }
081
082        String source, target, rate;
083        target = matcher.group(1);
084        rate = matcher.group(2);
085        source = matcher.groupCount() == 4 ? matcher.group(4) : matcher.group(3);
086        return ExchangeRate.of(new BigDecimal(rate), CurrencyUnit.getInstance(source), CurrencyUnit.getInstance(target));
087    }
088
089    /**
090     * Creates an identity rate for the given {@link CurrencyUnit}. The rate will be equal to 1.00 and both source and
091     * target currency units will be the same as the given one.
092     * 
093     * @param currency to be set for source and target currency units.
094     * @throws NullPointerException if the given parameter is <code>null</code>
095     * @return identity exchange rate having rate 1 and the same source and target currencies
096     */
097    public static ExchangeRate identity(CurrencyUnit currency) {
098        return new ExchangeRate(BigDecimal.ONE, currency, currency);
099    }
100
101    /**
102     * Creates an Exchange rate with the given parameters.
103     * 
104     * @param rate the conversion rate
105     * @param source source {@link CurrencyUnit}
106     * @param target target {@link CurrencyUnit}
107     * @throws NullPointerException if any of the given parameters is <code>null</code>
108     * @throws IllegalArgumentException if the given rate is less or equal to 0 or when source and currency units are
109     *             equal and rate is not equal to 1
110     * @return an instance of exchange rate parameterized as instructed
111     */
112    public static ExchangeRate of(BigDecimal rate, CurrencyUnit source, CurrencyUnit target) {
113        return new ExchangeRate(rate, source, target);
114    }
115
116    /**
117     * Constructs an instance of ExchangeRate with the given parameters.
118     * 
119     * @param rate the conversion rate
120     * @param source source {@link CurrencyUnit}
121     * @param target target {@link CurrencyUnit}
122     * @throws NullPointerException if any of the given parameters is <code>null</code>
123     * @throws IllegalArgumentException if the given rate is less or equal to 0 or when source and currency units are
124     *             equal and rate is not equal to 1
125     * @return an instance of exchange rate parameterized as instructed
126     */
127    ExchangeRate(BigDecimal rate, CurrencyUnit source, CurrencyUnit target) {
128        Utils.notNull(rate, "Rate must not be null");
129        Utils.notNull(source, "Source currency must not be null");
130        Utils.notNull(target, "Target currency must not be null");
131        Utils.isTrue(rate.compareTo(BigDecimal.ZERO) > 0, "Rate must be greater than 0");
132        if (source == target) {
133            Utils.isTrue(rate.compareTo(BigDecimal.ONE) == 0,
134                    "Rate must be 1 if source and target currencies are the same, got rate=%f, source=%s, target=%s", rate, source, target);
135        }
136        this.rate = rate;
137        this.source = source;
138        this.target = target;
139    }
140
141    /**
142     * Creates an ExchangeRate like this one with the given rate.
143     * 
144     * @param rate the rate that the new ExchangeRate should have
145     * @return a copy of this with the given rate and all other settings as this one
146     * @see #of(BigDecimal, CurrencyUnit, CurrencyUnit)
147     */
148    public ExchangeRate withRate(BigDecimal rate) {
149        return new ExchangeRate(rate, source, target);
150    }
151
152    /**
153     * Returns the conversion rate.
154     * 
155     * @return the conversion rate between this exchange rate's source and target currencies
156     */
157    public BigDecimal getRate() {
158        return rate;
159    }
160
161    /**
162     * Returns the source currency.
163     * 
164     * @return the source currency
165     */
166    public CurrencyUnit getSource() {
167        return source;
168    }
169
170    /**
171     * Returns the target currency.
172     * 
173     * @return the target currency
174     */
175    public CurrencyUnit getTarget() {
176        return target;
177    }
178
179    /**
180     * Creates an instance of an object that knows how to perform operations on {@link ExchangeRate}s.
181     * 
182     * @param scale scale that will be used for calculations
183     * @param roundingMode rounding mode to use if rounding is necessary
184     * @return an instance of {@link ExchangeRateOperations}
185     */
186    public ExchangeRateOperations operations(int scale, RoundingMode roundingMode) {
187        return new DefaultExchangeRateOperations(this, scale, roundingMode);
188    }
189
190    /**
191     * Creates an instance of an object that knows how to perform operations on {@link ExchangeRate}s with default
192     * values for <code>scale</code> and <code>roundingMode</code> that will be used for calculations. The values used
193     * as defaults are:
194     * <ul>
195     * <li>scale = 16</li>
196     * <li>roundingMode = {@link RoundingMode#HALF_EVEN}</li>
197     * </ul>
198     * 
199     * @return an instance of {@link ExchangeRateOperations}
200     */
201    public ExchangeRateOperations operations() {
202        return new DefaultExchangeRateOperations(this, 16, RoundingMode.HALF_EVEN);
203    }
204
205    @Override
206    public int hashCode() {
207        final int prime = 31;
208        int result = 1;
209        result = prime * result + ((rate == null) ? 0 : rate.hashCode());
210        result = prime * result + ((source == null) ? 0 : source.hashCode());
211        result = prime * result + ((target == null) ? 0 : target.hashCode());
212        return result;
213    }
214
215    @Override
216    public boolean equals(Object obj) {
217        if (this == obj) {
218            return true;
219        }
220        if (!(obj instanceof ExchangeRate)) {
221            return false;
222        }
223        ExchangeRate other = (ExchangeRate) obj;
224        boolean equal = true;
225        equal &= rate == null ? other.rate == null : rate.compareTo(other.rate) == 0;
226        if (!equal) {
227            return equal;
228        }
229        equal &= source == null ? other.source == null : source.equals(other.source);
230        if (!equal) {
231            return equal;
232        }
233        equal &= target == null ? other.target == null : target.equals(other.target);
234        return equal;
235    }
236
237    @Override
238    public String toString() {
239        return "ExchangeRate[1 " + target + " = " + rate + " " + source + "]";
240    }
241
242    /**
243     * Block malicious data streams.
244     * 
245     * @param ois the input stream, not null
246     * @throws InvalidObjectException
247     */
248    private void readObject(ObjectInputStream ois) throws InvalidObjectException {
249        throw new InvalidObjectException("Serialization delegate required");
250    }
251
252    /**
253     * Uses a serialization delegate.
254     * 
255     * @return the replacing object, never null
256     */
257    private Object writeReplace() {
258        return new Ser(Ser.EXCHANGE_RATE, this);
259    }
260}