001 /* Currency.java -- Representation of a currency
002 Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038 package java.util;
039
040 import java.io.IOException;
041 import java.io.ObjectStreamException;
042 import java.io.Serializable;
043 import java.text.NumberFormat;
044
045 /**
046 * Representation of a currency for a particular locale. Each currency
047 * is identified by its ISO 4217 code, and only one instance of this
048 * class exists per currency. As a result, instances are created
049 * via the <code>getInstance()</code> methods rather than by using
050 * a constructor.
051 *
052 * @see java.util.Locale
053 * @author Guilhem Lavaux (guilhem.lavaux@free.fr)
054 * @author Dalibor Topic (robilad@kaffe.org)
055 * @author Bryce McKinlay (mckinlay@redhat.com)
056 * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
057 * @since 1.4
058 */
059 public final class Currency
060 implements Serializable
061 {
062 /**
063 * For compatability with Sun's JDK
064 */
065 static final long serialVersionUID = -158308464356906721L;
066
067 /**
068 * The locale associated with this currency.
069 *
070 * @see #Currency(java.util.Locale)
071 * @see #getInstance(java.util.Locale)
072 * @see #getSymbol(java.util.Locale)
073 * @serial ignored.
074 */
075 private transient Locale locale;
076
077 /**
078 * The resource bundle which maps the currency to
079 * a ISO 4217 currency code.
080 *
081 * @see #getCurrencyCode()
082 * @serial ignored.
083 */
084 private transient ResourceBundle res;
085
086 /**
087 * The set of properties which map a currency to
088 * the currency information such as the ISO 4217
089 * currency code and the number of decimal points.
090 *
091 * @see #getCurrencyCode()
092 * @serial ignored.
093 */
094 private static transient Properties properties;
095
096 /**
097 * The ISO 4217 currency code associated with this
098 * particular instance.
099 *
100 * @see #getCurrencyCode()
101 * @serial the ISO 4217 currency code
102 */
103 private String currencyCode;
104
105 /**
106 * The number of fraction digits associated with this
107 * particular instance.
108 *
109 * @see #getDefaultFractionDigits()
110 * @serial the number of fraction digits
111 */
112 private transient int fractionDigits;
113
114 /**
115 * A cache of <code>Currency</code> instances to
116 * ensure the singleton nature of this class. The key
117 * is the locale of the currency.
118 *
119 * @see #getInstance(java.util.Locale)
120 * @see #readResolve()
121 * @serial ignored.
122 */
123 private static transient Map cache;
124
125 /**
126 * Instantiates the cache.
127 */
128 static
129 {
130 cache = new HashMap();
131 /* Create the properties object */
132 properties = new Properties();
133 /* Try and load the properties from our iso4217.properties resource */
134 try
135 {
136 properties.load(Currency.class.getResourceAsStream("iso4217.properties"));
137 }
138 catch (IOException exception)
139 {
140 System.out.println("Failed to load currency resource: " + exception);
141 }
142 }
143
144 /**
145 * Default constructor for deserialization
146 */
147 private Currency ()
148 {
149 }
150
151 /**
152 * Constructor to create a <code>Currency</code> object
153 * for a particular <code>Locale</code>.
154 * All components of the given locale, other than the
155 * country code, are ignored. The results of calling this
156 * method may vary over time, as the currency associated with
157 * a particular country changes. For countries without
158 * a given currency (e.g. Antarctica), the result is null.
159 *
160 * @param loc the locale for the new currency.
161 */
162 private Currency (Locale loc)
163 {
164 String countryCode;
165 String fractionDigitsKey;
166
167 /* Retrieve the country code from the locale */
168 countryCode = loc.getCountry();
169
170 /* If there is no country code, return */
171 if (countryCode.equals(""))
172 {
173 throw new
174 IllegalArgumentException("Invalid (empty) country code for locale:"
175 + loc);
176 }
177
178 this.locale = loc;
179 this.res = ResourceBundle.getBundle ("gnu.java.locale.LocaleInformation",
180 locale, ClassLoader.getSystemClassLoader());
181
182 /* Retrieve the ISO4217 currency code */
183 try
184 {
185 currencyCode = res.getString ("intlCurrencySymbol");
186 }
187 catch (Exception _)
188 {
189 currencyCode = null;
190 }
191
192 /* Construct the key for the fraction digits */
193 fractionDigitsKey = countryCode + ".fractionDigits";
194
195 /* Retrieve the fraction digits */
196 fractionDigits = Integer.parseInt(properties.getProperty(fractionDigitsKey));
197 }
198
199 /**
200 * Constructor for the "XXX" special case. This allows
201 * a Currency to be constructed from an assumed good
202 * currency code.
203 *
204 * @param code the code to use.
205 */
206 private Currency(String code)
207 {
208 currencyCode = code;
209 fractionDigits = -1; /* Pseudo currency */
210 }
211
212 /**
213 * Returns the ISO4217 currency code of this currency.
214 *
215 * @return a <code>String</code> containing currency code.
216 */
217 public String getCurrencyCode ()
218 {
219 return currencyCode;
220 }
221
222 /**
223 * Returns the number of digits which occur after the decimal point
224 * for this particular currency. For example, currencies such
225 * as the U.S. dollar, the Euro and the Great British pound have two
226 * digits following the decimal point to indicate the value which exists
227 * in the associated lower-valued coinage (cents in the case of the first
228 * two, pennies in the latter). Some currencies such as the Japanese
229 * Yen have no digits after the decimal point. In the case of pseudo
230 * currencies, such as IMF Special Drawing Rights, -1 is returned.
231 *
232 * @return the number of digits after the decimal separator for this currency.
233 */
234 public int getDefaultFractionDigits ()
235 {
236 return fractionDigits;
237 }
238
239 /**
240 * Builds a new currency instance for this locale.
241 * All components of the given locale, other than the
242 * country code, are ignored. The results of calling this
243 * method may vary over time, as the currency associated with
244 * a particular country changes. For countries without
245 * a given currency (e.g. Antarctica), the result is null.
246 *
247 * @param locale a <code>Locale</code> instance.
248 * @return a new <code>Currency</code> instance.
249 * @throws NullPointerException if the locale or its
250 * country code is null.
251 * @throws IllegalArgumentException if the country of
252 * the given locale is not a supported ISO3166 code.
253 */
254 public static Currency getInstance (Locale locale)
255 {
256 /**
257 * The new instance must be the only available instance
258 * for the currency it supports. We ensure this happens,
259 * while maintaining a suitable performance level, by
260 * creating the appropriate object on the first call to
261 * this method, and returning the cached instance on
262 * later calls.
263 */
264 Currency newCurrency;
265
266 /* Attempt to get the currency from the cache */
267 newCurrency = (Currency) cache.get(locale);
268 if (newCurrency == null)
269 {
270 /* Create the currency for this locale */
271 newCurrency = new Currency (locale);
272 /* Cache it */
273 cache.put(locale, newCurrency);
274 }
275 /* Return the instance */
276 return newCurrency;
277 }
278
279 /**
280 * Builds the currency corresponding to the specified currency code.
281 *
282 * @param currencyCode a string representing a currency code.
283 * @return a new <code>Currency</code> instance.
284 * @throws NullPointerException if currencyCode is null.
285 * @throws IllegalArgumentException if the supplied currency code
286 * is not a supported ISO 4217 code.
287 */
288 public static Currency getInstance (String currencyCode)
289 {
290 Locale[] allLocales = Locale.getAvailableLocales ();
291
292 /* Nasty special case to allow an erroneous currency... blame Sun */
293 if (currencyCode.equals("XXX"))
294 return new Currency("XXX");
295
296 for (int i = 0;i < allLocales.length; i++)
297 {
298 Currency testCurrency = getInstance (allLocales[i]);
299
300 if (testCurrency.getCurrencyCode() != null &&
301 testCurrency.getCurrencyCode().equals(currencyCode))
302 return testCurrency;
303 }
304 /*
305 * If we get this far, the code is not supported by any of
306 * our locales.
307 */
308 throw new IllegalArgumentException("The currency code, " + currencyCode +
309 ", is not supported.");
310 }
311
312 /**
313 * This method returns the symbol which precedes or follows a
314 * value in this particular currency. In cases where there is no
315 * such symbol for the currency, the ISO 4217 currency
316 * code is returned.
317 *
318 * @return the currency symbol, or the ISO 4217 currency code if
319 * one doesn't exist.
320 */
321 public String getSymbol()
322 {
323 try
324 {
325 /* What does this return if there is no mapping? */
326 return res.getString ("currencySymbol");
327 }
328 catch (Exception _)
329 {
330 return null;
331 }
332 }
333
334 /**
335 * <p>
336 * This method returns the symbol which precedes or follows a
337 * value in this particular currency. The returned value is
338 * the symbol used to denote the currency in the specified locale.
339 * </p>
340 * <p>
341 * For example, a supplied locale may specify a different symbol
342 * for the currency, due to conflicts with its own currency.
343 * This would be the case with the American currency, the dollar.
344 * Locales that also use a dollar-based currency (e.g. Canada, Australia)
345 * need to differentiate the American dollar using 'US$' rather than '$'.
346 * So, supplying one of these locales to <code>getSymbol()</code> would
347 * return this value, rather than the standard '$'.
348 * </p>
349 * <p>
350 * In cases where there is no such symbol for a particular currency,
351 * the ISO 4217 currency code is returned.
352 * </p>
353 *
354 * @param locale the locale to express the symbol in.
355 * @return the currency symbol, or the ISO 4217 currency code if
356 * one doesn't exist.
357 * @throws NullPointerException if the locale is null.
358 */
359 public String getSymbol(Locale locale)
360 {
361 // TODO. The behaviour is unclear if locale != this.locale.
362 // First we need to implement fully LocaleInformation*.java
363
364 /*
365 * FIXME: My reading of how this method works has this implementation
366 * as wrong. It should return a value relating to how the specified
367 * locale handles the symbol for this currency. This implementation
368 * seems to just do a variation of getInstance(locale).
369 */
370 try
371 {
372 ResourceBundle localeResource =
373 ResourceBundle.getBundle ("gnu.java.locale.LocaleInformation",
374 locale, Currency.class.getClassLoader());
375
376 if (localeResource.equals(res))
377 return localeResource.getString ("currencySymbol");
378 else
379 return localeResource.getString ("intlCurrencySymbol");
380 }
381 catch (Exception e1)
382 {
383 try
384 {
385 return res.getString ("intlCurrencySymbol");
386 }
387 catch (Exception e2)
388 {
389 return null;
390 }
391 }
392 }
393
394 /**
395 * Returns the international ISO4217 currency code of this currency.
396 *
397 * @return a <code>String</code> containing the ISO4217 currency code.
398 */
399 public String toString()
400 {
401 return getCurrencyCode();
402 }
403
404 /**
405 * Resolves the deserialized object to the singleton instance for its
406 * particular currency. The currency code of the deserialized instance
407 * is used to return the correct instance.
408 *
409 * @return the singleton instance for the currency specified by the
410 * currency code of the deserialized object. This replaces
411 * the deserialized object as the returned object from
412 * deserialization.
413 * @throws ObjectStreamException if a problem occurs with deserializing
414 * the object.
415 */
416 private Object readResolve()
417 throws ObjectStreamException
418 {
419 return getInstance(currencyCode);
420 }
421
422 }