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     { "&pound", CurPound },
139     { "Y",      CurYen },
140     { "JPY",    CurYen },
141     { "&yen",   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