001/*
002 * CREDIT SUISSE IS WILLING TO LICENSE THIS SPECIFICATION TO YOU ONLY UPON THE
003 * CONDITION THAT YOU ACCEPT ALL OF THE TERMS CONTAINED IN THIS AGREEMENT.
004 * PLEASE READ THE TERMS AND CONDITIONS OF THIS AGREEMENT CAREFULLY. BY
005 * DOWNLOADING THIS SPECIFICATION, YOU ACCEPT THE TERMS AND CONDITIONS OF THE
006 * AGREEMENT. IF YOU ARE NOT WILLING TO BE BOUND BY IT, SELECT THE "DECLINE"
007 * BUTTON AT THE BOTTOM OF THIS PAGE. Specification: JSR-354 Money and Currency
008 * API ("Specification") Copyright (c) 2012-2014, Credit Suisse All rights
009 * reserved.
010 */
011package org.javamoney.moneta;
012
013import java.io.Serializable;
014import java.util.ArrayList;
015import java.util.Arrays;
016import java.util.List;
017import java.util.Objects;
018
019import javax.money.CurrencyUnit;
020import javax.money.NumberValue;
021import javax.money.convert.ConversionContext;
022import javax.money.convert.ExchangeRate;
023import javax.money.convert.RateType;
024
025/**
026 * This class models an exchange rate, which defines the factor the numeric value of a base amount in some currency
027 * 'A' must be multiplied
028 * to get the corresponding amount in the terminating currency 'B'. Hereby
029 * <ul>
030 * <li>an exchange rate always models one rate from a base (source) to a term
031 * (target) {@link javax.money.CurrencyUnit}.</li>
032 * <li>an exchange rate is always bound to a rate type, which typically matches
033 * the data source of the conversion data, e.g. different credit card providers
034 * may use different rates for the same conversion.</li>
035 * <li>an exchange rate may restrict its validity. In most of the use cases a
036 * rates' validity will be well defined, but it is also possible that the data
037 * provider is not able to support the rate's validity, leaving it undefined-</li>
038 * <li>an exchange rate has a provider, which is responsible for defining the
039 * rate. A provider hereby may be, but must not be the same as the rate's data
040 * source.</li>
041 * <li>an exchange rate can be a <i>direct</i> rate, where its factor is
042 * represented by a single conversion step. Or it can model a <i>derived</i>
043 * rate, where multiple conversion steps are required to define the overall
044 * base/term conversion. In case of derived rates the chained rates define the
045 * overall factor, by multiplying the individual chain rate factors. Of course,
046 * this also requires that each subsequent rate's base currency in the chain
047 * does match the previous term currency (and vice versa):</li>
048 * <li>Whereas the factor should be directly implied by the internal rate chain
049 * for derived rates, this is obviously not the case for the validity range,
050 * since rates can have a undefined validity range. Nevertheless in many cases
051 * also the validity range can (but must not) be derived from the rate chain.</li>
052 * <li>Finally a conversion rate is always unidirectional. There might be cases
053 * where the reciprocal value of {@link #factor} matches the correct reverse
054 * rate. But in most use cases the reverse rate either has a different rate (not
055 * equal to the reciprocal value), or might not be defined at all. Therefore for
056 * reversing a ExchangeRate one must access an {@link javax.money.convert.ExchangeRateProvider} and
057 * query for the reverse rate.</li>
058 * </ul>
059 * <p>
060 * The class also implements {@link Comparable} to allow sorting of multiple
061 * exchange rates using the following sorting order;
062 * <ul>
063 * <li>Exchange rate type</li>
064 * <li>Exchange rate provider</li>
065 * <li>base currency</li>
066 * <li>term currency</li>
067 * </ul>
068 * <p>
069 * Finally ExchangeRate is modeled as an immutable and thread safe type. Also
070 * exchange rates are {@link java.io.Serializable}, hereby serializing in the following
071 * form and order:
072 * <ul>
073 * <li>The base {@link javax.money.CurrencyUnit}
074 * <li>The target {@link javax.money.CurrencyUnit}
075 * <li>The factor (NumberValue)
076 * <li>The {@link javax.money.convert.ConversionContext}
077 * <li>The rate chain
078 * </ul>
079 *
080 * @author Werner Keil
081 * @author Anatole Tresch
082 * @see <a
083 * href="https://en.wikipedia.org/wiki/Exchange_rate#Quotations">Wikipedia:
084 * Exchange Rate (Quotations)</a>
085 */
086public class DefaultExchangeRate implements ExchangeRate, Serializable, Comparable<ExchangeRate>{
087
088    /**
089     * serialVersionUID.
090     */
091    private static final long serialVersionUID = 5077295306570465837L;
092    /**
093     * The base currency.
094     */
095    private final CurrencyUnit base;
096    /**
097     * The terminating currency.
098     */
099    private final CurrencyUnit term;
100    /**
101     * The conversion factor.
102     */
103    private final NumberValue factor;
104    /**
105     * The {@link javax.money.convert.ConversionContext}
106     */
107    private final ConversionContext conversionContext;
108    /**
109     * The full chain, at least one instance long.
110     */
111    private List<ExchangeRate> chain = new ArrayList<>();
112
113
114    /**
115     * Creates a new instance with a custom chain of exchange rate type, e.g. or
116     * creating <i>derived</i> rates.
117     *
118     * @param builder The Builder, never {@code null}.
119     */
120    private DefaultExchangeRate(Builder builder){
121        Objects.requireNonNull(builder.base, "base may not be null.");
122        Objects.requireNonNull(builder.term, "term may not be null.");
123        Objects.requireNonNull(builder.factor, "factor may not be null.");
124        Objects.requireNonNull(builder.conversionContext, "exchangeRateType may not be null.");
125        this.base = builder.base;
126        this.term = builder.term;
127        this.factor = builder.factor;
128        this.conversionContext = builder.conversionContext;
129
130        setExchangeRateChain(builder.rateChain);
131    }
132
133    /**
134     * Internal method to set the rate chain, which also ensure that the chain
135     * passed, when not null, contains valid elements.
136     *
137     * @param chain the chain to set.
138     */
139    private void setExchangeRateChain(List<ExchangeRate> chain){
140        this.chain.clear();
141        if (Objects.isNull(chain) || chain.isEmpty()){
142            this.chain.add(this);
143        }else{
144            for (ExchangeRate rate : chain) {
145                if (Objects.isNull(rate)) {
146                    throw new IllegalArgumentException("Rate Chain element can not be null.");
147                }
148            }
149            this.chain.addAll(chain);
150        }
151    }
152
153    /**
154     * Access the {@link javax.money.convert.ConversionContext} of {@link javax.money.convert.ExchangeRate}.
155     *
156     * @return the conversion context, never null.
157     */
158    public final ConversionContext getConversionContext(){
159        return this.conversionContext;
160    }
161
162    /**
163     * Get the base (source) {@link javax.money.CurrencyUnit}.
164     *
165     * @return the base {@link javax.money.CurrencyUnit}.
166     */
167    public final CurrencyUnit getBase(){
168        return this.base;
169    }
170
171    /**
172     * Get the term (target) {@link javax.money.CurrencyUnit}.
173     *
174     * @return the term {@link javax.money.CurrencyUnit}.
175     */
176    public final CurrencyUnit getTerm(){
177        return this.term;
178    }
179
180    /**
181     * Access the rate's bid factor.
182     *
183     * @return the bid factor for this exchange rate, or {@code null}.
184     */
185    public final NumberValue getFactor(){
186        return this.factor;
187    }
188
189    /**
190     * Access the chain of exchange rates.
191     *
192     * @return the chain of rates, in case of a derived rate, this may be
193     * several instances. For a direct exchange rate, this equals to
194     * <code>new ExchangeRate[]{this}</code>.
195     */
196    public final List<ExchangeRate> getExchangeRateChain(){
197        return this.chain;
198    }
199
200    /**
201     * Allows to evaluate if this exchange rate is a derived exchange rate.
202     * <p>
203     * Derived exchange rates are defined by an ordered list of subconversions
204     * with intermediate steps, whereas a direct conversion is possible in one
205     * steps.
206     * <p>
207     * This method always returns {@code true}, if the chain contains more than
208     * one rate. Direct rates, have also a chain, but with exact one rate.
209     *
210     * @return true, if the exchange rate is derived.
211     */
212    public final boolean isDerived(){
213        return this.chain.size() > 1;
214    }
215
216    /*
217     * (non-Javadoc)
218     *
219     * @see java.lang.Comparable#compareTo(java.lang.Object)
220     */
221    @Override
222    public int compareTo(ExchangeRate o){
223        Objects.requireNonNull(o);
224        int compare = this.getBase().getCurrencyCode().compareTo(o.getBase().getCurrencyCode());
225        if(compare == 0){
226            compare = this.getTerm().getCurrencyCode().compareTo(o.getTerm().getCurrencyCode());
227        }
228        if(compare == 0){
229            compare = this.getConversionContext().getProvider().compareTo(o.getConversionContext().getProvider());
230        }
231        return compare;
232    }
233
234    /*
235     * (non-Javadoc)
236     *
237     * @see java.lang.Object#toString()
238     */
239    @Override
240    public String toString(){
241        return "ExchangeRate [base=" + base + ", factor=" + factor + ", conversionContext=" + conversionContext + "]";
242    }
243
244    /*
245     * (non-Javadoc)
246     *
247     * @see java.lang.Object#hashCode()
248     */
249    @Override
250    public int hashCode(){
251                return Objects.hash(base, conversionContext, factor, term, chain);
252    }
253
254    /*
255     * (non-Javadoc)
256     *
257     * @see java.lang.Object#equals(java.lang.Object)
258     */
259    @Override
260        public boolean equals(Object obj) {
261        if (obj == this) {
262            return true;
263        }
264        if (obj instanceof DefaultExchangeRate) {
265                DefaultExchangeRate other = (DefaultExchangeRate) obj;
266                        return Objects.equals(base, other.base)
267                                        && Objects.equals(chain, other.chain)
268                                        && Objects.equals(conversionContext,
269                                                        other.conversionContext)
270                                        && Objects.equals(factor, other.factor)
271                                        && Objects.equals(term, other.term); 
272        }
273        return false; 
274    }
275
276    /**
277     * Builder for creating new instances of {@link javax.money.convert.ExchangeRate}. Note that
278     * instances of this class are not thread-safe.
279     *
280     * @author Anatole Tresch
281     * @author Werner Keil
282     */
283    public static class Builder {
284
285        /**
286         * The {@link javax.money.convert.ConversionContext}.
287         */
288        private ConversionContext conversionContext;
289        /**
290         * The base (source) currency.
291         */
292        private CurrencyUnit base;
293        /**
294         * The term (target) currency.
295         */
296        private CurrencyUnit term;
297        /**
298         * The conversion factor.
299         */
300        private NumberValue factor;
301        /**
302         * The chain of invovled rates.
303         */
304        private List<ExchangeRate> rateChain = new ArrayList<>();
305
306        /**
307         * Sets the exchange rate type
308         *
309         * @param rateType the {@link javax.money.convert.RateType} contained
310         */
311        public Builder(String provider, RateType rateType){
312            this(ConversionContext.of(provider, rateType));
313        }
314
315        /**
316         * Sets the exchange rate type
317         *
318         * @param context the {@link javax.money.convert.ConversionContext} to be applied
319         */
320        public Builder(ConversionContext context){
321            setContext(context);
322        }
323
324        /**
325         * Sets the exchange rate type
326         *
327         * @param rate the {@link javax.money.convert.ExchangeRate} to be applied
328         */
329        public Builder(ExchangeRate rate){
330            setContext(rate.getConversionContext());
331            setFactor(rate.getFactor());
332            setTerm(rate.getTerm());
333            setBase(rate.getBase());
334            setRateChain(rate.getExchangeRateChain());
335        }
336
337        /**
338         * Sets the base {@link javax.money.CurrencyUnit}
339         *
340         * @param base to base (source) {@link javax.money.CurrencyUnit} to be applied
341         * @return the builder instance
342         */
343        public Builder setBase(CurrencyUnit base){
344            this.base = base;
345            return this;
346        }
347
348        /**
349         * Sets the terminating (target) {@link javax.money.CurrencyUnit}
350         *
351         * @param term to terminating {@link javax.money.CurrencyUnit} to be applied
352         * @return the builder instance
353         */
354        public Builder setTerm(CurrencyUnit term){
355            this.term = term;
356            return this;
357        }
358
359        /**
360         * Sets the {@link javax.money.convert.ExchangeRate} chain.
361         *
362         * @param exchangeRates the {@link javax.money.convert.ExchangeRate} chain to be applied
363         * @return the builder instance
364         */
365        public Builder setRateChain(ExchangeRate... exchangeRates){
366            this.rateChain.clear();
367            if (Objects.nonNull(exchangeRates)){
368                this.rateChain.addAll(Arrays.asList(exchangeRates.clone()));
369            }
370            return this;
371        }
372
373        /**
374         * Sets the {@link javax.money.convert.ExchangeRate} chain.
375         *
376         * @param exchangeRates the {@link javax.money.convert.ExchangeRate} chain to be applied
377         * @return the builder instance
378         */
379        public Builder setRateChain(List<ExchangeRate> exchangeRates){
380            this.rateChain.clear();
381            if (Objects.nonNull(exchangeRates)) {
382                this.rateChain.addAll(exchangeRates);
383            }
384            return this;
385        }
386
387
388        /**
389         * Sets the conversion factor, as the factor
390         * {@code base * factor = target}.
391         *
392         * @param factor the factor.
393         * @return The builder instance.
394         */
395        public Builder setFactor(NumberValue factor){
396            this.factor = factor;
397            return this;
398        }
399
400        /**
401         * Sets the provider to be applied.
402         *
403         * @param conversionContext the {@link javax.money.convert.ConversionContext}, not null.
404         * @return The builder.
405         */
406        public Builder setContext(ConversionContext conversionContext){
407            Objects.requireNonNull(conversionContext);
408            this.conversionContext = conversionContext;
409            return this;
410        }
411
412        /**
413         * Builds a new instance of {@link javax.money.convert.ExchangeRate}.
414         *
415         * @return a new instance of {@link javax.money.convert.ExchangeRate}.
416         * @throws IllegalArgumentException if the rate could not be built.
417         */
418        public DefaultExchangeRate build(){
419            return new DefaultExchangeRate(this);
420        }
421
422        /**
423         * Initialize the {@link Builder} with an {@link javax.money.convert.ExchangeRate}. This is
424         * useful for creating a new rate, reusing some properties from an
425         * existing one.
426         *
427         * @param rate the base rate
428         * @return the Builder, for chaining.
429         */
430        public Builder setRate(ExchangeRate rate){
431            this.base = rate.getBase();
432            this.term = rate.getTerm();
433            this.conversionContext = rate.getConversionContext();
434            this.factor = rate.getFactor();
435            this.rateChain = rate.getExchangeRateChain();
436            this.term = rate.getTerm();
437            return this;
438        }
439    }
440
441    /**
442     * Create a {@link Builder} based on the current rate instance.
443     *
444     * @return a new {@link Builder}, never {@code null}.
445     */
446    public Builder toBuilder(){
447        return new Builder(getConversionContext()).setBase(getBase()).setTerm(getTerm()).setFactor(getFactor())
448                .setRateChain(getExchangeRateChain());
449    }
450}