← joda-money  /  src/main/java/org/joda/money/CurrencyUnit.java

1
/*
2
 *  Copyright 2009-present, Stephen Colebourne
3
 *
4
 *  Licensed under the Apache License, Version 2.0 (the "License");
5
 *  you may not use this file except in compliance with the License.
6
 *  You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *  Unless required by applicable law or agreed to in writing, software
11
 *  distributed under the License is distributed on an "AS IS" BASIS,
12
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *  See the License for the specific language governing permissions and
14
 *  limitations under the License.
15
 */
16
package org.joda.money;
17
18
import java.io.InvalidObjectException;
19
import java.io.ObjectInputStream;
20
import java.io.Serializable;
21
import java.util.ArrayList;
22
import java.util.Collections;
23
import java.util.Currency;
24
import java.util.HashSet;
25
import java.util.List;
26
import java.util.Locale;
27
import java.util.Map.Entry;
28
import java.util.Set;
29
import java.util.concurrent.ConcurrentHashMap;
30
import java.util.concurrent.ConcurrentMap;
31
import java.util.concurrent.ConcurrentSkipListMap;
32
import java.util.regex.Pattern;
33
34
import org.joda.convert.FromString;
35
import org.joda.convert.ToString;
36
37
/**
38
 * A unit of currency.
39
 * <p>
40
 * This class represents a unit of currency such as the British Pound, Euro
41
 * or US Dollar.
42
 * <p>
43
 * The set of loaded currencies is provided by an instance of {@link CurrencyUnitDataProvider}.
44
 * The provider used is determined by the system property {@code org.joda.money.CurrencyUnitDataProvider}
45
 * which should be the fully qualified class name of the provider. The default provider loads the first
46
 * resource named {@code /org/joda/money/MoneyData.csv} on the classpath.
47
 * <p>
48
 * This class is immutable and thread-safe.
49
 */
