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