Coverage Report - org.apache.commons.configuration.plist.XMLPropertyListConfiguration
 
Classes in this File Line Coverage Branch Coverage Complexity
XMLPropertyListConfiguration
77%
100/129
73%
41/56
2,742
XMLPropertyListConfiguration$1
100%
2/2
N/A
2,742
XMLPropertyListConfiguration$ArrayNode
100%
5/5
N/A
2,742
XMLPropertyListConfiguration$PListNode
78%
30/38
50%
3/6
2,742
XMLPropertyListConfiguration$XMLPropertyListHandler
100%
56/56
100%
32/32
2,742
 
 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.math.BigDecimal;
 25  
 import java.math.BigInteger;
 26  
 import java.net.URL;
 27  
 import java.text.DateFormat;
 28  
 import java.text.ParseException;
 29  
 import java.text.SimpleDateFormat;
 30  
 import java.util.ArrayList;
 31  
 import java.util.Calendar;
 32  
 import java.util.Collection;
 33  
 import java.util.Date;
 34  
 import java.util.HashMap;
 35  
 import java.util.Iterator;
 36  
 import java.util.List;
 37  
 import java.util.Map;
 38  
 import java.util.TimeZone;
 39  
 
 40  
 import javax.xml.parsers.SAXParser;
 41  
 import javax.xml.parsers.SAXParserFactory;
 42  
 
 43  
 import org.apache.commons.codec.binary.Base64;
 44  
 import org.apache.commons.configuration.AbstractHierarchicalFileConfiguration;
 45  
 import org.apache.commons.configuration.Configuration;
 46  
 import org.apache.commons.configuration.ConfigurationException;
 47  
 import org.apache.commons.configuration.HierarchicalConfiguration;
 48  
 import org.apache.commons.configuration.MapConfiguration;
 49  
 import org.apache.commons.configuration.tree.ConfigurationNode;
 50  
 import org.apache.commons.lang.StringEscapeUtils;
 51  
 import org.apache.commons.lang.StringUtils;
 52  
 import org.xml.sax.Attributes;
 53  
 import org.xml.sax.EntityResolver;
 54  
 import org.xml.sax.InputSource;
 55  
 import org.xml.sax.SAXException;
 56  
 import org.xml.sax.helpers.DefaultHandler;
 57  
 
 58  
 /**
 59  
  * Property list file (plist) in XML FORMAT as used by Mac OS X (http://www.apple.com/DTDs/PropertyList-1.0.dtd).
 60  
  * This configuration doesn't support the binary FORMAT used in OS X 10.4.
 61  
  *
 62  
  * <p>Example:</p>
 63  
  * <pre>
 64  
  * &lt;?xml version="1.0"?>
 65  
  * &lt;!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
 66  
  * &lt;plist version="1.0">
 67  
  *     &lt;dict>
 68  
  *         &lt;key>string&lt;/key>
 69  
  *         &lt;string>value1&lt;/string>
 70  
  *
 71  
  *         &lt;key>integer&lt;/key>
 72  
  *         &lt;integer>12345&lt;/integer>
 73  
  *
 74  
  *         &lt;key>real&lt;/key>
 75  
  *         &lt;real>-123.45E-1&lt;/real>
 76  
  *
 77  
  *         &lt;key>boolean&lt;/key>
 78  
  *         &lt;true/>
 79  
  *
 80  
  *         &lt;key>date&lt;/key>
 81  
  *         &lt;date>2005-01-01T12:00:00Z&lt;/date>
 82  
  *
 83  
  *         &lt;key>data&lt;/key>
 84  
  *         &lt;data>RHJhY28gRG9ybWllbnMgTnVucXVhbSBUaXRpbGxhbmR1cw==&lt;/data>
 85  
  *
 86  
  *         &lt;key>array&lt;/key>
 87  
  *         &lt;array>
 88  
  *             &lt;string>value1&lt;/string>
 89  
  *             &lt;string>value2&lt;/string>
 90  
  *             &lt;string>value3&lt;/string>
 91  
  *         &lt;/array>
 92  
  *
 93  
  *         &lt;key>dictionnary&lt;/key>
 94  
  *         &lt;dict>
 95  
  *             &lt;key>key1&lt;/key>
 96  
  *             &lt;string>value1&lt;/string>
 97  
  *             &lt;key>key2&lt;/key>
 98  
  *             &lt;string>value2&lt;/string>
 99  
  *             &lt;key>key3&lt;/key>
 100  
  *             &lt;string>value3&lt;/string>
 101  
  *         &lt;/dict>
 102  
  *
 103  
  *         &lt;key>nested&lt;/key>
 104  
  *         &lt;dict>
 105  
  *             &lt;key>node1&lt;/key>
 106  
  *             &lt;dict>
 107  
  *                 &lt;key>node2&lt;/key>
 108  
  *                 &lt;dict>
 109  
  *                     &lt;key>node3&lt;/key>
 110  
  *                     &lt;string>value&lt;/string>
 111  
  *                 &lt;/dict>
 112  
  *             &lt;/dict>
 113  
  *         &lt;/dict>
 114  
  *
 115  
  *     &lt;/dict>
 116  
  * &lt;/plist>
 117  
  * </pre>
 118  
  *
 119  
  * @since 1.2
 120  
  *
 121  
  * @author Emmanuel Bourg
 122  
  * @version $Id: XMLPropertyListConfiguration.java 1368665 2012-08-02 19:48:26Z oheger $
 123  
  */
 124  
 public class XMLPropertyListConfiguration extends AbstractHierarchicalFileConfiguration
 125  
 {
 126  
     /**
 127  
      * The serial version UID.
 128  
      */
 129  
     private static final long serialVersionUID = -3162063751042475985L;
 130  
 
 131  
     /** Size of the indentation for the generated file. */
 132  
     private static final int INDENT_SIZE = 4;
 133  
 
 134  
     /**
 135  
      * Creates an empty XMLPropertyListConfiguration object which can be
 136  
      * used to synthesize a new plist file by adding values and
 137  
      * then saving().
 138  
      */
 139  
     public XMLPropertyListConfiguration()
 140  64
     {
 141  64
         initRoot();
 142  64
     }
 143  
 
 144  
     /**
 145  
      * Creates a new instance of {@code XMLPropertyListConfiguration} and
 146  
      * copies the content of the specified configuration into this object.
 147  
      *
 148  
      * @param configuration the configuration to copy
 149  
      * @since 1.4
 150  
      */
 151  
     public XMLPropertyListConfiguration(HierarchicalConfiguration configuration)
 152  
     {
 153  1
         super(configuration);
 154  1
     }
 155  
 
 156  
     /**
 157  
      * Creates and loads the property list from the specified file.
 158  
      *
 159  
      * @param fileName The name of the plist file to load.
 160  
      * @throws org.apache.commons.configuration.ConfigurationException Error
 161  
      * while loading the plist file
 162  
      */
 163  
     public XMLPropertyListConfiguration(String fileName) throws ConfigurationException
 164  
     {
 165  2
         super(fileName);
 166  2
     }
 167  
 
 168  
     /**
 169  
      * Creates and loads the property list from the specified file.
 170  
      *
 171  
      * @param file The plist file to load.
 172  
      * @throws ConfigurationException Error while loading the plist file
 173  
      */
 174  
     public XMLPropertyListConfiguration(File file) throws ConfigurationException
 175  
     {
 176  13
         super(file);
 177  13
     }
 178  
 
 179  
     /**
 180  
      * Creates and loads the property list from the specified URL.
 181  
      *
 182  
      * @param url The location of the plist file to load.
 183  
      * @throws ConfigurationException Error while loading the plist file
 184  
      */
 185  
     public XMLPropertyListConfiguration(URL url) throws ConfigurationException
 186  
     {
 187  0
         super(url);
 188  0
     }
 189  
 
 190  
     @Override
 191  
     public void setProperty(String key, Object value)
 192  
     {
 193  
         // special case for byte arrays, they must be stored as is in the configuration
 194  4
         if (value instanceof byte[])
 195  
         {
 196  2
             fireEvent(EVENT_SET_PROPERTY, key, value, true);
 197  2
             setDetailEvents(false);
 198  
             try
 199  
             {
 200  2
                 clearProperty(key);
 201  2
                 addPropertyDirect(key, value);
 202  
             }
 203  
             finally
 204  
             {
 205  2
                 setDetailEvents(true);
 206  2
             }
 207  2
             fireEvent(EVENT_SET_PROPERTY, key, value, false);
 208  
         }
 209  
         else
 210  
         {
 211  2
             super.setProperty(key, value);
 212  
         }
 213  4
     }
 214  
 
 215  
     @Override
 216  
     public void addProperty(String key, Object value)
 217  
     {
 218  14
         if (value instanceof byte[])
 219  
         {
 220  2
             fireEvent(EVENT_ADD_PROPERTY, key, value, true);
 221  2
             addPropertyDirect(key, value);
 222  2
             fireEvent(EVENT_ADD_PROPERTY, key, value, false);
 223  
         }
 224  
         else
 225  
         {
 226  12
             super.addProperty(key, value);
 227  
         }
 228  14
     }
 229  
 
 230  
     public void load(Reader in) throws ConfigurationException
 231  
     {
 232  
         // We have to make sure that the root node is actually a PListNode.
 233  
         // If this object was not created using the standard constructor, the
 234  
         // root node is a plain Node.
 235  26
         if (!(getRootNode() instanceof PListNode))
 236  
         {
 237  5
             initRoot();
 238  
         }
 239  
 
 240  
         // set up the DTD validation
 241  26
         EntityResolver resolver = new EntityResolver()
 242  26
         {
 243  
             public InputSource resolveEntity(String publicId, String systemId)
 244  
             {
 245  26
                 return new InputSource(getClass().getClassLoader().getResourceAsStream("PropertyList-1.0.dtd"));
 246  
             }
 247  
         };
 248  
 
 249  
         // parse the file
 250  26
         XMLPropertyListHandler handler = new XMLPropertyListHandler(getRoot());
 251  
         try
 252  
         {
 253  26
             SAXParserFactory factory = SAXParserFactory.newInstance();
 254  26
             factory.setValidating(true);
 255  
 
 256  26
             SAXParser parser = factory.newSAXParser();
 257  26
             parser.getXMLReader().setEntityResolver(resolver);
 258  26
             parser.getXMLReader().setContentHandler(handler);
 259  26
             parser.getXMLReader().parse(new InputSource(in));
 260  
         }
 261  0
         catch (Exception e)
 262  
         {
 263  0
             throw new ConfigurationException("Unable to parse the configuration file", e);
 264  26
         }
 265  26
     }
 266  
 
 267  
     public void save(Writer out) throws ConfigurationException
 268  
     {
 269  4
         PrintWriter writer = new PrintWriter(out);
 270  
 
 271  4
         if (getEncoding() != null)
 272  
         {
 273  0
             writer.println("<?xml version=\"1.0\" encoding=\"" + getEncoding() + "\"?>");
 274  
         }
 275  
         else
 276  
         {
 277  4
             writer.println("<?xml version=\"1.0\"?>");
 278  
         }
 279  
 
 280  4
         writer.println("<!DOCTYPE plist SYSTEM \"file://localhost/System/Library/DTDs/PropertyList.dtd\">");
 281  4
         writer.println("<plist version=\"1.0\">");
 282  
 
 283  4
         printNode(writer, 1, getRoot());
 284  
 
 285  4
         writer.println("</plist>");
 286  4
         writer.flush();
 287  4
     }
 288  
 
 289  
     /**
 290  
      * Append a node to the writer, indented according to a specific level.
 291  
      */
 292  
     private void printNode(PrintWriter out, int indentLevel, ConfigurationNode node)
 293  
     {
 294  54
         String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
 295  
 
 296  54
         if (node.getName() != null)
 297  
         {
 298  46
             out.println(padding + "<key>" + StringEscapeUtils.escapeXml(node.getName()) + "</key>");
 299  
         }
 300  
 
 301  54
         List<ConfigurationNode> children = node.getChildren();
 302  54
         if (!children.isEmpty())
 303  
         {
 304  16
             out.println(padding + "<dict>");
 305  
 
 306  16
             Iterator<ConfigurationNode> it = children.iterator();
 307  62
             while (it.hasNext())
 308  
             {
 309  46
                 ConfigurationNode child = it.next();
 310  46
                 printNode(out, indentLevel + 1, child);
 311  
 
 312  46
                 if (it.hasNext())
 313  
                 {
 314  30
                     out.println();
 315  
                 }
 316  46
             }
 317  
 
 318  16
             out.println(padding + "</dict>");
 319  16
         }
 320  38
         else if (node.getValue() == null)
 321  
         {
 322  2
             out.println(padding + "<dict/>");
 323  
         }
 324  
         else
 325  
         {
 326  36
             Object value = node.getValue();
 327  36
             printValue(out, indentLevel, value);
 328  
         }
 329  54
     }
 330  
 
 331  
     /**
 332  
      * Append a value to the writer, indented according to a specific level.
 333  
      */
 334  
     private void printValue(PrintWriter out, int indentLevel, Object value)
 335  
     {
 336  58
         String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
 337  
 
 338  58
         if (value instanceof Date)
 339  
         {
 340  4
             synchronized (PListNode.FORMAT)
 341  
             {
 342  4
                 out.println(padding + "<date>" + PListNode.FORMAT.format((Date) value) + "</date>");
 343  4
             }
 344  
         }
 345  54
         else if (value instanceof Calendar)
 346  
         {
 347  0
             printValue(out, indentLevel, ((Calendar) value).getTime());
 348  
         }
 349  54
         else if (value instanceof Number)
 350  
         {
 351  4
             if (value instanceof Double || value instanceof Float || value instanceof BigDecimal)
 352  
             {
 353  2
                 out.println(padding + "<real>" + value.toString() + "</real>");
 354  
             }
 355  
             else
 356  
             {
 357  2
                 out.println(padding + "<integer>" + value.toString() + "</integer>");
 358  
             }
 359  
         }
 360  50
         else if (value instanceof Boolean)
 361  
         {
 362  4
             if (((Boolean) value).booleanValue())
 363  
             {
 364  2
                 out.println(padding + "<true/>");
 365  
             }
 366  
             else
 367  
             {
 368  2
                 out.println(padding + "<false/>");
 369  
             }
 370  
         }
 371  46
         else if (value instanceof List)
 372  
         {
 373  10
             out.println(padding + "<array>");
 374  10
             Iterator<?> it = ((List<?>) value).iterator();
 375  32
             while (it.hasNext())
 376  
             {
 377  22
                 printValue(out, indentLevel + 1, it.next());
 378  
             }
 379  10
             out.println(padding + "</array>");
 380  10
         }
 381  36
         else if (value instanceof HierarchicalConfiguration)
 382  
         {
 383  4
             printNode(out, indentLevel, ((HierarchicalConfiguration) value).getRoot());
 384  
         }
 385  32
         else if (value instanceof Configuration)
 386  
         {
 387  
             // display a flat Configuration as a dictionary
 388  0
             out.println(padding + "<dict>");
 389  
 
 390  0
             Configuration config = (Configuration) value;
 391  0
             Iterator<String> it = config.getKeys();
 392  0
             while (it.hasNext())
 393  
             {
 394  
                 // create a node for each property
 395  0
                 String key = it.next();
 396  0
                 Node node = new Node(key);
 397  0
                 node.setValue(config.getProperty(key));
 398  
 
 399  
                 // print the node
 400  0
                 printNode(out, indentLevel + 1, node);
 401  
 
 402  0
                 if (it.hasNext())
 403  
                 {
 404  0
                     out.println();
 405  
                 }
 406  0
             }
 407  0
             out.println(padding + "</dict>");
 408  0
         }
 409  32
         else if (value instanceof Map)
 410  
         {
 411  
             // display a Map as a dictionary
 412  0
             Map<String, Object> map = transformMap((Map<?, ?>) value);
 413  0
             printValue(out, indentLevel, new MapConfiguration(map));
 414  0
         }
 415  32
         else if (value instanceof byte[])
 416  
         {
 417  4
             String base64 = new String(Base64.encodeBase64((byte[]) value));
 418  4
             out.println(padding + "<data>" + StringEscapeUtils.escapeXml(base64) + "</data>");
 419  4
         }
 420  28
         else if (value != null)
 421  
         {
 422  28
             out.println(padding + "<string>" + StringEscapeUtils.escapeXml(String.valueOf(value)) + "</string>");
 423  
         }
 424  
         else
 425  
         {
 426  0
             out.println(padding + "<string/>");
 427  
         }
 428  58
     }
 429  
 
 430  
     /**
 431  
      * Helper method for initializing the configuration's root node.
 432  
      */
 433  
     private void initRoot()
 434  
     {
 435  69
         setRootNode(new PListNode());
 436  69
     }
 437  
 
 438  
     /**
 439  
      * Transform a map of arbitrary types into a map with string keys and object
 440  
      * values. All keys of the source map which are not of type String are
 441  
      * dropped.
 442  
      *
 443  
      * @param src the map to be converted
 444  
      * @return the resulting map
 445  
      */
 446  
     private static Map<String, Object> transformMap(Map<?, ?> src)
 447  
     {
 448  0
         Map<String, Object> dest = new HashMap<String, Object>();
 449  0
         for (Map.Entry<?, ?> e : src.entrySet())
 450  
         {
 451  0
             if (e.getKey() instanceof String)
 452  
             {
 453  0
                 dest.put((String) e.getKey(), e.getValue());
 454  
             }
 455  0
         }
 456  0
         return dest;
 457  
     }
 458  
 
 459  
     /**
 460  
      * SAX Handler to build the configuration nodes while the document is being parsed.
 461  
      */
 462  
     private class XMLPropertyListHandler extends DefaultHandler
 463  
     {
 464  
         /** The buffer containing the text node being read */
 465  26
         private StringBuilder buffer = new StringBuilder();
 466  
 
 467  
         /** The stack of configuration nodes */
 468  26
         private List<Node> stack = new ArrayList<Node>();
 469  
 
 470  
         public XMLPropertyListHandler(Node root)
 471  26
         {
 472  26
             push(root);
 473  26
         }
 474  
 
 475  
         /**
 476  
          * Return the node on the top of the stack.
 477  
          */
 478  
         private Node peek()
 479  
         {
 480  1825
             if (!stack.isEmpty())
 481  
             {
 482  1799
                 return stack.get(stack.size() - 1);
 483  
             }
 484  
             else
 485  
             {
 486  26
                 return null;
 487  
             }
 488  
         }
 489  
 
 490  
         /**
 491  
          * Remove and return the node on the top of the stack.
 492  
          */
 493  
         private Node pop()
 494  
         {
 495  671
             if (!stack.isEmpty())
 496  
             {
 497  645
                 return stack.remove(stack.size() - 1);
 498  
             }
 499  
             else
 500  
             {
 501  26
                 return null;
 502  
             }
 503  
         }
 504  
 
 505  
         /**
 506  
          * Put a node on the top of the stack.
 507  
          */
 508  
         private void push(Node node)
 509  
         {
 510  645
             stack.add(node);
 511  645
         }
 512  
 
 513  
         @Override
 514  
         public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
 515  
         {
 516  1223
             if ("array".equals(qName))
 517  
             {
 518  111
                 push(new ArrayNode());
 519  
             }
 520  1112
             else if ("dict".equals(qName))
 521  
             {
 522  171
                 if (peek() instanceof ArrayNode)
 523  
                 {
 524  
                     // create the configuration
 525  42
                     XMLPropertyListConfiguration config = new XMLPropertyListConfiguration();
 526  
 
 527  
                     // add it to the ArrayNode
 528  42
                     ArrayNode node = (ArrayNode) peek();
 529  42
                     node.addValue(config);
 530  
 
 531  
                     // push the root on the stack
 532  42
                     push(config.getRoot());
 533  
                 }
 534  
             }
 535  1223
         }
 536  
 
 537  
         @Override
 538  
         public void endElement(String uri, String localName, String qName) throws SAXException
 539  
         {
 540  1223
             if ("key".equals(qName))
 541  
             {
 542  
                 // create a new node, link it to its parent and push it on the stack
 543  466
                 PListNode node = new PListNode();
 544  466
                 node.setName(buffer.toString());
 545  466
                 peek().addChild(node);
 546  466
                 push(node);
 547  466
             }
 548  757
             else if ("dict".equals(qName))
 549  
             {
 550  
                 // remove the root of the XMLPropertyListConfiguration previously pushed on the stack
 551  171
                 pop();
 552  
             }
 553  
             else
 554  
             {
 555  586
                 if ("string".equals(qName))
 556  
                 {
 557  299
                     ((PListNode) peek()).addValue(buffer.toString());
 558  
                 }
 559  287
                 else if ("integer".equals(qName))
 560  
                 {
 561  21
                     ((PListNode) peek()).addIntegerValue(buffer.toString());
 562  
                 }
 563  266
                 else if ("real".equals(qName))
 564  
                 {
 565  21
                     ((PListNode) peek()).addRealValue(buffer.toString());
 566  
                 }
 567  245
                 else if ("true".equals(qName))
 568  
                 {
 569  21
                     ((PListNode) peek()).addTrueValue();
 570  
                 }
 571  224
                 else if ("false".equals(qName))
 572  
                 {
 573  21
                     ((PListNode) peek()).addFalseValue();
 574  
                 }
 575  203
                 else if ("data".equals(qName))
 576  
                 {
 577  23
                     ((PListNode) peek()).addDataValue(buffer.toString());
 578  
                 }
 579  180
                 else if ("date".equals(qName))
 580  
                 {
 581  
                     try
 582  
                     {
 583  43
                         ((PListNode) peek()).addDateValue(buffer.toString());
 584  
                     }
 585  1
                     catch (IllegalArgumentException iex)
 586  
                     {
 587  1
                         getLogger().warn(
 588  
                                 "Ignoring invalid date property " + buffer);
 589  43
                     }
 590  
                 }
 591  137
                 else if ("array".equals(qName))
 592  
                 {
 593  111
                     ArrayNode array = (ArrayNode) pop();
 594  111
                     ((PListNode) peek()).addList(array);
 595  
                 }
 596  
 
 597  
                 // remove the plist node on the stack once the value has been parsed,
 598  
                 // array nodes remains on the stack for the next values in the list
 599  586
                 if (!(peek() instanceof ArrayNode))
 600  
                 {
 601  389
                     pop();
 602  
                 }
 603  
             }
 604  
 
 605  1223
             buffer.setLength(0);
 606  1223
         }
 607  
 
 608  
         @Override
 609  
         public void characters(char[] ch, int start, int length) throws SAXException
 610  
         {
 611  901
             buffer.append(ch, start, length);
 612  901
         }
 613  
     }
 614  
 
 615  
     /**
 616  
      * Node extension with addXXX methods to parse the typed data passed by the SAX handler.
 617  
      * <b>Do not use this class !</b> It is used internally by XMLPropertyConfiguration
 618  
      * to parse the configuration file, it may be removed at any moment in the future.
 619  
      */
 620  654
     public static class PListNode extends Node
 621  
     {
 622  
         /**
 623  
          * The serial version UID.
 624  
          */
 625  
         private static final long serialVersionUID = -7614060264754798317L;
 626  
 
 627  
         /**
 628  
          * The MacOS FORMAT of dates in plist files. Note: Because
 629  
          * {@code SimpleDateFormat} is not thread-safe, each access has to be
 630  
          * synchronized.
 631  
          */
 632  1
         private static final DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
 633  
         static
 634  
         {
 635  1
             FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
 636  
         }
 637  
 
 638  
         /**
 639  
          * The GNUstep FORMAT of dates in plist files. Note: Because
 640  
          * {@code SimpleDateFormat} is not thread-safe, each access has to be
 641  
          * synchronized.
 642  
          */
 643  1
         private static final DateFormat GNUSTEP_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
 644  
 
 645  
         /**
 646  
          * Update the value of the node. If the existing value is null, it's
 647  
          * replaced with the new value. If the existing value is a list, the
 648  
          * specified value is appended to the list. If the existing value is
 649  
          * not null, a list with the two values is built.
 650  
          *
 651  
          * @param value the value to be added
 652  
          */
 653  
         public void addValue(Object value)
 654  
         {
 655  362
             if (getValue() == null)
 656  
             {
 657  362
                 setValue(value);
 658  
             }
 659  0
             else if (getValue() instanceof Collection)
 660  
             {
 661  
                 // This is safe because we create the collections ourselves
 662  
                 @SuppressWarnings("unchecked")
 663  0
                 Collection<Object> collection = (Collection<Object>) getValue();
 664  0
                 collection.add(value);
 665  0
             }
 666  
             else
 667  
             {
 668  0
                 List<Object> list = new ArrayList<Object>();
 669  0
                 list.add(getValue());
 670  0
                 list.add(value);
 671  0
                 setValue(list);
 672  
             }
 673  362
         }
 674  
 
 675  
         /**
 676  
          * Parse the specified string as a date and add it to the values of the node.
 677  
          *
 678  
          * @param value the value to be added
 679  
          * @throws IllegalArgumentException if the date string cannot be parsed
 680  
          */
 681  
         public void addDateValue(String value)
 682  
         {
 683  
             try
 684  
             {
 685  43
                 if (value.indexOf(' ') != -1)
 686  
                 {
 687  
                     // parse the date using the GNUstep FORMAT
 688  20
                     synchronized (GNUSTEP_FORMAT)
 689  
                     {
 690  20
                         addValue(GNUSTEP_FORMAT.parse(value));
 691  19
                     }
 692  
                 }
 693  
                 else
 694  
                 {
 695  
                     // parse the date using the MacOS X FORMAT
 696  23
                     synchronized (FORMAT)
 697  
                     {
 698  23
                         addValue(FORMAT.parse(value));
 699  23
                     }
 700  
                 }
 701  
             }
 702  1
             catch (ParseException e)
 703  
             {
 704  1
                 throw new IllegalArgumentException(String.format(
 705  
                         "'%s' cannot be parsed to a date!", value), e);
 706  42
             }
 707  42
         }
 708  
 
 709  
         /**
 710  
          * Parse the specified string as a byte array in base 64 FORMAT
 711  
          * and add it to the values of the node.
 712  
          *
 713  
          * @param value the value to be added
 714  
          */
 715  
         public void addDataValue(String value)
 716  
         {
 717  23
             addValue(Base64.decodeBase64(value.getBytes()));
 718  23
         }
 719  
 
 720  
         /**
 721  
          * Parse the specified string as an Interger and add it to the values of the node.
 722  
          *
 723  
          * @param value the value to be added
 724  
          */
 725  
         public void addIntegerValue(String value)
 726  
         {
 727  21
             addValue(new BigInteger(value));
 728  21
         }
 729  
 
 730  
         /**
 731  
          * Parse the specified string as a Double and add it to the values of the node.
 732  
          *
 733  
          * @param value the value to be added
 734  
          */
 735  
         public void addRealValue(String value)
 736  
         {
 737  21
             addValue(new BigDecimal(value));
 738  21
         }
 739  
 
 740  
         /**
 741  
          * Add a boolean value 'true' to the values of the node.
 742  
          */
 743  
         public void addTrueValue()
 744  
         {
 745  21
             addValue(Boolean.TRUE);
 746  21
         }
 747  
 
 748  
         /**
 749  
          * Add a boolean value 'false' to the values of the node.
 750  
          */
 751  
         public void addFalseValue()
 752  
         {
 753  21
             addValue(Boolean.FALSE);
 754  21
         }
 755  
 
 756  
         /**
 757  
          * Add a sublist to the values of the node.
 758  
          *
 759  
          * @param node the node whose value will be added to the current node value
 760  
          */
 761  
         public void addList(ArrayNode node)
 762  
         {
 763  111
             addValue(node.getValue());
 764  111
         }
 765  
     }
 766  
 
 767  
     /**
 768  
      * Container for array elements. <b>Do not use this class !</b>
 769  
      * It is used internally by XMLPropertyConfiguration to parse the
 770  
      * configuration file, it may be removed at any moment in the future.
 771  
      */
 772  111
     public static class ArrayNode extends PListNode
 773  
     {
 774  
         /**
 775  
          * The serial version UID.
 776  
          */
 777  
         private static final long serialVersionUID = 5586544306664205835L;
 778  
 
 779  
         /** The list of values in the array. */
 780  111
         private List<Object> list = new ArrayList<Object>();
 781  
 
 782  
         /**
 783  
          * Add an object to the array.
 784  
          *
 785  
          * @param value the value to be added
 786  
          */
 787  
         @Override
 788  
         public void addValue(Object value)
 789  
         {
 790  239
             list.add(value);
 791  239
         }
 792  
 
 793  
         /**
 794  
          * Return the list of values in the array.
 795  
          *
 796  
          * @return the {@link List} of values
 797  
          */
 798  
         @Override
 799  
         public Object getValue()
 800  
         {
 801  111
             return list;
 802  
         }
 803  
     }
 804  
 }