50
public final class CurrencyUnit implements Comparable<CurrencyUnit>, Serializable {
51
52
    /**
53
     * The serialisation version.
54
     */
55
    private static final long serialVersionUID = 327835287287L;
56
    /**
57
     * The currency code pattern.
58
     */
59
    private static final Pattern CODE = Pattern.compile("[A-Z][A-Z][A-Z]");
60
    /**
61
     * Map of registered currencies by text code.
62
     */
63
    private static final ConcurrentMap<String, CurrencyUnit> currenciesByCode = new ConcurrentSkipListMap<>();
64
    /**
65
     * Map of registered currencies by numeric code.
66
     */
67
    private static final ConcurrentMap<Integer, CurrencyUnit> currenciesByNumericCode = new ConcurrentHashMap<>();
68
    /**
69
     * Map of registered currencies by country.
70
     */
71
    private static final ConcurrentMap<String, CurrencyUnit> currenciesByCountry = new ConcurrentSkipListMap<>();
72
    static {
73
        // load one data provider by system property
74
        try {
75
            try {
76
                var clsName = System.getProperty(
77
                        "org.joda.money.CurrencyUnitDataProvider",
78
                        "org.joda.money.DefaultCurrencyUnitDataProvider");
79
                Class<? extends CurrencyUnitDataProvider> cls =
80
                        CurrencyUnit.class.getClassLoader().loadClass(clsName).asSubclass(CurrencyUnitDataProvider.class);
81
                cls.getDeclaredConstructor().newInstance().registerCurrencies();
82
            } catch (SecurityException ex) {
83
                new DefaultCurrencyUnitDataProvider().registerCurrencies();
84
            }
85
        } catch (RuntimeException ex) {
86
            System.err.println("ERROR: " + ex.getMessage());
87
            ex.printStackTrace();
88
            throw ex;
89
        } catch (Exception ex) {
90
            System.err.println("ERROR: " + ex.getMessage());
91
            ex.printStackTrace();
92
            throw new RuntimeException(ex.toString(), ex);
93
        }
94
    }
95
96
    // a selection of commonly traded, stable currencies
97
    /**
98
     * The currency 'USD' - United States Dollar.
99
     */
100
    public static final CurrencyUnit USD = of("USD");
101
    /**
102
     * The currency 'EUR' - Euro.
103
     */
104
    public static final CurrencyUnit EUR = of("EUR");
105
    /**
106
     * The currency 'JPY' - Japanese Yen.
107
     */
108
    public static final CurrencyUnit JPY = of("JPY");
109
    /**
110
     * The currency 'GBP' - British pound.
111
     */
112
    public static final CurrencyUnit GBP = of("GBP");
113
    /**
114
     * The currency 'CHF' - Swiss Franc.
115
     */
116
    public static final CurrencyUnit CHF = of("CHF");
117
    /**
118
     * The currency 'AUD' - Australian Dollar.
119
     */
120
    public static final CurrencyUnit AUD = of("AUD");
121
    /**
122
     * The currency 'CAD' - Canadian Dollar.
123
     */
124
    public static final CurrencyUnit CAD = of("CAD");
125
126
    /**
127
     * The currency code, not null.
128
     */
129
    private final String code;
130
    /**
131
     * The numeric currency code.
132
     */
133
    private final short numericCode;
134
    /**
135
     * The number of decimal places.
136
     */
137
    private final short decimalPlaces;
138
139
    //-----------------------------------------------------------------------
140
    /**
141
     * Registers a currency and associated countries allowing it to be used.
142
     * <p>
143
     * This class only permits known currencies to be returned.
144
     * To achieve this, all currencies have to be registered in advance.
145
     * <p>
146
     * Since this method is public, it is possible to add currencies in
147
     * application code. It is recommended to do this only at startup, however
148
     * it is safe to do so later as the internal implementation is thread-safe.
149
     * <p>
150
     * The currency code must be three upper-case ASCII letters, based on ISO-4217.
151
     * The numeric code must be from 0 to 999, or -1 if not applicable.
152
     *
153
     * @param currencyCode  the three-letter upper-case currency code, not null
154
     * @param numericCurrencyCode  the numeric currency code, from 0 to 999, -1 if none
155
     * @param decimalPlaces  the number of decimal places that the currency
156
     *  normally has, from 0 to 30 (normally 0, 2 or 3), or -1 for a pseudo-currency
157
     * @param countryCodes  the country codes to register the currency under, not null
158
     * @return the new instance, never null
159
     * @throws IllegalArgumentException if the code is already registered, or the
160
     *  specified data is invalid
161
     */
162
    public static synchronized CurrencyUnit registerCurrency(
163
            String currencyCode,
164
            int numericCurrencyCode,
165
            int decimalPlaces,
166
            List<String> countryCodes) {
167
168
        return registerCurrency(currencyCode, numericCurrencyCode, decimalPlaces, countryCodes, false);
169
    }
170
171
    /**
172
     * Registers a currency and associated countries allowing it to be used, allowing replacement.
173
     * <p>
174
     * This class only permits known currencies to be returned.
175
     * To achieve this, all currencies have to be registered in advance.
176
     * <p>
177
     * Since this method is public, it is possible to add currencies in
178
     * application code. It is recommended to do this only at startup, however
179
     * it is safe to do so later as the internal implementation is thread-safe.
180
     * <p>
181
     * This method uses a flag to determine whether the registered currency
182
     * must be new, or can replace an existing currency.
183
     * <p>
184
     * The currency code must be three upper-case ASCII letters, based on ISO-4217.
185
     * The numeric code must be from 0 to 999, or -1 if not applicable.
186
     *
187
     * @param currencyCode  the three-letter upper-case currency code, not null
188
     * @param numericCurrencyCode  the numeric currency code, from 0 to 999, -1 if none
189
     * @param decimalPlaces  the number of decimal places that the currency
190
     *  normally has, from 0 to 30 (normally 0, 2 or 3), or -1 for a pseudo-currency
191
     * @param countryCodes  the country codes to register the currency under,
192
     *  use of ISO-3166 is recommended, not null
193
     * @param force  true to register forcefully, replacing any existing matching currency,
194
     *  false to validate that there is no existing matching currency
195
     * @return the new instance, never null
196
     * @throws IllegalArgumentException if the code is already registered and {@code force} is false;
197
     *  or if the specified data is invalid
198
     */
199
    public static synchronized CurrencyUnit registerCurrency(
200
            String currencyCode,
201
            int numericCurrencyCode,
202
            int decimalPlaces,
203
            List<String> countryCodes,
204
            boolean force) {
205
206
        MoneyUtils.checkNotNull(currencyCode, "Currency code must not be null");
207
        if (currencyCode.length() != 3) {
208
            throw new IllegalArgumentException("Invalid string code, must be length 3");
209
        }
210
        if (!CODE.matcher(currencyCode).matches()) {
211
            throw new IllegalArgumentException("Invalid string code, must be ASCII upper-case letters");
212
        }
213
        if (numericCurrencyCode < -1 || numericCurrencyCode > 999) {
214
            throw new IllegalArgumentException("Invalid numeric code");
215
        }
216
        if (decimalPlaces < -1 || decimalPlaces > 30) {
217
            throw new IllegalArgumentException("Invalid number of decimal places");
218
        }
219
        MoneyUtils.checkNotNull(countryCodes, "Country codes must not be null");
220
221
        var currency = new CurrencyUnit(currencyCode, (short) numericCurrencyCode, (short) decimalPlaces);
222
        if (force) {
223
            currenciesByCode.remove(currencyCode);
224
            currenciesByNumericCode.remove(numericCurrencyCode);
225
            for (String countryCode : countryCodes) {
226
                currenciesByCountry.remove(countryCode);
227
            }
228
        } else {
229
            if (currenciesByCode.containsKey(currencyCode) || currenciesByNumericCode.containsKey(numericCurrencyCode)) {
230
                throw new IllegalArgumentException("Currency already registered: " + currencyCode);
231
            }
232
            for (String countryCode : countryCodes) {
233
                if (currenciesByCountry.containsKey(countryCode)) {
234
                    throw new IllegalArgumentException("Currency already registered for country: " + countryCode);
235
                }
236
            }
237
        }
238
        currenciesByCode.putIfAbsent(currencyCode, currency);
239
        if (numericCurrencyCode >= 0) {
240
            currenciesByNumericCode.putIfAbsent(numericCurrencyCode, currency);
241
        }
242
        for (String countryCode : countryCodes) {
243
            registerCountry(countryCode, currency);
244
        }
245
        return currenciesByCode.get(currencyCode);
246
    }
247
248
    /**
249
     * Registers a currency allowing it to be used, allowing replacement.
250
     * <p>
251
     * This class only permits known currencies to be returned.
252
     * To achieve this, all currencies have to be registered in advance.
253
     * <p>
254
     * Since this method is public, it is possible to add currencies in
255
     * application code. It is recommended to do this only at startup, however
256
     * it is safe to do so later as the internal implementation is thread-safe.
257
     * <p>
258
     * This method uses a flag to determine whether the registered currency
259
     * must be new, or can replace an existing currency.
260
     * <p>
261
     * The currency code must be three upper-case ASCII letters, based on ISO-4217.
262
     * The numeric code must be from 0 to 999, or -1 if not applicable.
263
     *
264
     * @param currencyCode  the three-letter upper-case currency code, not null
265
     * @param numericCurrencyCode  the numeric currency code, from 0 to 999, -1 if none
266
     * @param decimalPlaces  the number of decimal places that the currency
267
     *  normally has, from 0 to 30 (normally 0, 2 or 3), or -1 for a pseudo-currency
268
     *  use of ISO-3166 is recommended, not null
269
     * @param force  true to register forcefully, replacing any existing matching currency,
270
     *  false to validate that there is no existing matching currency
271
     * @return the new instance, never null
272
     * @throws IllegalArgumentException if the code is already registered and {@code force} is false;
273
     *  or if the specified data is invalid
274
     */
275
    public static synchronized CurrencyUnit registerCurrency(
276
            String currencyCode,
277
            int numericCurrencyCode,
278
            int decimalPlaces,
279
            boolean force) {
280
281
        List<String> countryCodes = Collections.emptyList();
282
        return registerCurrency(currencyCode, numericCurrencyCode, decimalPlaces, countryCodes, force);
283
    }
284
285
    /**
286
     * Registers a country code, typically ISO 3166-1-alpha-2.
287
     * <p>
288
     * This registers a country code and the associated currency.
289
     * <p>
290
     * The country code is typically from ISO 3166-1-alpha-2, and is therefore two upper-case ASCII letters.
291
     * <p>
292
     * If the country code already exists, the data is replaced.
293
     *
294
     * @param countryCode  the country code, two upper case letters if following ISO 3166-1-alpha-2, not null
295
     * @param currency  the associated currency
296
     * @throws IllegalArgumentException if the code is already registered and {@code force} is false;
297
     *  or if the specified data is invalid
298
     */
299
    public static synchronized void registerCountry(String countryCode, CurrencyUnit currency) {
300
        currenciesByCountry.put(countryCode, currency);
301
    }
302
303
    //-----------------------------------------------------------------------
304
    /**
305
     * Gets the list of all registered currencies.
306
     * <p>
307
     * This class only permits known currencies to be returned, thus this list is
308
     * the complete list of valid singleton currencies. The list may change after
309
     * application startup, however this isn't recommended.
310
     *
311
     * @return the sorted, independent, list of all registered currencies, never null
312
     */
313
    public static List<CurrencyUnit> registeredCurrencies() {
314
        return new ArrayList<>(currenciesByCode.values());
315
    }
316
317
    /**
318
     * Gets the list of all registered countries.
319
     * <p>
320
     * This returns the list of known countries.
321
     * The list may change after application startup, however this isn't recommended.
322
     *
323
     * @return the sorted, independent, list of all registered countries, never null
324
     */
325
    public static List<String> registeredCountries() {
326
        return new ArrayList<>(currenciesByCountry.keySet());
327
    }
328
329
    //-----------------------------------------------------------------------
330
    /**
331
     * Obtains an instance of {@code CurrencyUnit} matching the specified JDK currency.
332
     * <p>
333
     * This converts the JDK currency instance to a currency unit using the code.
334
     *
335
     * @param currency  the currency, not null
336
     * @return the singleton instance, never null
337
     * @throws IllegalCurrencyException if the currency is unknown
338
     */
339
    public static CurrencyUnit of(Currency currency) {
340
        MoneyUtils.checkNotNull(currency, "Currency must not be null");
341
        return of(currency.getCurrencyCode());
342
    }
343
344
    /**
345
     * Obtains an instance of {@code CurrencyUnit} for the specified three letter currency code.
346
     * <p>
347
     * A currency is uniquely identified by a three letter code, based on ISO-4217.
348
     * Valid currency codes are three upper-case ASCII letters.
349
     *
350
     * @param currencyCode  the three-letter currency code, not null
351
     * @return the singleton instance, never null
352
     * @throws IllegalCurrencyException if the currency is unknown
353
     */
354
    @FromString
355
    public static CurrencyUnit of(String currencyCode) {
356
        MoneyUtils.checkNotNull(currencyCode, "Currency code must not be null");
357
        var currency = currenciesByCode.get(currencyCode);
358
        if (currency == null) {
359
            throw new IllegalCurrencyException("Unknown currency '" + currencyCode + '\'');
360
        }
361
        return currency;
362
    }
363
364
    /**
365
     * Obtains an instance of {@code CurrencyUnit} for the specified ISO-4217 numeric currency code.
366
     * <p>
367
     * The numeric code is an alternative to the three letter code.
368
     * This method is lenient and does not require the string to be left padded with zeroes.
369
     *
370
     * @param numericCurrencyCode  the currency code, not null
371
     * @return the singleton instance, never null
372
     * @throws IllegalCurrencyException if the currency is unknown
373
     */
374
    public static CurrencyUnit ofNumericCode(String numericCurrencyCode) {
375
        MoneyUtils.checkNotNull(numericCurrencyCode, "Currency code must not be null");
376
        return switch (numericCurrencyCode.length()) {
377
            case 1 -> ofNumericCode(numericCurrencyCode.charAt(0) - '0');
378
            case 2 -> ofNumericCode(
379
                                    (numericCurrencyCode.charAt(0) - '0') * 10 +
380
                                            numericCurrencyCode.charAt(1) - '0');
381
            case 3 -> ofNumericCode(
382
                                    (numericCurrencyCode.charAt(0) - '0') * 100 +
383
                                            (numericCurrencyCode.charAt(1) - '0') * 10 +
384
                                            numericCurrencyCode.charAt(2) - '0');
385
            default -> throw new IllegalCurrencyException("Unknown currency '" + numericCurrencyCode + '\'');
386
        };
387
    }
388
389
    /**
390
     * Obtains an instance of {@code CurrencyUnit} for the specified ISO-4217 numeric currency code.
391
     * <p>
392
     * The numeric code is an alternative to the three letter code.
393
     *
394
     * @param numericCurrencyCode  the numeric currency code, not null
395
     * @return the singleton instance, never null
396
     * @throws IllegalCurrencyException if the currency is unknown
397
     */
398
    public static CurrencyUnit ofNumericCode(int numericCurrencyCode) {
399
        var currency = currenciesByNumericCode.get(numericCurrencyCode);
400
        if (currency == null) {
401
            throw new IllegalCurrencyException("Unknown currency '" + numericCurrencyCode + '\'');
402
        }
403
        return currency;
404
    }
405
406
    /**
407
     * Obtains an instance of {@code CurrencyUnit} for the specified locale.
408
     * <p>
409
     * Only the country is used from the locale.
410
     *
411
     * @param locale  the locale, not null
412
     * @return the singleton instance, never null
413
     * @throws IllegalCurrencyException if the currency is unknown
414
     */
415
    public static CurrencyUnit of(Locale locale) {
416
        MoneyUtils.checkNotNull(locale, "Locale must not be null");
417
        var currency = currenciesByCountry.get(locale.getCountry());
418
        if (currency == null) {
419
            throw new IllegalCurrencyException("No currency found for locale '" + locale + '\'');
420
        }
421
        return currency;
422
    }
423
424
    /**
425
     * Obtains an instance of {@code CurrencyUnit} for the specified ISO-3166 country code.
426
     * <p>
427
     * Country codes should generally be in upper case.
428
     * This method is case sensitive.
429
     *
430
     * @param countryCode  the country code, typically ISO-3166, not null
431
     * @return the singleton instance, never null
432
     * @throws IllegalCurrencyException if the currency is unknown
433
     */
434
    public static CurrencyUnit ofCountry(String countryCode) {
435
        MoneyUtils.checkNotNull(countryCode, "Country code must not be null");
436
        var currency = currenciesByCountry.get(countryCode);
437
        if (currency == null) {
438
            throw new IllegalCurrencyException("No currency found for country '" + countryCode + '\'');
439
        }
440
        return currency;
441
    }
442
443
    //-----------------------------------------------------------------------
444
    /**
445
     * Constructor, creating a new currency instance.
446
     *
447
     * @param code  the three-letter currency code, not null
448
     * @param numericCode  the numeric currency code, from 0 to 999, -1 if none
449
     * @param decimalPlaces  the decimal places, not null
450
     */
451
    CurrencyUnit(String code, short numericCode, short decimalPlaces) {
452
        assert code != null : "Joda-Money bug: Currency code must not be null";
453
        this.code = code;
454
        this.numericCode = numericCode;
455
        this.decimalPlaces = decimalPlaces;
456
    }
457
458
    /**
459
     * Block malicious data streams.
460
     *
461
     * @param ois  the input stream, not null
462
     * @throws InvalidObjectException if an error occurs
463
     */
464
    private void readObject(ObjectInputStream ois) throws InvalidObjectException {
465
        throw new InvalidObjectException("Serialization delegate required");
466
    }
467
468
    /**
469
     * Uses a serialization delegate.
470
     *
471
     * @return the replacing object, never null
472
     */
473
    private Object writeReplace() {
474
        return new Ser(Ser.CURRENCY_UNIT, this);
475
    }
476
477
    //-----------------------------------------------------------------------
478
    /**
479
     * Gets the ISO-4217 three-letter currency code.
480
     * <p>
481
     * Each currency is uniquely identified by a three-letter upper-case code, based on ISO-4217.
482
     *
483
     * @return the three-letter upper-case currency code, never null
484
     */
485
    public String getCode() {
486
        return code;
487
    }
488
489
    /**
490
     * Gets the ISO-4217 numeric currency code.
491
     * <p>
492
     * The numeric code is an alternative to the standard string-based code.
493
     *
494
     * @return the numeric currency code, -1 if no numeric code
495
     */
496
    public int getNumericCode() {
497
        return numericCode;
498
    }
499
500
    /**
501
     * Gets the ISO-4217 numeric currency code as a three digit string.
502
     * <p>
503
     * This formats the numeric code as a three digit string prefixed by zeroes if necessary.
504
     * If there is no valid code, then an empty string is returned.
505
     *
506
     * @return the three digit numeric currency code, empty is no code, never null
507
     */
508
    public String getNumeric3Code() {
509
        if (numericCode < 0) {
510
            return "";
511
        }
512
        var str = Integer.toString(numericCode);
513
        if (str.length() == 1) {
514
            return "00" + str;
515
        }
516
        if (str.length() == 2) {
517
            return "0" + str;
518
        }
519
        return str;
520
    }
521
522
    /**
523
     * Gets the country codes applicable to this currency.
524
     * <p>
525
     * A currency is typically valid in one or more countries.
526
     * The codes are typically defined by ISO-3166.
527
     * An empty set indicates that no the currency is not associated with a country code.
528
     *
529
     * @return the country codes, may be empty, not null
530
     */
531
    public Set<String> getCountryCodes() {
532
        Set<String> countryCodes = new HashSet<>();
533
        for (Entry<String, CurrencyUnit> entry : currenciesByCountry.entrySet()) {
534
            if (this.equals(entry.getValue())) {
535
                countryCodes.add(entry.getKey());
536
            }
537
        }
538
        return countryCodes;
539
    }
540
541
    //-----------------------------------------------------------------------
542
    /**
543
     * Gets the number of decimal places typically used by this currency.
544
     * <p>
545
     * Different currencies have different numbers of decimal places by default.
546
     * For example, 'GBP' has 2 decimal places, but 'JPY' has zero.
547
     * Pseudo-currencies will return zero.
548
     *
549
     * @return the decimal places, from 0 to 9 (normally 0, 2 or 3)
550
     */
551
    public int getDecimalPlaces() {
552
        return decimalPlaces < 0 ? 0 : decimalPlaces;
553
    }
554
555
    /**
556
     * Checks if this is a pseudo-currency.
557
     *
558
     * @return true if this is a pseudo-currency
559
     */
560
    public boolean isPseudoCurrency() {
561
        return decimalPlaces < 0;
562
    }
563
564
    //-----------------------------------------------------------------------
565
    /**
566
     * Gets the symbol for this locale from the JDK.
567
     * <p>
568
     * If this currency doesn't have a JDK equivalent, then the currency code
569
     * is returned.
570
     * <p>
571
     * This method matches the API of {@link Currency}.
572
     *
573
     * @return the JDK currency instance, never null
574
     */
575
    public String getSymbol() {
576
        // Java 21 currency data uses a symbol, we want to retain this as XXX
577
        if ("XXX".equals(code)) {
578
            return code;
579
        }
580
        try {
581
            return Currency.getInstance(code).getSymbol();
582
        } catch (IllegalArgumentException ex) {
583
            return code;
584
        }
585
    }
586
587
    /**
588
     * Gets the symbol for this locale from the JDK.
589
     * <p>
590
     * If this currency doesn't have a JDK equivalent, then the currency code
591
     * is returned.
592
     * <p>
593
     * This method matches the API of {@link Currency}.
594
     *
595
     * @param locale  the locale to get the symbol for, not null
596
     * @return the JDK currency instance, never null
597
     */
598
    public String getSymbol(Locale locale) {
599
        MoneyUtils.checkNotNull(locale, "Locale must not be null");
600
        // Java 21 currency data uses a symbol, we want to retain this as XXX
601
        if ("XXX".equals(code)) {
602
            return code;
603
        }
604
        try {
605
            return Currency.getInstance(code).getSymbol(locale);
606
        } catch (IllegalArgumentException ex) {
607
            return code;
608
        }
609
    }
610
611
    //-----------------------------------------------------------------------
612
    /**
613
     * Gets the JDK currency instance equivalent to this currency.
614
     * <p>
615
     * This attempts to convert a {@code CurrencyUnit} to a JDK {@code Currency}.
616
     *
617
     * @return the JDK currency instance, never null
618
     * @throws IllegalArgumentException if no matching currency exists in the JDK
619
     */
620
    public Currency toCurrency() {
621
        return Currency.getInstance(code);
622
    }
623
624
    //-----------------------------------------------------------------------
625
    /**
626
     * Compares this currency to another by alphabetical comparison of the code.
627
     *
628
     * @param other  the other currency, not null
629
     * @return negative if earlier alphabetically, 0 if equal, positive if greater alphabetically
630
     */
631
    @Override
632
    public int compareTo(CurrencyUnit other) {
633
        return code.compareTo(other.code);
634
    }
635
636
    /**
637
     * Checks if this currency equals another currency.
638
     * <p>
639
     * The comparison checks the 3 letter currency code.
640
     *
641
     * @param obj  the other currency, null returns false
642
     * @return true if equal
643
     */
644
    @Override
645
    public boolean equals(Object obj) {
646
        if (obj == this) {
647
            return true;
648
        }
649
        if (obj instanceof CurrencyUnit) {
650
            return code.equals(((CurrencyUnit) obj).code);
651
        }
652
        return false;
653
    }
654
655
    /**
656
     * Returns a suitable hash code for the currency.
657
     *
658
     * @return the hash code
659
     */
660
    @Override
661
    public int hashCode() {
662
        return code.hashCode();
663
    }
664
665
    //-----------------------------------------------------------------------
666
    /**
667
     * Gets the currency code as a string.
668
     *
669
     * @return the currency code, never null
670
     */
671
    @Override
672
    @ToString
673
    public String toString() {
674
        return code;
675
    }
676
677
}
678