The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | |