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}