Coverage Report - org.apache.commons.configuration.plist.PropertyListConfiguration
 
Classes in this File Line Coverage Branch Coverage Complexity
PropertyListConfiguration
88%
106/121
96%
24/25
2,286
PropertyListConfiguration$1
N/A
N/A
2,286
PropertyListConfiguration$DateComponentParser
100%
7/7
100%
2/2
2,286
PropertyListConfiguration$DateFieldParser
100%
14/14
N/A
2,286
PropertyListConfiguration$DateSeparatorParser
100%
9/9
100%
1/1
2,286
PropertyListConfiguration$DateTimeZoneParser
100%
16/16
100%
1/1
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 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/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: 628705 $, $Date: 2008-02-18 13:37:19 +0100 (Mo, 18 Feb 2008) $
 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  
         }
 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  
         }
 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  
         }
 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  
             }
 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  
             }
 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  
                 }
 333  
             }
 334  6
             out.print(" )");
 335  
         }
 336  23
         else if (value instanceof HierarchicalConfiguration)
 337  
         {
 338  2
             printNode(out, indentLevel, ((HierarchicalConfiguration) value).getRoot());
 339  
         }
 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  
             }
 357  0
             out.println(padding + "}");
 358  
         }
 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  
         }
 365  21
         else if (value instanceof byte[])
 366  
         {
 367  4
             out.print("<" + new String(Hex.encodeHex((byte[]) value)) + ">");
 368  
         }
 369  17
         else if (value instanceof Date)
 370  
         {
 371  1
             out.print(formatDate((Date) value));
 372  
         }
 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  
             }
 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  
 }