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}