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