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