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}