001    /*
002     *  Copyright 2009-2013 Stephen Colebourne
003     *
004     *  Licensed under the Apache License, Version 2.0 (the "License");
005     *  you may not use this file except in compliance with the License.
006     *  You may obtain a copy of 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,
012     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     *  See the License for the specific language governing permissions and
014     *  limitations under the License.
015     */
016    package org.joda.money;
017    
018    import java.io.InvalidObjectException;
019    import java.io.ObjectInputStream;
020    import java.io.Serializable;
021    import java.util.ArrayList;
022    import java.util.Collections;
023    import java.util.Currency;
024    import java.util.List;
025    import java.util.Locale;
026    import java.util.concurrent.ConcurrentHashMap;
027    import java.util.concurrent.ConcurrentMap;
028    
029    import org.joda.convert.FromString;
030    import org.joda.convert.ToString;
031    
032    /**
033     * A unit of currency.
034     * <p>
035     * This class represents a unit of currency such as the British Pound, Euro
036     * or US Dollar.
037     * <p>
038     * The set of loaded currencies is provided by an instance of {@link CurrencyUnitDataProvider}.
039     * The provider used is determined by the system property {@code org.joda.money.CurrencyUnitDataProvider}
040     * which should be the fully qualified class name of the provider. The default provider loads the first
041     * resource named {@code /org/joda/money/MoneyData.csv} on the classpath.
042     * <p>
043     * This class is immutable and thread-safe.
044     */
045    public final class CurrencyUnit implements Comparable<CurrencyUnit>, Serializable {
046    
047        /**
048         * The serialisation version.
049         */
050        private static final long serialVersionUID = 327835287287L;
051        /**
052         * Map of registered currencies by text code.
053         */
054        private static final ConcurrentMap<String, CurrencyUnit> currenciesByCode = new ConcurrentHashMap<String, CurrencyUnit>();
055        /**
056         * Map of registered currencies by numeric code.
057         */
058        private static final ConcurrentMap<Integer, CurrencyUnit> currenciesByNumericCode = new ConcurrentHashMap<Integer, CurrencyUnit>();
059        /**
060         * Map of registered currencies by country.
061         */
062        private static final ConcurrentMap<String, CurrencyUnit> currenciesByCountry = new ConcurrentHashMap<String, CurrencyUnit>();
063        static {
064            // load one data provider by system property
065            try {
066                try {
067                    String clsName = System.getProperty(
068                            "org.joda.money.CurrencyUnitDataProvider", "org.joda.money.DefaultCurrencyUnitDataProvider");
069                    Class<? extends CurrencyUnitDataProvider> cls =
070                            CurrencyUnit.class.getClassLoader().loadClass(clsName).asSubclass(CurrencyUnitDataProvider.class);
071                    cls.newInstance().registerCurrencies();
072                } catch (SecurityException ex) {
073                    new DefaultCurrencyUnitDataProvider().registerCurrencies();
074                }
075            } catch (RuntimeException ex) {
076                throw ex;
077            } catch (Exception ex) {
078                throw new RuntimeException(ex.toString(), ex);
079            }
080        }
081    
082        // a selection of commonly traded, stable currencies
083        /**
084         * The currency 'USD' - United States Dollar.
085         */
086        public static final CurrencyUnit USD = of("USD");
087        /**
088         * The currency 'EUR' - Euro.
089         */
090        public static final CurrencyUnit EUR = of("EUR");
091        /**
092         * The currency 'JPY' - Japanese Yen.
093         */
094        public static final CurrencyUnit JPY = of("JPY");
095        /**
096         * The currency 'GBP' - British pound.
097         */
098        public static final CurrencyUnit GBP = of("GBP");
099        /**
100         * The currency 'CHF' - Swiss Franc.
101         */
102        public static final CurrencyUnit CHF = of("CHF");
103        /**
104         * The currency 'AUD' - Australian Dollar.
105         */
106        public static final CurrencyUnit AUD = of("AUD");
107        /**
108         * The currency 'CAD' - Canadian Dollar.
109         */
110        public static final CurrencyUnit CAD = of("CAD");
111    
112        /**
113         * The currency code, not null.
114         */
115        private final String code;
116        /**
117         * The numeric currency code.
118         */
119        private final short numericCode;
120        /**
121         * The number of decimal places.
122         */
123        private final short decimalPlaces;
124    
125        //-----------------------------------------------------------------------
126        /**
127         * Registers a currency allowing it to be used.
128         * <p>
129         * This class only permits known currencies to be returned.
130         * To achieve this, all currencies have to be registered in advance, at
131         * application startup.
132         *
133         * @param currencyCode  the currency code, not null
134         * @param numericCurrencyCode  the numeric currency code, -1 if none
135         * @param decimalPlaces  the number of decimal places that the currency
136         *  normally has, from 0 to 9 (normally 0, 2 or 3), or -1 for a pseudo-currency
137         * @param countryCodes  the country codes to register the currency under, not null
138         * @return the new instance, never null
139         */
140        static synchronized CurrencyUnit registerCurrency(
141                String currencyCode, int numericCurrencyCode, int decimalPlaces, List<String> countryCodes) {
142            MoneyUtils.checkNotNull(currencyCode, "Currency code must not be null");
143            if (numericCurrencyCode < -1 || numericCurrencyCode > 999) {
144                throw new IllegalArgumentException("Invalid numeric code");
145            }
146            if (decimalPlaces < -1 || decimalPlaces > 9) {
147                throw new IllegalArgumentException("Invalid number of decimal places");
148            }
149            MoneyUtils.checkNotNull(countryCodes, "Country codes must not be null");
150            
151            CurrencyUnit currency = new CurrencyUnit(currencyCode, (short) numericCurrencyCode, (short) decimalPlaces);
152            if (currenciesByCode.containsKey(currencyCode) || currenciesByNumericCode.containsKey(numericCurrencyCode)) {
153                throw new IllegalArgumentException("Currency already registered: " + currencyCode);
154            }
155            for (String countryCode : countryCodes) {
156                if (currenciesByCountry.containsKey(countryCode)) {
157                    throw new IllegalArgumentException("Currency already registered for country: " + countryCode);
158                }
159            }
160            currenciesByCode.putIfAbsent(currencyCode, currency);
161            if (numericCurrencyCode >= 0) {
162                currenciesByNumericCode.putIfAbsent(numericCurrencyCode, currency);
163            }
164            for (String countryCode : countryCodes) {
165                currenciesByCountry.put(countryCode, currency);
166            }
167            return currenciesByCode.get(currencyCode);
168        }
169    
170        /**
171         * Gets the list of all registered currencies.
172         * <p>
173         * This class only permits known currencies to be returned, thus this list is
174         * the complete list of valid singleton currencies. The list may change after
175         * application startup, however this isn't recommended.
176         *
177         * @return the sorted, independent, list of all registered currencies, never null
178         */
179        public static List<CurrencyUnit> registeredCurrencies() {
180            ArrayList<CurrencyUnit> list = new ArrayList<CurrencyUnit>(currenciesByCode.values());
181            Collections.sort(list);
182            return list;
183        }
184    
185        //-----------------------------------------------------------------------
186        /**
187         * Obtains an instance of {@code CurrencyUnit} matching the specified JDK currency.
188         * <p>
189         * This converts the JDK currency instance to a currency unit using the code.
190         *
191         * @param currency  the currency, not null
192         * @return the singleton instance, never null
193         */
194        public static CurrencyUnit of(Currency currency) {
195            MoneyUtils.checkNotNull(currency, "Currency must not be null");
196            return of(currency.getCurrencyCode());
197        }
198    
199        /**
200         * Obtains an instance of {@code CurrencyUnit} for the specified ISO-4217 three letter currency code.
201         * <p>
202         * A currency is uniquely identified by ISO-4217 three letter code.
203         *
204         * @param currencyCode  the currency code, not null
205         * @return the singleton instance, never null
206         * @throws IllegalCurrencyException if the currency is unknown
207         */
208        @FromString
209        public static CurrencyUnit of(String currencyCode) {
210            MoneyUtils.checkNotNull(currencyCode, "Currency code must not be null");
211            CurrencyUnit currency = currenciesByCode.get(currencyCode);
212            if (currency == null) {
213                throw new IllegalCurrencyException("Unknown currency '" + currencyCode + '\'');
214            }
215            return currency;
216        }
217    
218        /**
219         * Obtains an instance of {@code CurrencyUnit} for the specified ISO-4217 numeric currency code.
220         * <p>
221         * The numeric code is an alternative to the three letter code.
222         * This method is lenient and does not require the string to be left padded with zeroes.
223         *
224         * @param numericCurrencyCode  the currency code, not null
225         * @return the singleton instance, never null
226         * @throws IllegalCurrencyException if the currency is unknown
227         */
228        public static CurrencyUnit ofNumericCode(String numericCurrencyCode) {
229            MoneyUtils.checkNotNull(numericCurrencyCode, "Currency code must not be null");
230            switch (numericCurrencyCode.length()) {
231                case 1:
232                    return ofNumericCode(numericCurrencyCode.charAt(0) - '0');
233                case 2:
234                    return ofNumericCode((numericCurrencyCode.charAt(0) - '0') * 10 +
235                                          numericCurrencyCode.charAt(1) - '0');
236                case 3:
237                    return ofNumericCode((numericCurrencyCode.charAt(0) - '0') * 100 +
238                                         (numericCurrencyCode.charAt(1) - '0') * 10 +
239                                          numericCurrencyCode.charAt(2) - '0');
240                default:
241                    throw new IllegalCurrencyException("Unknown currency '" + numericCurrencyCode + '\'');
242            }
243        }
244    
245        /**
246         * Obtains an instance of {@code CurrencyUnit} for the specified ISO-4217 numeric currency code.
247         * <p>
248         * The numeric code is an alternative to the three letter code.
249         *
250         * @param numericCurrencyCode  the numeric currency code, not null
251         * @return the singleton instance, never null
252         * @throws IllegalCurrencyException if the currency is unknown
253         */
254        public static CurrencyUnit ofNumericCode(int numericCurrencyCode) {
255            CurrencyUnit currency = currenciesByNumericCode.get(numericCurrencyCode);
256            if (currency == null) {
257                throw new IllegalCurrencyException("Unknown currency '" + numericCurrencyCode + '\'');
258            }
259            return currency;
260        }
261    
262        /**
263         * Obtains an instance of {@code CurrencyUnit} for the specified locale.
264         * <p>
265         * Only the country is used from the locale.
266         *
267         * @param locale  the locale, not null
268         * @return the singleton instance, never null
269         * @throws IllegalCurrencyException if the currency is unknown
270         */
271        public static CurrencyUnit of(Locale locale) {
272            MoneyUtils.checkNotNull(locale, "Locale must not be null");
273            CurrencyUnit currency = currenciesByCountry.get(locale.getCountry());
274            if (currency == null) {
275                throw new IllegalCurrencyException("Unknown currency for locale '" + locale + '\'');
276            }
277            return currency;
278        }
279    
280        /**
281         * Obtains an instance of {@code CurrencyUnit} for the specified country code.
282         * <p>
283         * Country codes should generally be in upper case.
284         * This method is case sensitive.
285         *
286         * @param countryCode  the country code, not null
287         * @return the singleton instance, never null
288         * @throws IllegalCurrencyException if the currency is unknown
289         */
290        public static CurrencyUnit ofCountry(String countryCode) {
291            MoneyUtils.checkNotNull(countryCode, "Country code must not be null");
292            CurrencyUnit currency = currenciesByCountry.get(countryCode);
293            if (currency == null) {
294                throw new IllegalCurrencyException("Unknown currency for country '" + countryCode + '\'');
295            }
296            return currency;
297        }
298    
299        //-----------------------------------------------------------------------
300        /**
301         * Obtains an instance of {@code CurrencyUnit} for the specified currency code.
302         * <p>
303         * This method exists to match the API of {@link Currency}.
304         *
305         * @param currencyCode  the currency code, not null
306         * @return the singleton instance, never null
307         * @throws IllegalCurrencyException if the currency is unknown
308         */
309        public static CurrencyUnit getInstance(String currencyCode) {
310            return CurrencyUnit.of(currencyCode);
311        }
312    
313        /**
314         * Obtains an instance of {@code CurrencyUnit} for the specified locale.
315         * <p>
316         * This method exists to match the API of {@link Currency}.
317         *
318         * @param locale  the locale, not null
319         * @return the singleton instance, never null
320         * @throws IllegalCurrencyException if the currency is unknown
321         */
322        public static CurrencyUnit getInstance(Locale locale) {
323            return CurrencyUnit.of(locale);
324        }
325    
326        //-----------------------------------------------------------------------
327        /**
328         * Constructor, creating a new currency instance.
329         * 
330         * @param code  the currency code, not null
331         * @param numericCurrencyCode  the numeric currency code, -1 if none
332         * @param decimalPlaces  the decimal places, not null
333         */
334        CurrencyUnit(String code, short numericCurrencyCode, short decimalPlaces) {
335            assert code != null : "Joda-Money bug: Currency code must not be null";
336            this.code = code;
337            this.numericCode = numericCurrencyCode;
338            this.decimalPlaces = decimalPlaces;
339        }
340    
341        /**
342         * Block malicious data streams.
343         * 
344         * @param ois  the input stream, not null
345         * @throws InvalidObjectException
346         */
347        private void readObject(ObjectInputStream ois) throws InvalidObjectException {
348            throw new InvalidObjectException("Serialization delegate required");
349        }
350    
351        /**
352         * Uses a serialization delegate.
353         * 
354         * @return the replacing object, never null
355         */
356        private Object writeReplace() {
357            return new Ser(Ser.CURRENCY_UNIT, this);
358        }
359    
360        //-----------------------------------------------------------------------
361        /**
362         * Gets the ISO-4217 three letter currency code.
363         * <p>
364         * Each currency is uniquely identified by a three-letter code.
365         * 
366         * @return the three letter ISO-4217 currency code, never null
367         */
368        public String getCode() {
369            return code;
370        }
371    
372        /**
373         * Gets the ISO-4217 numeric currency code.
374         * <p>
375         * The numeric code is an alternative to the standard three letter code.
376         * 
377         * @return the numeric currency code
378         */
379        public int getNumericCode() {
380            return numericCode;
381        }
382    
383        /**
384         * Gets the ISO-4217 numeric currency code as a three digit string.
385         * <p>
386         * This formats the numeric code as a three digit string prefixed by zeroes if necessary.
387         * If there is no valid code, then an empty string is returned.
388         * 
389         * @return the three digit numeric currency code, empty is no code, never null
390         */
391        public String getNumeric3Code() {
392            if (numericCode < 0) {
393                return "";
394            }
395            String str = Integer.toString(numericCode);
396            if (str.length() == 1) {
397                return "00" + str;
398            }
399            if (str.length() == 2) {
400                return "0" + str;
401            }
402            return str;
403        }
404    
405        /**
406         * Gets the number of decimal places typically used by this currency.
407         * <p>
408         * Different currencies have different numbers of decimal places by default.
409         * For example, 'GBP' has 2 decimal places, but 'JPY' has zero.
410         * Pseudo-currencies will return zero.
411         * <p>
412         * See also {@link #getDefaultFractionDigits()}.
413         * 
414         * @return the decimal places, from 0 to 9 (normally 0, 2 or 3)
415         */
416        public int getDecimalPlaces() {
417            return decimalPlaces < 0 ? 0 : decimalPlaces;
418        }
419    
420        /**
421         * Checks if this is a pseudo-currency.
422         * 
423         * @return true if this is a pseudo-currency
424         */
425        public boolean isPseudoCurrency() {
426            return decimalPlaces < 0;
427        }
428    
429        //-----------------------------------------------------------------------
430        /**
431         * Gets the ISO-4217 three-letter currency code.
432         * <p>
433         * This method matches the API of {@link Currency}.
434         * 
435         * @return the currency code, never null
436         */
437        public String getCurrencyCode() {
438            return code;
439        }
440    
441        /**
442         * Gets the number of fractional digits typically used by this currency.
443         * <p>
444         * Different currencies have different numbers of fractional digits by default.
445         * For example, 'GBP' has 2 fractional digits, but 'JPY' has zero.
446         * Pseudo-currencies are indicated by -1.
447         * <p>
448         * This method matches the API of {@link Currency}.
449         * The alternative method {@link #getDecimalPlaces()} may be more useful.
450         * 
451         * @return the fractional digits, from 0 to 9 (normally 0, 2 or 3), or -1 for pseudo-currencies
452         */
453        public int getDefaultFractionDigits() {
454            return decimalPlaces;
455        }
456    
457        //-----------------------------------------------------------------------
458        /**
459         * Gets the symbol for this locale from the JDK.
460         * <p>
461         * If this currency doesn't have a JDK equivalent, then the currency code
462         * is returned.
463         * <p>
464         * This method matches the API of {@link Currency}.
465         * 
466         * @return the JDK currency instance, never null
467         */
468        public String getSymbol() {
469            try {
470                return Currency.getInstance(code).getSymbol();
471            } catch (IllegalArgumentException ex) {
472                return code;
473            }
474        }
475    
476        /**
477         * Gets the symbol for this locale from the JDK.
478         * <p>
479         * If this currency doesn't have a JDK equivalent, then the currency code
480         * is returned.
481         * <p>
482         * This method matches the API of {@link Currency}.
483         * 
484         * @param locale  the locale to get the symbol for, not null
485         * @return the JDK currency instance, never null
486         */
487        public String getSymbol(Locale locale) {
488            MoneyUtils.checkNotNull(locale, "Locale must not be null");
489            try {
490                return Currency.getInstance(code).getSymbol(locale);
491            } catch (IllegalArgumentException ex) {
492                return code;
493            }
494        }
495    
496        //-----------------------------------------------------------------------
497        /**
498         * Gets the JDK currency instance equivalent to this currency.
499         * <p>
500         * This attempts to convert a {@code CurrencyUnit} to a JDK {@code Currency}.
501         * 
502         * @return the JDK currency instance, never null
503         * @throws IllegalArgumentException if no matching currency exists in the JDK
504         */
505        public Currency toCurrency() {
506            return Currency.getInstance(code);
507        }
508    
509        //-----------------------------------------------------------------------
510        /**
511         * Compares this currency to another by alphabetical comparison of the code.
512         * 
513         * @param other  the other currency, not null
514         * @return negative if earlier alphabetically, 0 if equal, positive if greater alphabetically
515         */
516        public int compareTo(CurrencyUnit other) {
517            return code.compareTo(other.code);
518        }
519    
520        /**
521         * Checks if this currency equals another currency.
522         * <p>
523         * The comparison checks the 3 letter currency code.
524         * 
525         * @param obj  the other currency, null returns false
526         * @return true if equal
527         */
528        @Override
529        public boolean equals(Object obj) {
530            if (obj == this) {
531                return true;
532            }
533            if (obj instanceof CurrencyUnit) {
534                return code.equals(((CurrencyUnit) obj).code);
535            }
536            return false;
537        }
538    
539        /**
540         * Returns a suitable hash code for the currency.
541         * 
542         * @return the hash code
543         */
544        @Override
545        public int hashCode() {
546            return code.hashCode();
547        }
548    
549        //-----------------------------------------------------------------------
550        /**
551         * Gets the currency code as a string.
552         * 
553         * @return the currency code, never null
554         */
555        @Override
556        @ToString
557        public String toString() {
558            return code;
559        }
560    
561    }