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: package java.util; 39: 40: import java.io.IOException; 41: import java.io.ObjectStreamException; 42: import java.io.Serializable; 43: import java.text.NumberFormat; 44: 45: /** 46: * Representation of a currency for a particular locale. Each currency 47: * is identified by its ISO 4217 code, and only one instance of this 48: * class exists per currency. As a result, instances are created 49: * via the <code>getInstance()</code> methods rather than by using 50: * a constructor. 51: * 52: * @see java.util.Locale 53: * @author Guilhem Lavaux (guilhem.lavaux@free.fr) 54: * @author Dalibor Topic (robilad@kaffe.org) 55: * @author Bryce McKinlay (mckinlay@redhat.com) 56: * @author Andrew John Hughes (gnu_andrew@member.fsf.org) 57: * @since 1.4 58: */ 59: public final class Currency 60: implements Serializable 61: { 62: /** 63: * For compatability with Sun's JDK 64: */ 65: static final long serialVersionUID = -158308464356906721L; 66: 67: /** 68: * The locale associated with this currency. 69: * 70: * @see #Currency(java.util.Locale) 71: * @see #getInstance(java.util.Locale) 72: * @see #getSymbol(java.util.Locale) 73: * @serial ignored. 74: */ 75: private transient Locale locale; 76: 77: /** 78: * The resource bundle which maps the currency to 79: * a ISO 4217 currency code. 80: * 81: * @see #getCurrencyCode() 82: * @serial ignored. 83: */ 84: private transient ResourceBundle res; 85: 86: /** 87: * The set of properties which map a currency to 88: * the currency information such as the ISO 4217 89: * currency code and the number of decimal points. 90: * 91: * @see #getCurrencyCode() 92: * @serial ignored. 93: */ 94: private static transient Properties properties; 95: 96: /** 97: * The ISO 4217 currency code associated with this 98: * particular instance. 99: * 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: }