##### Coverage Report - org.apache.commons.configuration.plist.PropertyListConfiguration

Classes in this File | Line Coverage | Branch Coverage | Complexity
PropertyListConfiguration |
87% | 116/134
96% | 24/25
| 2.2857142857142856;2,286
PropertyListConfiguration$1 |
N/A | N/A
N/A | N/A
| 2.2857142857142856;2,286
PropertyListConfiguration$DateComponentParser |
100% | 7/7
100% | 2/2
| 2.2857142857142856;2,286
PropertyListConfiguration$DateFieldParser |
100% | 14/14
N/A | N/A
| 2.2857142857142856;2,286
PropertyListConfiguration$DateSeparatorParser |
100% | 9/9
100% | 1/1
| 2.2857142857142856;2,286
PropertyListConfiguration$DateTimeZoneParser |
100% | 17/17
100% | 1/1
| 2.2857142857142856;2,286

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 support 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/Articles/OldStylePListsConcept.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: 590474 $, $Date: 2007-10-30 22:35:11 +0100 (Di, 30 Okt 2007) $
82 |  |
      */
83 |  |
     public class PropertyListConfiguration extends AbstractHierarchicalFileConfiguration
84 |  |
     {
85 |  |
         /** Constant for the separator parser for the date part. */
86 | 3 |
         private static final DateComponentParser DATE_SEPARATOR_PARSER = new DateSeparatorParser(
87 |  |
                 "-");
88 |  |
89 |  |
         /** Constant for the separator parser for the time part. */
90 | 3 |
         private static final DateComponentParser TIME_SEPARATOR_PARSER = new DateSeparatorParser(
91 |  |
                 ":");
92 |  |
93 |  |
         /** Constant for the separator parser for blanks between the parts. */
94 | 3 |
         private static final DateComponentParser BLANK_SEPARATOR_PARSER = new DateSeparatorParser(
95 |  |
                 " ");
96 |  |
97 |  |
         /** An array with the component parsers for dealing with dates. */
98 | 3 |
         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 | 291 |
         {
137 | 291 |
         }
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 | 1 |
             super(c);
149 | 1 |
         }
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 | 2 |
             super(fileName);
160 | 2 |
         }
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 | 11 |
             super(file);
171 | 11 |
         }
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 | 0 |
             super(url);
182 | 0 |
         }
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 | 4 |
             if (value instanceof byte[])
188 |  |
             {
189 | 2 |
                 fireEvent(EVENT_SET_PROPERTY, key, value, true);
190 | 2 |
                 setDetailEvents(false);
191 |  |
                 try
192 |  |
                 {
193 | 2 |
                     clearProperty(key);
194 | 2 |
                     addPropertyDirect(key, value);
195 |  |
                 }
196 |  |
                 finally
197 |  |
                 {
198 | 2 |
                     setDetailEvents(true);
199 | 2 |
                 }
200 | 2 |
                 fireEvent(EVENT_SET_PROPERTY, key, value, false);
201 | 2 |
             }
202 |  |
             else
203 |  |
             {
204 | 2 |
                 super.setProperty(key, value);
205 |  |
             }
206 | 4 |
         }
207 |  |
208 |  |
         public void addProperty(String key, Object value)
209 |  |
         {
210 | 14 |
             if (value instanceof byte[])
211 |  |
             {
212 | 2 |
                 fireEvent(EVENT_ADD_PROPERTY, key, value, true);
213 | 2 |
                 addPropertyDirect(key, value);
214 | 2 |
                 fireEvent(EVENT_ADD_PROPERTY, key, value, false);
215 | 2 |
             }
216 |  |
             else
217 |  |
             {
218 | 12 |
                 super.addProperty(key, value);
219 |  |
             }
220 | 14 |
         }
221 |  |
222 |  |
         public void load(Reader in) throws ConfigurationException
223 |  |
         {
224 | 36 |
             PropertyListParser parser = new PropertyListParser(in);
225 |  |
             try
226 |  |
             {
227 | 36 |
                 HierarchicalConfiguration config = parser.parse();
228 | 35 |
                 setRoot(config.getRoot());
229 |  |
             }
230 | 1 |
             catch (ParseException e)
231 |  |
             {
232 | 1 |
                 throw new ConfigurationException(e);
233 | 35 |
             }
234 | 35 |
         }
235 |  |
236 |  |
         public void save(Writer out) throws ConfigurationException
237 |  |
         {
238 | 3 |
             PrintWriter writer = new PrintWriter(out);
239 | 3 |
             printNode(writer, 0, getRoot());
240 | 3 |
             writer.flush();
241 | 3 |
         }
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 | 27 |
             String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
249 |  |
250 | 27 |
             if (node.getName() != null)
251 |  |
             {
252 | 22 |
                 out.print(padding + quoteString(node.getName()) + " = ");
253 |  |
             }
254 |  |
255 |  |
             // get all non trivial nodes
256 | 27 |
             List children = new ArrayList(node.getChildren());
257 | 27 |
             Iterator it = children.iterator();
