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 format 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 */
084@SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
085public final class TestExchangeRate implements ExchangeRate, Serializable, Comparable<ExchangeRate> {
086
087    /**
088     * serialVersionUID.
089     */
090    private static final long serialVersionUID = 5077295306570465837L;
091    /**
092     * The base currency.
093     */
094    private final CurrencyUnit base;
095    /**
096     * The terminating currency.
097     */
098    private final CurrencyUnit term;
099    /**
100     * The conversion factor.
101     */
102    private final NumberValue factor;
103    /**
104     * The {@link javax.money.convert.ConversionContext}
105     */
106    private final ConversionContext conversionContext;
107    /**
108     * The full chain, at least one instance long.
109     */
110    private List<ExchangeRate> chain = new ArrayList<>();
111
112
113    /**
114     * Creates a new instance with a custom chain of exchange rate type, e.g. or
115     * creating <i>derived</i> rates.
116     *
117     * @param builder The Builder, never {@code null}.
118     */
119    private TestExchangeRate(Builder builder) {
120        Objects.requireNonNull(builder.base, "base may not be null.");
121        Objects.requireNonNull(builder.term, "term may not be null.");
122        Objects.requireNonNull(builder.factor, "factor may not be null.");
123        Objects.requireNonNull(builder.conversionContext, "exchangeRateType may not be null.");
124        this.base = builder.base;
125        this.term = builder.term;
126        this.factor = builder.factor;
127        this.conversionContext = builder.conversionContext;
128
129        setExchangeRateChain(builder.rateChain);
130    }
131
132    /**
133     * Internal method to set the rate chain, which also ensure that the chain
134     * passed, when not null, contains valid elements.
135     *
136     * @param chain the chain to set.
137     */
138    private void setExchangeRateChain(List<ExchangeRate> chain) {
139        this.chain.clear();
140        if (chain == null || chain.isEmpty()) {
141            this.chain.add(this);
142        } else {
143            for (ExchangeRate aChain : chain) {
144                if (chain == null) {
145                    throw new IllegalArgumentException("Chain element can not be null.");
146                }
147            }
148            this.chain.addAll(chain);
149        }
150    }
151
152    /**
153     * Access the {@link javax.money.convert.ConversionContext} of {@link javax.money.convert.ExchangeRate}.
154     *
155     * @return the conversion context, never null.
156     */
157    public final ConversionContext getContext() {
158        return this.conversionContext;
159    }
160
161    /**
162     * Get the base (source) {@link javax.money.CurrencyUnit}.
163     *
164     * @return the base {@link javax.money.CurrencyUnit}.
165     */
166    public final CurrencyUnit getBaseCurrency() {
167        return this.base;
168    }
169
170    /**
171     * Get the term (target) {@link javax.money.CurrencyUnit}.
172     *
173     * @return the term {@link javax.money.CurrencyUnit}.
174     */
175    public final CurrencyUnit getCurrency() {
176        return this.term;
177    }
178
179    /**
180     * Access the rate's bid factor.
181     *
182     * @return the bid factor for this exchange rate, or {@code null}.
183     */
184    public final NumberValue getFactor() {
185        return this.factor;
186    }
187
188    /**
189     * Access the chain of exchange rates.
190     *
191     * @return the chain of rates, in case of a derived rate, this may be
192     * several instances. For a direct exchange rate, this equals to
193     * <code>new ExchangeRate[]{this}</code>.
194     */
195    public final List<ExchangeRate> getExchangeRateChain() {
196        return this.chain;
197    }
198
199    /**
200     * Allows to evaluate if this exchange rate is a derived exchange rate.
201     * Derived exchange rates are defined by an ordered list of subconversions
202     * with intermediate steps, whereas a direct conversion is possible in one
203     * steps.
204     * <p>
205     * This method always returns {@code true}, if the chain contains more than
206     * one rate. Direct rates, have also a chain, but with exact one rate.
207     *
208     * @return true, if the exchange rate is derived.
209     */
210    public final boolean isDerived() {
211        return this.chain.size() > 1;
212    }
213
214    /*
215     * (non-Javadoc)
216     *
217     * @see java.lang.Comparable#compareTo(java.lang.Object)
218     */
219    @Override
220    public int compareTo(ExchangeRate o) {
221        if (o == null) {
222            return -1;
223        }
224        int compare = this.getBaseCurrency().getCurrencyCode().compareTo(o.getBaseCurrency().getCurrencyCode());
225        if (compare == 0) {
226            compare = this.getCurrency().getCurrencyCode().compareTo(o.getCurrency().getCurrencyCode());
227        }
228        if (compare == 0) {
229            compare = this.getContext().getProviderName().compareTo(o.getContext().getProviderName());
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        final int prime = 31;
252        int result = 1;
253        result = prime * result + ((base == null) ? 0 : base.hashCode());
254        result = prime * result + ((conversionContext == null) ? 0 : conversionContext.hashCode());
255        result = prime * result + ((factor == null) ? 0 : factor.hashCode());
256        result = prime * result + ((term == null) ? 0 : term.hashCode());
257        result = prime * result + ((chain == null) ? 0 : chain.hashCode());
258        return result;
259    }
260
261    /*
262     * (non-Javadoc)
263     *
264     * @see java.lang.Object#equals(java.lang.Object)
265     */
266    @Override
267    public boolean equals(Object obj) {
268        if (this == obj) {
269            return true;
270        }
271        if (obj == null) {
272            return false;
273        }
274        if (getClass() != obj.getClass()) {
275            return false;
276        }
277        TestExchangeRate other = (TestExchangeRate) obj;
278        if (base == null) {
279            if (other.base != null) {
280                return false;
281            }
282        } else if (!base.equals(other.base)) {
283            return false;
284        }
285        if (!chain.equals(other.getExchangeRateChain())) {
286            return false;
287        }
288        if (conversionContext == null) {
289            if (other.conversionContext != null) {
290                return false;
291            }
292        } else if (!conversionContext.equals(other.conversionContext)) {
293            return false;
294        }
295        if (factor == null) {
296            if (other.factor != null) {
297                return false;
298            }
299        } else if (!factor.equals(other.factor)) {
300            return false;
301        }
302        if (term == null) {
303            if (other.term != null) {
304                return false;
305            }
306        } else if (!term.equals(other.term)) {
307            return false;
308        }
309        return true;
310    }
311
312    /**
313     * Builder for creating new instances of {@link javax.money.convert.ExchangeRate}. Note that
314     * instances of this class are not thread-safe.
315     *
316     * @author Anatole Tresch
317     * @author Werner Keil
318     */
319    public static class Builder {
320
321        /**
322         * The {@link javax.money.convert.ConversionContext}.
323         */
324        private ConversionContext conversionContext;
325        /**
326         * The base (source) currency.
327         */
328        private CurrencyUnit base;
329        /**
330         * The term (target) currency.
331         */
332        private CurrencyUnit term;
333        /**
334         * The conversion factor.
335         */
336        private NumberValue factor;
337        /**
338         * The chain of invovled rates.
339         */
340        private List<ExchangeRate> rateChain = new ArrayList<>();
341
342        /**
343         * Sets the exchange rate type
344         *
345         * @param rateType the {@link javax.money.convert.RateType} contained
346         */
347        public Builder(String provider, RateType rateType) {
348            this(ConversionContext.of(provider, rateType));
349        }
350
351        /**
352         * Sets the exchange rate type
353         *
354         * @param context the {@link javax.money.convert.ConversionContext} to be applied
355         */
356        public Builder(ConversionContext context) {
357            setContext(context);
358        }
359
360        /**
361         * Sets the exchange rate type
362         *
363         * @param rate the {@link javax.money.convert.ExchangeRate} to be applied
364         */
365        public Builder(ExchangeRate rate) {
366            setContext(rate.getContext());
367            setFactor(rate.getFactor());
368            setTerm(rate.getCurrency());
369            setBase(rate.getBaseCurrency());
370            setRateChain(rate.getExchangeRateChain());
371        }
372
373        /**
374         * Sets the base {@link javax.money.CurrencyUnit}
375         *
376         * @param base to base (source) {@link javax.money.CurrencyUnit} to be applied
377         * @return the builder instance
378         */
379        public Builder setBase(CurrencyUnit base) {
380            this.base = base;
381            return this;
382        }
383
384        /**
385         * Sets the terminating (target) {@link javax.money.CurrencyUnit}
386         *
387         * @param term to terminating {@link javax.money.CurrencyUnit} to be applied
388         * @return the builder instance
389         */
390        public Builder setTerm(CurrencyUnit term) {
391            this.term = term;
392            return this;
393        }
394
395        /**
396         * Sets the {@link javax.money.convert.ExchangeRate} chain.
397         *
398         * @param exchangeRates the {@link javax.money.convert.ExchangeRate} chain to be applied
399         * @return the builder instance
400         */
401        public Builder setRateChain(ExchangeRate... exchangeRates) {
402            this.rateChain.clear();
403            if (exchangeRates != null) {
404                this.rateChain.addAll(Arrays.asList(exchangeRates.clone()));
405            }
406            return this;
407        }
408
409        /**
410         * Sets the {@link javax.money.convert.ExchangeRate} chain.
411         *
412         * @param exchangeRates the {@link javax.money.convert.ExchangeRate} chain to be applied
413         * @return the builder instance
414         */
415        public Builder setRateChain(List<ExchangeRate> exchangeRates) {
416            this.rateChain.clear();
417            if (exchangeRates != null) {
418                this.rateChain.addAll(exchangeRates);
419            }
420            return this;
421        }
422
423
424        /**
425         * Sets the conversion factor, as the factor
426         * {@code base * factor = target}.
427         *
428         * @param factor the factor.
429         * @return The builder instance.
430         */
431        public Builder setFactor(NumberValue factor) {
432            this.factor = factor;
433            return this;
434        }
435
436        /**
437         * Sets the provider to be applied.
438         *
439         * @param conversionContext the {@link javax.money.convert.ConversionContext}, not null.
440         * @return The builder.
441         */
442        public Builder setContext(ConversionContext conversionContext) {
443            Objects.requireNonNull(conversionContext);
444            this.conversionContext = conversionContext;
445            return this;
446        }
447
448        /**
449         * Builds a new instance of {@link javax.money.convert.ExchangeRate}.
450         *
451         * @return a new instance of {@link javax.money.convert.ExchangeRate}.
452         * @throws IllegalArgumentException if the rate could not be built.
453         */
454        public TestExchangeRate build() {
455            return new TestExchangeRate(this);
456        }
457
458        /**
459         * Initialize the {@link Builder} with an {@link javax.money.convert.ExchangeRate}. This is
460         * useful for creating a new rate, reusing some properties from an
461         * existing one.
462         *
463         * @param rate the base rate
464         * @return the Builder, for chaining.
465         */
466        public Builder setRate(ExchangeRate rate) {
467            this.base = rate.getBaseCurrency();
468            this.term = rate.getCurrency();
469            this.conversionContext = rate.getContext();
470            this.factor = rate.getFactor();
471            this.rateChain = rate.getExchangeRateChain();
472            this.term = rate.getCurrency();
473            return this;
474        }
475    }
476
477    /**
478     * Create a {@link Builder} based on the current rate instance.
479     *
480     * @return a new {@link Builder}, never {@code null}.
481     */
482    public Builder toBuilder() {
483        return new Builder(getContext()).setBase(getBaseCurrency()).setTerm(getCurrency())
484                .setFactor(getFactor()).setRateChain(getExchangeRateChain());
485    }
486}