blob: b436b506f321dbbb5884d6116aea519227454962 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
10namespace android {
11
12#define FAIL_HERE() do { \
13 printf("Parsing failed at line %d\n", __LINE__); \
14 return UNKNOWN_ERROR; \
15 } while(0)
16
17EventRecurrence::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
45EventRecurrence::~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
59enum 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
77struct LHSProc
78{
79 const char16_t* text;
80 size_t textSize;
81 uint32_t value;
82};
83
84const char16_t FREQ_text[] = { 'F', 'R', 'E', 'Q' };
85const char16_t UNTIL_text[] = { 'U', 'N', 'T', 'I', 'L' };
86const char16_t COUNT_text[] = { 'C', 'O', 'U', 'N', 'T' };
87const char16_t INTERVAL_text[] = { 'I', 'N', 'T', 'E', 'R', 'V', 'A', 'L'};
88const char16_t BYSECOND_text[] = { 'B', 'Y', 'S', 'E', 'C', 'O', 'N', 'D' };
89const char16_t BYMINUTE_text[] = { 'B', 'Y', 'M', 'I', 'N', 'U', 'T', 'E' };
90const char16_t BYHOUR_text[] = { 'B', 'Y', 'H', 'O', 'U', 'R' };
91const char16_t BYDAY_text[] = { 'B', 'Y', 'D', 'A', 'Y' };
92const char16_t BYMONTHDAY_text[] = { 'B','Y','M','O','N','T','H','D','A','Y' };
93const char16_t BYYEARDAY_text[] = { 'B','Y','Y','E','A','R','D','A','Y' };
94const char16_t BYWEEKNO_text[] = { 'B', 'Y', 'W', 'E', 'E', 'K', 'N', 'O' };
95const char16_t BYMONTH_text[] = { 'B', 'Y', 'M', 'O', 'N', 'T', 'H' };
96const char16_t BYSETPOS_text[] = { 'B', 'Y', 'S', 'E', 'T', 'P', 'O', 'S' };
97const char16_t WKST_text[] = { 'W', 'K', 'S', 'T' };
98
99#define SIZ(x) (sizeof(x)/sizeof(x[0]))
100
101const 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
119const char16_t SECONDLY_text[] = { 'S','E','C','O','N','D','L','Y' };
120const char16_t MINUTELY_text[] = { 'M','I','N','U','T','E','L','Y' };
121const char16_t HOURLY_text[] = { 'H','O','U','R','L','Y' };
122const char16_t DAILY_text[] = { 'D','A','I','L','Y' };
123const char16_t WEEKLY_text[] = { 'W','E','E','K','L','Y' };
124const char16_t MONTHLY_text[] = { 'M','O','N','T','H','L','Y' };
125const char16_t YEARLY_text[] = { 'Y','E','A','R','L','Y' };
126
127typedef LHSProc FreqProc;
128
129const 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
140const char16_t SU_text[] = { 'S','U' };
141const char16_t MO_text[] = { 'M','O' };
142const char16_t TU_text[] = { 'T','U' };
143const char16_t WE_text[] = { 'W','E' };
144const char16_t TH_text[] = { 'T','H' };
145const char16_t FR_text[] = { 'F','R' };
146const char16_t SA_text[] = { 'S','A' };
147
148const 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
160inline static int
161match_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
177static status_t
178parse_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
227static status_t
228parse_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
277bail:
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.
284static status_t
285parse_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
326static void
327postprocess_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 }
345status_t
346EventRecurrence::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