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
20// BEGIN android-added
21import com.ibm.icu4jni.util.LocaleData;
22import com.ibm.icu4jni.util.Resources;
23import java.util.logging.Logger;
24import org.apache.harmony.luni.util.Msg;
25// END android-added
26
27import java.security.AccessController;
28import java.io.Serializable;
29import java.security.PrivilegedAction;
30
31/**
32 * This class represents a currency as identified in the ISO 4217 currency
33 * codes.
34 */
35public final class Currency implements Serializable {
36
37    private static final long serialVersionUID = -158308464356906721L;
38
39    private static final Hashtable<String, Currency> codesToCurrencies = new Hashtable<String, Currency>();
40    private static final Hashtable<Locale, Currency> localesToCurrencies = new Hashtable<Locale, Currency>();
41
42    private final String currencyCode;
43
44    // BEGIN android-added
45    // TODO: this isn't set if we're restored from serialized form,
46    // so getDefaultFractionDigits always returns 0!
47    private transient int defaultFractionDigits;
48    // END android-added
49
50    private Currency(String currencyCode) {
51        // BEGIN android-changed
52        this.currencyCode = currencyCode;
53
54        // In some places the code XXX is used as the fall back currency.
55        // The RI returns -1, but ICU defaults to 2 for unknown currencies.
56        if (currencyCode.equals("XXX")) {
57            this.defaultFractionDigits = -1;
58            return;
59        }
60
61        this.defaultFractionDigits = Resources.getCurrencyFractionDigitsNative(currencyCode);
62        if (defaultFractionDigits < 0) {
63            throw new IllegalArgumentException(Msg.getString("K0322", currencyCode));
64        }
65        // END android-changed
66    }
67
68    /**
69     * Returns the {@code Currency} instance for the given currency code.
70     * <p>
71     *
72     * @param currencyCode
73     *            the currency code.
74     * @return the {@code Currency} instance for this currency code.
75     *
76     * @throws IllegalArgumentException
77     *             if the currency code is not a supported ISO 4217 currency
78     *             code.
79     */
80    public static Currency getInstance(String currencyCode) {
81        // BEGIN android-changed
82        Currency currency = codesToCurrencies.get(currencyCode);
83        if (currency == null) {
84            currency = new Currency(currencyCode);
85            codesToCurrencies.put(currencyCode, currency);
86        }
87        return currency;
88        // END android-changed
89    }
90
91    /**
92     * Returns the {@code Currency} instance for this {@code Locale}'s country.
93     *
94     * @param locale
95     *            the {@code Locale} of a country.
96     * @return the {@code Currency} used in the country defined by the locale parameter.
97     *
98     * @throws IllegalArgumentException
99     *             if the locale's country is not a supported ISO 3166 Country.
100     */
101    public static Currency getInstance(Locale locale) {
102        // BEGIN android-changed
103        Currency currency = localesToCurrencies.get(locale);
104        if (currency != null) {
105            return currency;
106        }
107        String country = locale.getCountry();
108        String variant = locale.getVariant();
109        if (variant.length() > 0 && (variant.equals("EURO") || variant.equals("HK") ||
110                variant.equals("PREEURO"))) {
111            country = country + "_" + variant;
112        }
113
114        String currencyCode = Resources.getCurrencyCodeNative(country);
115        if (currencyCode == null) {
116            throw new IllegalArgumentException(Msg.getString("K0323", locale.toString()));
117        } else if (currencyCode.equals("None")) {
118            return null;
119        }
120        Currency result = getInstance(currencyCode);
121        localesToCurrencies.put(locale, result);
122        return result;
123        // END android-changed
124    }
125
126    /**
127     * Returns this {@code Currency}'s ISO 4217 currency code.
128     *
129     * @return this {@code Currency}'s ISO 4217 currency code.
130     */
131    public String getCurrencyCode() {
132        return currencyCode;
133    }
134
135    /**
136     * Returns the symbol for this currency in the default locale. For instance,
137     * if the default locale is the US, the symbol of the US dollar is "$". For
138     * other locales it may be "US$". If no symbol can be determined, the ISO
139     * 4217 currency code of the US dollar is returned.
140     *
141     * @return the symbol for this {@code Currency} in the default {@code Locale}.
142     */
143    public String getSymbol() {
144        return getSymbol(Locale.getDefault());
145    }
146
147    /**
148     * Returns the symbol for this currency in the given {@code Locale}.
149     * That is, given "USD" and Locale.US, you'd get "$", but given "USD" and a non-US locale,
150     * you'd get "US$".
151     * <p>
152     * If the locale only specifies a language rather than a language and a countries (e.g.
153     * {@code Locale.JAPANESE, new Locale("en","")}), the the ISO
154     * 4217 currency code is returned.
155     * <p>
156     * If there is no currency symbol specific to this locale does not exist, the
157     * ISO 4217 currency code is returned.
158     * <p>
159     *
160     * @param locale
161     *            the locale for which the currency symbol should be returned.
162     * @return the representation of this {@code Currency}'s symbol in the specified
163     *         locale.
164     */
165    public String getSymbol(Locale locale) {
166        // BEGIN android-changed
167        if (locale.getCountry().length() == 0) {
168            return currencyCode;
169        }
170
171        // Check the locale first, in case the locale has the same currency.
172        LocaleData localeData = Resources.getLocaleData(locale);
173        if (localeData.internationalCurrencySymbol.equals(currencyCode)) {
174            return localeData.currencySymbol;
175        }
176
177        // Try ICU, and fall back to the currency code if ICU has nothing.
178        String symbol = Resources.getCurrencySymbolNative(locale.toString(), currencyCode);
179        return symbol != null ? symbol : currencyCode;
180        // END android-changed
181    }
182
183    /**
184     * Returns the default number of fraction digits for this currency. For
185     * instance, the default number of fraction digits for the US dollar is 2.
186     * For the Japanese Yen the number is 0. In the case of pseudo-currencies,
187     * such as IMF Special Drawing Rights, -1 is returned.
188     *
189     * @return the default number of fraction digits for this currency.
190     */
191    public int getDefaultFractionDigits() {
192        // BEGIN android-changed
193        // return com.ibm.icu.util.Currency.getInstance(currencyCode).getDefaultFractionDigits();
194        return defaultFractionDigits;
195        // END android-changed
196    }
197
198    /**
199     * Returns this currency's ISO 4217 currency code.
200     *
201     * @return this currency's ISO 4217 currency code.
202     */
203    @Override
204    public String toString() {
205        return currencyCode;
206    }
207
208    private Object readResolve() {
209        return getInstance(currencyCode);
210    }
211
212    // TODO: remove this in favor of direct access (and no ResourceBundle cruft).
213    private static ResourceBundle getCurrencyBundle(final Locale locale) {
214        return AccessController.doPrivileged(new PrivilegedAction<ResourceBundle>() {
215            public ResourceBundle run() {
216                String bundle = "org.apache.harmony.luni.internal.locale.Currency";
217                return ResourceBundle.getBundle(bundle, locale);
218            }
219        });
220    }
221}
222