View Javadoc

    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     {
    134     }
    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         super(fileName);
    145     }
    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         super(file);
    156     }
    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         super(url);
    167     }
    168
    169     public void load(Reader in) throws ConfigurationException
    170     {
    171         // set up the digester
    172         Digester digester = new Digester();
    173
    174         // set up the DTD validation
    175         digester.setEntityResolver(new EntityResolver()
    176         {
    177             public InputSource resolveEntity(String publicId, String systemId)
    178             {
    179                 return new InputSource(getClass().getClassLoader().getResourceAsStream("PropertyList-1.0.dtd"));
    180             }
    181         });
    182         digester.setValidating(true);
    183
    184         // dictionary rules
    185         digester.addRule("*/key", new ObjectCreateRule(PListNode.class)
    186         {
    187             public void end() throws Exception
    188             {
    189                 // leave the node on the stack to set the value
    190             }
    191         });
    192
    193         digester.addCallMethod("*/key", "setName", 0);
    194
    195         digester.addRule("*/dict/string", new SetNextAndPopRule("addChild"));
    196         digester.addRule("*/dict/data", new SetNextAndPopRule("addChild"));
    197         digester.addRule("*/dict/integer", new SetNextAndPopRule("addChild"));
    198         digester.addRule("*/dict/real", new SetNextAndPopRule("addChild"));
    199         digester.addRule("*/dict/true", new SetNextAndPopRule("addChild"));
    200         digester.addRule("*/dict/false", new SetNextAndPopRule("addChild"));
    201         digester.addRule("*/dict/date", new SetNextAndPopRule("addChild"));
    202         digester.addRule("*/dict/dict", new SetNextAndPopRule("addChild"));
    203
    204         digester.addCallMethod("*/dict/string", "addValue", 0);
    205         digester.addCallMethod("*/dict/data", "addDataValue", 0);
    206         digester.addCallMethod("*/dict/integer", "addIntegerValue", 0);
    207         digester.addCallMethod("*/dict/real", "addRealValue", 0);
    208         digester.addCallMethod("*/dict/true", "addTrueValue");
    209         digester.addCallMethod("*/dict/false", "addFalseValue");
    210         digester.addCallMethod("*/dict/date", "addDateValue", 0);
    211
    212         // rules for arrays
    213         digester.addRule("*/dict/array", new SetNextAndPopRule("addChild"));
    214         digester.addRule("*/dict/array", new ObjectCreateRule(ArrayNode.class));
    215         digester.addSetNext("*/dict/array", "addList");
    216
    217         digester.addRule("*/array/array", new ObjectCreateRule(ArrayNode.class));
    218         digester.addSetNext("*/array/array", "addList");
    219
    220         digester.addCallMethod("*/array/string", "addValue", 0);
    221         digester.addCallMethod("*/array/data", "addDataValue", 0);
    222         digester.addCallMethod("*/array/integer", "addIntegerValue", 0);
    223         digester.addCallMethod("*/array/real", "addRealValue", 0);
    224         digester.addCallMethod("*/array/true", "addTrueValue");
    225         digester.addCallMethod("*/array/false", "addFalseValue");
    226         digester.addCallMethod("*/array/date", "addDateValue", 0);
    227
    228         // rule for a dictionary in an array
    229         digester.addFactoryCreate("*/array/dict", new AbstractObjectCreationFactory()
    230         {
    231             public Object createObject(Attributes attributes) throws Exception
    232             {
    233                 // create the configuration
    234                 XMLPropertyListConfiguration config = new XMLPropertyListConfiguration();
    235
    236                 // add it to the ArrayNode
    237                 ArrayNode node = (ArrayNode) getDigester().peek();
    238                 node.addValue(config);
    239
    240                 // push the root on the stack
    241                 return config.getRoot();
    242             }
    243         });
    244
    245         // parse the file
    246         digester.push(getRoot());
    247         try
    248         {
    249             digester.parse(in);
    250         }
    251         catch (Exception e)
    252         {
    253             throw new ConfigurationException("Unable to parse the configuration file", e);
    254         }
    255     }
    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         {
    266             super(methodName);
    267         }
    268
    269         public void end(String namespace, String name) throws Exception
    270         {
    271             super.end(namespace, name);
    272             digester.pop();
    273         }
    274     }
    275
    276     public void save(Writer out) throws ConfigurationException
    277     {
    278         PrintWriter writer = new PrintWriter(out);
    279
    280         if (getEncoding() != null)
    281         {
    282             writer.println("<?xml version=\"1.0\" encoding=\"" + getEncoding() + "\"?>");
    283         }
    284         else
    285         {
    286             writer.println("<?xml version=\"1.0\"?>");
    287         }
    288
    289         writer.println("<!DOCTYPE plist SYSTEM \"file://localhost/System/Library/DTDs/PropertyList.dtd\">");
    290         writer.println("<plist version=\"1.0\">");
    291
    292         printNode(writer, 1, getRoot());
    293
    294         writer.println("</plist>");
    295         writer.flush();
    296     }
    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         String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
    304
    305         if (node.getName() != null)
    306         {
    307             out.println(padding + "<key>" + StringEscapeUtils.escapeXml(node.getName()) + "</key>");
    308         }
    309
    310         List children = node.getChildren();
    311         if (!children.isEmpty())
    312         {
    313             out.println(padding + "<dict>");
    314
    315             Iterator it = children.iterator();
    316             while (it.hasNext())
    317             {
    318                 Node child = (Node) it.next();
    319                 printNode(out, indentLevel + 1, child);
    320
    321                 if (it.hasNext())
    322                 {
    323                     out.println();
    324                 }
    325             }
    326
    327             out.println(padding + "</dict>");
    328         }
    329         else
    330         {
    331             Object value = node.getValue();
    332             printValue(out, indentLevel, value);
    333         }
    334     }
    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         String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
    342
    343         if (value instanceof Date)
    344         {
    345             out.println(padding + "<date>" + PListNode.format.format((Date) value) + "</date>");
    346         }
    347         else if (value instanceof Calendar)
    348         {
    349             printValue(out, indentLevel, ((Calendar) value).getTime());
    350         }
    351         else if (value instanceof Number)
    352         {
    353             if (value instanceof Double || value instanceof Float || value instanceof BigDecimal)
    354             {
    355                 out.println(padding + "<real>" + value.toString() + "</real>");
    356             }
    357             else
    358             {
    359                 out.println(padding + "<integer>" + value.toString() + "</integer>");
    360             }
    361         }
    362         else if (value instanceof Boolean)
    363         {
    364             if (((Boolean) value).booleanValue())
    365             {
    366                 out.println(padding + "<true/>");
    367             }
    368             else
    369             {
    370                 out.println(padding + "<false/>");
    371             }
    372         }
    373         else if (value instanceof List)
    374         {
    375             out.println(padding + "<array>");
    376             Iterator it = ((List) value).iterator();
    377             while (it.hasNext())
    378             {
    379                 printValue(out, indentLevel + 1, it.next());
    380             }
    381             out.println(padding + "</array>");
    382         }
    383         else if (value instanceof HierarchicalConfiguration)
    384         {
    385             printNode(out, indentLevel, ((HierarchicalConfiguration) value).getRoot());
    386         }
    387         else if (value instanceof Configuration)
    388         {
    389             // display a flat Configuration as a dictionary
    390             out.println(padding + "<dict>");
    391
    392             Configuration config = (Configuration) value;
    393             Iterator it = config.getKeys();
    394             while (it.hasNext())
    395             {
    396                 // create a node for each property
    397                 String key = (String) it.next();
    398                 Node node = new Node(key);
    399                 node.setValue(config.getProperty(key));
    400
    401                 // print the node
    402                 printNode(out, indentLevel + 1, node);
    403
    404                 if (it.hasNext())
    405                 {
    406                     out.println();
    407                 }
    408             }
    409             out.println(padding + "</dict>");
    410         }
    411         else if (value instanceof Map)
    412         {
    413             // display a Map as a dictionary
    414             Map map = (Map) value;
    415             printValue(out, indentLevel, new MapConfiguration(map));
    416         }
    417         else if (value instanceof byte[])
    418         {
    419             String base64 = new String(Base64.encodeBase64((byte[]) value));
    420             out.println(padding + "<data>" + StringEscapeUtils.escapeXml(base64) + "</data>");
    421         }
    422         else
    423         {
    424             out.println(padding + "<string>" + StringEscapeUtils.escapeXml(String.valueOf(value)) + "</string>");
    425         }
    426     }
    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     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         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             if (getValue() == null)
    455             {
    456                 setValue(value);
    457             }
    458             else if (getValue() instanceof List)
    459             {
    460                 List list = (List) getValue();
    461                 list.add(value);
    462             }
    463             else
    464             {
    465                 List list = new ArrayList();
    466                 list.add(getValue());
    467                 list.add(value);
    468                 setValue(list);
    469             }
    470         }
    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                 addValue(format.parse(value));
    482             }
    483             catch (ParseException e)
    484             {
    485                 e.printStackTrace();
    486             }
    487         }
    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             addValue(Base64.decodeBase64(value.getBytes()));
    498         }
    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             addValue(new Integer(value));
    508         }
    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             addValue(new Double(value));
    518         }
    519
    520         /***
    521          * Add a boolean value 'true' to the values of the node.
    522          */
    523         public void addTrueValue()
    524         {
    525             addValue(Boolean.TRUE);
    526         }
    527
    528         /***
    529          * Add a boolean value 'false' to the values of the node.
    530          */
    531         public void addFalseValue()
    532         {
    533             addValue(Boolean.FALSE);
    534         }
    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             addValue(node.getValue());
    544         }
    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     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         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             list.add(value);
    570         }
    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             return list;
    580         }
    581     }
    582 }