diff options
Diffstat (limited to 'libs/ui/EventRecurrence.cpp')
-rw-r--r-- | libs/ui/EventRecurrence.cpp | 484 |
1 files changed, 484 insertions, 0 deletions
diff --git a/libs/ui/EventRecurrence.cpp b/libs/ui/EventRecurrence.cpp new file mode 100644 index 0000000..b436b50 --- /dev/null +++ b/libs/ui/EventRecurrence.cpp @@ -0,0 +1,484 @@ +/* + * Copyright 2006 The Android Open Source Project + */ + +#include <pim/EventRecurrence.h> +#include <utils/String8.h> +#include <stdio.h> +#include <limits.h> + +namespace android { + +#define FAIL_HERE() do { \ + printf("Parsing failed at line %d\n", __LINE__); \ + return UNKNOWN_ERROR; \ + } while(0) + +EventRecurrence::EventRecurrence() + :freq((freq_t)0), + until(), + count(0), + interval(0), + bysecond(0), + bysecondCount(0), + byminute(0), + byminuteCount(0), + byhour(0), + byhourCount(0), + byday(0), + bydayNum(0), + bydayCount(0), + bymonthday(0), + bymonthdayCount(0), + byyearday(0), + byyeardayCount(0), + byweekno(0), + byweeknoCount(0), + bymonth(0), + bymonthCount(0), + bysetpos(0), + bysetposCount(0), + wkst(0) +{ +} + +EventRecurrence::~EventRecurrence() +{ + delete[] bysecond; + delete[] byminute; + delete[] byhour; + delete[] byday; + delete[] bydayNum; + delete[] byyearday; + delete[] bymonthday; + delete[] byweekno; + delete[] bymonth; + delete[] bysetpos; +} + +enum LHS { + NONE_LHS = 0, + FREQ, + UNTIL, + COUNT, + INTERVAL, + BYSECOND, + BYMINUTE, + BYHOUR, + BYDAY, + BYMONTHDAY, + BYYEARDAY, + BYWEEKNO, + BYMONTH, + BYSETPOS, + WKST +}; + +struct LHSProc +{ + const char16_t* text; + size_t textSize; + uint32_t value; +}; + +const char16_t FREQ_text[] = { 'F', 'R', 'E', 'Q' }; +const char16_t UNTIL_text[] = { 'U', 'N', 'T', 'I', 'L' }; +const char16_t COUNT_text[] = { 'C', 'O', 'U', 'N', 'T' }; +const char16_t INTERVAL_text[] = { 'I', 'N', 'T', 'E', 'R', 'V', 'A', 'L'}; +const char16_t BYSECOND_text[] = { 'B', 'Y', 'S', 'E', 'C', 'O', 'N', 'D' }; +const char16_t BYMINUTE_text[] = { 'B', 'Y', 'M', 'I', 'N', 'U', 'T', 'E' }; +const char16_t BYHOUR_text[] = { 'B', 'Y', 'H', 'O', 'U', 'R' }; +const char16_t BYDAY_text[] = { 'B', 'Y', 'D', 'A', 'Y' }; +const char16_t BYMONTHDAY_text[] = { 'B','Y','M','O','N','T','H','D','A','Y' }; +const char16_t BYYEARDAY_text[] = { 'B','Y','Y','E','A','R','D','A','Y' }; +const char16_t BYWEEKNO_text[] = { 'B', 'Y', 'W', 'E', 'E', 'K', 'N', 'O' }; +const char16_t BYMONTH_text[] = { 'B', 'Y', 'M', 'O', 'N', 'T', 'H' }; +const char16_t BYSETPOS_text[] = { 'B', 'Y', 'S', 'E', 'T', 'P', 'O', 'S' }; +const char16_t WKST_text[] = { 'W', 'K', 'S', 'T' }; + +#define SIZ(x) (sizeof(x)/sizeof(x[0])) + +const LHSProc LHSPROC[] = { + { FREQ_text, SIZ(FREQ_text), FREQ }, + { UNTIL_text, SIZ(UNTIL_text), UNTIL }, + { COUNT_text, SIZ(COUNT_text), COUNT }, + { INTERVAL_text, SIZ(INTERVAL_text), INTERVAL }, + { BYSECOND_text, SIZ(BYSECOND_text), BYSECOND }, + { BYMINUTE_text, SIZ(BYMINUTE_text), BYMINUTE }, + { BYHOUR_text, SIZ(BYHOUR_text), BYHOUR }, + { BYDAY_text, SIZ(BYDAY_text), BYDAY }, + { BYMONTHDAY_text, SIZ(BYMONTHDAY_text), BYMONTHDAY }, + { BYYEARDAY_text, SIZ(BYYEARDAY_text), BYYEARDAY }, + { BYWEEKNO_text, SIZ(BYWEEKNO_text), BYWEEKNO }, + { BYMONTH_text, SIZ(BYMONTH_text), BYMONTH }, + { BYSETPOS_text, SIZ(BYSETPOS_text), BYSETPOS }, + { WKST_text, SIZ(WKST_text), WKST }, + { NULL, 0, NONE_LHS }, +}; + +const char16_t SECONDLY_text[] = { 'S','E','C','O','N','D','L','Y' }; +const char16_t MINUTELY_text[] = { 'M','I','N','U','T','E','L','Y' }; +const char16_t HOURLY_text[] = { 'H','O','U','R','L','Y' }; +const char16_t DAILY_text[] = { 'D','A','I','L','Y' }; +const char16_t WEEKLY_text[] = { 'W','E','E','K','L','Y' }; +const char16_t MONTHLY_text[] = { 'M','O','N','T','H','L','Y' }; +const char16_t YEARLY_text[] = { 'Y','E','A','R','L','Y' }; + +typedef LHSProc FreqProc; + +const FreqProc FREQPROC[] = { + { SECONDLY_text, SIZ(SECONDLY_text), EventRecurrence::SECONDLY }, + { MINUTELY_text, SIZ(MINUTELY_text), EventRecurrence::MINUTELY }, + { HOURLY_text, SIZ(HOURLY_text), EventRecurrence::HOURLY }, + { DAILY_text, SIZ(DAILY_text), EventRecurrence::DAILY }, + { WEEKLY_text, SIZ(WEEKLY_text), EventRecurrence::WEEKLY }, + { MONTHLY_text, SIZ(MONTHLY_text), EventRecurrence::MONTHLY }, + { YEARLY_text, SIZ(YEARLY_text), EventRecurrence::YEARLY }, + { NULL, 0, NONE_LHS }, +}; + +const char16_t SU_text[] = { 'S','U' }; +const char16_t MO_text[] = { 'M','O' }; +const char16_t TU_text[] = { 'T','U' }; +const char16_t WE_text[] = { 'W','E' }; +const char16_t TH_text[] = { 'T','H' }; +const char16_t FR_text[] = { 'F','R' }; +const char16_t SA_text[] = { 'S','A' }; + +const FreqProc WEEKDAYPROC[] = { + { SU_text, SIZ(SU_text), EventRecurrence::SU }, + { MO_text, SIZ(MO_text), EventRecurrence::MO }, + { TU_text, SIZ(TU_text), EventRecurrence::TU }, + { WE_text, SIZ(WE_text), EventRecurrence::WE }, + { TH_text, SIZ(TH_text), EventRecurrence::TH }, + { FR_text, SIZ(FR_text), EventRecurrence::FR }, + { SA_text, SIZ(SA_text), EventRecurrence::SA }, + { NULL, 0, NONE_LHS }, +}; + +// returns the index into LHSPROC for the match or -1 if not found +inline static int +match_proc(const LHSProc* p, const char16_t* str, size_t len) +{ + int i = 0; + while (p->text != NULL) { + if (p->textSize == len) { + if (0 == memcmp(p->text, str, len*sizeof(char16_t))) { + return i; + } + } + p++; + i++; + } + return -1; +} + +// rangeMin and rangeMax are inclusive +static status_t +parse_int(const char16_t* str, size_t len, int* out, + int rangeMin, int rangeMax, bool zeroOK) +{ + char16_t c; + size_t i=0; + + if (len == 0) { + FAIL_HERE(); + } + bool negative = false; + c = str[0]; + if (c == '-' ) { + negative = true; + i++; + } + else if (c == '+') { + i++; + } + int n = 0; + for (; i<len; i++) { + c = str[i]; + if (c < '0' || c > '9') { + FAIL_HERE(); + } + int prev = n; + n *= 10; + // the spec doesn't address how big these numbers can be, + // so we're not going to worry about not being able to represent + // INT_MIN, and if we're going to wrap, we'll just clamp to + // INT_MAX instead + if (n < prev) { + n = INT_MAX; + } else { + n += c - '0'; + } + } + if (negative) { + n = -n; + } + if (n < rangeMin || n > rangeMax) { + FAIL_HERE(); + } + if (!zeroOK && n == 0) { + FAIL_HERE(); + } + *out = n; + return NO_ERROR; +} + +static status_t +parse_int_list(const char16_t* str, size_t len, int* countOut, int** listOut, + int rangeMin, int rangeMax, bool zeroOK, + status_t (*func)(const char16_t*,size_t,int*,int,int,bool)=parse_int) +{ + status_t err; + + if (len == 0) { + *countOut = 0; + *listOut = NULL; + return NO_ERROR; + } + + // make one pass through looking for commas so we know how big to make our + // out array. + int count = 1; + for (size_t i=0; i<len; i++) { + if (str[i] == ',') { + count++; + } + } + + int* list = new int[count]; + const char16_t* p = str; + int commaIndex = 0; + size_t i; + + for (i=0; i<len; i++) { + if (str[i] == ',') { + err = func(p, (str+i-p), list+commaIndex, rangeMin, + rangeMax, zeroOK); + if (err != NO_ERROR) { + goto bail; + } + commaIndex++; + p = str+i+1; + } + } + + err = func(p, (str+i-p), list+commaIndex, rangeMin, rangeMax, zeroOK); + if (err != NO_ERROR) { + goto bail; + } + commaIndex++; + + *countOut = count; + *listOut = list; + + return NO_ERROR; + +bail: + delete[] list; + FAIL_HERE(); +} + +// the numbers here are small, so we pack them both into one value, and then +// split it out later. it lets us reuse all the comma separated list code. +static status_t +parse_byday(const char16_t* s, size_t len, int* out, + int rangeMin, int rangeMax, bool zeroOK) +{ + status_t err; + int n = 0; + const char16_t* p = s; + size_t plen = len; + + if (len > 0) { + char16_t c = s[0]; + if (c == '-' || c == '+' || (c >= '0' && c <= '9')) { + if (len > 1) { + size_t nlen = 0; + c = s[nlen]; + while (nlen < len + && (c == '-' || c == '+' || (c >= '0' && c <= '9'))) { + c = s[nlen]; + nlen++; + } + if (nlen > 0) { + nlen--; + err = parse_int(s, nlen, &n, rangeMin, rangeMax, zeroOK); + if (err != NO_ERROR) { + FAIL_HERE(); + } + p += nlen; + plen -= nlen; + } + } + } + + int index = match_proc(WEEKDAYPROC, p, plen); + if (index >= 0) { + *out = (0xffff0000 & WEEKDAYPROC[index].value) + | (0x0000ffff & n); + return NO_ERROR; + } + } + return UNKNOWN_ERROR; +} + +static void +postprocess_byday(int count, int* byday, int** bydayNum) +{ + int* bdn = new int[count]; + *bydayNum = bdn; + for (int i=0; i<count; i++) { + uint32_t v = byday[i]; + int16_t num = v & 0x0000ffff; + byday[i] = v & 0xffff0000; + // will sign extend: + bdn[i] = num; + } +} + +#define PARSE_INT_LIST_CHECKED(name, rangeMin, rangeMax, zeroOK) \ + if (name##Count != 0 || NO_ERROR != parse_int_list(s, slen, \ + &name##Count, &name, rangeMin, rangeMax, zeroOK)) { \ + FAIL_HERE(); \ + } +status_t +EventRecurrence::parse(const String16& str) +{ + char16_t const* work = str.string(); + size_t len = str.size(); + + int lhsIndex = NONE_LHS; + int index; + + size_t start = 0; + for (size_t i=0; i<len; i++) { + char16_t c = work[i]; + if (c != ';' && i == len-1) { + c = ';'; + i++; + } + if (c == ';' || c == '=') { + if (i != start) { + const char16_t* s = work+start; + const size_t slen = i-start; + + String8 thestring(String16(s, slen)); + + switch (c) + { + case '=': + if (lhsIndex == NONE_LHS) { + lhsIndex = match_proc(LHSPROC, s, slen); + if (lhsIndex >= 0) { + break; + } + } + FAIL_HERE(); + case ';': + { + switch (LHSPROC[lhsIndex].value) + { + case FREQ: + if (this->freq != 0) { + FAIL_HERE(); + } + index = match_proc(FREQPROC, s, slen); + if (index >= 0) { + this->freq = (freq_t)FREQPROC[index].value; + } + break; + case UNTIL: + // XXX should check that this is a valid time + until.setTo(String16(s, slen)); + break; + case COUNT: + if (count != 0 + || NO_ERROR != parse_int(s, slen, + &count, INT_MIN, INT_MAX, true)) { + FAIL_HERE(); + } + break; + case INTERVAL: + if (interval != 0 + || NO_ERROR != parse_int(s, slen, + &interval, INT_MIN, INT_MAX, false)) { + FAIL_HERE(); + } + break; + case BYSECOND: + PARSE_INT_LIST_CHECKED(bysecond, 0, 59, true) + break; + case BYMINUTE: + PARSE_INT_LIST_CHECKED(byminute, 0, 59, true) + break; + case BYHOUR: + PARSE_INT_LIST_CHECKED(byhour, 0, 23, true) + break; + case BYDAY: + if (bydayCount != 0 || NO_ERROR != + parse_int_list(s, slen, &bydayCount, + &byday, -53, 53, false, + parse_byday)) { + FAIL_HERE(); + } + postprocess_byday(bydayCount, byday, &bydayNum); + break; + case BYMONTHDAY: + PARSE_INT_LIST_CHECKED(bymonthday, -31, 31, + false) + break; + case BYYEARDAY: + PARSE_INT_LIST_CHECKED(byyearday, -366, 366, + false) + break; + case BYWEEKNO: + PARSE_INT_LIST_CHECKED(byweekno, -53, 53, + false) + break; + case BYMONTH: + PARSE_INT_LIST_CHECKED(bymonth, 1, 12, false) + break; + case BYSETPOS: + PARSE_INT_LIST_CHECKED(bysetpos, + INT_MIN, INT_MAX, true) + break; + case WKST: + if (this->wkst != 0) { + FAIL_HERE(); + } + index = match_proc(WEEKDAYPROC, s, slen); + if (index >= 0) { + this->wkst = (int)WEEKDAYPROC[index].value; + } + break; + default: + FAIL_HERE(); + } + lhsIndex = NONE_LHS; + break; + } + } + + start = i+1; + } + } + } + + // enforce that there was a FREQ + if (freq == 0) { + FAIL_HERE(); + } + + // default wkst to MO if it wasn't specified + if (wkst == 0) { + wkst = MO; + } + + return NO_ERROR; +} + + +}; // namespace android + + |