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