258 | 50 |
             while (it.hasNext())
259 |  |
             {
260 | 23 |
                 Node child = (Node) it.next();
261 | 23 |
                 if (child.getValue() == null && (child.getChildren() == null || child.getChildren().isEmpty()))
262 |  |
                 {
263 | 1 |
                     it.remove();
264 |  |
                 }
265 | 23 |
             }
266 |  |
267 | 27 |
             if (!children.isEmpty())
268 |  |
             {
269 |  |
                 // skip a line, except for the root dictionary
270 | 9 |
                 if (indentLevel > 0)
271 |  |
                 {
272 | 6 |
                     out.println();
273 |  |
                 }
274 |  |
275 | 9 |
                 out.println(padding + "{");
276 |  |
277 |  |
                 // display the children
278 | 9 |
                 it = children.iterator();
279 | 31 |
                 while (it.hasNext())
280 |  |
                 {
281 | 22 |
                     Node child = (Node) it.next();
282 |  |
283 | 22 |
                     printNode(out, indentLevel + 1, child);
284 |  |
285 |  |
                     // add a semi colon for elements that are not dictionaries
286 | 22 |
                     Object value = child.getValue();
287 | 22 |
                     if (value != null && !(value instanceof Map) && !(value instanceof Configuration))
288 |  |
                     {
289 | 18 |
                         out.println(";");
290 |  |
                     }
291 |  |
292 |  |
                     // skip a line after arrays and dictionaries
293 | 22 |
                     if (it.hasNext() && (value == null || value instanceof List))
294 |  |
                     {
295 | 6 |
                         out.println();
296 |  |
                     }
297 | 22 |
                 }
298 |  |
299 | 9 |
                 out.print(padding + "}");
300 |  |
301 |  |
                 // line feed if the dictionary is not in an array
302 | 9 |
                 if (node.getParent() != null)
303 |  |
                 {
304 | 4 |
                     out.println();
305 | 4 |
                 }
306 |  |
             }
307 |  |
             else
308 |  |
             {
309 |  |
                 // display the leaf value
310 | 18 |
                 Object value = node.getValue();
311 | 18 |
                 printValue(out, indentLevel, value);
312 |  |
             }
313 | 27 |
         }
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 | 29 |
             String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
321 |  |
322 | 29 |
             if (value instanceof List)
323 |  |
             {
324 | 6 |
                 out.print("( ");
325 | 6 |
                 Iterator it = ((List) value).iterator();
326 | 17 |
                 while (it.hasNext())
327 |  |
                 {
328 | 11 |
                     printValue(out, indentLevel + 1, it.next());
329 | 11 |
                     if (it.hasNext())
330 |  |
                     {
331 | 6 |
                         out.print(", ");
332 | 6 |
                     }
333 |  |
                 }
334 | 6 |
                 out.print(" )");
335 | 6 |
             }
336 | 23 |
             else if (value instanceof HierarchicalConfiguration)
337 |  |
             {
338 | 2 |
                 printNode(out, indentLevel, ((HierarchicalConfiguration) value).getRoot());
339 | 2 |
             }
340 | 21 |
             else if (value instanceof Configuration)
341 |  |
             {
342 |  |
                 // display a flat Configuration as a dictionary
343 | 0 |
                 out.println();
344 | 0 |
                 out.println(padding + "{");
345 |  |
346 | 0 |
                 Configuration config = (Configuration) value;
347 | 0 |
                 Iterator it = config.getKeys();
348 | 0 |
                 while (it.hasNext())
349 |  |
                 {
350 | 0 |
                     String key = (String) it.next();
351 | 0 |
                     Node node = new Node(key);
352 | 0 |
                     node.setValue(config.getProperty(key));
353 |  |
354 | 0 |
                     printNode(out, indentLevel + 1, node);
355 | 0 |
                     out.println(";");
356 | 0 |
                 }
357 | 0 |
                 out.println(padding + "}");
358 | 0 |
             }
359 | 21 |
             else if (value instanceof Map)
360 |  |
             {
361 |  |
                 // display a Map as a dictionary
362 | 0 |
                 Map map = (Map) value;
363 | 0 |
                 printValue(out, indentLevel, new MapConfiguration(map));
364 | 0 |
             }
365 | 21 |
             else if (value instanceof byte[])
366 |  |
             {
367 | 4 |
                 out.print("<" + new String(Hex.encodeHex((byte[]) value)) + ">");
368 | 4 |
             }
369 | 17 |
             else if (value instanceof Date)
370 |  |
             {
371 | 1 |
                 out.print(formatDate((Date) value));
372 | 1 |
             }
373 | 16 |
             else if (value != null)
374 |  |
             {
375 | 16 |
                 out.print(quoteString(String.valueOf(value)));
376 |  |
             }
377 | 29 |
         }
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 | 43 |
             if (s == null)
399 |  |
             {
400 | 1 |
                 return null;
401 |  |
             }
402 |  |
403 | 42 |
             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 | 5 |
                 s = StringUtils.replace(s, "\"", "\\\"");
