GNU Classpath (0.95) | |
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: System.out.println("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: /* Attempt to get the currency from the cache */ 277: String code = (String) countryMap.get(country); 278: if (code == null) 279: { 280: /* Create the currency for this locale */ 281: newCurrency = new Currency(locale); 282: /* 283: * If the currency code is null, then creation failed 284: * and we return null. 285: */ 286: code = newCurrency.getCurrencyCode(); 287: if (code == null) 288: { 289: return null; 290: } 291: else 292: { 293: /* Cache it */ 294: countryMap.put(country, code); 295: cache.put(code, newCurrency); 296: } 297: } 298: else 299: { 300: newCurrency = (Currency) cache.get(code); 301: } 302: /* Return the instance */ 303: return newCurrency; 304: } 305: 306: /** 307: * Builds the currency corresponding to the specified currency code. 308: * 309: * @param currencyCode a string representing a currency code. 310: * @return a new <code>Currency</code> instance. 311: * @throws NullPointerException if currencyCode is null. 312: * @throws IllegalArgumentException if the supplied currency code 313: * is not a supported ISO 4217 code. 314: */ 315: public static Currency getInstance(String currencyCode) 316: { 317: Locale[] allLocales; 318: 319: /* 320: * Throw a null pointer exception explicitly if currencyCode is null. 321: * One is not thrown otherwise. It results in an 322: * IllegalArgumentException. 323: */ 324: if (currencyCode == null) 325: { 326: throw new NullPointerException("The supplied currency code is null."); 327: } 328: /* Nasty special case to allow an erroneous currency... blame Sun */ 329: if (currencyCode.equals("XXX")) 330: return new Currency("XXX"); 331: Currency newCurrency = (Currency) cache.get(currencyCode); 332: if (newCurrency == null) 333: { 334: /* Get all locales */ 335: allLocales = Locale.getAvailableLocales(); 336: /* Loop through each locale, looking for the code */ 337: for (int i = 0;i < allLocales.length; i++) 338: { 339: try 340: { 341: Currency testCurrency = getInstance (allLocales[i]); 342: if (testCurrency != null && 343: testCurrency.getCurrencyCode().equals(currencyCode)) 344: { 345: return testCurrency; 346: } 347: } 348: catch (IllegalArgumentException exception) 349: { 350: /* Ignore locales without valid countries */ 351: } 352: } 353: /* 354: * If we get this far, the code is not supported by any of 355: * our locales. 356: */ 357: throw new IllegalArgumentException("The currency code, " + currencyCode + 358: ", is not supported."); 359: } 360: else 361: { 362: return newCurrency; 363: } 364: } 365: 366: /** 367: * This method returns the symbol which precedes or follows a 368: * value in this particular currency in the default locale. 369: * In cases where there is no such symbol for the currency, 370: * the ISO 4217 currency code is returned. 371: * 372: * @return the currency symbol, or the ISO 4217 currency code if 373: * one doesn't exist. 374: */ 375: public String getSymbol() 376: { 377: return getSymbol(Locale.getDefault()); 378: } 379: 380: /** 381: * <p> 382: * This method returns the symbol which precedes or follows a 383: * value in this particular currency. The returned value is 384: * the symbol used to denote the currency in the specified locale. 385: * </p> 386: * <p> 387: * For example, a supplied locale may specify a different symbol 388: * for the currency, due to conflicts with its own currency. 389: * This would be the case with the American currency, the dollar. 390: * Locales that also use a dollar-based currency (e.g. Canada, Australia) 391: * need to differentiate the American dollar using 'US$' rather than '$'. 392: * So, supplying one of these locales to <code>getSymbol()</code> would 393: * return this value, rather than the standard '$'. 394: * </p> 395: * <p> 396: * In cases where there is no such symbol for a particular currency, 397: * the ISO 4217 currency code is returned. 398: * </p> 399: * 400: * @param locale the locale to express the symbol in. 401: * @return the currency symbol, or the ISO 4217 currency code if 402: * one doesn't exist. 403: * @throws NullPointerException if the locale is null. 404: */ 405: public String getSymbol(Locale locale) 406: { 407: String property = "currenciesSymbol." + currencyCode; 408: try 409: { 410: return ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", 411: locale).getString(property); 412: } 413: catch (MissingResourceException exception) 414: { 415: /* This means runtime support for the locale 416: * is not available, so we check providers. */ 417: } 418: for (CurrencyNameProvider p : 419: ServiceLoader.load(CurrencyNameProvider.class)) 420: { 421: for (Locale loc : p.getAvailableLocales()) 422: { 423: if (loc.equals(locale)) 424: { 425: String localizedString = p.getSymbol(currencyCode, 426: locale); 427: if (localizedString != null) 428: return localizedString; 429: break; 430: } 431: } 432: } 433: if (locale.equals(Locale.ROOT)) // Base case 434: return currencyCode; 435: return getSymbol(LocaleHelper.getFallbackLocale(locale)); 436: } 437: 438: /** 439: * Returns the international ISO4217 currency code of this currency. 440: * 441: * @return a <code>String</code> containing the ISO4217 currency code. 442: */ 443: public String toString() 444: { 445: return getCurrencyCode(); 446: } 447: 448: /** 449: * Resolves the deserialized object to the singleton instance for its 450: * particular currency. The currency code of the deserialized instance 451: * is used to return the correct instance. 452: * 453: * @return the singleton instance for the currency specified by the 454: * currency code of the deserialized object. This replaces 455: * the deserialized object as the returned object from 456: * deserialization. 457: * @throws ObjectStreamException if a problem occurs with deserializing 458: * the object. 459: */ 460: private Object readResolve() 461: throws ObjectStreamException 462: { 463: return getInstance(currencyCode); 464: } 465: 466: }
GNU Classpath (0.95) |