1 package com.jbidwatcher.util; 2 /* 3 * Copyright (c) 2000-2007, CyberFOX Software, Inc. All Rights Reserved. 4 * 5 * Developed by mrs (Morgan Schweers) 6 */ 7 8 import com.jbidwatcher.util.config.JConfig; 9 import org.jetbrains.annotations.NotNull; 10 11 import java.text.NumberFormat; 12 import java.util.Locale; 13 import java.util.Map; 14 import java.util.HashMap; 15 import java.beans.PersistenceDelegate; 16 import java.beans.DefaultPersistenceDelegate; 17 18 public class Currency implements Comparable { 19 public static final String VALUE_REGEX="^(\\s?\\$)?[0-9]+([,.0-9]*)$"; 20 public static final String NAME_REGEX = "(USD|GBP|JPY|CHF|FRF|EUR|CAD|AUD|NTD|TWD|HKD|MYR|SGD|INR|US)"; 21 22 private static NumberFormat df = NumberFormat.getNumberInstance(Locale.US); // We create a lot of these, so minimizing memory usage is good. 23 public static final int NONE=0, US_DOLLAR=1, UK_POUND=2, JP_YEN=3, GER_MARK=4, FR_FRANC=5, CAN_DOLLAR=6; 24 public static final int EURO=7, AU_DOLLAR=8, CH_FRANC=9, NT_DOLLAR=10, TW_DOLLAR=10, HK_DOLLAR=11; 25 public static final int MY_REAL=12, SG_DOLLAR=13, IND_RUPEE=14; 26 private static Currency _noValue = null; 27 28 /** 29 * @brief This provides a concept of a currency value that is 30 * invalid, not just 'zero' in some arbitrary currency. 31 * 32 * @return A single, consistent, 'Empty Value', which indicates an 33 * invalid currency. 34 */ NoValue()35 public static Currency NoValue() { 36 if(_noValue == null) _noValue = new Currency(NONE, 0.0); 37 38 return _noValue; 39 } 40 41 protected int mCurrencyType; 42 protected double mValue; 43 private static final char pound = '\u00A3'; 44 private static final Character objPound = '\u00A3'; 45 46 private static Map<Integer,Double> sCurrencyMap = new HashMap<Integer,Double>(); 47 48 /** 49 * Convert a non-US currency to USD, usually for sorting purposes. 50 * 51 * Takes two values (usd, non-usd) which are believed to be 52 * equivalent, and a currency amount to convert based off the 53 * ratio between the first two. If the USD amount is null or $0, 54 * it looks in a table it keeps around for converting. If that fails, 55 * it just returns the non-USD's value. 56 * 57 * @param usd - A sample US dollar amount. 58 * @param nonusd - A non-US dollar amount that is equivalent to the usd paramter. 59 * @param cvt - The non-USD amount to convert to USD. 60 * 61 * @return - 'cvt' converted by the ratio of usd:nonusd, or by an internal table if 62 * it couldn't figure out the ratio, or just the non-usd's amount as a USD amount if 63 * there wasn't even an entry in the table. 64 */ convertToUSD(Currency usd, Currency nonusd, Currency cvt)65 public static Currency convertToUSD(Currency usd, Currency nonusd, Currency cvt) { 66 if(cvt != null && !cvt.isNull() && cvt.getCurrencyType() != US_DOLLAR) { 67 double multiple; 68 if(usd == null || usd.isNull() || usd.getValue() == 0.0 || 69 nonusd == null || nonusd.isNull() || nonusd.getValue() == 0.0) { 70 if(sCurrencyMap.containsKey(cvt.getCurrencyType())) { 71 multiple = sCurrencyMap.get(cvt.getCurrencyType()); 72 } else { 73 // If we have nothing else to go on, treat it as exactly equal to USD. 74 multiple = 1.0; 75 } 76 } else { 77 multiple = usd.getValue() / nonusd.getValue(); 78 if(multiple != 0.0) sCurrencyMap.put(nonusd.getCurrencyType(), multiple); 79 } 80 return getCurrency(US_DOLLAR, multiple*cvt.getValue()); 81 } 82 83 return cvt; 84 } 85 86 /*!@class CurrencyTypeException 87 * 88 * @brief A class to yell about currency type comparison exceptions. 89 * 90 * This is used when comparing two currencies of disparate monies. 91 */ 92 public static class CurrencyTypeException extends Exception { 93 String _associatedString; 94 CurrencyTypeException(String inString)95 public CurrencyTypeException(String inString) { 96 _associatedString = inString; 97 } toString()98 public String toString() { 99 return _associatedString; 100 } 101 } 102 103 private static final Integer CurDollar = US_DOLLAR; // American Dollar 104 private static final Integer CurPound = UK_POUND; // British Pound 105 private static final Integer CurYen = JP_YEN; // Japanese Yen 106 private static final Integer CurMark = GER_MARK; // German Mark 107 private static final Integer CurFranc = FR_FRANC; // French Franc 108 private static final Integer CurSwiss = CH_FRANC; // Swiss Franc 109 private static final Integer CurCan = CAN_DOLLAR; // Canadian Dollar 110 private static final Integer CurEuro = EURO; // Euro 111 private static final Integer CurAu = AU_DOLLAR; // Australian Dollar 112 private static final Integer CurTaiwan = NT_DOLLAR; // New Taiwanese Dollar 113 private static final Integer CurHK = HK_DOLLAR; // Hong Kong Dollar 114 private static final Integer CurMyr = MY_REAL; // Malaysia Real(?) 115 private static final Integer CurSGD = SG_DOLLAR; // Singapore Dollar 116 private static final Integer CurRupee = IND_RUPEE; // Indian Rupee 117 118 // The fundamental list of the textual representation for different 119 // currencies, and the Currency type it translates to. 120 private static final Object xlateTable[][] = { 121 { "USD", CurDollar }, 122 { "US $", CurDollar }, 123 { "AU $", CurAu }, 124 { "au$", CurAu }, 125 { "AU", CurAu }, 126 { "AUD", CurAu }, 127 { "US", CurDollar }, 128 { "USD $", CurDollar }, 129 { "$", CurDollar }, 130 { "C", CurCan }, 131 { "C $", CurCan }, 132 { "CAD", CurCan }, 133 { "c$", CurCan }, 134 { "GBP", CurPound }, 135 { objPound.toString(), CurPound }, 136 { "pound", CurPound }, 137 { "\u00A3", CurPound }, 138 { "£", CurPound }, 139 { "Y", CurYen }, 140 { "JPY", CurYen }, 141 { "¥", CurYen }, 142 { "\u00A5", CurYen }, 143 { "DM", CurMark }, 144 { "FRF", CurFranc }, 145 { "fr", CurFranc }, 146 { "CHF", CurSwiss }, 147 { "chf", CurSwiss }, 148 { "dm", CurMark }, 149 { "\u20AC", CurEuro }, 150 { "eur", CurEuro }, 151 { "EUR", CurEuro }, 152 { "Eur", CurEuro }, 153 { "NT$", CurTaiwan }, 154 { "nt$", CurTaiwan }, 155 { "NTD", CurTaiwan }, 156 { "HK$", CurHK }, 157 { "hk$", CurHK }, 158 { "HKD", CurHK }, 159 { "MYR", CurMyr }, 160 { "myr", CurMyr }, 161 { "SGD", CurSGD }, 162 { "sgd", CurSGD }, 163 { "INR", CurRupee }, 164 { "inr", CurRupee } 165 }; 166 167 /** 168 * @brief Convert from a string containing a recognized symbol into 169 * a currency type. 170 * 171 * @param symbol - The string representation of a currency. 172 * 173 * @return - The integer value associated with the provided 174 * currency, or NONE for unrecognized currencies. 175 */ xlateSymbolToType(String symbol)176 private int xlateSymbolToType(String symbol) { 177 for (Object[] aXlateTable : xlateTable) { 178 if (symbol.equals(aXlateTable[0])) { 179 return (Integer) aXlateTable[1]; 180 } 181 } 182 183 return NONE; 184 } 185 isDigit(char ch)186 private boolean isDigit(char ch) { 187 return(ch>='0' && ch<='9'); 188 } 189 isCurrency(String test)190 public static boolean isCurrency(String test) { 191 return !getCurrency(test).isNull(); 192 } 193 194 @NotNull getCurrency(String wholeValue)195 public static Currency getCurrency(String wholeValue) { 196 if(wholeValue == null || wholeValue.length() == 0 || wholeValue.startsWith("UNK")) return NoValue(); 197 198 return new Currency(wholeValue); 199 } 200 getCurrency(int whatType, double startValue)201 public static Currency getCurrency(int whatType, double startValue) { 202 if(whatType == NONE) return NoValue(); 203 204 return new Currency(whatType, startValue); 205 } 206 getCurrency(String symbol, double startValue)207 public static Currency getCurrency(String symbol, double startValue) { 208 if(symbol == null || symbol.equalsIgnoreCase("UNK")) return NoValue(); 209 210 return new Currency(symbol, startValue); 211 } 212 getCurrency(String symbol, String startValue)213 public static Currency getCurrency(String symbol, String startValue) { 214 if(symbol == null || symbol.equalsIgnoreCase("UNK")) return NoValue(); 215 216 return new Currency(symbol, startValue); 217 } 218 Currency(String wholeValue)219 public Currency(String wholeValue) { 220 setValues(wholeValue); 221 } 222 Currency(int whatType, double startValue)223 public Currency(int whatType, double startValue) { 224 setValues(whatType, startValue); 225 } 226 Currency(String symbol, double startValue)227 public Currency(String symbol, double startValue) { 228 setValues(symbol, startValue); 229 } 230 Currency(String symbol, String startValue)231 public Currency(String symbol, String startValue) { 232 setValues(symbol, Double.parseDouble(cleanCommas(startValue))); 233 } 234 235 // Convert [###.###.]###,## to [###,###,]###.## cleanCommas(String startValue)236 private static String cleanCommas(String startValue) { 237 int decimalPos = startValue.length()-3; 238 if(decimalPos > 0) { 239 if (startValue.charAt(decimalPos) == '.') { 240 startValue = startValue.replaceAll(",", ""); 241 } else if(startValue.charAt(decimalPos) == ',') { 242 startValue = startValue.replaceAll("\\.", "").replaceAll(",", "."); 243 } 244 } 245 return startValue; 246 } 247 checkLengthMatchStart(String value, String currencyName)248 private int checkLengthMatchStart(String value, String currencyName) { 249 String lowVal = value.toLowerCase(); 250 String curNam = currencyName.toLowerCase(); 251 if(lowVal.startsWith(curNam + " ")) { 252 return currencyName.length()+1; 253 } 254 if(lowVal.startsWith(curNam)) { 255 int len = currencyName.length(); 256 while(len < value.length() && !Character.isDigit(value.charAt(len))) len++; 257 return len; 258 } 259 260 return 0; 261 } 262 263 /** 264 * @brief Provided an entire string containing a currency prefix and 265 * an amount, extract the two and set this object's value to equal 266 * the result. 267 * 268 * Is there a reason this doesn't use xlateSymbolToType? 269 * BUGBUG -- mrs: 03-January-2003 01:28 270 * 271 * @param wholeValue - The string containing an entire currency+amount text. 272 */ setValues(String wholeValue)273 private void setValues(String wholeValue) { 274 if(wholeValue == null || wholeValue.equals("null")) { 275 setValues(Currency.NONE, 0.0); 276 } else { 277 char firstChar = wholeValue.charAt(0); 278 279 int eurLen = checkLengthMatchStart(wholeValue, "EUR"); 280 int gbpLen = checkLengthMatchStart(wholeValue, "GBP"); 281 int frfLen = checkLengthMatchStart(wholeValue, "FRF"); 282 int chfLen = checkLengthMatchStart(wholeValue, "CHF"); 283 int cdnLen = checkLengthMatchStart(wholeValue, "CAD"); 284 int ntdLen = checkLengthMatchStart(wholeValue, "NTD"); 285 int audLen = checkLengthMatchStart(wholeValue, "AUD"); 286 int usdLen = checkLengthMatchStart(wholeValue, "USD"); 287 288 String parseCurrency; 289 String valuePortion; 290 if(wholeValue.startsWith("US $")) { 291 parseCurrency = "US $"; 292 valuePortion = wholeValue.substring(4); 293 } else if(wholeValue.startsWith("USD $")) { 294 // In case eBay ever corrects to the RIGHT currency code for USD. 295 parseCurrency = "USD $"; 296 valuePortion = wholeValue.substring(5); 297 } else if(wholeValue.startsWith("AU $")) { 298 parseCurrency = "AU $"; 299 valuePortion = wholeValue.substring(4); 300 } else if(usdLen != 0) { 301 parseCurrency = "USD"; 302 valuePortion = wholeValue.substring(usdLen); 303 } else if(eurLen != 0) { 304 parseCurrency = "EUR"; 305 valuePortion = wholeValue.substring(eurLen); 306 } else if(gbpLen != 0) { 307 parseCurrency = "GBP"; 308 valuePortion = wholeValue.substring(gbpLen); 309 } else if(frfLen != 0) { 310 parseCurrency = "FRF"; 311 valuePortion = wholeValue.substring(frfLen); 312 } else if(chfLen != 0) { 313 parseCurrency = "CHF"; 314 valuePortion = wholeValue.substring(chfLen); 315 } else if(cdnLen != 0) { 316 parseCurrency = "CAD"; 317 valuePortion = wholeValue.substring(cdnLen); 318 } else if(ntdLen != 0) { 319 parseCurrency = "NTD"; 320 valuePortion = wholeValue.substring(ntdLen); 321 } else if(audLen != 0) { 322 parseCurrency = "AUD"; 323 valuePortion = wholeValue.substring(audLen); 324 } else if(wholeValue.startsWith("NT$")) { 325 parseCurrency = "NTD"; 326 valuePortion = wholeValue.substring(3); 327 } else if(wholeValue.startsWith("SGD")) { 328 parseCurrency = "SGD"; 329 valuePortion = wholeValue.substring(3); 330 } else if(wholeValue.startsWith("sgd")) { 331 parseCurrency = "SGD"; 332 valuePortion = wholeValue.substring(3); 333 } else if(wholeValue.startsWith("INR")) { 334 parseCurrency = "INR"; 335 valuePortion = wholeValue.substring(3); 336 } else if(wholeValue.startsWith("inr")) { 337 parseCurrency = "INR"; 338 valuePortion = wholeValue.substring(3); 339 } else if(wholeValue.startsWith("nt$")) { 340 parseCurrency = "NTD"; 341 valuePortion = wholeValue.substring(3); 342 } else if(wholeValue.startsWith("au$")) { 343 parseCurrency = "AUD"; 344 valuePortion = wholeValue.substring(3); 345 } else if(wholeValue.startsWith("C $")) { 346 parseCurrency = "C $"; 347 valuePortion = wholeValue.substring(3); 348 } else if(wholeValue.charAt(0) == pound) { 349 parseCurrency = "GBP"; 350 valuePortion = wholeValue.substring(1); 351 } else { 352 if(!isDigit(firstChar) && firstChar != '$') { 353 int semiIndex = wholeValue.indexOf(";"); 354 if(semiIndex == -1) { 355 semiIndex = wholeValue.indexOf(" "); 356 } 357 if(semiIndex != -1) { 358 parseCurrency = wholeValue.substring(0, semiIndex); 359 valuePortion = wholeValue.substring(parseCurrency.length()+1); 360 } else { 361 parseCurrency = "$"; 362 valuePortion = wholeValue; 363 } 364 } else { 365 parseCurrency = "$"; 366 if(isDigit(firstChar)) { 367 valuePortion = wholeValue; 368 } else { 369 valuePortion = wholeValue.substring(1); 370 } 371 } 372 } 373 374 // Kill off non-digit characters. 375 while(valuePortion.length() != 0 && !Character.isDigit(valuePortion.charAt(0))) valuePortion = valuePortion.substring(1); 376 377 // If anything's left, try and parse it. 378 if(valuePortion.length() != 0) { 379 double actualValue; 380 try { 381 String cvt = cleanCommas(valuePortion); 382 actualValue = df.parse(cvt).doubleValue(); 383 } catch(java.text.ParseException e) { 384 JConfig.log().handleException("currency parse!", e); 385 actualValue = 0.0; 386 } 387 388 setValues(parseCurrency, actualValue); 389 } else { 390 setValues(null); 391 } 392 } 393 } 394 395 /** 396 * @brief If it's set as two seperate entries, then we use the MUCH 397 * cleaner xlateSymbolToType function. 398 * 399 * This should be the basic method that setValues works also. 400 * 401 * @param symbol - The string form of a currency symbol. 402 * @param startValue - The amount associated with the currency. 403 */ setValues(String symbol, double startValue)404 private void setValues(String symbol, double startValue) { 405 setValues(xlateSymbolToType(symbol), startValue); 406 } 407 408 /** 409 * @brief The underlying setter that assigns the currency and amounts. 410 * 411 * @param whatType - The Currency type to set to. 412 * @param startValue - The amount represented. 413 */ setValues(int whatType, double startValue)414 private void setValues(int whatType, double startValue) { 415 mCurrencyType = whatType; 416 mValue = startValue; 417 df.setMinimumFractionDigits(2); 418 df.setMaximumFractionDigits(2); 419 } 420 421 /** 422 * @brief Get the full, storable textual name for the currency type 423 * of this object. 424 * 425 * @return A string containing a full ISO currency name. 426 */ fullCurrencyName()427 public String fullCurrencyName() { 428 switch(mCurrencyType) { 429 case US_DOLLAR: return("USD"); 430 case AU_DOLLAR: return("AUD"); 431 case NT_DOLLAR: return("NTD"); 432 case HK_DOLLAR: return("HKD"); 433 case MY_REAL: return("MYR"); 434 case SG_DOLLAR: return("SGD"); 435 case IND_RUPEE: return("INR"); 436 case UK_POUND: return("GBP"); 437 case JP_YEN: return("JPY"); 438 case GER_MARK: return("DM"); 439 case FR_FRANC: return("FRF"); 440 case CH_FRANC: return("CHF"); 441 case CAN_DOLLAR: return("CAD"); 442 case EURO: return("EUR"); 443 default: return("UNK"); 444 } 445 } 446 getValue()447 public double getValue() { return mValue; } 448 fullCurrency()449 public String fullCurrency() { 450 return fullCurrencyName() + " " + getValueString(); 451 } 452 453 /** 454 * @brief Add two currencies and return a new currency containing 455 * the result of the two added together. 456 * 457 * @param addValue - The currency value/amount to add. It must be 458 * of the same currency type as 'this'. 459 * 460 * @return A new currency object containing the sum of the two 461 * amounts provided, with the same currency type as them. 462 * 463 * @throws CurrencyTypeException if the two objects are of different currencies. 464 */ add(Currency addValue)465 public Currency add(Currency addValue) throws CurrencyTypeException { 466 if(addValue == null) throw new CurrencyTypeException("Cannot add null Currency."); 467 468 if(addValue.getCurrencyType() == mCurrencyType) { 469 return new Currency(mCurrencyType, mValue + addValue.getValue()); 470 } 471 472 // If only one currency is known, return the result as the known currency. 473 if (mCurrencyType == NONE) return new Currency(addValue.getCurrencyType(), mValue + addValue.getValue()); 474 if (addValue.getCurrencyType() == NONE) return new Currency(mCurrencyType, mValue + addValue.getValue()); 475 476 throw new CurrencyTypeException("Cannot add " + fullCurrencyName() + " to " + addValue.fullCurrencyName() + "."); 477 } 478 479 /** 480 * @brief Subtract two currencies and return a new currency containing 481 * the result of the passed value subtracted from this objects value. 482 * 483 * @param subValue - The currency value/amount to subtract. It must be 484 * of the same currency type as 'this'. 485 * 486 * @return A new currency object containing the difference of the two 487 * amounts provided, with the same currency type as them. 488 * 489 * @throws CurrencyTypeException if the two objects are of different currencies. 490 */ subtract(Currency subValue)491 public Currency subtract(Currency subValue) throws CurrencyTypeException { 492 if(subValue == null) throw new CurrencyTypeException("Cannot subtract null Currency."); 493 494 if(subValue.getCurrencyType() == mCurrencyType) { 495 return new Currency(mCurrencyType, mValue - subValue.getValue()); 496 } 497 498 // If only one currency is known, return the result as the known currency. 499 if(mCurrencyType == NONE) return new Currency(subValue.getCurrencyType(), mValue - subValue.getValue()); 500 if(subValue.getCurrencyType() == NONE) return new Currency(mCurrencyType, mValue - subValue.getValue()); 501 502 throw new CurrencyTypeException("Cannot subtract " + fullCurrencyName() + " from " + subValue.fullCurrencyName() + "."); 503 } 504 getCurrencyType()505 public int getCurrencyType() { return mCurrencyType; } 506 getCurrencySymbol()507 public String getCurrencySymbol() { 508 switch(mCurrencyType) { 509 case US_DOLLAR: return("$"); 510 case NT_DOLLAR: return("nt$"); 511 case HK_DOLLAR: return("hk$"); 512 case MY_REAL: return("myr"); 513 case SG_DOLLAR: return("sgd"); 514 case IND_RUPEE: return("Rs."); 515 case UK_POUND: return(objPound.toString()); 516 case JP_YEN: return("\u00A5"); // HACKHACK 517 case FR_FRANC: return("fr"); 518 case CH_FRANC: return("chf"); 519 case GER_MARK: return("dm"); 520 case CAN_DOLLAR: return("c$"); 521 case AU_DOLLAR: return("au$"); 522 case EURO: return("\u20AC"); 523 default: return("unk"); 524 } 525 } 526 527 /** 528 * @brief Format the currency and amount as appropriate for the 529 * current locale. 530 * 531 * This is kind of interesting, because it will display in one 532 * fashion, but when it snipes or bids, it's all against the 533 * US sites, so it's all operating in US forms at that point. 534 * 535 * @return A nicely formatted, locale-correct money value, prefixed 536 * with the best currency symbol for the currency type. 537 */ toString()538 public String toString() { 539 if(isNull()) { 540 return("null"); 541 } else { 542 String cvtToString = getCurrencySymbol(); 543 544 cvtToString += df.format(mValue); 545 546 return(cvtToString); 547 } 548 } 549 550 /** 551 * @brief Format the amount as appropriate for the current locale. 552 * 553 * This is kind of interesting, because it will display in one 554 * fashion, but when it snipes or bids, it's all against the 555 * US sites, so it's all operating in US forms at that point. 556 * 557 * @return A nicely formatted, locale-correct money value, prefixed 558 * with the best currency symbol for the currency type. 559 */ getValueString()560 public String getValueString() { 561 if(isNull()) { 562 return("null"); 563 } else { 564 return df.format(mValue); 565 } 566 } 567 568 /** 569 * @brief Implementing equals means I should implement hashCode(). 570 * 571 * @return - The hash code of the string consisting of the full 572 * currency named followed by the value as a string. Null/invalid 573 * currency entries return 0. 574 */ hashCode()575 public int hashCode() { 576 if(isNull()) return 0; 577 578 String tmp = fullCurrencyName() + getValueString(); 579 return tmp.hashCode(); 580 } 581 582 /** 583 * @brief Must be able to compare currency values for equality. 584 * 585 * @param inValue - The value to compare against. 586 * 587 * @return True if the two values are the same, or the currency and 588 * amount are the same. False otherwise, including false if it is 589 * an entirely different class. Differing currencies are always 590 * unequal. 591 */ equals(Object inValue)592 public boolean equals(Object inValue) { 593 // Be careful not to compare with null. 594 if(inValue == null) return false; 595 // Shortcut for this.equals(this) 596 if(inValue == this) return true; 597 // Is it this class even? 598 if(!(inValue instanceof Currency)) return false; 599 // Okay, now cast it because it's safe. 600 Currency otherValue = (Currency) inValue; 601 boolean sameCurrency = (otherValue.getCurrencyType() == mCurrencyType); 602 boolean sameValue = ((int) (otherValue.getValue() * 1000)) == ((int) (mValue * 1000)); 603 604 return(sameCurrency && sameValue); 605 } 606 607 /** 608 * @brief Determine if (this < otherValue). 609 * 610 * This only works for items of the same currency type. 611 * 612 * @param otherValue - The value to compare against. 613 * 614 * @return - True if this amount is less than the otherValue amount 615 * and both currency types are equal. If the otherValue is null, 616 * the same object as this (this.less(this)), or this amount is 617 * actually less, then it returns false. 618 * 619 * @throws CurrencyTypeException if you try to compare different currencies. 620 */ less(Currency otherValue)621 public boolean less(Currency otherValue) throws CurrencyTypeException { 622 // Be careful 623 if(otherValue == null) return false; 624 // Shortcut 625 if(otherValue == this) return false; 626 627 boolean sameCurrency = (otherValue.getCurrencyType() == mCurrencyType); 628 if(!sameCurrency) { 629 throw new CurrencyTypeException("Cannot compare different currencies."); 630 } 631 632 boolean lowerValue = Double.compare((double) ((int) (otherValue.getValue() * 1000)), (double) (int) (mValue * 1000)) == 1; 633 634 return(lowerValue); 635 } 636 637 /** 638 * @brief Utility function to check if this is a purely invalid currency. 639 * 640 * It should probably check against the invalid currency object first... 641 * 642 * @return True if this is a 'null currency' object. 643 */ isNull()644 public boolean isNull() { 645 return(mValue == 0.0 && mCurrencyType == NONE); 646 } 647 648 /** 649 * @brief The comparable interface defines this, and so I'm 650 * comparing using the well defined set of rules for Comparables. 651 * 652 * Defined with 'equals' and less', but both should be special cases 653 * of this, since some checks are duplicated. 654 * 655 * @param o - The object to compare against. 656 * 657 * @return -1 if o's class is Currency, it's the same currency type, 658 * and the amount of this is less than o's amount. 659 * 0 if o's class is Currency, it's the same currency type, 660 * and the amount of this is the same as o's amount. 661 * 1 if o's class is Currency, it's the same currency type, 662 * and the amount of this is greater than o's amount. 663 * 664 * @throws ClassCastException if you try to compareTo non-Currency classes. 665 */ compareTo(Object o)666 public int compareTo(Object o) { 667 // We are always greater than null 668 if(o == null) return 1; 669 // We are always equal to ourselves 670 if(o == this) return 0; 671 // This is an incorrect usage and should be caught. 672 if(!(o instanceof Currency)) throw new ClassCastException("Currency cannot compareTo different classes!"); 673 674 // Okay, now cast it because it's safe. 675 Currency otherValue = (Currency) o; 676 677 if(otherValue.isNull()) return 1; 678 if(isNull()) return -1; 679 try { 680 if(less(otherValue)) return -1; 681 } catch(ClassCastException e) { 682 /* This should be impossible */ 683 throw new ClassCastException("Currency cannot compareTo different classes!\n" + e); 684 } catch (CurrencyTypeException e) { 685 // Can't re-throw (or not catch!) because Object.compareTo doesn't throw CurrencyTypeException! 686 throw new ClassCastException("Currency cannot compareTo different currencies!\n" + e); 687 } 688 if(equals(otherValue)) return 0; 689 return 1; 690 } 691 getDelegate()692 public static PersistenceDelegate getDelegate() { 693 return new DefaultPersistenceDelegate(new String[]{"mCurrencyType", "mValue"}); 694 } 695 } 696