Home | History | Annotate | Download | only in impl
      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 
     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
     36     public List<CurrencyInfo> currencyInfo(CurrencyFilter filter) {
     37         return collect(new InfoCollector(), filter);
     38     }
     39 
     40     @Override
     41     public List<String> currencies(CurrencyFilter filter) {
     42         return collect(new CurrencyCollector(), filter);
     43    }
     44 
     45     @Override
     46     public List<String> regions(CurrencyFilter filter) {
     47         return collect(new RegionCollector(), filter);
     48     }
     49 
     50     @Override
     51     public CurrencyDigits currencyDigits(String isoCode) {
     52         return currencyDigits(isoCode, CurrencyUsage.STANDARD);
     53     }
     54 
     55     @Override
     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 
     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 
    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;
    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 
    181         private static <T> UniqueList<T> create() {
    182             return new UniqueList<T>();
    183         }
    184 
    185         void add(T value) {
    186             if (!seen.contains(value)) {
    187                 list.add(value);
    188                 seen.add(value);
    189             }
    190         }
    191 
    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
    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
    208         public List<CurrencyInfo> getList() {
    209             return Collections.unmodifiableList(result);
    210         }
    211 
    212         @Override
    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
    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
    228         public int collects() {
    229             return Region;
    230         }
    231 
    232         @Override
    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
    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
    248         public int collects() {
    249             return Currency;
    250         }
    251 
    252         @Override
    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          */
    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          */
    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          */
    287         List<T> getList();
    288     }
    289 }
    290