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 }