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-2016, International Business Machines Corporation and
6  * others. All Rights Reserved.
7  *******************************************************************************
8  */
9 package com.ibm.icu.impl;
10 
11 import java.util.ArrayList;
12 import java.util.Collections;
13 import java.util.HashSet;
14 import java.util.List;
15 import java.util.Set;
16 
17 import com.ibm.icu.text.CurrencyMetaInfo;
18 import com.ibm.icu.util.Currency.CurrencyUsage;
19 
20 /**
21  * ICU's currency meta info data.
22  */
23 public class ICUCurrencyMetaInfo extends CurrencyMetaInfo {
24     private ICUResourceBundle regionInfo;
25     private ICUResourceBundle digitInfo;
26 
ICUCurrencyMetaInfo()27     public ICUCurrencyMetaInfo() {
28         ICUResourceBundle bundle = (ICUResourceBundle) ICUResourceBundle.getBundleInstance(
29             ICUData.ICU_CURR_BASE_NAME, "supplementalData",
30             ICUResourceBundle.ICU_DATA_CLASS_LOADER);
31         regionInfo = bundle.findTopLevel("CurrencyMap");
32         digitInfo = bundle.findTopLevel("CurrencyMeta");
33     }
34 
35     @Override
currencyInfo(CurrencyFilter filter)36     public List<CurrencyInfo> currencyInfo(CurrencyFilter filter) {
37         return collect(new InfoCollector(), filter);
38     }
39 
40     @Override
currencies(CurrencyFilter filter)41     public List<String> currencies(CurrencyFilter filter) {
42         return collect(new CurrencyCollector(), filter);
43    }
44 
45     @Override
regions(CurrencyFilter filter)46     public List<String> regions(CurrencyFilter filter) {
47         return collect(new RegionCollector(), filter);
48     }
49 
50     @Override
currencyDigits(String isoCode)51     public CurrencyDigits currencyDigits(String isoCode) {
52         return currencyDigits(isoCode, CurrencyUsage.STANDARD);
53     }
54 
55     @Override
currencyDigits(String isoCode, CurrencyUsage currencyPurpose)56     public CurrencyDigits currencyDigits(String isoCode, CurrencyUsage currencyPurpose) {
57         ICUResourceBundle b = digitInfo.findWithFallback(isoCode);
58         if (b == null) {
59             b = digitInfo.findWithFallback("DEFAULT");
60         }
61         int[] data = b.getIntVector();
62         if (currencyPurpose == CurrencyUsage.CASH) {
63             return new CurrencyDigits(data[2], data[3]);
64         } else if (currencyPurpose == CurrencyUsage.STANDARD) {
65             return new CurrencyDigits(data[0], data[1]);
66         } else {
67             return new CurrencyDigits(data[0], data[1]);
68         }
69     }
70 
collect(Collector<T> collector, CurrencyFilter filter)71     private <T> List<T> collect(Collector<T> collector, CurrencyFilter filter) {
72         // We rely on the fact that the data lists the regions in order, and the
73         // priorities in order within region.  This means we don't need
74         // to sort the results to ensure the ordering matches the spec.
75 
76         if (filter == null) {
77             filter = CurrencyFilter.all();
78         }
79         int needed = collector.collects();
80         if (filter.region != null) {
81             needed |= Region;
82         }
83         if (filter.currency != null) {
84             needed |= Currency;
85         }
86         if (filter.from != Long.MIN_VALUE || filter.to != Long.MAX_VALUE) {
87             needed |= Date;
88         }
89         if (filter.tenderOnly) {
90             needed |= Tender;
91         }
92 
93         if (needed != 0) {
94             if (filter.region != null) {
95                 ICUResourceBundle b = regionInfo.findWithFallback(filter.region);
96                 if (b != null) {
97                     collectRegion(collector, filter, needed, b);
98                 }
99             } else {
100                 for (int i = 0; i < regionInfo.getSize(); i++) {
101                     collectRegion(collector, filter, needed, regionInfo.at(i));
102                 }
103             }
104         }
105 
106         return collector.getList();
107     }
108 
collectRegion(Collector<T> collector, CurrencyFilter filter, int needed, ICUResourceBundle b)109     private <T> void collectRegion(Collector<T> collector, CurrencyFilter filter,
110             int needed, ICUResourceBundle b) {
111 
112         String region = b.getKey();
113         if (needed == Region) {
114             collector.collect(b.getKey(), null, 0, 0, -1, false);
115             return;
116         }
117 
118         for (int i = 0; i < b.getSize(); i++) {
119             ICUResourceBundle r = b.at(i);
120             if (r.getSize() == 0) {
121                 // AQ[0] is an empty array instead of a table, so the bundle is null.
122                 // There's no data here, so we skip this entirely.
123                 // We'd do a type test, but the ResourceArray type is private.
124                 continue;
125             }
126             String currency = null;
127             long from = Long.MIN_VALUE;
128             long to = Long.MAX_VALUE;
129             boolean tender = true;
130 
131             if ((needed & Currency) != 0) {
132                 ICUResourceBundle currBundle = r.at("id");
133                 currency = currBundle.getString();
134                 if (filter.currency != null && !filter.currency.equals(currency)) {
135                     continue;
136                 }
137             }
138 
139             if ((needed & Date) != 0) {
140                 from = getDate(r.at("from"), Long.MIN_VALUE, false);
141                 to = getDate(r.at("to"), Long.MAX_VALUE, true);
142                 // In the data, to is always > from.  This means that when we have a range
143                 // from == to, the comparisons below will always do the right thing, despite
144                 // the range being technically empty.  It really should be [from, from+1) but
145                 // this way we don't need to fiddle with it.
146                 if (filter.from > to) {
147                     continue;
148                 }
149                 if (filter.to < from) {
150                     continue;
151                 }
152             }
153             if ((needed & Tender) != 0) {
154                 ICUResourceBundle tenderBundle = r.at("tender");
155                 tender = tenderBundle == null || "true".equals(tenderBundle.getString());
156                 if (filter.tenderOnly && !tender) {
157                     continue;
158                 }
159             }
160 
161             // data lists elements in priority order, so 'i' suffices
162             collector.collect(region, currency, from, to, i, tender);
163         }
164     }
165 
166     private static final long MASK = 4294967295L;
getDate(ICUResourceBundle b, long defaultValue, boolean endOfDay)167     private long getDate(ICUResourceBundle b, long defaultValue, boolean endOfDay) {
168         if (b == null) {
169             return defaultValue;
170         }
171         int[] values = b.getIntVector();
172         return ((long) values[0] << 32) | ((values[1]) & MASK);
173     }
174 
175     // Utility, just because I don't like the n^2 behavior of using list.contains to build a
176     // list of unique items.  If we used java 6 we could use their class for this.
177     private static class UniqueList<T> {
178         private Set<T> seen = new HashSet<T>();
179         private List<T> list = new ArrayList<T>();
180 
create()181         private static <T> UniqueList<T> create() {
182             return new UniqueList<T>();
183         }
184 
add(T value)185         void add(T value) {
186             if (!seen.contains(value)) {
187                 list.add(value);
188                 seen.add(value);
189             }
190         }
191 
list()192         List<T> list() {
193             return Collections.unmodifiableList(list);
194         }
195     }
196 
197     private static class InfoCollector implements Collector<CurrencyInfo> {
198         // Data is already unique by region/priority, so we don't need to be concerned
199         // about duplicates.
200         private List<CurrencyInfo> result = new ArrayList<CurrencyInfo>();
201 
202         @Override
collect(String region, String currency, long from, long to, int priority, boolean tender)203         public void collect(String region, String currency, long from, long to, int priority, boolean tender) {
204             result.add(new CurrencyInfo(region, currency, from, to, priority, tender));
205         }
206 
207         @Override
getList()208         public List<CurrencyInfo> getList() {
209             return Collections.unmodifiableList(result);
210         }
211 
212         @Override
collects()213         public int collects() {
214             return Everything;
215         }
216     }
217 
218     private static class RegionCollector implements Collector<String> {
219         private final UniqueList<String> result = UniqueList.create();
220 
221         @Override
collect( String region, String currency, long from, long to, int priority, boolean tender)222         public void collect(
223                 String region, String currency, long from, long to, int priority, boolean tender) {
224             result.add(region);
225         }
226 
227         @Override
collects()228         public int collects() {
229             return Region;
230         }
231 
232         @Override
getList()233         public List<String> getList() {
234             return result.list();
235         }
236     }
237 
238     private static class CurrencyCollector implements Collector<String> {
239         private final UniqueList<String> result = UniqueList.create();
240 
241         @Override
collect( String region, String currency, long from, long to, int priority, boolean tender)242         public void collect(
243                 String region, String currency, long from, long to, int priority, boolean tender) {
244             result.add(currency);
245         }
246 
247         @Override
collects()248         public int collects() {
249             return Currency;
250         }
251 
252         @Override
getList()253         public List<String> getList() {
254             return result.list();
255         }
256     }
257 
258     private static final int Region = 1;
259     private static final int Currency = 2;
260     private static final int Date = 4;
261     private static final int Tender = 8;
262     private static final int Everything = Integer.MAX_VALUE;
263 
264     private static interface Collector<T> {
265         /**
266          * A bitmask of Region/Currency/Date indicating which features we collect.
267          * @return the bitmask
268          */
collects()269         int collects();
270 
271         /**
272          * Called with data passed by filter.  Values not collected by filter should be ignored.
273          * @param region the region code (null if ignored)
274          * @param currency the currency code (null if ignored)
275          * @param from start time (0 if ignored)
276          * @param to end time (0 if ignored)
277          * @param priority priority (-1 if ignored)
278          * @param tender true if currency is legal tender.
279          */
collect(String region, String currency, long from, long to, int priority, boolean tender)280         void collect(String region, String currency, long from, long to, int priority, boolean tender);
281 
282         /**
283          * Return the list of unique items in the order in which we encountered them for the
284          * first time.  The returned list is unmodifiable.
285          * @return the list
286          */
getList()287         List<T> getList();
288     }
289 }
290