Frames | No Frames |
1: /* Currency.java -- Representation of a currency 2: Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package java.util; 40: 41: import gnu.java.locale.LocaleHelper; 42: 43: import java.io.IOException; 44: import java.io.ObjectStreamException; 45: import java.io.Serializable; 46: 47: import java.util.spi.CurrencyNameProvider; 48: 49: /** 50: * Representation of a currency for a particular locale. Each currency 51: * is identified by its ISO 4217 code, and only one instance of this 52: * class exists per currency. As a result, instances are created 53: * via the <code>getInstance()</code> methods rather than by using 54: * a constructor. 55: * 56: * @see java.util.Locale 57: * @author Guilhem Lavaux (guilhem.lavaux@free.fr) 58: * @author Dalibor Topic (robilad@kaffe.org) 59: * @author Bryce McKinlay (mckinlay@redhat.com) 60: * @author Andrew John Hughes (gnu_andrew@member.fsf.org) 61: * @since 1.4 62: */ 63: public final class Currency 64: implements Serializable 65: { 66: /** 67: * For compatability with Sun's JDK 68: */ 69: static final long serialVersionUID = -158308464356906721L; 70: 71: /** 72: * The set of properties which map a currency to 73: * the currency information such as the ISO 4217 74: * currency code and the number of decimal points. 75: * 76: * @see #getCurrencyCode() 77: * @serial ignored. 78: */ 79: private static transient Properties properties; 80: 81: /** 82: * The ISO 4217 currency code associated with this 83: * particular instance. 84: * 85: * @see #getCurrencyCode() 86: * @serial the ISO 4217 currency code 87: */ 88: private String currencyCode; 89: 90: /** 91: * The number of fraction digits associated with this 92: * particular instance. 93: * 94: * @see #getDefaultFractionDigits() 95: * @serial the number of fraction digits 96: */ 97: private transient int fractionDigits; 98: 99: /** 100: * A cached map of country codes 101: * instances to international currency code 102: * <code>String</code>s. Seperating this 103: * from the <code>Currency</code> instances 104: * ensures we have a common lookup between 105: * the two <code>getInstance()</code> methods. 106: * 107: * @see #getInstance(java.util.Locale) 108: * @serial ignored. 109: */ 110: private static transient Map countryMap; 111: 112: /** 113: * A cache of <code>Currency</code> instances to 114: * ensure the singleton nature of this class. The key 115: * is the international currency code. 116: * 117: * @see #getInstance(java.util.Locale) 118: * @see #getInstance(java.lang.String) 119: * @see #readResolve() 120: * @serial ignored. 121: */ 122: private static transient Map cache; 123: 124: /** 125: * Instantiates the cache and reads in the properties. 126: */ 127: static 128: { 129: /* Create a hash map for the locale mappings */ 130: countryMap = new HashMap(); 131: /* Create a hash map for the cache */ 132: cache = new HashMap(); 133: /* Create the properties object */ 134: properties = new Properties(); 135: /* Try and load the properties from our iso4217.properties resource */ 136: try 137: { 138: properties.load(Currency.class.getResourceAsStream("iso4217.properties")); 139: } 140: catch (IOException exception) 141: { 142: throw new InternalError("Failed to load currency resource: " + exception); 143: } 144: } 145: 146: /** 147: * Default constructor for deserialization 148: */ 149: private Currency() 150: { 151: } 152: 153: /** 154: * Constructor to create a <code>Currency</code> object 155: * for a particular <code>Locale</code>. 156: * All components of the given locale, other than the 157: * country code, are ignored. The results of calling this 158: * method may vary over time, as the currency associated with 159: * a particular country changes. For countries without 160: * a given currency (e.g. Antarctica), the result is null. 161: * 162: * @param loc the locale for the new currency, or null if 163: * there is no country code specified or a currency 164: * for this country. 165: */ 166: private Currency(Locale loc) 167: { 168: String countryCode; 169: String currencyKey; 170: String fractionDigitsKey; 171: int commaPosition; 172: 173: /* Retrieve the country code from the locale */ 174: countryCode = loc.getCountry(); 175: /* If there is no country code, return */ 176: if (countryCode.equals("")) 177: { 178: throw new 179: IllegalArgumentException("Invalid (empty) country code for locale:" 180: + loc); 181: } 182: /* Construct the key for the currency */ 183: currencyKey = countryCode + ".currency"; 184: /* Construct the key for the fraction digits */ 185: fractionDigitsKey = countryCode + ".fractionDigits"; 186: /* Retrieve the currency */ 187: currencyCode = properties.getProperty(currencyKey); 188: /* Return if the currency code is null */ 189: if (currencyCode == null) 190: { 191: return; 192: } 193: /* Split off the first currency code (we only use the first for now) */ 194: commaPosition = currencyCode.indexOf(","); 195: if (commaPosition != -1) 196: { 197: currencyCode = currencyCode.substring(0, commaPosition); 198: } 199: /* Retrieve the fraction digits */ 200: fractionDigits = Integer.parseInt(properties.getProperty(fractionDigitsKey)); 201: } 202: 203: /** 204: * Constructor for the "XXX" special case. This allows 205: * a Currency to be constructed from an assumed good 206: * currency code. 207: * 208: * @param code the code to use. 209: */ 210: private Currency(String code) 211: { 212: currencyCode = code; 213: fractionDigits = -1; /* Pseudo currency */ 214: } 215: 216: /** 217: * Returns the ISO4217 currency code of this currency. 218: * 219: * @return a <code>String</code> containing currency code. 220: */ 221: public String getCurrencyCode() 222: { 223: return currencyCode; 224: } 225: 226: /** 227: * Returns the number of digits which occur after the decimal point 228: * for this particular currency. For example, currencies such 229: * as the U.S. dollar, the Euro and the Great British pound have two 230: * digits following the decimal point to indicate the value which exists 231: * in the associated lower-valued coinage (cents in the case of the first 232: * two, pennies in the latter). Some currencies such as the Japanese 233: * Yen have no digits after the decimal point. In the case of pseudo 234: * currencies, such as IMF Special Drawing Rights, -1 is returned. 235: * 236: * @return the number of digits after the decimal separator for this currency. 237: */ 238: public int getDefaultFractionDigits() 239: { 240: return fractionDigits; 241: } 242: 243: /** 244: * Builds a new currency instance for this locale. 245: * All components of the given locale, other than the 246: * country code, are ignored. The results of calling this 247: * method may vary over time, as the currency associated with 248: * a particular country changes. For countries without 249: * a given currency (e.g. Antarctica), the result is null. 250: * 251: * @param locale a <code>Locale</code> instance. 252: * @return a new <code>Currency</code> instance. 253: * @throws NullPointerException if the locale or its 254: * country code is null. 255: * @throws IllegalArgumentException if the country of 256: * the given locale is not a supported ISO3166 code. 257: */ 258: public static Currency getInstance(Locale locale) 259: { 260: /** 261: * The new instance must be the only available instance 262: * for the currency it supports. We ensure this happens, 263: * while maintaining a suitable performance level, by 264: * creating the appropriate object on the first call to 265: * this method, and returning the cached instance on 266: * later calls. 267: */ 268: Currency newCurrency; 269: 270: String country = locale.getCountry(); 271: if (locale == null || country == null) 272: { 273: throw new 274: NullPointerException("The locale or its country is null."); 275: } 276: 277: /* Check that country of locale given is valid. */ 278: if (country.length() != 2) 279: throw new IllegalArgumentException(); 280: 281: /* Attempt to get the currency from the cache */ 282: String code = (String) countryMap.get(country); 283: if (code == null) 284: { 285: /* Create the currency for this locale */ 286: newCurrency = new Currency(locale); 287: /* 288: * If the currency code is null, then creation failed 289: * and we return null. 290: */ 291: code = newCurrency.getCurrencyCode(); 292: if (code == null) 293: { 294: return null; 295: } 296: else 297: { 298: /* Cache it */ 299: countryMap.put(country, code); 300: cache.put(code, newCurrency); 301: } 302: } 303: else 304: { 305: newCurrency = (Currency) cache.get(code); 306: } 307: /* Return the instance */ 308: return newCurrency; 309: } 310: 311: /** 312: * Builds the currency corresponding to the specified currency code. 313: * 314: * @param currencyCode a string representing a currency code. 315: * @return a new <code>Currency</code> instance. 316: * @throws NullPointerException if currencyCode is null. 317: * @throws IllegalArgumentException if the supplied currency code 318: * is not a supported ISO 4217 code. 319: */ 320: public static Currency getInstance(String currencyCode) 321: { 322: Locale[] allLocales; 323: 324: /* 325: * Throw a null pointer exception explicitly if currencyCode is null. 326: * One is not thrown otherwise. It results in an 327: * IllegalArgumentException. 328: */ 329: if (currencyCode == null) 330: { 331: throw new NullPointerException("The supplied currency code is null."); 332: } 333: /* Nasty special case to allow an erroneous currency... blame Sun */ 334: if (currencyCode.equals("XXX")) 335: return new Currency("XXX"); 336: Currency newCurrency = (Currency) cache.get(currencyCode); 337: if (newCurrency == null) 338: { 339: /* Get all locales */ 340: allLocales = Locale.getAvailableLocales(); 341: /* Loop through each locale, looking for the code */ 342: for (int i = 0;i < allLocales.length; i++) 343: { 344: try 345: { 346: Currency testCurrency = getInstance (allLocales[i]); 347: if (testCurrency != null && 348: testCurrency.getCurrencyCode().equals(currencyCode)) 349: { 350: return testCurrency; 351: } 352: } 353: catch (IllegalArgumentException exception) 354: { 355: /* Ignore locales without valid countries */ 356: } 357: } 358: /* 359: * If we get this far, the code is not supported by any of 360: * our locales. 361: */ 362: throw new IllegalArgumentException("The currency code, " + currencyCode + 363: ", is not supported."); 364: } 365: else 366: { 367: return newCurrency; 368: } 369: } 370: 371: /** 372: * This method returns the symbol which precedes or follows a 373: * value in this particular currency in the default locale. 374: * In cases where there is no such symbol for the currency, 375: * the ISO 4217 currency code is returned. 376: * 377: * @return the currency symbol, or the ISO 4217 currency code if 378: * one doesn't exist. 379: */ 380: public String getSymbol() 381: { 382: return getSymbol(Locale.getDefault()); 383: } 384: 385: /** 386: * <p> 387: * This method returns the symbol which precedes or follows a 388: * value in this particular currency. The returned value is 389: * the symbol used to denote the currency in the specified locale. 390: * </p> 391: * <p> 392: * For example, a supplied locale may specify a different symbol 393: * for the currency, due to conflicts with its own currency. 394: * This would be the case with the American currency, the dollar. 395: * Locales that also use a dollar-based currency (e.g. Canada, Australia) 396: * need to differentiate the American dollar using 'US$' rather than '$'. 397: * So, supplying one of these locales to <code>getSymbol()</code> would 398: * return this value, rather than the standard '$'. 399: * </p> 400: * <p> 401: * In cases where there is no such symbol for a particular currency, 402: * the ISO 4217 currency code is returned. 403: * </p> 404: * 405: * @param locale the locale to express the symbol in. 406: * @return the currency symbol, or the ISO 4217 currency code if 407: * one doesn't exist. 408: * @throws NullPointerException if the locale is null. 409: */ 410: public String getSymbol(Locale locale) 411: { 412: String property = "currenciesSymbol." + currencyCode; 413: try 414: { 415: return ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", 416: locale).getString(property); 417: } 418: catch (MissingResourceException exception) 419: { 420: /* This means runtime support for the locale 421: * is not available, so we check providers. */ 422: } 423: for (CurrencyNameProvider p : 424: ServiceLoader.load(CurrencyNameProvider.class)) 425: { 426: for (Locale loc : p.getAvailableLocales()) 427: { 428: if (loc.equals(locale)) 429: { 430: String localizedString = p.getSymbol(currencyCode, 431: locale); 432: if (localizedString != null) 433: return localizedString; 434: break; 435: } 436: } 437: } 438: if (locale.equals(Locale.ROOT)) // Base case 439: return currencyCode; 440: return getSymbol(LocaleHelper.getFallbackLocale(locale)); 441: } 442: 443: /** 444: * Returns the international ISO4217 currency code of this currency. 445: * 446: * @return a <code>String</code> containing the ISO4217 currency code. 447: */ 448: public String toString() 449: { 450: return getCurrencyCode(); 451: } 452: 453: /** 454: * Resolves the deserialized object to the singleton instance for its 455: * particular currency. The currency code of the deserialized instance 456: * is used to return the correct instance. 457: * 458: * @return the singleton instance for the currency specified by the 459: * currency code of the deserialized object. This replaces 460: * the deserialized object as the returned object from 461: * deserialization. 462: * @throws ObjectStreamException if a problem occurs with deserializing 463: * the object. 464: */ 465: private Object readResolve() 466: throws ObjectStreamException 467: { 468: return getInstance(currencyCode); 469: } 470: 471: }