root/trunk/LogicMail/src/org/logicprobe/LogicMail/util/StringParser.java

Revision 336, 31.5 kB (checked in by octorian, 6 weeks ago)
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1/*-
2 * Copyright (c) 2006, Derek Konigsberg
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the project nor the names of its
15 *    contributors may be used to endorse or promote products derived
16 *    from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29 * OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31package org.logicprobe.LogicMail.util;
32
33import net.rim.device.api.util.Arrays;
34import net.rim.device.api.util.DateTimeUtilities;
35import net.rim.device.api.util.NumberUtilities;
36
37import java.io.ByteArrayInputStream;
38import java.io.ByteArrayOutputStream;
39import java.io.IOException;
40import java.io.InputStream;
41import java.io.UnsupportedEncodingException;
42
43import java.util.Calendar;
44import java.util.Date;
45import java.util.Hashtable;
46import java.util.TimeZone;
47import java.util.Vector;
48
49
50/**
51 * This class provides a collection of string parsing
52 * utilities that are generally useful for handling
53 * E-Mail protocol server responses.
54 */
55public class StringParser {
56    private static final long ONE_SECOND = 1000;
57    private static final long ONE_MINUTE = ONE_SECOND * 60;
58    private static final long ONE_HOUR = ONE_MINUTE * 60;
59
60    private StringParser() {
61    }
62
63    /**
64     * Parse a string containing a date/time
65     * and return a usable Date object.
66     *
67     * @param rawDate Text containing the date
68     * @return Date object instance
69     */
70    public static Date parseDateString(String rawDate) {
71        int p = 0;
72        int q = 0;
73
74        int[] fields = new int[7];
75
76        // Clean up the date string for simple parsing
77        p = rawDate.indexOf(",");
78
79        if (p != -1) {
80            p++;
81
82            while (rawDate.charAt(p) == ' ')
83                p++;
84
85            rawDate = rawDate.substring(p);
86        }
87
88        if (rawDate.charAt(rawDate.length() - 1) == ')') {
89            rawDate = rawDate.substring(0, rawDate.lastIndexOf(' '));
90        }
91
92        // Set the time zone
93        Calendar cal;
94        String tz = rawDate.substring(rawDate.lastIndexOf(' ') + 1);
95        p = tz.indexOf(':');
96
97        if ((p == -1) || (tz.indexOf(':', p) == -1)) {
98            cal = Calendar.getInstance(createTimeZone(tz));
99        } else {
100            cal = Calendar.getInstance(TimeZone.getDefault());
101        }
102
103        // Day
104        p = 0;
105        q = rawDate.indexOf(" ", p + 1);
106        fields[2] = Integer.parseInt(rawDate.substring(p, q).trim());
107
108        // Month
109        p = q + 1;
110        q = rawDate.indexOf(" ", p + 1);
111
112        String monthStr = rawDate.substring(p, q);
113
114        if (monthStr.equals("Jan")) {
115            fields[1] = 0;
116        } else if (monthStr.equals("Feb")) {
117            fields[1] = 1;
118        } else if (monthStr.equals("Mar")) {
119            fields[1] = 2;
120        } else if (monthStr.equals("Apr")) {
121            fields[1] = 3;
122        } else if (monthStr.equals("May")) {
123            fields[1] = 4;
124        } else if (monthStr.equals("Jun")) {
125            fields[1] = 5;
126        } else if (monthStr.equals("Jul")) {
127            fields[1] = 6;
128        } else if (monthStr.equals("Aug")) {
129            fields[1] = 7;
130        } else if (monthStr.equals("Sep")) {
131            fields[1] = 8;
132        } else if (monthStr.equals("Oct")) {
133            fields[1] = 9;
134        } else if (monthStr.equals("Nov")) {
135            fields[1] = 10;
136        } else if (monthStr.equals("Dec")) {
137            fields[1] = 11;
138        }
139
140        // Year
141        p = q + 1;
142        q = rawDate.indexOf(" ", p + 1);
143        fields[0] = Integer.parseInt(rawDate.substring(p, q).trim());
144
145        if (fields[0] < 100) {
146            // Handle 2-digit years according to RFC 2822:
147            // 50-99 is assumed to be 19xx
148            // 00-49 is assumed to be 20xx
149            if (fields[0] >= 50) {
150                fields[0] += 1900;
151            } else {
152                fields[0] += 2000;
153            }
154        } else if ((fields[0] >= 100) && (fields[0] < 1000)) {
155            // Handle 3-digit years according to RFC 2822
156            fields[0] += 2000;
157        }
158
159        // Hour
160        p = q + 1;
161        q = rawDate.indexOf(":", p + 1);
162        fields[3] = Integer.parseInt(rawDate.substring(p, q).trim());
163
164        // Minute and Second
165        p = q + 1;
166        q = rawDate.indexOf(":", p + 1);
167
168        if (q == -1) {
169            // The second field is missing, so handle accordingly
170            q = rawDate.indexOf(" ", p + 1);
171            fields[4] = Integer.parseInt(rawDate.substring(p, q).trim());
172            fields[5] = 0;
173            fields[6] = 0;
174        } else {
175            // Otherwise parse minutes and seconds as normal
176            fields[4] = Integer.parseInt(rawDate.substring(p, q).trim());
177
178            p = q + 1;
179            q = rawDate.indexOf(" ", p + 1);
180
181            if (q == -1) {
182                q = rawDate.length();
183            }
184
185            fields[5] = Integer.parseInt(rawDate.substring(p, q).trim());
186            fields[6] = 0;
187        }
188
189        DateTimeUtilities.setCalendarFields(cal, fields);
190
191        return cal.getTime();
192    }
193
194    /**
195     * Create a TimeZone object from an input string.
196     *
197     * @param tz Input string
198     * @return TimeZone object
199     */
200    private static TimeZone createTimeZone(String tz) {
201        TimeZone result;
202
203        if (tz.startsWith("-") || tz.startsWith("+")) {
204            result = TimeZone.getTimeZone("GMT" + tz);
205        } else if (tz.indexOf('/') != -1) {
206            result = TimeZone.getTimeZone(tz);
207        } else if (tz.equals("MIT")) {
208            result = TimeZone.getTimeZone("GMT-11:00");
209        } else if (tz.equals("HST")) {
210            result = TimeZone.getTimeZone("GMT-10:00");
211        } else if (tz.equals("AST")) {
212            result = TimeZone.getTimeZone("GMT-9:00");
213        } else if (tz.equals("PST")) {
214            result = TimeZone.getTimeZone("GMT-8:00");
215        } else if (tz.equals("PDT")) {
216            result = TimeZone.getTimeZone("GMT-7:00");
217        } else if (tz.equals("PST8PDT")) {
218            result = TimeZone.getTimeZone("GMT-8:00");
219        } else if (tz.equals("MST")) {
220            result = TimeZone.getTimeZone("GMT-7:00");
221        } else if (tz.equals("MDT")) {
222            result = TimeZone.getTimeZone("GMT-6:00");
223        } else if (tz.equals("MST7MDT")) {
224            result = TimeZone.getTimeZone("GMT-7:00");
225        } else if (tz.equals("PNT")) {
226            result = TimeZone.getTimeZone("GMT-7:00");
227        } else if (tz.equals("CST")) {
228            result = TimeZone.getTimeZone("GMT-6:00");
229        } else if (tz.equals("CDT")) {
230            result = TimeZone.getTimeZone("GMT-5:00");
231        } else if (tz.equals("CST6CDT")) {
232            result = TimeZone.getTimeZone("GMT-6:00");
233        } else if (tz.equals("EST")) {
234            result = TimeZone.getTimeZone("GMT-5:00");
235        } else if (tz.equals("EDT")) {
236            result = TimeZone.getTimeZone("GMT-4:00");
237        } else if (tz.equals("EST5EDT")) {
238            result = TimeZone.getTimeZone("GMT-5:00");
239        } else if (tz.equals("IET")) {
240            result = TimeZone.getTimeZone("GMT-5:00");
241        } else if (tz.equals("PRT")) {
242            result = TimeZone.getTimeZone("GMT-4:00");
243        } else if (tz.equals("CNT")) {
244            result = TimeZone.getTimeZone("GMT-3:5");
245        } else if (tz.equals("AGT")) {
246            result = TimeZone.getTimeZone("GMT-3:00");
247        } else if (tz.equals("BET")) {
248            result = TimeZone.getTimeZone("GMT-3:00");
249        } else if (tz.equals("UCT")) {
250            result = TimeZone.getTimeZone("GMT");
251        } else if (tz.equals("UTC")) {
252            result = TimeZone.getTimeZone("GMT");
253        } else if (tz.equals("WET")) {
254            result = TimeZone.getTimeZone("GMT");
255        } else if (tz.equals("CET")) {
256            result = TimeZone.getTimeZone("GMT+1:00");
257        } else if (tz.equals("ECT")) {
258            result = TimeZone.getTimeZone("GMT+1:00");
259        } else if (tz.equals("MET")) {
260            result = TimeZone.getTimeZone("GMT+1:00");
261        } else if (tz.equals("ART")) {
262            result = TimeZone.getTimeZone("GMT+2:00");
263        } else if (tz.equals("CAT")) {
264            result = TimeZone.getTimeZone("GMT+2:00");
265        } else if (tz.equals("EET")) {
266            result = TimeZone.getTimeZone("GMT+2:00");
267        } else if (tz.equals("EAT")) {
268            result = TimeZone.getTimeZone("GMT+3:00");
269        } else if (tz.equals("NET")) {
270            result = TimeZone.getTimeZone("GMT+4:00");
271        } else if (tz.equals("PLT")) {
272            result = TimeZone.getTimeZone("GMT+5:00");
273        } else if (tz.equals("IST")) {
274            result = TimeZone.getTimeZone("GMT+5:30");
275        } else if (tz.equals("BST")) {
276            result = TimeZone.getTimeZone("GMT+6:00");
277        } else if (tz.equals("VST")) {
278            result = TimeZone.getTimeZone("GMT+7:00");
279        } else if (tz.equals("CTT")) {
280            result = TimeZone.getTimeZone("GMT+8:00");
281        } else if (tz.equals("PRC")) {
282            result = TimeZone.getTimeZone("GMT+8:00");
283        } else if (tz.equals("JST")) {
284            result = TimeZone.getTimeZone("GMT+9:00");
285        } else if (tz.equals("ROK")) {
286            result = TimeZone.getTimeZone("GMT+9:00");
287        } else if (tz.equals("ACT")) {
288            result = TimeZone.getTimeZone("GMT+9:30");
289        } else if (tz.equals("AET")) {
290            result = TimeZone.getTimeZone("GMT+10:00");
291        } else if (tz.equals("SST")) {
292            result = TimeZone.getTimeZone("GMT+11:00");
293        } else if (tz.equals("NST")) {
294            result = TimeZone.getTimeZone("GMT+12:00");
295        } else {
296            result = TimeZone.getTimeZone(tz);
297        }
298
299        return result;
300    }
301
302    /**
303     * Create a proper E-Mail date string from a Java.util.Date object
304     *
305     * @param time Date to convert
306     * @return String to add to an E-Mail
307     */
308    public static String createDateString(Date time) {
309        return createDateString(time, TimeZone.getDefault());
310    }
311
312    /**
313     * Create a proper E-Mail date string from a Java.util.Date object
314     *
315     * @param time Date to convert
316     * @param zone Time zone for the date
317     * @return String to add to an E-Mail
318     */
319    public static String createDateString(Date time, TimeZone zone) {
320        Calendar cal = Calendar.getInstance(zone);
321        cal.setTime(time);
322
323        StringBuffer buf = new StringBuffer();
324
325        switch (cal.get(Calendar.DAY_OF_WEEK)) {
326        case Calendar.SUNDAY:
327            buf.append("Sun, ");
328
329            break;
330
331        case Calendar.MONDAY:
332            buf.append("Mon, ");
333
334            break;
335
336        case Calendar.TUESDAY:
337            buf.append("Tue, ");
338
339            break;
340
341        case Calendar.WEDNESDAY:
342            buf.append("Wed, ");
343
344            break;
345
346        case Calendar.THURSDAY:
347            buf.append("Thu, ");
348
349            break;
350
351        case Calendar.FRIDAY:
352            buf.append("Fri, ");
353
354            break;
355
356        case Calendar.SATURDAY:
357            buf.append("Sat, ");
358
359            break;
360        }
361
362        buf.append(cal.get(Calendar.DAY_OF_MONTH));
363        buf.append(' ');
364
365        switch (cal.get(Calendar.MONTH)) {
366        case Calendar.JANUARY:
367            buf.append("Jan ");
368
369            break;
370
371        case Calendar.FEBRUARY:
372            buf.append("Feb ");
373
374            break;
375
376        case Calendar.MARCH:
377            buf.append("Mar ");
378
379            break;
380
381        case Calendar.APRIL:
382            buf.append("Apr ");
383
384            break;
385
386        case Calendar.MAY:
387            buf.append("May ");
388
389            break;
390
391        case Calendar.JUNE:
392            buf.append("Jun ");
393
394            break;
395
396        case Calendar.JULY:
397            buf.append("Jul ");
398
399            break;
400
401        case Calendar.AUGUST:
402            buf.append("Aug ");
403
404            break;
405
406        case Calendar.SEPTEMBER:
407            buf.append("Sep ");
408
409            break;
410
411        case Calendar.OCTOBER:
412            buf.append("Oct ");
413
414            break;
415
416        case Calendar.NOVEMBER:
417            buf.append("Nov ");
418
419            break;
420
421        case Calendar.DECEMBER:
422            buf.append("Dec ");
423
424            break;
425        }
426
427        buf.append(cal.get(Calendar.YEAR));
428        buf.append(' ');
429
430        buf.append(NumberUtilities.toString(cal.get(Calendar.HOUR_OF_DAY), 10, 2));
431        buf.append(':');
432        buf.append(NumberUtilities.toString(cal.get(Calendar.MINUTE), 10, 2));
433        buf.append(':');
434        buf.append(NumberUtilities.toString(cal.get(Calendar.SECOND), 10, 2));
435
436        buf.append(' ');
437
438        int tzOffset = cal.getTimeZone().getOffset(1, cal.get(Calendar.YEAR),
439                cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
440                cal.get(Calendar.DAY_OF_WEEK),
441                (int) (((long) cal.get(Calendar.HOUR_OF_DAY) * ONE_HOUR) +
442                (cal.get(Calendar.MINUTE) * ONE_MINUTE) +
443                (cal.get(Calendar.SECOND) * ONE_SECOND))) / 36000;
444
445        if (tzOffset < 0) {
446            buf.append(NumberUtilities.toString(tzOffset, 10, 5));
447        } else {
448            buf.append("+");
449            buf.append(NumberUtilities.toString(tzOffset, 10, 4));
450        }
451
452        return buf.toString();
453    }
454
455    /**
456     * Recursively parse a nested paren string.
457     * Parses through a string of the form "(A (B C (D) E F))" and
458     * returns a tree of Vector and String objects representing its
459     * contents.  This is useful for parsing the response to the
460     * IMAP "ENVELOPE" fetch command.
461     *
462     * @param rawText The raw text to be parsed
463     * @return A tree of Vector and String objects
464     */
465    public static Vector nestedParenStringLexer(String rawText) {
466        Vector parsedText = new Vector();
467
468        // Sanity checking
469        if (!((rawText.charAt(0) == '(') &&
470                (rawText.charAt(rawText.length() - 1) == ')'))) {
471            return null;
472        }
473
474        int p = 1;
475        int q = p;
476        int len;
477        String tmpText;
478        boolean inQuote = false;
479
480        while (q < rawText.length()) {
481            if (isQuoteChar(rawText, q, p)) {
482                if (!inQuote) {
483                    inQuote = true;
484                    p = q;
485                } else {
486                    parsedText.addElement(rawText.substring(p + 1, q));
487                    p = q + 1;
488                    inQuote = false;
489                }
490            } else if ((rawText.charAt(q) == '{') && !inQuote) {
491                p = rawText.indexOf('}', q);
492                len = Integer.parseInt(rawText.substring(q + 1, p));
493                p++;
494
495                while ((rawText.charAt(p) == '\r') ||
496                        (rawText.charAt(p) == '\n'))
497                    p++;
498
499                // Quick kludge for length miscalculation due to the way
500                // line breaks are currently handled
501                tmpText = rawText.substring(p, p + len);
502
503                if (tmpText.endsWith(" NIL")) {
504                    len -= 4;
505                } else if (tmpText.endsWith(" NI")) {
506                    len -= 3;
507                } else if (tmpText.endsWith(" N")) {
508                    len -= 2;
509                } else if (tmpText.endsWith(" ")) {
510                    len -= 1;
511                }
512
513                parsedText.addElement(rawText.substring(p, p + len));
514                q = p + len;
515            } else if (((rawText.charAt(q) == ' ') && !inQuote) ||
516                    (q == (rawText.length() - 1))) {
517                if ((q - p) > 0) {
518                    parsedText.addElement(rawText.substring(p, q).trim());
519                    p = q;
520                } else {
521                    p++;
522                }
523            } else if ((rawText.charAt(q) == '(') && !inQuote) {
524                p = q;
525
526                // paren matching
527                int level = 0;
528                boolean subInQuote = false;
529
530                for (int i = q + 1; i < rawText.length(); i++) {
531                    if ((rawText.charAt(i) == '{') && !subInQuote) {
532                        int matchIndex = rawText.indexOf('}', i);
533
534                        if ((matchIndex > (i + 1)) &&
535                                (rawText.charAt(matchIndex + 1) != ' ')) {
536                            int matchLen = Integer.parseInt(rawText.substring(i +
537                                        1, matchIndex));
538                            matchIndex++;
539
540                            while ((rawText.charAt(matchIndex) == '\r') ||
541                                    (rawText.charAt(matchIndex) == '\n')) {
542                                matchIndex++;
543                            }
544
545                            i = matchIndex + matchLen;
546                        }
547                    }
548
549                    if (