Home | History | Annotate | Download | only in ui
      1 /*
      2  *  Copyright 2006 The Android Open Source Project
      3  */
      4 
      5 #include <pim/EventRecurrence.h>
      6 #include <utils/String8.h>
      7 #include <stdio.h>
      8 #include <limits.h>
      9 
     10 namespace android {
     11 
     12 #define FAIL_HERE() do { \
     13             printf("Parsing failed at line %d\n", __LINE__); \
     14             return UNKNOWN_ERROR; \
     15         } while(0)
     16 
     17 EventRecurrence::EventRecurrence()
     18     :freq((freq_t)0),
     19      until(),
     20      count(0),
     21      interval(0),
     22      bysecond(0),
     23      bysecondCount(0),
     24      byminute(0),
     25      byminuteCount(0),
     26      byhour(0),
     27      byhourCount(0),
     28      byday(0),
     29      bydayNum(0),
     30      bydayCount(0),
     31      bymonthday(0),
     32      bymonthdayCount(0),
     33      byyearday(0),
     34      byyeardayCount(0),
     35      byweekno(0),
     36      byweeknoCount(0),
     37      bymonth(0),
     38      bymonthCount(0),
     39      bysetpos(0),
     40      bysetposCount(0),
     41      wkst(0)
     42 {
     43 }
     44 
     45 EventRecurrence::~EventRecurrence()
     46 {
     47     delete[] bysecond;
     48     delete[] byminute;
     49     delete[] byhour;
     50     delete[] byday;
     51     delete[] bydayNum;
     52     delete[] byyearday;
     53     delete[] bymonthday;
     54     delete[] byweekno;
     55     delete[] bymonth;
     56     delete[] bysetpos;
     57 }
     58 
     59 enum LHS {
     60     NONE_LHS = 0,
     61     FREQ,
     62     UNTIL,
     63     COUNT,
     64     INTERVAL,
     65     BYSECOND,
     66     BYMINUTE,
     67     BYHOUR,
     68     BYDAY,
     69     BYMONTHDAY,
     70     BYYEARDAY,
     71     BYWEEKNO,
     72     BYMONTH,
     73     BYSETPOS,
     74     WKST
     75 };
     76 
     77 struct LHSProc
     78 {
     79     const char16_t* text;
     80     size_t textSize;
     81     uint32_t value;
     82 };
     83 
     84 const char16_t FREQ_text[] = { 'F', 'R', 'E', 'Q' };
     85 const char16_t UNTIL_text[] = { 'U', 'N', 'T', 'I', 'L' };
     86 const char16_t COUNT_text[] = { 'C', 'O', 'U', 'N', 'T' };
     87 const char16_t INTERVAL_text[] = { 'I', 'N', 'T', 'E', 'R', 'V', 'A', 'L'};
     88 const char16_t BYSECOND_text[] = { 'B', 'Y', 'S', 'E', 'C', 'O', 'N', 'D' };
     89 const char16_t BYMINUTE_text[] = { 'B', 'Y', 'M', 'I', 'N', 'U', 'T', 'E' };
     90 const char16_t BYHOUR_text[] = { 'B', 'Y', 'H', 'O', 'U', 'R' };
     91 const char16_t BYDAY_text[] = { 'B', 'Y', 'D', 'A', 'Y' };
     92 const char16_t BYMONTHDAY_text[] = { 'B','Y','M','O','N','T','H','D','A','Y' };
     93 const char16_t BYYEARDAY_text[] = { 'B','Y','Y','E','A','R','D','A','Y' };
     94 const char16_t BYWEEKNO_text[] = { 'B', 'Y', 'W', 'E', 'E', 'K', 'N', 'O' };
     95 const char16_t BYMONTH_text[] = { 'B', 'Y', 'M', 'O', 'N', 'T', 'H' };
     96 const char16_t BYSETPOS_text[] = { 'B', 'Y', 'S', 'E', 'T', 'P', 'O', 'S' };
     97 const char16_t WKST_text[] = { 'W', 'K', 'S', 'T' };
     98 
     99 #define SIZ(x) (sizeof(x)/sizeof(x[0]))
    100 
    101 const LHSProc LHSPROC[] = {
    102     { FREQ_text, SIZ(FREQ_text), FREQ },
    103     { UNTIL_text, SIZ(UNTIL_text), UNTIL },
    104     { COUNT_text, SIZ(COUNT_text), COUNT },
    105     { INTERVAL_text, SIZ(INTERVAL_text), INTERVAL },
    106     { BYSECOND_text, SIZ(BYSECOND_text), BYSECOND },
    107     { BYMINUTE_text, SIZ(BYMINUTE_text), BYMINUTE },
    108     { BYHOUR_text, SIZ(BYHOUR_text), BYHOUR },
    109     { BYDAY_text, SIZ(BYDAY_text), BYDAY },
    110     { BYMONTHDAY_text, SIZ(BYMONTHDAY_text), BYMONTHDAY },
    111     { BYYEARDAY_text, SIZ(BYYEARDAY_text), BYYEARDAY },
    112     { BYWEEKNO_text, SIZ(BYWEEKNO_text), BYWEEKNO },
    113     { BYMONTH_text, SIZ(BYMONTH_text), BYMONTH },
    114     { BYSETPOS_text, SIZ(BYSETPOS_text), BYSETPOS },
    115     { WKST_text, SIZ(WKST_text), WKST },
    116     { NULL, 0, NONE_LHS },
    117 };
    118 
    119 const char16_t SECONDLY_text[] = { 'S','E','C','O','N','D','L','Y' };
    120 const char16_t MINUTELY_text[] = { 'M','I','N','U','T','E','L','Y' };
    121 const char16_t HOURLY_text[] = { 'H','O','U','R','L','Y' };
    122 const char16_t DAILY_text[] = { 'D','A','I','L','Y' };
    123 const char16_t WEEKLY_text[] = { 'W','E','E','K','L','Y' };
    124 const char16_t MONTHLY_text[] = { 'M','O','N','T','H','L','Y' };
    125 const char16_t YEARLY_text[] = { 'Y','E','A','R','L','Y' };
    126 
    127 typedef LHSProc FreqProc;
    128 
    129 const FreqProc FREQPROC[] = {
    130     { SECONDLY_text, SIZ(SECONDLY_text), EventRecurrence::SECONDLY },
    131     { MINUTELY_text, SIZ(MINUTELY_text), EventRecurrence::MINUTELY },
    132     { HOURLY_text, SIZ(HOURLY_text), EventRecurrence::HOURLY },
    133     { DAILY_text, SIZ(DAILY_text), EventRecurrence::DAILY },
    134     { WEEKLY_text, SIZ(WEEKLY_text), EventRecurrence::WEEKLY },
    135     { MONTHLY_text, SIZ(MONTHLY_text), EventRecurrence::MONTHLY },
    136     { YEARLY_text, SIZ(YEARLY_text), EventRecurrence::YEARLY },
    137     { NULL, 0, NONE_LHS },
    138 };
    139 
    140 const char16_t SU_text[] = { 'S','U' };
    141 const char16_t MO_text[] = { 'M','O' };
    142 const char16_t TU_text[] = { 'T','U' };
    143 const char16_t WE_text[] = { 'W','E' };
    144 const char16_t TH_text[] = { 'T','H' };
    145 const char16_t FR_text[] = { 'F','R' };
    146 const char16_t SA_text[] = { 'S','A' };
    147 
    148 const FreqProc WEEKDAYPROC[] = {
    149     { SU_text, SIZ(SU_text), EventRecurrence::SU },
    150     { MO_text, SIZ(MO_text), EventRecurrence::MO },
    151     { TU_text, SIZ(TU_text), EventRecurrence::TU },
    152     { WE_text, SIZ(WE_text), EventRecurrence::WE },
    153     { TH_text, SIZ(TH_text), EventRecurrence::TH },
    154     { FR_text, SIZ(FR_text), EventRecurrence::FR },
    155     { SA_text, SIZ(SA_text), EventRecurrence::SA },
    156     { NULL, 0, NONE_LHS },
    157 };
    158 
    159 // returns the index into LHSPROC for the match or -1 if not found
    160 inline static int
    161 match_proc(const LHSProc* p, const char16_t* str, size_t len)
    162 {
    163     int i = 0;
    164     while (p->text != NULL) {
    165         if (p->textSize == len) {
    166             if (0 == memcmp(p->text, str, len*sizeof(char16_t))) {
    167                 return i;
    168             }
    169         }
    170         p++;
    171         i++;
    172     }
    173     return -1;
    174 }
    175 
    176 // rangeMin and rangeMax are inclusive
    177 static status_t
    178 parse_int(const char16_t* str, size_t len, int* out,
    179             int rangeMin, int rangeMax, bool zeroOK)
    180 {
    181     char16_t c;
    182     size_t i=0;
    183 
    184     if (len == 0) {
    185         FAIL_HERE();
    186     }
    187     bool negative = false;
    188     c = str[0];
    189     if (c == '-' ) {
    190         negative = true;
    191         i++;
    192     }
    193     else if (c == '+') {
    194         i++;
    195     }
    196     int n = 0;
    197     for (; i<len; i++) {
    198         c = str[i];
    199         if (c < '0' || c > '9') {
    200             FAIL_HERE();
    201         }
    202         int prev = n;
    203         n *= 10;
    204         // the spec doesn't address how big these numbers can be,
    205         // so we're not going to worry about not being able to represent
    206         // INT_MIN, and if we're going to wrap, we'll just clamp to
    207         // INT_MAX instead
    208         if (n < prev) {
    209             n = INT_MAX;
    210         } else {
    211             n += c - '0';
    212         }
    213     }
    214     if (negative) {
    215         n = -n;
    216     }
    217     if (n < rangeMin || n > rangeMax) {
    218         FAIL_HERE();
    219     }
    220     if (!zeroOK && n == 0) {
    221         FAIL_HERE();
    222     }
    223     *out = n;
    224     return NO_ERROR;
    225 }
    226 
    227 static status_t
    228 parse_int_list(const char16_t* str, size_t len, int* countOut, int** listOut,
    229           int rangeMin, int rangeMax, bool zeroOK,
    230           status_t (*func)(const char16_t*,size_t,int*,int,int,bool)=parse_int)
    231 {
    232     status_t err;
    233 
    234     if (len == 0) {
    235         *countOut = 0;
    236         *listOut = NULL;
    237         return NO_ERROR;
    238     }
    239 
    240     // make one pass through looking for commas so we know how big to make our
    241     // out array.
    242     int count = 1;
    243     for (size_t i=0; i<len; i++) {
    244         if (str[i] == ',') {
    245             count++;
    246         }
    247     }
    248 
    249     int* list = new int[count];
    250     const char16_t* p = str;
    251     int commaIndex = 0;
    252     size_t i;
    253 
    254     for (i=0; i<len; i++) {
    255         if (str[i] == ',') {
    256             err = func(p, (str+i-p), list+commaIndex, rangeMin,
    257                     rangeMax, zeroOK);
    258             if (err != NO_ERROR) {
    259                 goto bail;
    260             }
    261             commaIndex++;
    262             p = str+i+1;
    263         }
    264     }
    265 
    266     err = func(p, (str+i-p), list+commaIndex, rangeMin, rangeMax, zeroOK);
    267     if (err != NO_ERROR) {
    268         goto bail;
    269     }
    270     commaIndex++;
    271 
    272     *countOut = count;
    273     *listOut = list;
    274 
    275     return NO_ERROR;
    276 
    277 bail:
    278     delete[] list;
    279     FAIL_HERE();
    280 }
    281 
    282 // the numbers here are small, so we pack them both into one value, and then
    283 // split it out later.  it lets us reuse all the comma separated list code.
    284 static status_t
    285 parse_byday(const char16_t* s, size_t len, int* out,
    286             int rangeMin, int rangeMax, bool zeroOK)
    287 {
    288     status_t err;
    289     int n = 0;
    290     const char16_t* p = s;
    291     size_t plen = len;
    292 
    293     if (len > 0) {
    294         char16_t c = s[0];
    295         if (c == '-' || c == '+' || (c >= '0' && c <= '9')) {
    296             if (len > 1) {
    297                 size_t nlen = 0;
    298                 c = s[nlen];
    299                 while (nlen < len
    300                         && (c == '-' || c == '+' || (c >= '0' && c <= '9'))) {
    301                     c = s[nlen];
    302                     nlen++;
    303                 }
    304                 if (nlen > 0) {
    305                     nlen--;
    306                     err = parse_int(s, nlen, &n, rangeMin, rangeMax, zeroOK);
    307                     if (err != NO_ERROR) {
    308                         FAIL_HERE();
    309                     }
    310                     p += nlen;
    311                     plen -= nlen;
    312                 }
    313             }
    314         }
    315 
    316         int index = match_proc(WEEKDAYPROC, p, plen);
    317         if (index >= 0) {
    318             *out = (0xffff0000 & WEEKDAYPROC[index].value)
    319                     | (0x0000ffff & n);
    320             return NO_ERROR;
    321         }
    322     }
    323     return UNKNOWN_ERROR;
    324 }
    325 
    326 static void
    327 postprocess_byday(int count, int* byday, int** bydayNum)
    328 {
    329     int* bdn = new int[count];
    330     *bydayNum = bdn;
    331     for (int i=0; i<count; i++) {
    332         uint32_t v = byday[i];
    333         int16_t num = v & 0x0000ffff;
    334         byday[i] = v & 0xffff0000;
    335         // will sign extend:
    336         bdn[i] = num;
    337     }
    338 }
    339 
    340 #define PARSE_INT_LIST_CHECKED(name, rangeMin, rangeMax, zeroOK) \
    341     if (name##Count != 0 || NO_ERROR != parse_int_list(s, slen, \
    342                          &name##Count, &name, rangeMin, rangeMax, zeroOK)) { \
    343         FAIL_HERE(); \
    344     }
    345 status_t
    346 EventRecurrence::parse(const String16& str)
    347 {
    348     char16_t const* work = str.string();
    349     size_t len = str.size();
    350 
    351     int lhsIndex = NONE_LHS;
    352     int index;
    353 
    354     size_t start = 0;
    355     for (size_t i=0; i<len; i++) {
    356         char16_t c = work[i];
    357         if (c != ';' && i == len-1) {
    358             c = ';';
    359             i++;
    360         }
    361         if (c == ';' || c == '=') {
    362             if (i != start) {
    363                 const char16_t* s = work+start;
    364                 const size_t slen = i-start;
    365 
    366                 String8 thestring(String16(s, slen));
    367 
    368                 switch (c)
    369                 {
    370                     case '=':
    371                         if (lhsIndex == NONE_LHS) {
    372                             lhsIndex = match_proc(LHSPROC, s, slen);
    373                             if (lhsIndex >= 0) {
    374                                 break;
    375                             }
    376                         }
    377                         FAIL_HERE();
    378                     case ';':
    379                     {
    380                         switch (LHSPROC[lhsIndex].value)
    381                         {
    382                             case FREQ:
    383                                 if (this->freq != 0) {
    384                                     FAIL_HERE();
    385                                 }
    386                                 index = match_proc(FREQPROC, s, slen);
    387                                 if (index >= 0) {
    388                                     this->freq = (freq_t)FREQPROC[index].value;
    389                                 }
    390                                 break;
    391                             case UNTIL:
    392                                 // XXX should check that this is a valid time
    393                                 until.setTo(String16(s, slen));
    394                                 break;
    395                             case COUNT:
    396                                 if (count != 0
    397                                      || NO_ERROR != parse_int(s, slen,
    398                                              &count, INT_MIN, INT_MAX, true)) {
    399                                     FAIL_HERE();
    400                                 }
    401                                 break;
    402                             case INTERVAL:
    403                                 if (interval != 0
    404                                      || NO_ERROR != parse_int(s, slen,
    405                                          &interval, INT_MIN, INT_MAX, false)) {
    406                                     FAIL_HERE();
    407                                 }
    408                                 break;
    409                             case BYSECOND:
    410                                 PARSE_INT_LIST_CHECKED(bysecond, 0, 59, true)
    411                                 break;
    412                             case BYMINUTE:
    413                                 PARSE_INT_LIST_CHECKED(byminute, 0, 59, true)
    414                                 break;
    415                             case BYHOUR:
    416                                 PARSE_INT_LIST_CHECKED(byhour, 0, 23, true)
    417                                 break;
    418                             case BYDAY:
    419                                 if (bydayCount != 0 || NO_ERROR !=
    420                                         parse_int_list(s, slen, &bydayCount,
    421                                               &byday, -53, 53, false,
    422                                               parse_byday)) {
    423                                     FAIL_HERE();
    424                                 }
    425                                 postprocess_byday(bydayCount, byday, &bydayNum);
    426                                 break;
    427                             case BYMONTHDAY:
    428                                 PARSE_INT_LIST_CHECKED(bymonthday, -31, 31,
    429                                                         false)
    430                                 break;
    431                             case BYYEARDAY:
    432                                 PARSE_INT_LIST_CHECKED(byyearday, -366, 366,
    433                                                         false)
    434                                 break;
    435                             case BYWEEKNO:
    436                                 PARSE_INT_LIST_CHECKED(byweekno, -53, 53,
    437                                                         false)
    438                                 break;
    439                             case BYMONTH:
    440                                 PARSE_INT_LIST_CHECKED(bymonth, 1, 12, false)
    441                                 break;
    442                             case BYSETPOS:
    443                                 PARSE_INT_LIST_CHECKED(bysetpos,
    444                                                         INT_MIN, INT_MAX, true)
    445                                 break;
    446                             case WKST:
    447                                 if (this->wkst != 0) {
    448                                     FAIL_HERE();
    449                                 }
    450                                 index = match_proc(WEEKDAYPROC, s, slen);
    451                                 if (index >= 0) {
    452                                     this->wkst = (int)WEEKDAYPROC[index].value;
    453                                 }
    454                                 break;
    455                             default:
    456                                 FAIL_HERE();
    457                         }
    458                         lhsIndex = NONE_LHS;
    459                         break;
    460                     }
    461                 }
    462 
    463                 start = i+1;
    464             }
    465         }
    466     }
    467 
    468     // enforce that there was a FREQ
    469     if (freq == 0) {
    470         FAIL_HERE();
    471     }
    472 
    473     // default wkst to MO if it wasn't specified
    474     if (wkst == 0) {
    475         wkst = MO;
    476     }
    477 
    478     return NO_ERROR;
    479 }
    480 
    481 
    482 }; // namespace android
    483 
    484 
    485