1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html#License
3/*
4 *******************************************************************************
5 * Copyright (C) 2009-2015, International Business Machines Corporation and    *
6 * others. All Rights Reserved.                                                *
7 *******************************************************************************
8 */
9package com.ibm.icu.text;
10
11import java.lang.reflect.Field;
12import java.util.Collections;
13import java.util.Date;
14import java.util.List;
15
16import com.ibm.icu.impl.Grego;
17import com.ibm.icu.impl.Utility;
18import com.ibm.icu.util.Currency.CurrencyUsage;
19
20/**
21 * Provides information about currencies that is not specific to a locale.
22 *
23 * A note about currency dates.  The CLDR data provides data to the day,
24 * inclusive.  The date information used by CurrencyInfo and CurrencyFilter
25 * is represented by milliseconds, which is overly precise.  These times are
26 * in GMT, so queries involving dates should use GMT times, but more generally
27 * you should avoid relying on time of day in queries.
28 *
29 * This class is not intended for public subclassing.
30 *
31 * @stable ICU 4.4
32 */
33public class CurrencyMetaInfo {
34    private static final CurrencyMetaInfo impl;
35    private static final boolean hasData;
36
37    /**
38     * Returns the unique instance of the currency meta info.
39     * @return the meta info
40     * @stable ICU 4.4
41     */
42    public static CurrencyMetaInfo getInstance() {
43        return impl;
44    }
45
46    /**
47     * Returns the unique instance of the currency meta info, or null if
48     * noSubstitute is true and there is no data to support this API.
49     * @param noSubstitute true if no substitute data should be used
50     * @return the meta info, or null
51     * @stable ICU 49
52     */
53    public static CurrencyMetaInfo getInstance(boolean noSubstitute) {
54        return hasData ? impl : null;
55    }
56
57    /**
58     * Returns true if there is data for the currency meta info.
59     * @return true if there is actual data
60     * @internal
61     * @deprecated This API is ICU internal only.
62     */
63    @Deprecated
64    public static boolean hasData() {
65        return hasData;
66    }
67
68    /**
69     * Subclass constructor.
70     * @internal
71     * @deprecated This API is ICU internal only.
72     */
73    @Deprecated
74    protected CurrencyMetaInfo() {
75    }
76
77    /**
78     * A filter used to select which currency info is returned.
79     * @stable ICU 4.4
80     */
81    public static final class CurrencyFilter {
82        /**
83         * The region to filter on.  If null, accepts any region.
84         * @stable ICU 4.4
85         */
86        public final String region;
87
88        /**
89         * The currency to filter on.  If null, accepts any currency.
90         * @stable ICU 4.4
91         */
92        public final String currency;
93
94        /**
95         * The from date to filter on (as milliseconds).  Accepts any currency on or after this date.
96         * @stable ICU 4.4
97         */
98        public final long from;
99
100        /**
101         * The to date to filter on (as milliseconds).  Accepts any currency on or before this date.
102         * @stable ICU 4.4
103         */
104        public final long to;
105
106        /**
107         * true if we are filtering only for currencies used as legal tender.
108         * @internal
109         * @deprecated This API is ICU internal only.
110         */
111        @Deprecated
112        public final boolean tenderOnly;
113
114        private CurrencyFilter(String region, String currency, long from, long to, boolean tenderOnly) {
115            this.region = region;
116            this.currency = currency;
117            this.from = from;
118            this.to = to;
119            this.tenderOnly = tenderOnly;
120
121        }
122
123        private static final CurrencyFilter ALL = new CurrencyFilter(
124                null, null, Long.MIN_VALUE, Long.MAX_VALUE, false);
125
126        /**
127         * Returns a filter that accepts all currency data.
128         * @return a filter
129         * @stable ICU 4.4
130         */
131        public static CurrencyFilter all() {
132            return ALL;
133        }
134
135        /**
136         * Returns a filter that accepts all currencies in use as of the current date.
137         * @return a filter
138         * @see #withDate(Date)
139         * @stable ICU 4.4
140         */
141        public static CurrencyFilter now() {
142            return ALL.withDate(new Date());
143        }
144
145        /**
146         * Returns a filter that accepts all currencies ever used in the given region.
147         * @param region the region code
148         * @return a filter
149         * @see #withRegion(String)
150         * @stable ICU 4.4
151         */
152        public static CurrencyFilter onRegion(String region) {
153            return ALL.withRegion(region);
154        }
155
156        /**
157         * Returns a filter that accepts the given currency.
158         * @param currency the currency code
159         * @return a filter
160         * @see #withCurrency(String)
161         * @stable ICU 4.4
162         */
163        public static CurrencyFilter onCurrency(String currency) {
164            return ALL.withCurrency(currency);
165        }
166
167        /**
168         * Returns a filter that accepts all currencies in use on the given date.
169         * @param date the date
170         * @return a filter
171         * @see #withDate(Date)
172         * @stable ICU 4.4
173         */
174        public static CurrencyFilter onDate(Date date) {
175            return ALL.withDate(date);
176        }
177
178        /**
179         * Returns a filter that accepts all currencies that were in use at some point between
180         * the given dates, or if dates are equal, currencies in use on that date.
181         * @param from date on or after a currency must have been in use
182         * @param to date on or before which a currency must have been in use,
183         * or if equal to from, the date on which a currency must have been in use
184         * @return a filter
185         * @see #withDateRange(Date, Date)
186         * @stable ICU 49
187         */
188        public static CurrencyFilter onDateRange(Date from, Date to) {
189            return ALL.withDateRange(from, to);
190        }
191
192        /**
193         * Returns a filter that accepts all currencies in use on the given date.
194         * @param date the date as milliseconds after Jan 1, 1970
195         * @stable ICU 51
196         */
197        public static CurrencyFilter onDate(long date) {
198            return ALL.withDate(date);
199        }
200
201        /**
202         * Returns a filter that accepts all currencies that were in use at some
203         * point between the given dates, or if dates are equal, currencies in
204         * use on that date.
205         * @param from The date on or after a currency must have been in use.
206         *   Measured in milliseconds since Jan 1, 1970 GMT.
207         * @param to The date on or before which a currency must have been in use.
208         *   Measured in milliseconds since Jan 1, 1970 GMT.
209         * @stable ICU 51
210         */
211        public static CurrencyFilter onDateRange(long from, long to) {
212            return ALL.withDateRange(from, to);
213        }
214
215        /**
216         * Returns a CurrencyFilter for finding currencies that were either once used,
217         * are used, or will be used as tender.
218         * @stable ICU 51
219         */
220        public static CurrencyFilter onTender() {
221            return ALL.withTender();
222        }
223
224        /**
225         * Returns a copy of this filter, with the specified region.  Region can be null to
226         * indicate no filter on region.
227         * @param region the region code
228         * @return the filter
229         * @see #onRegion(String)
230         * @stable ICU 4.4
231         */
232        public CurrencyFilter withRegion(String region) {
233            return new CurrencyFilter(region, this.currency, this.from, this.to, this.tenderOnly);
234        }
235
236        /**
237         * Returns a copy of this filter, with the specified currency.  Currency can be null to
238         * indicate no filter on currency.
239         * @param currency the currency code
240         * @return the filter
241         * @see #onCurrency(String)
242         * @stable ICU 4.4
243         */
244        public CurrencyFilter withCurrency(String currency) {
245            return new CurrencyFilter(this.region, currency, this.from, this.to, this.tenderOnly);
246        }
247
248        /**
249         * Returns a copy of this filter, with from and to set to the given date.
250         * @param date the date on which the currency must have been in use
251         * @return the filter
252         * @see #onDate(Date)
253         * @stable ICU 4.4
254         */
255        public CurrencyFilter withDate(Date date) {
256            return new CurrencyFilter(this.region, this.currency, date.getTime(), date.getTime(), this.tenderOnly);
257        }
258
259        /**
260         * Returns a copy of this filter, with from and to set to the given dates.
261         * @param from date on or after which the currency must have been in use
262         * @param to date on or before which the currency must have been in use
263         * @return the filter
264         * @see #onDateRange(Date, Date)
265         * @stable ICU 49
266         */
267        public CurrencyFilter withDateRange(Date from, Date to) {
268            long fromLong = from == null ? Long.MIN_VALUE : from.getTime();
269            long toLong = to == null ? Long.MAX_VALUE : to.getTime();
270            return new CurrencyFilter(this.region, this.currency, fromLong, toLong, this.tenderOnly);
271        }
272
273        /**
274         * Returns a copy of this filter that accepts all currencies in use on
275         * the given date.
276         * @param date the date as milliseconds after Jan 1, 1970
277         * @stable ICU 51
278         */
279        public CurrencyFilter withDate(long date) {
280            return new CurrencyFilter(this.region, this.currency, date, date, this.tenderOnly);
281        }
282
283        /**
284         * Returns a copy of this filter that accepts all currencies that were
285         * in use at some point between the given dates, or if dates are equal,
286         * currencies in use on that date.
287         * @param from The date on or after a currency must have been in use.
288         *   Measured in milliseconds since Jan 1, 1970 GMT.
289         * @param to The date on or before which a currency must have been in use.
290         *   Measured in milliseconds since Jan 1, 1970 GMT.
291         * @stable ICU 51
292         */
293        public CurrencyFilter withDateRange(long from, long to) {
294            return new CurrencyFilter(this.region, this.currency, from, to, this.tenderOnly);
295        }
296
297        /**
298         * Returns a copy of this filter that filters for currencies that were
299         * either once used, are used, or will be used as tender.
300         * @stable ICU 51
301         */
302        public CurrencyFilter withTender() {
303            return new CurrencyFilter(this.region, this.currency, this.from, this.to, true);
304        }
305
306        /**
307         * {@inheritDoc}
308         * @stable ICU 4.4
309         */
310        @Override
311        public boolean equals(Object rhs) {
312            return rhs instanceof CurrencyFilter &&
313                equals((CurrencyFilter) rhs);
314        }
315
316        /**
317         * Type-safe override of {@link #equals(Object)}.
318         * @param rhs the currency filter to compare to
319         * @return true if the filters are equal
320         * @stable ICU 4.4
321         */
322        public boolean equals(CurrencyFilter rhs) {
323          return Utility.sameObjects(this, rhs) || (rhs != null &&
324                    equals(this.region, rhs.region) &&
325                    equals(this.currency, rhs.currency) &&
326                    this.from == rhs.from &&
327                    this.to == rhs.to &&
328                    this.tenderOnly == rhs.tenderOnly);
329        }
330
331        /**
332         * {@inheritDoc}
333         * @stable ICU 4.4
334         */
335        @Override
336        public int hashCode() {
337            int hc = 0;
338            if (region != null) {
339                hc = region.hashCode();
340            }
341            if (currency != null) {
342                hc = hc * 31 + currency.hashCode();
343            }
344            hc = hc * 31 + (int) from;
345            hc = hc * 31 + (int) (from >>> 32);
346            hc = hc * 31 + (int) to;
347            hc = hc * 31 + (int) (to >>> 32);
348            hc = hc * 31 + (tenderOnly ? 1 : 0);
349            return hc;
350        }
351
352        /**
353         * Returns a string representing the filter, for debugging.
354         * @return A string representing the filter.
355         * @stable ICU 4.4
356         */
357        @Override
358        public String toString() {
359            return debugString(this);
360        }
361
362        private static boolean equals(String lhs, String rhs) {
363            return (Utility.sameObjects(lhs, rhs) ||
364                    (lhs != null && lhs.equals(rhs)));
365        }
366    }
367
368    /**
369     * Represents the raw information about fraction digits and rounding increment.
370     * @stable ICU 4.4
371     */
372    public static final class CurrencyDigits {
373        /**
374         * Number of fraction digits used to display this currency.
375         * @stable ICU 49
376         */
377        public final int fractionDigits;
378        /**
379         * Rounding increment used when displaying this currency.
380         * @stable ICU 49
381         */
382        public final int roundingIncrement;
383
384        /**
385         * Constructor for CurrencyDigits.
386         * @param fractionDigits the fraction digits
387         * @param roundingIncrement the rounding increment
388         * @stable ICU 4.4
389         */
390        public CurrencyDigits(int fractionDigits, int roundingIncrement) {
391            this.fractionDigits = fractionDigits;
392            this.roundingIncrement = roundingIncrement;
393        }
394
395        /**
396         * Returns a string representing the currency digits, for debugging.
397         * @return A string representing the currency digits.
398         * @stable ICU 4.4
399         */
400        @Override
401        public String toString() {
402            return debugString(this);
403        }
404    }
405
406    /**
407     * Represents a complete currency info record listing the region, currency, from and to dates,
408     * and priority.
409     * Use {@link CurrencyMetaInfo#currencyInfo(CurrencyFilter)}
410     * for a list of info objects matching the filter.
411     * @stable ICU 4.4
412     */
413    public static final class CurrencyInfo {
414        /**
415         * Region code where currency is used.
416         * @stable ICU 4.4
417         */
418        public final String region;
419
420        /**
421         * The three-letter ISO currency code.
422         * @stable ICU 4.4
423         */
424        public final String code;
425
426        /**
427         * Date on which the currency was first officially used in the region.
428         * This is midnight at the start of the first day on which the currency was used, GMT.
429         * If there is no date, this is Long.MIN_VALUE;
430         * @stable ICU 4.4
431         */
432        public final long from;
433
434        /**
435         * Date at which the currency stopped being officially used in the region.
436         * This is one millisecond before midnight at the end of the last day on which the currency was used, GMT.
437         * If there is no date, this is Long.MAX_VALUE.
438         *
439         * @stable ICU 4.4
440         */
441        public final long to;
442
443        /**
444         * Preference order of currencies being used at the same time in the region.  Lower
445         * values are preferred (generally, this is a transition from an older to a newer
446         * currency).  Priorities within a single country are unique.
447         * @stable ICU 49
448         */
449        public final int priority;
450
451
452        private final boolean tender;
453
454        /**
455         * @deprecated ICU 51 Use {@link CurrencyMetaInfo#currencyInfo(CurrencyFilter)} instead.
456         */
457        @Deprecated
458        public CurrencyInfo(String region, String code, long from, long to, int priority) {
459            this(region, code, from, to, priority, true);
460        }
461
462        /**
463         * Constructs a currency info.
464         *
465         * @internal
466         * @deprecated This API is ICU internal only.
467         */
468        @Deprecated
469        public CurrencyInfo(String region, String code, long from, long to, int priority, boolean tender) {
470            this.region = region;
471            this.code = code;
472            this.from = from;
473            this.to = to;
474            this.priority = priority;
475            this.tender = tender;
476        }
477
478        /**
479         * Returns a string representation of this object, useful for debugging.
480         * @return A string representation of this object.
481         * @stable ICU 4.4
482         */
483        @Override
484        public String toString() {
485            return debugString(this);
486        }
487
488        /**
489         * Determine whether or not this currency was once used, is used,
490         * or will be used as tender in this region.
491         * @stable ICU 51
492         */
493        public boolean isTender() {
494            return tender;
495        }
496    }
497
498///CLOVER:OFF
499    /**
500     * Returns the list of CurrencyInfos matching the provided filter.  Results
501     * are ordered by country code, then by highest to lowest priority (0 is highest).
502     * The returned list is unmodifiable.
503     * @param filter the filter to control which currency info to return
504     * @return the matching information
505     * @stable ICU 4.4
506     */
507    public List<CurrencyInfo> currencyInfo(CurrencyFilter filter) {
508        return Collections.emptyList();
509    }
510
511    /**
512     * Returns the list of currency codes matching the provided filter.
513     * Results are ordered as in {@link #currencyInfo(CurrencyFilter)}.
514     * The returned list is unmodifiable.
515     * @param filter the filter to control which currencies to return.  If filter is null,
516     * returns all currencies for which information is available.
517     * @return the matching currency codes
518     * @stable ICU 4.4
519     */
520    public List<String> currencies(CurrencyFilter filter) {
521        return Collections.emptyList();
522    }
523
524    /**
525     * Returns the list of region codes matching the provided filter.
526     * Results are ordered as in {@link #currencyInfo(CurrencyFilter)}.
527     * The returned list is unmodifiable.
528     * @param filter the filter to control which regions to return.  If filter is null,
529     * returns all regions for which information is available.
530     * @return the matching region codes
531     * @stable ICU 4.4
532     */
533    public List<String> regions(CurrencyFilter filter) {
534        return Collections.emptyList();
535    }
536///CLOVER:ON
537
538    /**
539     * Returns the CurrencyDigits for the currency code.
540     * This is equivalent to currencyDigits(isoCode, CurrencyUsage.STANDARD);
541     * @param isoCode the currency code
542     * @return the CurrencyDigits
543     * @stable ICU 4.4
544     */
545    public CurrencyDigits currencyDigits(String isoCode) {
546        return currencyDigits(isoCode, CurrencyUsage.STANDARD);
547    }
548
549    /**
550     * Returns the CurrencyDigits for the currency code with Context Usage.
551     * @param isoCode the currency code
552     * @param currencyUsage the currency usage
553     * @return the CurrencyDigits
554     * @stable ICU 54
555     */
556    public CurrencyDigits currencyDigits(String isoCode, CurrencyUsage currencyUsage) {
557        return defaultDigits;
558    }
559
560    /**
561     * @internal
562     * @deprecated This API is ICU internal only.
563     */
564    @Deprecated
565    protected static final CurrencyDigits defaultDigits = new CurrencyDigits(2, 0);
566
567    static {
568        CurrencyMetaInfo temp = null;
569        boolean tempHasData = false;
570        try {
571            Class<?> clzz = Class.forName("com.ibm.icu.impl.ICUCurrencyMetaInfo");
572            temp = (CurrencyMetaInfo) clzz.newInstance();
573            tempHasData = true;
574        } catch (Throwable t) {
575            temp = new CurrencyMetaInfo();
576        }
577        impl = temp;
578        hasData = tempHasData;
579    }
580
581    private static String dateString(long date) {
582        if (date == Long.MAX_VALUE || date == Long.MIN_VALUE) {
583            return null;
584        }
585        return Grego.timeToString(date);
586    }
587
588    private static String debugString(Object o) {
589        StringBuilder sb = new StringBuilder();
590        try {
591            for (Field f : o.getClass().getFields()) {
592                Object v = f.get(o);
593                if (v != null) {
594                    String s;
595                    if (v instanceof Date) {
596                        s = dateString(((Date)v).getTime());
597                    } else if (v instanceof Long) {
598                        s = dateString(((Long)v).longValue());
599                    } else {
600                        s = String.valueOf(v);
601                    }
602                    if (s == null) {
603                        continue;
604                    }
605                    if (sb.length() > 0) {
606                        sb.append(",");
607                    }
608                    sb.append(f.getName())
609                        .append("='")
610                        .append(s)
611                        .append("'");
612                }
613            }
614        } catch (Throwable t) {
615        }
616        sb.insert(0, o.getClass().getSimpleName() + "(");
617        sb.append(")");
618        return sb.toString();
619    }
620}
621