Coverage report

  %line %branch
org.apache.commons.configuration.plist.XMLPropertyListConfiguration$PListNode
79% 
95% 

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

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.