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    }