1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.util;
19
20import java.io.Serializable;
21import libcore.icu.ICU;
22import libcore.icu.LocaleData;
23
24/**
25 * A currency corresponding to an <a href="http://en.wikipedia.org/wiki/ISO_4217">ISO 4217</a>
26 * currency code such as "EUR" or "USD".
27 */
28public final class Currency implements Serializable {
29    private static final long serialVersionUID = -158308464356906721L;
30
31    private static final HashMap<String, Currency> codesToCurrencies = new HashMap<String, Currency>();
32    private static final HashMap<Locale, Currency> localesToCurrencies = new HashMap<Locale, Currency>();
33
34    private final String currencyCode;
35
36    private Currency(String currencyCode) {
37        this.currencyCode = currencyCode;
38        String symbol = ICU.getCurrencySymbol(Locale.US.toString(), currencyCode);
39        if (symbol == null) {
40            throw new IllegalArgumentException("Unsupported ISO 4217 currency code: " +
41                    currencyCode);
42        }
43    }
44
45    /**
46     * Returns the {@code Currency} instance for the given ISO 4217 currency code.
47     * @throws IllegalArgumentException
48     *             if the currency code is not a supported ISO 4217 currency code.
49     */
50    public static Currency getInstance(String currencyCode) {
51        synchronized (codesToCurrencies) {
52            Currency currency = codesToCurrencies.get(currencyCode);
53            if (currency == null) {
54                currency = new Currency(currencyCode);
55                codesToCurrencies.put(currencyCode, currency);
56            }
57            return currency;
58        }
59    }
60
61    /**
62     * Returns the {@code Currency} instance for this {@code Locale}'s country.
63     * @throws IllegalArgumentException
64     *             if the locale's country is not a supported ISO 3166 country.
65     */
66    public static Currency getInstance(Locale locale) {
67        synchronized (localesToCurrencies) {
68            Currency currency = localesToCurrencies.get(locale);
69            if (currency != null) {
70                return currency;
71            }
72            String country = locale.getCountry();
73            String variant = locale.getVariant();
74            if (!variant.isEmpty() && (variant.equals("EURO") || variant.equals("HK") ||
75                    variant.equals("PREEURO"))) {
76                country = country + "_" + variant;
77            }
78
79            String currencyCode = ICU.getCurrencyCode(country);
80            if (currencyCode == null) {
81                throw new IllegalArgumentException("Unsupported ISO 3166 country: " + locale);
82            } else if (currencyCode.equals("XXX")) {
83                return null;
84            }
85            Currency result = getInstance(currencyCode);
86            localesToCurrencies.put(locale, result);
87            return result;
88        }
89    }
90
91    /**
92     * Returns a set of all known currencies.
93     * @since 1.7
94     * @hide 1.7
95     */
96    public static Set<Currency> getAvailableCurrencies() {
97        Set<Currency> result = new LinkedHashSet<Currency>();
98        String[] currencyCodes = ICU.getAvailableCurrencyCodes();
99        for (String currencyCode : currencyCodes) {
100            result.add(Currency.getInstance(currencyCode));
101        }
102        return result;
103    }
104
105    /**
106     * Returns this currency's ISO 4217 currency code.
107     */
108    public String getCurrencyCode() {
109        return currencyCode;
110    }
111
112    /**
113     * Equivalent to {@code getDisplayName(Locale.getDefault())}.
114     * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".
115     * @since 1.7
116     * @hide 1.7
117     */
118    public String getDisplayName() {
119        return getDisplayName(Locale.getDefault());
120    }
121
122    /**
123     * Returns the localized name of this currency in the given {@code locale}.
124     * Returns the ISO 4217 currency code if no localized name is available.
125     * @since 1.7
126     * @hide 1.7
127     */
128    public String getDisplayName(Locale locale) {
129        return ICU.getCurrencyDisplayName(locale.toString(), currencyCode);
130    }
131
132    /**
133     * Equivalent to {@code getSymbol(Locale.getDefault())}.
134     * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".
135     */
136    public String getSymbol() {
137        return getSymbol(Locale.getDefault());
138    }
139
140    /**
141     * Returns the localized currency symbol for this currency in {@code locale}.
142     * That is, given "USD" and Locale.US, you'd get "$", but given "USD" and a non-US locale,
143     * you'd get "US$".
144     *
145     * <p>If the locale only specifies a language rather than a language and a country (such as
146     * {@code Locale.JAPANESE} or {new Locale("en", "")} rather than {@code Locale.JAPAN} or
147     * {new Locale("en", "US")}), the ISO 4217 currency code is returned.
148     *
149     * <p>If there is no locale-specific currency symbol, the ISO 4217 currency code is returned.
150     */
151    public String getSymbol(Locale locale) {
152        if (locale.getCountry().length() == 0) {
153            return currencyCode;
154        }
155
156        // Check the locale first, in case the locale has the same currency.
157        LocaleData localeData = LocaleData.get(locale);
158        if (localeData.internationalCurrencySymbol.equals(currencyCode)) {
159            return localeData.currencySymbol;
160        }
161
162        // Try ICU, and fall back to the currency code if ICU has nothing.
163        String symbol = ICU.getCurrencySymbol(locale.toString(), currencyCode);
164        return symbol != null ? symbol : currencyCode;
165    }
166
167    /**
168     * Returns the default number of fraction digits for this currency.
169     * For instance, the default number of fraction digits for the US dollar is 2 because there are
170     * 100 US cents in a US dollar. For the Japanese Yen, the number is 0 because coins smaller
171     * than 1 Yen became invalid in 1953. In the case of pseudo-currencies, such as
172     * IMF Special Drawing Rights, -1 is returned.
173     */
174    public int getDefaultFractionDigits() {
175        // In some places the code XXX is used as the fall back currency.
176        // The RI returns -1, but ICU defaults to 2 for unknown currencies.
177        if (currencyCode.equals("XXX")) {
178            return -1;
179        }
180        return ICU.getCurrencyFractionDigits(currencyCode);
181    }
182
183    /**
184     * Returns this currency's ISO 4217 currency code.
185     */
186    @Override
187    public String toString() {
188        return currencyCode;
189    }
190
191    private Object readResolve() {
192        return getInstance(currencyCode);
193    }
194}
195