~ubuntu-branches/ubuntu/saucy/commons-configuration/saucy

« back to all changes in this revision

Viewing changes to src/java/org/apache/commons/configuration/plist/PropertyListConfiguration.java

  • Committer: Package Import Robot
  • Author(s): Emmanuel Bourg
  • Date: 2013-07-01 16:29:44 UTC
  • mfrom: (1.1.4)
  • Revision ID: package-import@ubuntu.com-20130701162944-98waq5gogha5gpn1
Tags: 1.9-1
* New upstream release
* debian/control:
  - Updated Standards-Version to 3.9.4 (no changes)
  - Use canonical URLs for the Vcs-* fields
  - Added new build dependencies (libjavacc-maven-plugin-java, junit4)
  - Upgraded the dependency on the Servlet API (2.5 -> 3.0)
  - Removed the dependency on the Activation Framework (glassfish-activation)
  - Replaced the dependency on glassfish-mail with libgnumail-java
  - Removed the unused dependencies:
    liblog4j1.2-java-doc, libmaven-assembly-plugin-java
  - Replaced the dependency on libcommons-jexl-java by libcommons-jexl2-java
* debian/watch: Changed to point the official Apache distribution server
* Removed the obsolete file debian/ant.properties
* Installed the upstream changelog in the binary packages
* Added the report plugins to maven.ignoreRules
* Added the classpath attribute to the jar manifest

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 
 * contributor license agreements.  See the NOTICE file distributed with
4
 
 * this work for additional information regarding copyright ownership.
5
 
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 
 * (the "License"); you may not use this file except in compliance with
7
 
 * the License.  You may obtain a copy of the License at
8
 
 *
9
 
 *     http://www.apache.org/licenses/LICENSE-2.0
10
 
 *
11
 
 * Unless required by applicable law or agreed to in writing, software
12
 
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 
 * See the License for the specific language governing permissions and
15
 
 * limitations under the License.
16
 
 */
17
 
 
18
 
package org.apache.commons.configuration.plist;
19
 
 
20
 
import java.io.File;
21
 
import java.io.PrintWriter;
22
 
import java.io.Reader;
23
 
import java.io.Writer;
24
 
import java.net.URL;
25
 
import java.util.ArrayList;
26
 
import java.util.Calendar;
27
 
import java.util.Date;
28
 
import java.util.Iterator;
29
 
import java.util.List;
30
 
import java.util.Map;
31
 
import java.util.TimeZone;
32
 
 
33
 
import org.apache.commons.codec.binary.Hex;
34
 
import org.apache.commons.configuration.AbstractHierarchicalFileConfiguration;
35
 
import org.apache.commons.configuration.Configuration;
36
 
import org.apache.commons.configuration.ConfigurationException;
37
 
import org.apache.commons.configuration.HierarchicalConfiguration;
38
 
import org.apache.commons.configuration.MapConfiguration;
39
 
import org.apache.commons.lang.StringUtils;
40
 
 
41
 
/**
42
 
 * NeXT / OpenStep style configuration. This configuration can read and write
43
 
 * ASCII plist files. It supports the GNUStep extension to specify date objects.
44
 
 * <p>
45
 
 * References:
46
 
 * <ul>
47
 
 *   <li><a
48
 
 * href="http://developer.apple.com/documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html">
49
 
 * Apple Documentation - Old-Style ASCII Property Lists</a></li>
50
 
 *   <li><a
51
 
 * href="http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html">
52
 
 * GNUStep Documentation</a></li>
53
 
 * </ul>
54
 
 *
55
 
 * <p>Example:</p>
56
 
 * <pre>
57
 
 * {
58
 
 *     foo = "bar";
59
 
 *
60
 
 *     array = ( value1, value2, value3 );
61
 
 *
62
 
 *     data = &lt;4f3e0145ab>;
63
 
 *
64
 
 *     date = &lt;*D2007-05-05 20:05:00 +0100>;
65
 
 *
66
 
 *     nested =
67
 
 *     {
68
 
 *         key1 = value1;
69
 
 *         key2 = value;
70
 
 *         nested =
71
 
 *         {
72
 
 *             foo = bar
73
 
 *         }
74
 
 *     }
75
 
 * }
76
 
 * </pre>
77
 
 *
78
 
 * @since 1.2
79
 
 *
80
 
 * @author Emmanuel Bourg
81
 
 * @version $Revision: 797282 $, $Date: 2009-07-24 02:39:29 +0200 (Fr, 24. Jul 2009) $
82
 
 */