417 | 5 |
                 s = "\"" + s + "\"";
418 |  |
             }
419 |  |
420 | 42 |
             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 | 38 |
             Calendar cal = Calendar.getInstance();
434 | 38 |
             cal.clear();
435 | 38 |
             int index = 0;
436 |  |
437 | 566 |
             for (int i = 0; i < DATE_PARSERS.length; i++)
438 |  |
             {
439 | 532 |
                 index += DATE_PARSERS[i].parseComponent(s, index, cal);
440 |  |
             }
441 |  |
442 | 34 |
             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 | 3 |
             StringBuffer buf = new StringBuffer();
455 |  |
456 | 48 |
             for (int i = 0; i < DATE_PARSERS.length; i++)
457 |  |
             {
458 | 45 |
                 DATE_PARSERS[i].formatComponent(buf, cal);
459 |  |
             }
460 |  |
461 | 3 |
             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 | 1 |
             Calendar cal = Calendar.getInstance();
473 | 1 |
             cal.setTime(date);
474 | 1 |
             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 | 72 |
         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 | 532 |
                 int len = (s == null) ? 0 : s.length();
521 | 532 |
                 if (index + length > len)
522 |  |
                 {
523 | 1 |
                     throw new ParseException("Input string too short: " + s
524 |  |
                             + ", index: " + index);
525 |  |
                 }
526 | 531 |
             }
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 | 24 |
                 buf.append(StringUtils.leftPad(String.valueOf(num), length,
539 |  |
                         PAD_CHAR));
540 | 24 |
             }
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 | 15 |
                 this(calFld, len, 0);
568 | 15 |
             }
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 | 18 |
             {
580 | 18 |
                 calendarField = calFld;
581 | 18 |
                 length = len;
582 | 18 |
                 offset = ofs;
583 | 18 |
             }
584 |  |
585 |  |
             public void formatComponent(StringBuffer buf, Calendar cal)
586 |  |
             {
587 | 18 |
                 padNum(buf, cal.get(calendarField) + offset, length);
588 | 18 |
             }
589 |  |
590 |  |
             public int parseComponent(String s, int index, Calendar cal)
591 |  |
                     throws ParseException
592 |  |
             {
593 | 214 |
                 checkLength(s, index, length);
594 |  |
                 try
595 |  |
                 {
596 | 214 |
                     cal.set(calendarField, Integer.parseInt(s.substring(index,
597 |  |
                             index + length))
598 |  |
                             - offset);
599 | 212 |
                     return length;
600 |  |
                 }
601 | 2 |
                 catch (NumberFormatException nfex)
602 |  |
                 {
603 | 2 |
                     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 | 15 |
             {
626 | 15 |
                 separator = sep;
627 | 15 |
             }
628 |  |
629 |  |
             public void formatComponent(StringBuffer buf, Calendar cal)
630 |  |
             {
631 | 24 |
                 buf.append(separator);
632 | 24 |
             }
633 |  |
634 |  |
             public int parseComponent(String s, int index, Calendar cal)
635 |  |
                     throws ParseException
636 |  |
             {
637 | 284 |
                 checkLength(s, index, separator.length());
638 | 283 |
                 if (!s.startsWith(separator, index))
639 |  |
                 {
640 | 1 |
                     throw new ParseException("Invalid input: " + s + ", index "
641 |  |
                             + index + ", expected " + separator);
642 |  |
                 }
643 | 282 |
                 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 | 6 |
         private static class DateTimeZoneParser extends DateComponentParser
652 |  |
         {
653 |  |
             public void formatComponent(StringBuffer buf, Calendar cal)
654 |  |
             {
655 | 3 |
                 TimeZone tz = cal.getTimeZone();
656 | 3 |
                 int ofs = tz.getRawOffset() / MILLIS_PER_MINUTE;
657 | 3 |
                 if (ofs < 0)
658 |  |
                 {
659 | 1 |
                     buf.append('-');
660 | 1 |
                     ofs = -ofs;
661 | 1 |
                 }
662 |  |
                 else
663 |  |
                 {
664 | 2 |
                     buf.append('+');
665 |  |
                 }
666 | 3 |
                 int hour = ofs / MINUTES_PER_HOUR;
667 | 3 |
                 int min = ofs % MINUTES_PER_HOUR;
668 | 3 |
                 padNum(buf, hour, 2);
669 | 3 |
                 padNum(buf, min, 2);
670 | 3 |
             }
671 |  |
672 |  |
             public int parseComponent(String s, int index, Calendar cal)
673 |  |
                     throws ParseException
674 |  |
             {
675 | 34 |
                 checkLength(s, index, TIME_ZONE_LENGTH);
676 | 34 |
                 TimeZone tz = TimeZone.getTimeZone(TIME_ZONE_PREFIX
677 |  |
                         + s.substring(index, index + TIME_ZONE_LENGTH));
678 | 34 |
                 cal.setTimeZone(tz);
679 | 34 |
                 return TIME_ZONE_LENGTH;
680 |  |
             }
681 |  |
         }
682 |  |
     }