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