83
 
public class PropertyListConfiguration extends AbstractHierarchicalFileConfiguration
84
 
{
85
 
    /** Constant for the separator parser for the date part. */
86
 
    private static final DateComponentParser DATE_SEPARATOR_PARSER = new DateSeparatorParser(
87
 
            "-");
88
 
 
89
 
    /** Constant for the separator parser for the time part. */
90
 
    private static final DateComponentParser TIME_SEPARATOR_PARSER = new DateSeparatorParser(
91
 
            ":");
92
 
 
93
 
    /** Constant for the separator parser for blanks between the parts. */
94
 
    private static final DateComponentParser BLANK_SEPARATOR_PARSER = new DateSeparatorParser(
95
 
            " ");
96
 
 
97
 
    /** An array with the component parsers for dealing with dates. */
98
 
    private static final DateComponentParser[] DATE_PARSERS =
99
 
    {new DateSeparatorParser("<*D"), new DateFieldParser(Calendar.YEAR, 4),
100
 
            DATE_SEPARATOR_PARSER, new DateFieldParser(Calendar.MONTH, 2, 1),
101
 
            DATE_SEPARATOR_PARSER, new DateFieldParser(Calendar.DATE, 2),
102
 
            BLANK_SEPARATOR_PARSER,
103
 
            new DateFieldParser(Calendar.HOUR_OF_DAY, 2),
104
 
            TIME_SEPARATOR_PARSER, new DateFieldParser(Calendar.MINUTE, 2),
105
 
            TIME_SEPARATOR_PARSER, new DateFieldParser(Calendar.SECOND, 2),
106
 
            BLANK_SEPARATOR_PARSER, new DateTimeZoneParser(),
107
 
            new DateSeparatorParser(">")};
108
 
 
109
 
    /** Constant for the ID prefix for GMT time zones. */
110
 
    private static final String TIME_ZONE_PREFIX = "GMT";
111
 
 
112
 
    /** The serial version UID. */
113
 
    private static final long serialVersionUID = 3227248503779092127L;
114
 
 
115
 
    /** Constant for the milliseconds of a minute.*/
116
 
    private static final int MILLIS_PER_MINUTE = 1000 * 60;
117
 
 
118
 
    /** Constant for the minutes per hour.*/
119
 
    private static final int MINUTES_PER_HOUR = 60;
120
 
 
121
 
    /** Size of the indentation for the generated file. */
122
 
    private static final int INDENT_SIZE = 4;
123
 
 
124
 
    /** Constant for the length of a time zone.*/
125
 
    private static final int TIME_ZONE_LENGTH = 5;
126
 
 
127
 
    /** Constant for the padding character in the date format.*/
128
 
    private static final char PAD_CHAR = '0';
129
 
 
130
 
    /**
131
 
     * Creates an empty PropertyListConfiguration object which can be
132
 
     * used to synthesize a new plist file by adding values and
133
 
     * then saving().
134
 
     */
135
 
    public PropertyListConfiguration()
136
 
    {
137
 
    }
138
 
 
139
 
    /**
140
 
     * Creates a new instance of <code>PropertyListConfiguration</code> and
141
 
     * copies the content of the specified configuration into this object.
142
 
     *
143
 
     * @param c the configuration to copy
144
 
     * @since 1.4
145
 
     */
146
 
    public PropertyListConfiguration(HierarchicalConfiguration c)
147
 
    {
148
 
        super(c);
149
 
    }
150
 
 
151
 
    /**
152
 
     * Creates and loads the property list from the specified file.
153
 
     *
154
 
     * @param fileName The name of the plist file to load.
155
 
     * @throws ConfigurationException Error while loading the plist file
156
 
     */
157
 
    public PropertyListConfiguration(String fileName) throws ConfigurationException
158
 
    {
159
 
        super(fileName);
160
 
    }
161
 
 
162
 
    /**
163
 
     * Creates and loads the property list from the specified file.
164
 
     *
165
 
     * @param file The plist file to load.
166
 
     * @throws ConfigurationException Error while loading the plist file
167
 
     */
168
 
    public PropertyListConfiguration(File file) throws ConfigurationException
169
 
    {
170
 
        super(file);
171
 
    }
172
 
 
173
 
    /**
174
 
     * Creates and loads the property list from the specified URL.
175
 
     *
176
 
     * @param url The location of the plist file to load.
177
 
     * @throws ConfigurationException Error while loading the plist file
178
 
     */
179
 
    public PropertyListConfiguration(URL url) throws ConfigurationException
180
 
    {
181
 
        super(url);
182
 
    }
183
 
 
184
 
    public void setProperty(String key, Object value)
185
 
    {
186
 
        // special case for byte arrays, they must be stored as is in the configuration
187
 
        if (value instanceof byte[])
188
 
        {
189
 
            fireEvent(EVENT_SET_PROPERTY, key, value, true);
190
 
            setDetailEvents(false);
191
 
            try
192
 
            {
193
 
                clearProperty(key);
194
 
                addPropertyDirect(key, value);
195
 
            }
196
 
            finally
197
 
            {
198
 
                setDetailEvents(true);
199
 
            }
200
 
            fireEvent(EVENT_SET_PROPERTY, key, value, false);
201
 
        }
202
 
        else
203
 
        {
204
 
            super.setProperty(key, value);
205
 
        }
206
 
    }
207
 
 
208
 
    public void addProperty(String key, Object value)
209
 
    {
210
 
        if (value instanceof byte[])
211
 
        {
212
 
            fireEvent(EVENT_ADD_PROPERTY, key, value, true);
213
 
            addPropertyDirect(key, value);
214
 
            fireEvent(EVENT_ADD_PROPERTY, key, value, false);
215
 
        }
216
 
        else
217
 
        {
218
 
            super.addProperty(key, value);
219
 
        }
220
 
    }
221
 
 
222
 
    public void load(Reader in) throws ConfigurationException
223
 
    {
224
 
        PropertyListParser parser = new PropertyListParser(in);
225
 
        try
226
 
        {
227
 
            HierarchicalConfiguration config = parser.parse();
228
 
            setRoot(config.getRoot());
229
 
        }
230
 
        catch (ParseException e)
231
 
        {
232
 
            throw new ConfigurationException(e);
233
 
        }
234
 
    }
235
 
 
236
 
    public void save(Writer out) throws ConfigurationException
237
 
    {
238
 
        PrintWriter writer = new PrintWriter(out);
239
 
        printNode(writer, 0, getRoot());
240
 
        writer.flush();
241
 
    }
242
 
 
243
 
    /**
244
 
     * Append a node to the writer, indented according to a specific level.
245
 
     */
246
 
    private void printNode(PrintWriter out, int indentLevel, Node node)
247
 
    {
248
 
        String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
249
 
 
250
 
        if (node.getName() != null)
251
 
        {
252
 
            out.print(padding + quoteString(node.getName()) + " = ");
253
 
        }
254
 
 
255
 
        List children = new ArrayList(node.getChildren());
256
 
        if (!children.isEmpty())
257
 
        {
258
 
            // skip a line, except for the root dictionary
259
 
            if (indentLevel > 0)
260
 
            {
261
 
                out.println();
262
 
            }
263
 
 
264
 
            out.println(padding + "{");
265
 
 
266
 
            // display the children
267
 
            Iterator it = children.iterator();
268
 
            while (it.hasNext())
269
 
            {
270
 
                Node child = (Node) it.next();
271
 
 
272
 
                printNode(out, indentLevel + 1, child);
273
 
 
274
 
                // add a semi colon for elements that are not dictionaries
275
 
                Object value = child.getValue();
276
 
                if (value != null && !(value instanceof Map) && !(value instanceof Configuration))
277
 
                {
278
 
                    out.println(";");
279
 
                }
280
 
 
281
 
                // skip a line after arrays and dictionaries
282
 
                if (it.hasNext() && (value == null || value instanceof List))
283
 
                {
284
 
                    out.println();
285
 
                }
286
 
            }
287
 
 
288
 
            out.print(padding + "}");
289
 
 
290
 
            // line feed if the dictionary is not in an array
291
 
            if (node.getParent() != null)
292
 
            {
293
 
                out.println();
294
 
            }
295
 
        }
296
 
        else if (node.getValue() == null)
297
 
        {
298
 
            out.println();
299
 
            out.print(padding + "{ };");
300
 
 
301
 
            // line feed if the dictionary is not in an array
302
 
            if (node.getParentNode() != null)
303
 
            {
304
 
                out.println();
305
 
            }
306
 
        }
307
 
        else
308
 
        {
309
 
            // display the leaf value
310
 
            Object value = node.getValue();
311
 
            printValue(out, indentLevel, value);
312
 
        }
313
 
    }
314
 
 
315
 
    /**
316
 
     * Append a value to the writer, indented according to a specific level.
317
 
     */
318
 
    private void printValue(PrintWriter out, int indentLevel, Object value)
319
 
    {
320
 
        String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
321
 
 
322
 
        if (value instanceof List)
323
 
        {
324
 
            out.print("( ");
325
 
            Iterator it = ((List) value).iterator();
326
 
            while (it.hasNext())
327
 
            {
328
 
                printValue(out, indentLevel + 1, it.next());
329
 
                if (it.hasNext())
330
 
                {
331
 
                    out.print(", ");
332
 
                }
333
 
            }
334
 
            out.print(" )");
335
 
        }
336
 
        else if (value instanceof HierarchicalConfiguration)
337
 
        {
338
 
            printNode(out, indentLevel, ((HierarchicalConfiguration) value).getRoot());
339
 
        }
340
 
        else if (value instanceof Configuration)
341
 
        {
342
 
            // display a flat Configuration as a dictionary
343
 
            out.println();
344
 
            out.println(padding + "{");
345
 
 
346
 
            Configuration config = (Configuration) value;
347
 
            Iterator it = config.getKeys();
348
 
            while (it.hasNext())
349
 
            {
350
 
                String key = (String) it.next();
351
 
                Node node = new Node(key);
352
 
                node.setValue(config.getProperty(key));
353
 
 
354
 
                printNode(out, indentLevel + 1, node);
355
 
                out.println(";");
356
 
            }
357
 
            out.println(padding + "}");
358
 
        }
359
 
        else if (value instanceof Map)
360
 
        {
361
 
            // display a Map as a dictionary
362
 
            Map map = (Map) value;
363
 
            printValue(out, indentLevel, new MapConfiguration(map));
364
 
        }
365
 
        else if (value instanceof byte[])
366
 
        {
367
 
            out.print("<" + new String(Hex.encodeHex((byte[]) value)) + ">");
368
 
        }
369
 
        else if (value instanceof Date)
370
 
        {
371
 
            out.print(formatDate((Date) value));
372
 
        }
373
 
        else if (value != null)
374
 
        {
375
 
            out.print(quoteString(String.valueOf(value)));
376
 
        }
377
 
    }
378
 
 
379
 
    /**
380
 
     * Quote the specified string if necessary, that's if the string contains:
381
 
     * <ul>
382
 
     *   <li>a space character (' ', '\t', '\r', '\n')</li>
383
 
     *   <li>a quote '"'</li>
384
 
     *   <li>special characters in plist files ('(', ')', '{', '}', '=', ';', ',')</li>
385
 
     * </ul>
386
 
     * Quotes within the string are escaped.
387
 
     *
388
 
     * <p>Examples:</p>
389
 
     * <ul>
390
 
     *   <li>abcd -> abcd</li>
391
 
     *   <li>ab cd -> "ab cd"</li>
392
 
     *   <li>foo"bar -> "foo\"bar"</li>
393
 
     *   <li>foo;bar -> "foo;bar"</li>
394
 
     * </ul>
395
 
     */
396
 
    String quoteString(String s)
397
 
    {
398
 
        if (s == null)
399
 
        {
400
 
            return null;
401
 
        }
402
 
 
403
 
        if (s.indexOf(' ') != -1
404
 
                || s.indexOf('\t') != -1
405
 
                || s.indexOf('\r') != -1
406
 
                || s.indexOf('\n') != -1
407
 
                || s.indexOf('"') != -1
408
 
                || s.indexOf('(') != -1
409
 
                || s.indexOf(')') != -1
410
 
                || s.indexOf('{') != -1
411
 
                || s.indexOf('}') != -1
412
 
                || s.indexOf('=') != -1
413
 
                || s.indexOf(',') != -1
414
 
                || s.indexOf(';') != -1)
415
 
        {
416
 
            s = StringUtils.replace(s, "\"", "\\\"");
417
 
            s = "\"" + s + "\"";
418
 
        }
419
 
 
420
 
        return s;
421
 
    }
422
 
 
423
 
    /**
424
 
     * Parses a date in a format like
425
 
     * <code>&lt;*D2002-03-22 11:30:00 +0100&gt;</code>.
426
 
     *
427
 
     * @param s the string with the date to be parsed
428
 
     * @return the parsed date
429
 
     * @throws ParseException if an error occurred while parsing the string
430
 
     */
431
 
    static Date parseDate(String s) throws ParseException
432
 
    {
433
 
        Calendar cal = Calendar.getInstance();
434
 
        cal.clear();
435
 
        int index = 0;
436
 
 
437
 
        for (int i = 0; i < DATE_PARSERS.length; i++)
438
 
        {
439
 
            index += DATE_PARSERS[i].parseComponent(s, index, cal);
440
 
        }
441
 
 
442
 
        return cal.getTime();
443
 
    }
444
 
 
445
 
    /**
446
 
     * Returns a string representation for the date specified by the given
447
 
     * calendar.
448
 
     *
449
 
     * @param cal the calendar with the initialized date
450
 
     * @return a string for this date
451
 
     */
452
 
    static String formatDate(Calendar cal)
453
 
    {
454
 
        StringBuffer buf = new StringBuffer();
455
 
 
456
 
        for (int i = 0; i < DATE_PARSERS.length; i++)
457
 
        {
458
 
            DATE_PARSERS[i].formatComponent(buf, cal);
459
 
        }
460
 
 
461
 
        return buf.toString();
462
 
    }
463
 
 
464
 
    /**
465
 
     * Returns a string representation for the specified date.
466
 
     *
467
 
     * @param date the date
468
 
     * @return a string for this date
469
 
     */
470
 
    static String formatDate(Date date)
471
 
    {
472
 
        Calendar cal = Calendar.getInstance();
473
 
        cal.setTime(date);
474
 
        return formatDate(cal);
475
 
    }
476
 
 
477
 
    /**
478
 
     * A helper class for parsing and formatting date literals. Usually we would
479
 
     * use <code>SimpleDateFormat</code> for this purpose, but in Java 1.3 the
480
 
     * functionality of this class is limited. So we have a hierarchy of parser
481
 
     * classes instead that deal with the different components of a date
482
 
     * literal.
483
 
     */
484
 
    private abstract static class DateComponentParser
485
 
    {
486
 
        /**
487
 
         * Parses a component from the given input string.
488
 
         *
489
 
         * @param s the string to be parsed
490
 
         * @param index the current parsing position
491
 
         * @param cal the calendar where to store the result
492
 
         * @return the length of the processed component
493
 
         * @throws ParseException if the component cannot be extracted
494
 
         */
495
 
        public abstract int parseComponent(String s, int index, Calendar cal)
496
 
                throws ParseException;
497
 
 
498
 
        /**
499
 
         * Formats a date component. This method is used for converting a date
500
 
         * in its internal representation into a string literal.
501
 
         *
502
 
         * @param buf the target buffer
503
 
         * @param cal the calendar with the current date
504
 
         */
505
 
        public abstract void formatComponent(StringBuffer buf, Calendar cal);
506
 
 
507
 
        /**
508
 
         * Checks whether the given string has at least <code>length</code>
509
 
         * characters starting from the given parsing position. If this is not
510
 
         * the case, an exception will be thrown.
511
 
         *
512
 
         * @param s the string to be tested
513
 
         * @param index the current index
514
 
         * @param length the minimum length after the index
515
 
         * @throws ParseException if the string is too short
516
 
         */
517
 
        protected void checkLength(String s, int index, int length)
518
 
                throws ParseException
519
 
        {
520
 
            int len = (s == null) ? 0 : s.length();
521
 
            if (index + length > len)
522
 
            {
523
 
                throw new ParseException("Input string too short: " + s
524
 
                        + ", index: " + index);
525
 
            }
526
 
        }
527
 
 
528
 
        /**
529
 
         * Adds a number to the given string buffer and adds leading '0'
530
 
         * characters until the given length is reached.
531
 
         *
532
 
         * @param buf the target buffer
533
 
         * @param num the number to add
534
 
         * @param length the required length
535
 
         */
536
 
        protected void padNum(StringBuffer buf, int num, int length)
537
 
        {
538
 
            buf.append(StringUtils.leftPad(String.valueOf(num), length,
539
 
                    PAD_CHAR));
540
 
        }
541
 
    }
542
 
 
543
 
    /**
544
 
     * A specialized date component parser implementation that deals with
545
 
     * numeric calendar fields. The class is able to extract fields from a
546
 
     * string literal and to format a literal from a calendar.
547
 
     */
548
 
    private static class DateFieldParser extends DateComponentParser
549
 
    {
550
 
        /** Stores the calendar field to be processed. */
551
 
        private int calendarField;
552
 
 
553
 
        /** Stores the length of this field. */
554
 
        private int length;
555
 
 
556
 
        /** An optional offset to add to the calendar field. */
557
 
        private int offset;
558
 
 
559
 
        /**
560
 
         * Creates a new instance of <code>DateFieldParser</code>.
561
 
         *
562
 
         * @param calFld the calendar field code
563
 
         * @param len the length of this field
564
 
         */
565
 
        public DateFieldParser(int calFld, int len)
566
 
        {
567
 
            this(calFld, len, 0);
568
 
        }
569
 
 
570
 
        /**
571
 
         * Creates a new instance of <code>DateFieldParser</code> and fully
572
 
         * initializes it.
573
 
         *
574
 
         * @param calFld the calendar field code
575
 
         * @param len the length of this field
576
 
         * @param ofs an offset to add to the calendar field
577
 
         */
578
 
        public DateFieldParser(int calFld, int len, int ofs)
579
 
        {
580
 
            calendarField = calFld;
581
 
            length = len;
582
 
            offset = ofs;
583
 
        }
584
 
 
585
 
        public void formatComponent(StringBuffer buf, Calendar cal)
586
 
        {
587
 
            padNum(buf, cal.get(calendarField) + offset, length);
588
 
        }
589
 
 
590
 
        public int parseComponent(String s, int index, Calendar cal)
591
 
                throws ParseException
592
 
        {
593
 
            checkLength(s, index, length);
594
 
            try
595
 
            {
596
 
                cal.set(calendarField, Integer.parseInt(s.substring(index,
597
 
                        index + length))
598
 
                        - offset);
599
 
                return length;
600
 
            }
601
 
            catch (NumberFormatException nfex)
602
 
            {
603
 
                throw new ParseException("Invalid number: " + s + ", index "
604
 
                        + index);
605
 
            }
606
 
        }
607
 
    }
608
 
 
609
 
    /**
610
 
     * A specialized date component parser implementation that deals with
611
 
     * separator characters.
612
 
     */
613
 
    private static class DateSeparatorParser extends DateComponentParser
614
 
    {
615
 
        /** Stores the separator. */
616
 
        private String separator;
617
 
 
618
 
        /**
619
 
         * Creates a new instance of <code>DateSeparatorParser</code> and sets
620
 
         * the separator string.
621
 
         *
622
 
         * @param sep the separator string
623
 
         */
624
 
        public DateSeparatorParser(String sep)
625
 
        {
626
 
            separator = sep;
627
 
        }
628
 
 
629
 
        public void formatComponent(StringBuffer buf, Calendar cal)
630
 
        {
631
 
            buf.append(separator);
632
 
        }
633
 
 
634
 
        public int parseComponent(String s, int index, Calendar cal)
635
 
                throws ParseException
636
 
        {
637
 
            checkLength(s, index, separator.length());
638
 
            if (!s.startsWith(separator, index))
639
 
            {
640
 
                throw new ParseException("Invalid input: " + s + ", index "
641
 
                        + index + ", expected " + separator);
642
 
            }
643
 
            return separator.length();
644
 
        }
645
 
    }
646
 
 
647
 
    /**
648
 
     * A specialized date component parser implementation that deals with the
649
 
     * time zone part of a date component.
650
 
     */
651
 
    private static class DateTimeZoneParser extends DateComponentParser
652
 
    {
653
 
        public void formatComponent(StringBuffer buf, Calendar cal)
654
 
        {
655
 
            TimeZone tz = cal.getTimeZone();
656
 
            int ofs = tz.getRawOffset() / MILLIS_PER_MINUTE;
657
 
            if (ofs < 0)
658
 
            {
659
 
                buf.append('-');
660
 
                ofs = -ofs;
661
 
            }
662
 
            else
663
 
            {
664
 
                buf.append('+');
665
 
            }
666
 
            int hour = ofs / MINUTES_PER_HOUR;
667
 
            int min = ofs % MINUTES_PER_HOUR;
668
 
            padNum(buf, hour, 2);
669
 
            padNum(buf, min, 2);
670
 
        }
671
 
 
672
 
        public int parseComponent(String s, int index, Calendar cal)
673
 
                throws ParseException
674
 
        {
675
 
            checkLength(s, index, TIME_ZONE_LENGTH);
676
 
            TimeZone tz = TimeZone.getTimeZone(TIME_ZONE_PREFIX
677
 
                    + s.substring(index, index + TIME_ZONE_LENGTH));
678
 
            cal.setTimeZone(tz);
679
 
            return TIME_ZONE_LENGTH;
680
 
        }
681
 
    }
682
 
}