001/* 002 * Copyright (c) 2012, 2013, Credit Suisse (Anatole Tresch), Werner Keil. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016package org.javamoney.moneta; 017 018import java.io.Serializable; 019import java.util.Currency; 020import java.util.Map; 021import java.util.concurrent.ConcurrentHashMap; 022 023import javax.money.CurrencyUnit; 024 025/** 026 * Platform RI: Adapter that implements the new {@link CurrencyUnit} interface 027 * using the JDK's {@link Currency}. 028 * 029 * @version 0.5.1 030 * @author Anatole Tresch 031 * @author Werner Keil 032 */ 033public final class MoneyCurrency implements CurrencyUnit, Serializable, 034 Comparable<CurrencyUnit> { 035 036 /** 037 * serialVersionUID. 038 */ 039 private static final long serialVersionUID = -2523936311372374236L; 040 041 /** currency code for this currency. */ 042 private String currencyCode; 043 /** numeric code, or -1. */ 044 private int numericCode; 045 /** fraction digits, or -1. */ 046 private int defaultFractionDigits; 047 /** The cache rounding value, -1 if not defined. */ 048 private int cacheRounding = -1; 049 050 private static final Map<String, MoneyCurrency> CACHED = new ConcurrentHashMap<String, MoneyCurrency>(); 051 052 public static final String ISO_NAMESPACE = "IDO 4217"; 053 054 /** 055 * Private constructor. 056 * 057 * @param currency 058 */ 059 private MoneyCurrency(String code, 060 int numCode, 061 int fractionDigits) { 062 this.currencyCode = code; 063 this.numericCode = numCode; 064 this.defaultFractionDigits = fractionDigits; 065 } 066 067 /** 068 * Private constructor. 069 * 070 * @param currency 071 */ 072 private MoneyCurrency(Currency currency) { 073 if (currency == null) { 074 throw new IllegalArgumentException("Currency required."); 075 } 076 this.currencyCode = currency.getCurrencyCode(); 077 this.numericCode = currency.getNumericCode(); 078 this.defaultFractionDigits = currency.getDefaultFractionDigits(); 079 } 080 081 /** 082 * Access a new instance based on {@link Currency}. 083 * 084 * @param currency 085 * the currency unit not null. 086 * @return the new instance, never null. 087 */ 088 public static MoneyCurrency of(Currency currency) { 089 String key = currency.getCurrencyCode(); 090 MoneyCurrency cachedItem = CACHED.get(key); 091 if (cachedItem == null) { 092 cachedItem = new MoneyCurrency(currency); 093 CACHED.put(key, cachedItem); 094 } 095 return cachedItem; 096 } 097 098 /** 099 * Access a new instance based on the ISO currency code. The code must 100 * return a {@link Currency} when passed to 101 * {@link Currency#getInstance(String)}. 102 * 103 * @param namespace 104 * the target namespace. 105 * @param currencyCode 106 * the ISO currency code, not null. 107 * @return the corresponding {@link MonetaryCurrency} instance. 108 * @throws IllegalArgumentException 109 * if no such currency exists. 110 */ 111 public static MoneyCurrency of(String currencyCode) { 112 MoneyCurrency cu = CACHED.get(currencyCode); 113 if (cu == null) { 114 if (MoneyCurrency.isJavaCurrency(currencyCode)) { 115 Currency cur = Currency.getInstance(currencyCode); 116 if (cur != null) { 117 return of(cur); 118 } 119 } 120 } 121 if (cu == null) { 122 throw new IllegalArgumentException("No such currency: " 123 + currencyCode); 124 } 125 return cu; 126 } 127 128 /* 129 * (non-Javadoc) 130 * 131 * @see javax.money.CurrencyUnit#getCurrencyCode() 132 */ 133 public String getCurrencyCode() { 134 return currencyCode; 135 } 136 137 /** 138 * Gets a numeric currency code. within the ISO-4217 name space, this equals 139 * to the ISO numeric code. In other currency name spaces this number may be 140 * different, or even undefined (-1). 141 * <p> 142 * The numeric code is an optional alternative to the standard currency 143 * code. If defined, the numeric code is required to be unique within its 144 * namespace. 145 * <p> 146 * This method matches the API of <type>java.util.Currency</type>. 147 * 148 * @see #getNamespace() 149 * @return the numeric currency code 150 */ 151 public int getNumericCode() { 152 return numericCode; 153 } 154 155 /** 156 * Gets the number of fractional digits typically used by this currency. 157 * <p> 158 * Different currencies have different numbers of fractional digits by 159 * default. * For example, 'GBP' has 2 fractional digits, but 'JPY' has 160 * zero. * virtual currencies or those with no applicable fractional are 161 * indicated by -1. * 162 * <p> 163 * This method matches the API of <type>java.util.Currency</type>. 164 * 165 * @return the fractional digits, from 0 to 9 (normally 0, 2 or 3), or -1 166 * for pseudo-currencies. 167 * 168 */ 169 public int getDefaultFractionDigits() { 170 return defaultFractionDigits; 171 } 172 173 /** 174 * Get the rounding steps in minor units for when using a cash amount of 175 * this currency. E.g. Swiss Francs in cash are always rounded in 5 minor 176 * unit steps. This results in {@code 1.00, 1.05, 1.10} etc. The cash 177 * rounding consequently extends the default fraction units for certain 178 * currencies. 179 * 180 * @return the cash rounding, or -1, if not defined. 181 */ 182 public int getCashRounding() { 183 return cacheRounding; 184 } 185 186 /* 187 * (non-Javadoc) 188 * 189 * @see java.lang.Comparable#compareTo(java.lang.Object) 190 */ 191 public int compareTo(CurrencyUnit currency) { 192 return getCurrencyCode().compareTo(currency.getCurrencyCode()); 193 } 194 195 /* 196 * (non-Javadoc) 197 * 198 * @see java.lang.Object#hashCode() 199 */ 200 @Override 201 public int hashCode() { 202 final int prime = 31; 203 int result = 1; 204 result = prime * result 205 + ((currencyCode == null) ? 0 : currencyCode.hashCode()); 206 return result; 207 } 208 209 /* 210 * (non-Javadoc) 211 * 212 * @see java.lang.Object#equals(java.lang.Object) 213 */ 214 @Override 215 public boolean equals(Object obj) { 216 if (this == obj) 217 return true; 218 if (obj == null) 219 return false; 220 if (getClass() != obj.getClass()) 221 return false; 222 MoneyCurrency other = (MoneyCurrency) obj; 223 if (currencyCode == null) { 224 if (other.currencyCode != null) 225 return false; 226 } else if (!currencyCode.equals(other.currencyCode)) 227 return false; 228 return true; 229 } 230 231 /** 232 * Returns {@link #getCurrencyCode()} 233 * 234 * @see java.lang.Object#toString() 235 */ 236 @Override 237 public String toString() { 238 return currencyCode; 239 } 240 241 /** 242 * Platform RI: Builder class that supports building complex instances of 243 * {@link MoneyCurrency}. 244 * 245 * @author Anatole Tresch 246 */ 247 public static final class Builder { 248 /** currency code for this currency. */ 249 private String currencyCode; 250 /** numeric code, or -1. */ 251 private int numericCode = -1; 252 /** fraction digits, or -1. */ 253 private int defaultFractionDigits = -1; 254 /** Cache rounding. */ 255 private int cacheRounding = -1; 256 257 /** 258 * Creates a new {@link Builder}. 259 */ 260 public Builder() { 261 } 262 263 /** 264 * Set the currency code. 265 * 266 * @param namespace 267 * the currency code, not null 268 * @return the builder, for chaining 269 */ 270 public Builder withCurrencyCode(String currencyCode) { 271 if (currencyCode == null) { 272 throw new IllegalArgumentException( 273 "currencyCode may not be null."); 274 } 275 this.currencyCode = currencyCode; 276 return this; 277 } 278 279 /** 280 * Set the default fraction digits. 281 * 282 * @param defaultFractionDigits 283 * the default fraction digits 284 * @return the builder, for chaining 285 */ 286 public Builder withDefaultFractionDigits(int defaultFractionDigits) { 287 if (defaultFractionDigits < -1) { 288 throw new IllegalArgumentException( 289 "Invalid value for defaultFractionDigits: " 290 + defaultFractionDigits); 291 } 292 this.defaultFractionDigits = defaultFractionDigits; 293 return this; 294 } 295 296 /** 297 * Set the default fraction digits. 298 * 299 * @param defaultFractionDigits 300 * the default fraction digits 301 * @return the builder, for chaining 302 */ 303 public Builder withCashRounding(int cacheRounding) { 304 if (cacheRounding < -1) { 305 throw new IllegalArgumentException( 306 "Invalid value for cacheRounding: " + cacheRounding); 307 } 308 this.cacheRounding = cacheRounding; 309 return this; 310 } 311 312 /** 313 * Set the numeric currency code. 314 * 315 * @param numericCode 316 * the numeric currency code 317 * @return the builder, for chaining 318 */ 319 public Builder withNumericCode(int numericCode) { 320 if (numericCode < -1) { 321 throw new IllegalArgumentException( 322 "Invalid value for numericCode: " + numericCode); 323 } 324 this.numericCode = numericCode; 325 return this; 326 } 327 328 /** 329 * Builds a new currency instance, the instance build is not cached 330 * internally. 331 * 332 * @see #build(boolean) 333 * @return a new instance of {@link MoneyCurrency}. 334 */ 335 public MoneyCurrency build() { 336 return build(true); 337 } 338 339 /** 340 * Builds a new currency instance, which ia additinoally stored to the 341 * internal cache for reuse. 342 * 343 * @param cache 344 * flag to optionally store the instance created into the 345 * locale cache. 346 * @return a new instance of {@link MoneyCurrency}. 347 */ 348 public MoneyCurrency build(boolean cache) { 349 if (cache) { 350 MoneyCurrency current = CACHED.get(currencyCode); 351 if (current == null) { 352 current = new MoneyCurrency(currencyCode, 353 numericCode, defaultFractionDigits); 354 CACHED.put(currencyCode, current); 355 } 356 return current; 357 } 358 return new MoneyCurrency(currencyCode, numericCode, 359 defaultFractionDigits); 360 } 361 } 362 363 public static MoneyCurrency from(CurrencyUnit currency) { 364 if (MoneyCurrency.class == currency.getClass()) { 365 return (MoneyCurrency) currency; 366 } 367 return MoneyCurrency.of(currency.getCurrencyCode()); 368 } 369 370 public static boolean isJavaCurrency(String code) { 371 try { 372 return Currency.getInstance(code) != null; 373 } catch (Exception e) { 374 return false; 375 } 376 } 377 378 public static boolean isAvailable(String code) { 379 try { 380 of(code); 381 return true; 382 } catch (Exception e) { 383 return false; 384 } 385 } 386 387}