Coverage Report - org.apache.commons.configuration.plist.XMLPropertyListConfiguration
 
Classes in this File Line Coverage Branch Coverage Complexity
XMLPropertyListConfiguration
82%
93/113
91%
20/22
2,043
XMLPropertyListConfiguration$1
100%
2/2
N/A
2,043
XMLPropertyListConfiguration$2
100%
2/2
N/A
2,043
XMLPropertyListConfiguration$3
100%
5/5
N/A
2,043
XMLPropertyListConfiguration$ArrayNode
100%
5/5
N/A
2,043
XMLPropertyListConfiguration$PListNode
71%
20/28
50%
1/2
2,043
XMLPropertyListConfiguration$SetNextAndPopRule
100%
6/6
N/A
2,043
 
 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.net.URL;
 26  
 import java.text.DateFormat;
 27  
 import java.text.ParseException;
 28  
 import java.text.SimpleDateFormat;
 29  
 import java.util.ArrayList;
 30  
 import java.util.Calendar;
 31  
 import java.util.Date;
 32  
 import java.util.Iterator;
 33  
 import java.util.List;
 34  
 import java.util.Map;
 35  
 
 36  
 import org.apache.commons.codec.binary.Base64;
 37  
 import org.apache.commons.configuration.AbstractHierarchicalFileConfiguration;
 38  
 import org.apache.commons.configuration.Configuration;
 39  
 import org.apache.commons.configuration.ConfigurationException;
 40  
 import org.apache.commons.configuration.HierarchicalConfiguration;
 41  
 import org.apache.commons.configuration.MapConfiguration;
 42  
 import org.apache.commons.digester.AbstractObjectCreationFactory;
 43  
 import org.apache.commons.digester.Digester;
 44  
 import org.apache.commons.digester.ObjectCreateRule;
 45  
 import org.apache.commons.digester.SetNextRule;
 46  
 import org.apache.commons.lang.StringEscapeUtils;
 47  
 import org.apache.commons.lang.StringUtils;
 48  
 import org.xml.sax.Attributes;
 49  
 import org.xml.sax.EntityResolver;
 50  
 import org.xml.sax.InputSource;
 51  
 
 52  
 /**
 53  
  * Mac OS X configuration file (http://www.apple.com/DTDs/PropertyList-1.0.dtd).
 54  
  *
 55  
  * <p>Example:</p>
 56  
  * <pre>
 57  
  * &lt;?xml version="1.0"?>
 58  
  * &lt;!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
 59  
  * &lt;plist version="1.0">
 60  
  *     &lt;dict>
 61  
  *         &lt;key>string&lt;/key>
 62  
  *         &lt;string>value1&lt;/string>
 63  
  *
 64  
  *         &lt;key>integer&lt;/key>
 65  
  *         &lt;integer>12345&lt;/integer>
 66  
  *
 67  
  *         &lt;key>real&lt;/key>
 68  
  *         &lt;real>-123.45E-1&lt;/real>
 69  
  *
 70  
  *         &lt;key>boolean&lt;/key>
 71  
  *         &lt;true/>
 72  
  *
 73  
  *         &lt;key>date&lt;/key>
 74  
  *         &lt;date>2005-01-01T12:00:00-0700&lt;/date>
 75  
  *
 76  
  *         &lt;key>data&lt;/key>
 77  
  *         &lt;data>RHJhY28gRG9ybWllbnMgTnVucXVhbSBUaXRpbGxhbmR1cw==&lt;/data>
 78  
  *
 79  
  *         &lt;key>array&lt;/key>
 80  
  *         &lt;array>
 81  
  *             &lt;string>value1&lt;/string>
 82  
  *             &lt;string>value2&lt;/string>
 83  
  *             &lt;string>value3&lt;/string>
 84  
  *         &lt;/array>
 85  
  *
 86  
  *         &lt;key>dictionnary&lt;/key>
 87  
  *         &lt;dict>
 88  
  *             &lt;key>key1&lt;/key>
 89  
  *             &lt;string>value1&lt;/string>
 90  
  *             &lt;key>key2&lt;/key>
 91  
  *             &lt;string>value2&lt;/string>
 92  
  *             &lt;key>key3&lt;/key>
 93  
  *             &lt;string>value3&lt;/string>
 94  
  *         &lt;/dict>
 95  
  *
 96  
  *         &lt;key>nested&lt;/key>
 97  
  *         &lt;dict>
 98  
  *             &lt;key>node1&lt;/key>
 99  
  *             &lt;dict>
 100  
  *                 &lt;key>node2&lt;/key>
 101  
  *                 &lt;dict>
 102  
  *                     &lt;key>node3&lt;/key>
 103  
  *                     &lt;string>value&lt;/string>
 104  
  *                 &lt;/dict>
 105  
  *             &lt;/dict>
 106  
  *         &lt;/dict>
 107  
  *
 108  
  *     &lt;/dict>
 109  
  * &lt;/plist>
 110  
  * </pre>
 111  
  *
 112  
  * @since 1.2
 113  
  *
 114  
  * @author Emmanuel Bourg
 115  
  * @version $Revision: 439648 $, $Date: 2006-09-02 22:42:10 +0200 (Sa, 02 Sep 2006) $
 116  
  */
 117  
 public class XMLPropertyListConfiguration extends AbstractHierarchicalFileConfiguration
 118  
 {
 119  
     /**
 120  
      * The serial version UID.
 121  
      */
 122  
     private static final long serialVersionUID = -3162063751042475985L;
 123  
 
 124  
     /** Size of the indentation for the generated file. */
 125  
     private static final int INDENT_SIZE = 4;
 126  
 
 127  
     /**
 128  
      * Creates an empty XMLPropertyListConfiguration object which can be
 129  
      * used to synthesize a new plist file by adding values and
 130  
      * then saving().
 131  
      */
 132  
     public XMLPropertyListConfiguration()
 133  35
     {
 134  35
     }
 135  
 
 136  
     /**
 137  
      * Creates and loads the property list from the specified file.
 138  
      *
 139  
      * @param fileName The name of the plist file to load.
 140  
      * @throws org.apache.commons.configuration.ConfigurationException Error while loading the plist file
 141  
      */
 142  
     public XMLPropertyListConfiguration(String fileName) throws ConfigurationException
 143  
     {
 144  0
         super(fileName);
 145  0
     }
 146  
 
 147  
     /**
 148  
      * Creates and loads the property list from the specified file.
 149  
      *
 150  
      * @param file The plist file to load.
 151  
      * @throws ConfigurationException Error while loading the plist file
 152  
      */
 153  
     public XMLPropertyListConfiguration(File file) throws ConfigurationException
 154  
     {
 155  1
         super(file);
 156  1
     }
 157  
 
 158  
     /**
 159  
      * Creates and loads the property list from the specified URL.
 160  
      *
 161  
      * @param url The location of the plist file to load.
 162  
      * @throws ConfigurationException Error while loading the plist file
 163  
      */
 164  
     public XMLPropertyListConfiguration(URL url) throws ConfigurationException
 165  
     {
 166  0
         super(url);
 167  0
     }
 168  
 
 169  
     public void load(Reader in) throws ConfigurationException
 170  
     {
 171  
         // set up the digester
 172  12
         Digester digester = new Digester();
 173  
 
 174  
         // set up the DTD validation
 175  14
         digester.setEntityResolver(new EntityResolver()
 176  
         {
 177  12
             public InputSource resolveEntity(String publicId, String systemId)
 178  
             {
 179  12
                 return new InputSource(getClass().getClassLoader().getResourceAsStream("PropertyList-1.0.dtd"));
 180  
             }
 181  
         });
 182  12
         digester.setValidating(true);
 183  
 
 184  
         // dictionary rules
 185  12
         digester.addRule("*/key", new ObjectCreateRule(PListNode.class)
 186  
         {
 187  12
             public void end() throws Exception
 188  
             {
 189  
                 // leave the node on the stack to set the value
 190  240
             }
 191  
         });
 192  
 
 193  12
         digester.addCallMethod("*/key", "setName", 0);
 194  
 
 195  12
         digester.addRule("*/dict/string", new SetNextAndPopRule("addChild"));
 196  12
         digester.addRule("*/dict/data", new SetNextAndPopRule("addChild"));
 197  12
         digester.addRule("*/dict/integer", new SetNextAndPopRule("addChild"));
 198  12
         digester.addRule("*/dict/real", new SetNextAndPopRule("addChild"));
 199  12
         digester.addRule("*/dict/true", new SetNextAndPopRule("addChild"));
 200  12
         digester.addRule("*/dict/false", new SetNextAndPopRule("addChild"));
 201  12
         digester.addRule("*/dict/date", new SetNextAndPopRule("addChild"));
 202  12
         digester.addRule("*/dict/dict", new SetNextAndPopRule("addChild"));
 203  
 
 204  12
         digester.addCallMethod("*/dict/string", "addValue", 0);
 205  12
         digester.addCallMethod("*/dict/data", "addDataValue", 0);
 206  12
         digester.addCallMethod("*/dict/integer", "addIntegerValue", 0);
 207  12
         digester.addCallMethod("*/dict/real", "addRealValue", 0);
 208  12
         digester.addCallMethod("*/dict/true", "addTrueValue");
 209  12
         digester.addCallMethod("*/dict/false", "addFalseValue");
 210  12
         digester.addCallMethod("*/dict/date", "addDateValue", 0);
 211  
 
 212  
         // rules for arrays
 213  12
         digester.addRule("*/dict/array", new SetNextAndPopRule("addChild"));
 214  12
         digester.addRule("*/dict/array", new ObjectCreateRule(ArrayNode.class));
 215  12
         digester.addSetNext("*/dict/array", "addList");
 216  
 
 217  12
         digester.addRule("*/array/array", new ObjectCreateRule(ArrayNode.class));
 218  12
         digester.addSetNext("*/array/array", "addList");
 219  
 
 220  12
         digester.addCallMethod("*/array/string", "addValue", 0);
 221  12
         digester.addCallMethod("*/array/data", "addDataValue", 0);
 222  12
         digester.addCallMethod("*/array/integer", "addIntegerValue", 0);
 223  12
         digester.addCallMethod("*/array/real", "addRealValue", 0);
 224  12
         digester.addCallMethod("*/array/true", "addTrueValue");
 225  12
         digester.addCallMethod("*/array/false", "addFalseValue");
 226  12
         digester.addCallMethod("*/array/date", "addDateValue", 0);
 227  
 
 228  
         // rule for a dictionary in an array
 229  12
         digester.addFactoryCreate("*/array/dict", new AbstractObjectCreationFactory()
 230  
         {
 231  12
             public Object createObject(Attributes attributes) throws Exception
 232  
             {
 233  
                 // create the configuration
 234  24
                 XMLPropertyListConfiguration config = new XMLPropertyListConfiguration();
 235  
 
 236  
                 // add it to the ArrayNode
 237  24
                 ArrayNode node = (ArrayNode) getDigester().peek();
 238  24
                 node.addValue(config);
 239  
 
 240  
                 // push the root on the stack
 241  24
                 return config.getRoot();
 242  
             }
 243  
         });
 244  
 
 245  
         // parse the file
 246  12
         digester.push(getRoot());
 247  
         try
 248  
         {
 249  12
             digester.parse(in);
 250  12
         }
 251  
         catch (Exception e)
 252  
         {
 253  0
             throw new ConfigurationException("Unable to parse the configuration file", e);
 254  
         }
 255  12
     }
 256  
 
 257  
     /**
 258  
      * Digester rule that sets the object on the stack to the n-1 object
 259  
      * and remove both of them from the stack. This rule is used to remove
 260  
      * the configuration node from the stack once its value has been parsed.
 261  
      */
 262  
     private class SetNextAndPopRule extends SetNextRule
 263  
     {
 264  
         public SetNextAndPopRule(String methodName)
 265  108
         {
 266  108
             super(methodName);
 267  108
         }
 268  
 
 269  
         public void end(String namespace, String name) throws Exception
 270  
         {
 271  240
             super.end(namespace, name);
 272  240
             digester.pop();
 273  240
         }
 274  
     }
 275  
 
 276  
     public void save(Writer out) throws ConfigurationException
 277  
     {
 278  1
         PrintWriter writer = new PrintWriter(out);
 279  
 
 280  1
         if (getEncoding() != null)
 281  
         {
 282  0
             writer.println("<?xml version=\"1.0\" encoding=\"" + getEncoding() + "\"?>");
 283  
         }
 284  
         else
 285  
         {
 286  1
             writer.println("<?xml version=\"1.0\"?>");
 287  
         }
 288  
 
 289  1
         writer.println("<!DOCTYPE plist SYSTEM \"file://localhost/System/Library/DTDs/PropertyList.dtd\">");
 290  1
         writer.println("<plist version=\"1.0\">");
 291  
 
 292  1
         printNode(writer, 1, getRoot());
 293  
 
 294  1
         writer.println("</plist>");
 295  1
         writer.flush();
 296  1
     }
 297  
 
 298  
     /**
 299  
      * Append a node to the writer, indented according to a specific level.
 300  
      */
 301  
     private void printNode(PrintWriter out, int indentLevel, Node node)
 302  
     {
 303  23
         String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
 304  
 
 305  23
         if (node.getName() != null)
 306  
         {
 307  20
             out.println(padding + "<key>" + StringEscapeUtils.escapeXml(node.getName()) + "</key>");
 308  
         }
 309  
 
 310  23
         List children = node.getChildren();
 311  23
         if (!children.isEmpty())
 312  
         {
 313  7
             out.println(padding + "<dict>");
 314  
 
 315  7
             Iterator it = children.iterator();
 316  34
             while (it.hasNext())
 317  
             {
 318  20
                 Node child = (Node) it.next();
 319  20
                 printNode(out, indentLevel + 1, child);
 320  
 
 321  20
                 if (it.hasNext())
 322  
                 {
 323  13
                     out.println();
 324  
                 }
 325  
             }
 326  
 
 327  7
             out.println(padding + "</dict>");
 328  
         }
 329  
         else
 330  
         {
 331  16
             Object value = node.getValue();
 332  16
             printValue(out, indentLevel, value);
 333  
         }
 334  23
     }
 335  
 
 336  
     /**
 337  
      * Append a value to the writer, indented according to a specific level.
 338  
      */
 339  
     private void printValue(PrintWriter out, int indentLevel, Object value)
 340  
     {
 341  27
         String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
 342  
 
 343  27
         if (value instanceof Date)
 344  
         {
 345  1
             out.println(padding + "<date>" + PListNode.format.format((Date) value) + "</date>");
 346  
         }
 347  26
         else if (value instanceof Calendar)
 348  
         {
 349  0
             printValue(out, indentLevel, ((Calendar) value).getTime());
 350  
         }
 351  26
         else if (value instanceof Number)
 352  
         {
 353  2
             if (value instanceof Double || value instanceof Float || value instanceof BigDecimal)
 354  
             {
 355  1
                 out.println(padding + "<real>" + value.toString() + "</real>");
 356  
             }
 357  
             else
 358  
             {
 359  1
                 out.println(padding + "<integer>" + value.toString() + "</integer>");
 360  
             }
 361  
         }
 362  24
         else if (value instanceof Boolean)
 363  
         {
 364  2
             if (((Boolean) value).booleanValue())
 365  
             {
 366  1
                 out.println(padding + "<true/>");
 367  
             }
 368  
             else
 369  
             {
 370  1
                 out.println(padding + "<false/>");
 371  
             }
 372  
         }
 373  22
         else if (value instanceof List)
 374  
         {
 375  5
             out.println(padding + "<array>");
 376  5
             Iterator it = ((List) value).iterator();
 377  21
             while (it.hasNext())
 378  
             {
 379  11
                 printValue(out, indentLevel + 1, it.next());
 380  
             }
 381  5
             out.println(padding + "</array>");
 382  
         }
 383  17
         else if (value instanceof HierarchicalConfiguration)
 384  
         {
 385  2
             printNode(out, indentLevel, ((HierarchicalConfiguration) value).getRoot());
 386  
         }
 387  15
         else if (value instanceof Configuration)
 388  
         {
 389  
             // display a flat Configuration as a dictionary
 390  0
             out.println(padding + "<dict>");
 391  
 
 392  0
             Configuration config = (Configuration) value;
 393  0
             Iterator it = config.getKeys();
 394  0
             while (it.hasNext())
 395  
             {
 396  
                 // create a node for each property
 397  0
                 String key = (String) it.next();
 398  0
                 Node node = new Node(key);
 399  0
                 node.setValue(config.getProperty(key));
 400  
 
 401  
                 // print the node
 402  0
                 printNode(out, indentLevel + 1, node);
 403  
 
 404  0
                 if (it.hasNext())
 405  
                 {
 406  0
                     out.println();
 407  
                 }
 408  
             }
 409  0
             out.println(padding + "</dict>");
 410  
         }
 411  15
         else if (value instanceof Map)
 412  
         {
 413  
             // display a Map as a dictionary
 414  0
             Map map = (Map) value;
 415  0
             printValue(out, indentLevel, new MapConfiguration(map));
 416  
         }
 417  15
         else if (value instanceof byte[])
 418  
         {
 419  1
             String base64 = new String(Base64.encodeBase64((byte[]) value));
 420  1
             out.println(padding + "<data>" + StringEscapeUtils.escapeXml(base64) + "</data>");
 421  
         }
 422  
         else
 423  
         {
 424  14
             out.println(padding + "<string>" + StringEscapeUtils.escapeXml(String.valueOf(value)) + "</string>");
 425  
         }
 426  27
     }
 427  
 
 428  
 
 429  
     /**
 430  
      * Node extension with addXXX methods to parse the typed data passed by Digester.
 431  
      * <b>Do not use this class !</b> It is used internally by XMLPropertyConfiguration
 432  
      * to parse the configuration file, it may be removed at any moment in the future.
 433  
      */
 434  301
     public static class PListNode extends Node
 435  
     {
 436  
         /**
 437  
          * The serial version UID.
 438  
          */
 439  
         private static final long serialVersionUID = -7614060264754798317L;
 440  
 
 441  
         /** The standard format of dates in plist files. */
 442  1
         private static DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
 443  
 
 444  
         /**
 445  
          * Update the value of the node. If the existing value is null, it's
 446  
          * replaced with the new value. If the existing value is a list, the
 447  
          * specified value is appended to the list. If the existing value is
 448  
          * not null, a list with the two values is built.
 449  
          *
 450  
          * @param value the value to be added
 451  
          */
 452  
         public void addValue(Object value)
 453  
         {
 454  192
             if (getValue() == null)
 455  
             {
 456  192
                 setValue(value);
 457  
             }
 458  0
             else if (getValue() instanceof List)
 459  
             {
 460  0
                 List list = (List) getValue();
 461  0
                 list.add(value);
 462  
             }
 463  
             else
 464  
             {
 465  0
                 List list = new ArrayList();
 466  0
                 list.add(getValue());
 467  0
                 list.add(value);
 468  0
                 setValue(list);
 469  
             }
 470  192
         }
 471  
 
 472  
         /**
 473  
          * Parse the specified string as a date and add it to the values of the node.
 474  
          *
 475  
          * @param value the value to be added
 476  
          */
 477  
         public void addDateValue(String value)
 478  
         {
 479  
             try
 480  
             {
 481  12
                 addValue(format.parse(value));
 482  12
             }
 483  
             catch (ParseException e)
 484  
             {
 485  0
                 e.printStackTrace();
 486  
             }
 487  12
         }
 488  
 
 489  
         /**
 490  
          * Parse the specified string as a byte array in base 64 format
 491  
          * and add it to the values of the node.
 492  
          *
 493  
          * @param value the value to be added
 494  
          */
 495  
         public void addDataValue(String value)
 496  
         {
 497  12
             addValue(Base64.decodeBase64(value.getBytes()));
 498  12
         }
 499  
 
 500  
         /**
 501  
          * Parse the specified string as an Interger and add it to the values of the node.
 502  
          *
 503  
          * @param value the value to be added
 504  
          */
 505  
         public void addIntegerValue(String value)
 506  
         {
 507  12
             addValue(new Integer(value));
 508  12
         }
 509  
 
 510  
         /**
 511  
          * Parse the specified string as a Double and add it to the values of the node.
 512  
          *
 513  
          * @param value the value to be added
 514  
          */
 515  
         public void addRealValue(String value)
 516  
         {
 517  12
             addValue(new Double(value));
 518  12
         }
 519  
 
 520  
         /**
 521  
          * Add a boolean value 'true' to the values of the node.
 522  
          */
 523  
         public void addTrueValue()
 524  
         {
 525  12
             addValue(Boolean.TRUE);
 526  12
         }
 527  
 
 528  
         /**
 529  
          * Add a boolean value 'false' to the values of the node.
 530  
          */
 531  
         public void addFalseValue()
 532  
         {
 533  12
             addValue(Boolean.FALSE);
 534  12
         }
 535  
 
 536  
         /**
 537  
          * Add a sublist to the values of the node.
 538  
          *
 539  
          * @param node the node whose value will be added to the current node value
 540  
          */
 541  
         public void addList(ArrayNode node)
 542  
         {
 543  60
             addValue(node.getValue());
 544  60
         }
 545  
     }
 546  
 
 547  
     /**
 548  
      * Container for array elements. <b>Do not use this class !</b>
 549  
      * It is used internally by XMLPropertyConfiguration to parse the
 550  
      * configuration file, it may be removed at any moment in the future.
 551  
      */
 552  120
     public static class ArrayNode extends PListNode
 553  
     {
 554  
         /**
 555  
          * The serial version UID.
 556  
          */
 557  
         private static final long serialVersionUID = 5586544306664205835L;
 558  
 
 559  
         /** The list of values in the array. */
 560  60
         private List list = new ArrayList();
 561  
 
 562  
         /**
 563  
          * Add an object to the array.
 564  
          *
 565  
          * @param value the value to be added
 566  
          */
 567  
         public void addValue(Object value)
 568  
         {
 569  132
             list.add(value);
 570  132
         }
 571  
 
 572  
         /**
 573  
          * Return the list of values in the array.
 574  
          *
 575  
          * @return the {@link List} of values
 576  
          */
 577  
         public Object getValue()
 578  
         {
 579  60
             return list;
 580  
         }
 581  
     }
 582  
 }