Home | History | Annotate | Download | only in impl
      1 /* GENERATED SOURCE. DO NOT MODIFY. */
      2 //  2016 and later: Unicode, Inc. and others.
      3 // License & terms of use: http://www.unicode.org/copyright.html#License
      4 /*
      5  *******************************************************************************
      6  * Copyright (C) 2016, International Business Machines Corporation and
      7  * others. All Rights Reserved.
      8  *******************************************************************************
      9  */
     10 package android.icu.impl;
     11 
     12 import java.util.HashMap;
     13 import java.util.Map;
     14 
     15 import android.icu.util.ICUException;
     16 import android.icu.util.ULocale;
     17 import android.icu.util.UResourceBundle;
     18 
     19 /**
     20  * @hide Only a subset of ICU is exposed in Android
     21  */
     22 public final class DayPeriodRules {
     23     public enum DayPeriod {
     24         MIDNIGHT,
     25         NOON,
     26         MORNING1,
     27         AFTERNOON1,
     28         EVENING1,
     29         NIGHT1,
     30         MORNING2,
     31         AFTERNOON2,
     32         EVENING2,
     33         NIGHT2,
     34         AM,
     35         PM;
     36 
     37         public static DayPeriod[] VALUES = DayPeriod.values();
     38 
     39         private static DayPeriod fromStringOrNull(CharSequence str) {
     40             if ("midnight".contentEquals(str)) { return MIDNIGHT; }
     41             if ("noon".contentEquals(str)) { return NOON; }
     42             if ("morning1".contentEquals(str)) { return MORNING1; }
     43             if ("afternoon1".contentEquals(str)) { return AFTERNOON1; }
     44             if ("evening1".contentEquals(str)) { return EVENING1; }
     45             if ("night1".contentEquals(str)) { return NIGHT1; }
     46             if ("morning2".contentEquals(str)) { return MORNING2; }
     47             if ("afternoon2".contentEquals(str)) { return AFTERNOON2; }
     48             if ("evening2".contentEquals(str)) { return EVENING2; }
     49             if ("night2".contentEquals(str)) { return NIGHT2; }
     50             if ("am".contentEquals(str)) { return AM; }
     51             if ("pm".contentEquals(str)) { return PM; }
     52             return null;
     53         }
     54     }
     55 
     56     private enum CutoffType {
     57         BEFORE,
     58         AFTER,  // TODO: AFTER is deprecated in CLDR 29. Remove.
     59         FROM,
     60         AT;
     61 
     62         private static CutoffType fromStringOrNull(CharSequence str) {
     63             if ("from".contentEquals(str)) { return CutoffType.FROM; }
     64             if ("before".contentEquals(str)) { return CutoffType.BEFORE; }
     65             if ("after".contentEquals(str)) { return CutoffType.AFTER; }
     66             if ("at".contentEquals(str)) { return CutoffType.AT; }
     67             return null;
     68         }
     69     }
     70 
     71     private static final class DayPeriodRulesData {
     72         Map<String, Integer> localesToRuleSetNumMap = new HashMap<String, Integer>();
     73         DayPeriodRules[] rules;
     74         int maxRuleSetNum = -1;
     75     }
     76 
     77     private static final class DayPeriodRulesDataSink extends UResource.Sink {
     78         private DayPeriodRulesData data;
     79 
     80         private DayPeriodRulesDataSink(DayPeriodRulesData data) {
     81             this.data = data;
     82         }
     83 
     84         @Override
     85         public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
     86             UResource.Table dayPeriodData = value.getTable();
     87             for (int i = 0; dayPeriodData.getKeyAndValue(i, key, value); ++i) {
     88                 if (key.contentEquals("locales")) {
     89                     UResource.Table locales = value.getTable();
     90                     for (int j = 0; locales.getKeyAndValue(j, key, value); ++j) {
     91                         int setNum = parseSetNum(value.getString());
     92                         data.localesToRuleSetNumMap.put(key.toString(), setNum);
     93                     }
     94                 } else if (key.contentEquals("rules")) {
     95                     UResource.Table rules = value.getTable();
     96                     processRules(rules, key, value);
     97                 }
     98             }
     99         }
    100 
    101         private void processRules(UResource.Table rules, UResource.Key key, UResource.Value value) {
    102             for (int i = 0; rules.getKeyAndValue(i, key, value); ++i) {
    103                 ruleSetNum = parseSetNum(key.toString());
    104                 data.rules[ruleSetNum] = new DayPeriodRules();
    105 
    106                 UResource.Table ruleSet = value.getTable();
    107                 for (int j = 0; ruleSet.getKeyAndValue(j, key, value); ++j) {
    108                     period = DayPeriod.fromStringOrNull(key);
    109                     if (period == null) { throw new ICUException("Unknown day period in data."); }
    110 
    111                     UResource.Table periodDefinition = value.getTable();
    112                     for (int k = 0; periodDefinition.getKeyAndValue(k, key, value); ++k) {
    113                         if (value.getType() == UResourceBundle.STRING) {
    114                             // Key-value pairs (e.g. before{6:00})
    115                             CutoffType type = CutoffType.fromStringOrNull(key);
    116                             addCutoff(type, value.getString());
    117                         } else {
    118                             // Arrays (e.g. before{6:00, 24:00}
    119                             cutoffType = CutoffType.fromStringOrNull(key);
    120                             UResource.Array cutoffArray = value.getArray();
    121                             int length = cutoffArray.getSize();
    122                             for (int l = 0; l < length; ++l) {
    123                                 cutoffArray.getValue(l, value);
    124                                 addCutoff(cutoffType, value.getString());
    125                             }
    126                         }
    127                     }
    128                     setDayPeriodForHoursFromCutoffs();
    129                     for (int k = 0; k < cutoffs.length; ++k) {
    130                         cutoffs[k] = 0;
    131                     }
    132                 }
    133                 for (DayPeriod period : data.rules[ruleSetNum].dayPeriodForHour) {
    134                     if (period == null) {
    135                         throw new ICUException("Rules in data don't cover all 24 hours (they should).");
    136                     }
    137                 }
    138             }
    139         }
    140 
    141         // Members.
    142         private int cutoffs[] = new int[25];  // [0] thru [24]; 24 is allowed is "before 24".
    143 
    144         // "Path" to data.
    145         private int ruleSetNum;
    146         private DayPeriod period;
    147         private CutoffType cutoffType;
    148 
    149         // Helpers.
    150         private void addCutoff(CutoffType type, String hourStr) {
    151             if (type == null) { throw new ICUException("Cutoff type not recognized."); }
    152             int hour = parseHour(hourStr);
    153             cutoffs[hour] |= 1 << type.ordinal();
    154         }
    155 
    156         private void setDayPeriodForHoursFromCutoffs() {
    157             DayPeriodRules rule = data.rules[ruleSetNum];
    158             for (int startHour = 0; startHour <= 24; ++startHour) {
    159                 // AT cutoffs must be either midnight or noon.
    160                 if ((cutoffs[startHour] & (1 << CutoffType.AT.ordinal())) > 0) {
    161                     if (startHour == 0 && period == DayPeriod.MIDNIGHT) {
    162                         rule.hasMidnight = true;
    163                     } else if (startHour == 12 && period == DayPeriod.NOON) {
    164                         rule.hasNoon = true;
    165                     } else {
    166                         throw new ICUException("AT cutoff must only be set for 0:00 or 12:00.");
    167                     }
    168                 }
    169 
    170                 // FROM/AFTER and BEFORE must come in a pair.
    171                 if ((cutoffs[startHour] & (1 << CutoffType.FROM.ordinal())) > 0 ||
    172                         (cutoffs[startHour] & (1 << CutoffType.AFTER.ordinal())) > 0) {
    173                     for (int hour = startHour + 1;; ++hour) {
    174                         if (hour == startHour) {
    175                             // We've gone around the array once and can't find a BEFORE.
    176                             throw new ICUException(
    177                                     "FROM/AFTER cutoffs must have a matching BEFORE cutoff.");
    178                         }
    179                         if (hour == 25) { hour = 0; }
    180                         if ((cutoffs[hour] & (1 << CutoffType.BEFORE.ordinal())) > 0) {
    181                             rule.add(startHour, hour, period);
    182                             break;
    183                         }
    184                     }
    185                 }
    186             }
    187         }
    188 
    189         private static int parseHour(String str) {
    190             int firstColonPos = str.indexOf(':');
    191             if (firstColonPos < 0 || !str.substring(firstColonPos).equals(":00")) {
    192                 throw new ICUException("Cutoff time must end in \":00\".");
    193             }
    194 
    195             String hourStr = str.substring(0, firstColonPos);
    196             if (firstColonPos != 1 && firstColonPos != 2) {
    197                 throw new ICUException("Cutoff time must begin with h: or hh:");
    198             }
    199 
    200             int hour = Integer.parseInt(hourStr);
    201             // parseInt() throws NumberFormatException if hourStr isn't proper.
    202 
    203             if (hour < 0 || hour > 24) {
    204                 throw new ICUException("Cutoff hour must be between 0 and 24, inclusive.");
    205             }
    206 
    207             return hour;
    208         }
    209     }  // DayPeriodRulesDataSink
    210 
    211     private static class DayPeriodRulesCountSink extends UResource.Sink {
    212         private DayPeriodRulesData data;
    213 
    214         private DayPeriodRulesCountSink(DayPeriodRulesData data) {
    215             this.data = data;
    216         }
    217 
    218         @Override
    219         public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
    220             UResource.Table rules = value.getTable();
    221             for (int i = 0; rules.getKeyAndValue(i, key, value); ++i) {
    222                 int setNum = parseSetNum(key.toString());
    223                 if (setNum > data.maxRuleSetNum) {
    224                     data.maxRuleSetNum = setNum;
    225                 }
    226             }
    227         }
    228     }
    229 
    230     private static final DayPeriodRulesData DATA = loadData();
    231 
    232     private boolean hasMidnight;
    233     private boolean hasNoon;
    234     private DayPeriod[] dayPeriodForHour;
    235 
    236     private DayPeriodRules() {
    237         hasMidnight = false;
    238         hasNoon = false;
    239         dayPeriodForHour = new DayPeriod[24];
    240     }
    241 
    242     /**
    243      * Get a DayPeriodRules object given a locale.
    244      * If data hasn't been loaded, it will be loaded for all locales at once.
    245      * @param locale locale for which the DayPeriodRules object is requested.
    246      * @return a DayPeriodRules object for `locale`.
    247      */
    248     public static DayPeriodRules getInstance(ULocale locale) {
    249         String localeCode = locale.getBaseName();
    250         if (localeCode.isEmpty()) { localeCode = "root"; }
    251 
    252         Integer ruleSetNum = null;
    253         while (ruleSetNum == null) {
    254             ruleSetNum = DATA.localesToRuleSetNumMap.get(localeCode);
    255             if (ruleSetNum == null) {
    256                 localeCode = ULocale.getFallback(localeCode);
    257                 if (localeCode.isEmpty()) {
    258                     // Saves a lookup in the map.
    259                     break;
    260                 }
    261             } else {
    262                 break;
    263             }
    264         }
    265 
    266         if (ruleSetNum == null || DATA.rules[ruleSetNum] == null) {
    267             // Data doesn't exist for the locale requested.
    268             return null;
    269         }
    270 
    271         return DATA.rules[ruleSetNum];
    272     }
    273 
    274     public double getMidPointForDayPeriod(DayPeriod dayPeriod) {
    275         int startHour = getStartHourForDayPeriod(dayPeriod);
    276         int endHour = getEndHourForDayPeriod(dayPeriod);
    277 
    278         double midPoint = (startHour + endHour) / 2.0;
    279 
    280         if (startHour > endHour) {
    281             // dayPeriod wraps around midnight. Shift midPoint by 12 hours, in the direction that
    282             // lands it in [0, 24).
    283             midPoint += 12;
    284             if (midPoint >= 24) {
    285                 midPoint -= 24;
    286             }
    287         }
    288 
    289         return midPoint;
    290     }
    291 
    292     private static DayPeriodRulesData loadData() {
    293         DayPeriodRulesData data = new DayPeriodRulesData();
    294         ICUResourceBundle rb = ICUResourceBundle.getBundleInstance(
    295                 ICUData.ICU_BASE_NAME,
    296                 "dayPeriods",
    297                 ICUResourceBundle.ICU_DATA_CLASS_LOADER,
    298                 true);
    299 
    300         DayPeriodRulesCountSink countSink = new DayPeriodRulesCountSink(data);
    301         rb.getAllItemsWithFallback("rules", countSink);
    302 
    303         data.rules = new DayPeriodRules[data.maxRuleSetNum + 1];
    304         DayPeriodRulesDataSink sink = new DayPeriodRulesDataSink(data);
    305         rb.getAllItemsWithFallback("", sink);
    306 
    307         return data;
    308     }
    309 
    310     private int getStartHourForDayPeriod(DayPeriod dayPeriod) throws IllegalArgumentException {
    311         if (dayPeriod == DayPeriod.MIDNIGHT) { return 0; }
    312         if (dayPeriod == DayPeriod.NOON) { return 12; }
    313 
    314         if (dayPeriodForHour[0] == dayPeriod && dayPeriodForHour[23] == dayPeriod) {
    315             // dayPeriod wraps around midnight. Start hour is later than end hour.
    316             for (int i = 22; i >= 1; --i) {
    317                 if (dayPeriodForHour[i] != dayPeriod) {
    318                     return (i + 1);
    319                 }
    320             }
    321         } else {
    322             for (int i = 0; i <= 23; ++i) {
    323                 if (dayPeriodForHour[i] == dayPeriod) {
    324                     return i;
    325                 }
    326             }
    327         }
    328 
    329         // dayPeriod doesn't exist in rule set; throw exception.
    330         throw new IllegalArgumentException();
    331     }
    332 
    333     private int getEndHourForDayPeriod(DayPeriod dayPeriod) {
    334         if (dayPeriod == DayPeriod.MIDNIGHT) { return 0; }
    335         if (dayPeriod == DayPeriod.NOON) { return 12; }
    336 
    337         if (dayPeriodForHour[0] == dayPeriod && dayPeriodForHour[23] == dayPeriod) {
    338             // dayPeriod wraps around midnight. End hour is before start hour.
    339             for (int i = 1; i <= 22; ++i) {
    340                 if (dayPeriodForHour[i] != dayPeriod) {
    341                     // i o'clock is when a new period starts, therefore when the old period ends.
    342                     return i;
    343                 }
    344             }
    345         } else {
    346             for (int i = 23; i >= 0; --i) {
    347                 if (dayPeriodForHour[i] == dayPeriod) {
    348                     return (i + 1);
    349                 }
    350             }
    351         }
    352 
    353         // dayPeriod doesn't exist in rule set; throw exception.
    354         throw new IllegalArgumentException();
    355     }
    356 
    357     // Getters.
    358     public boolean hasMidnight() { return hasMidnight; }
    359     public boolean hasNoon() { return hasNoon; }
    360     public DayPeriod getDayPeriodForHour(int hour) { return dayPeriodForHour[hour]; }
    361 
    362     // Helpers.
    363     private void add(int startHour, int limitHour, DayPeriod period) {
    364         for (int i = startHour; i != limitHour; ++i) {
    365             if (i == 24) { i = 0; }
    366             dayPeriodForHour[i] = period;
    367         }
    368     }
    369 
    370     private static int parseSetNum(String setNumStr) {
    371         if (!setNumStr.startsWith("set")) {
    372             throw new ICUException("Set number should start with \"set\".");
    373         }
    374 
    375         String numStr = setNumStr.substring(3);  // e.g. "set17" -> "17"
    376         return Integer.parseInt(numStr);  // This throws NumberFormatException if numStr isn't a proper number.
    377     }
    378 }
    379