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.net.URL;
    25  import java.util.ArrayList;
    26  import java.util.Iterator;
    27  import java.util.List;
    28  import java.util.Map;
    29
    30  import org.apache.commons.codec.binary.Hex;
    31  import org.apache.commons.configuration.AbstractHierarchicalFileConfiguration;
    32  import org.apache.commons.configuration.Configuration;
    33  import org.apache.commons.configuration.ConfigurationException;
    34  import org.apache.commons.configuration.HierarchicalConfiguration;
    35  import org.apache.commons.configuration.MapConfiguration;
    36  import org.apache.commons.lang.StringUtils;
    37
    38  /***
    39   * NeXT / OpenStep style configuration.
    40   * (http://developer.apple.com/documentation/Cocoa/Conceptual/PropertyLists/Concepts/OldStylePListsConcept.html)
    41   *
    42   * <p>Example:</p>
    43   * <pre>
    44   * {
    45   *     foo = "bar";
    46   *
    47   *     array = ( value1, value2, value3 );
    48   *
    49   *     data = &lt;4f3e0145ab>;
    50   *
    51   *     nested =
    52   *     {
    53   *         key1 = value1;
    54   *         key2 = value;
    55   *         nested =
    56   *         {
    57   *             foo = bar
    58   *         }
    59   *     }
    60   * }
    61   * </pre>
    62   *
    63   * @since 1.2
    64   *
    65   * @author Emmanuel Bourg
    66   * @version $Revision: 439648 $, $Date: 2006-09-02 22:42:10 +0200 (Sa, 02 Sep 2006) $
    67   */
    68  public class PropertyListConfiguration extends AbstractHierarchicalFileConfiguration
    69  {
    70      /***
    71       * The serial version UID.
    72       */
    73      private static final long serialVersionUID = 3227248503779092127L;
    74
    75      /*** Size of the indentation for the generated file. */
    76      private static final int INDENT_SIZE = 4;
    77
    78      /***
    79       * Creates an empty PropertyListConfiguration object which can be
    80       * used to synthesize a new plist file by adding values and
    81       * then saving().
    82       */
    83      public PropertyListConfiguration()
    84      {
    85      }
    86
    87      /***
    88       * Creates and loads the property list from the specified file.
    89       *
    90       * @param fileName The name of the plist file to load.
    91       * @throws ConfigurationException Error while loading the plist file
    92       */
    93      public PropertyListConfiguration(String fileName) throws ConfigurationException
    94      {
    95          super(fileName);
    96      }
    97
    98      /***
    99       * Creates and loads the property list from the specified file.
    100      *
    101      * @param file The plist file to load.
    102      * @throws ConfigurationException Error while loading the plist file
    103      */
    104     public PropertyListConfiguration(File file) throws ConfigurationException
    105     {
    106         super(file);
    107     }
    108
    109     /***
    110      * Creates and loads the property list from the specified URL.
    111      *
    112      * @param url The location of the plist file to load.
    113      * @throws ConfigurationException Error while loading the plist file
    114      */
    115     public PropertyListConfiguration(URL url) throws ConfigurationException
    116     {
    117         super(url);
    118     }
    119
    120     public void load(Reader in) throws ConfigurationException
    121     {
    122         PropertyListParser parser = new PropertyListParser(in);
    123         try
    124         {
    125
    126             HierarchicalConfiguration config = parser.parse();
    127             setRoot(config.getRoot());
    128         }
    129         catch (ParseException e)
    130         {
    131             throw new ConfigurationException(e);
    132         }
    133     }
    134
    135     public void save(Writer out) throws ConfigurationException
    136     {
    137         PrintWriter writer = new PrintWriter(out);
    138         printNode(writer, 0, getRoot());
    139         writer.flush();
    140     }
    141
    142     /***
    143      * Append a node to the writer, indented according to a specific level.
    144      */
    145     private void printNode(PrintWriter out, int indentLevel, Node node)
    146     {
    147         String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
    148
    149         if (node.getName() != null)
    150         {
    151             out.print(padding + quoteString(node.getName()) + " = ");
    152         }
    153
    154         // get all non trivial nodes
    155         List children = new ArrayList(node.getChildren());
    156         Iterator it = children.iterator();
    157         while (it.hasNext())
    158         {
    159             Node child = (Node) it.next();
    160             if (child.getValue() == null && (child.getChildren() == null || child.getChildren().isEmpty()))
    161             {
    162                 it.remove();
    163             }
    164         }
    165
    166         if (!children.isEmpty())
    167         {
    168             // skip a line, except for the root dictionary
    169             if (indentLevel > 0)
    170             {
    171                 out.println();
    172             }
    173
    174             out.println(padding + "{");
    175
    176             // display the children
    177             it = children.iterator();
    178             while (it.hasNext())
    179             {
    180                 Node child = (Node) it.next();
    181
    182                 printNode(out, indentLevel + 1, child);
    183
    184                 // add a semi colon for elements that are not dictionaries
    185                 Object value = child.getValue();
    186                 if (value != null && !(value instanceof Map) && !(value instanceof Configuration))
    187                 {
    188                     out.println(";");
    189                 }
    190
    191                 // skip a line after arrays and dictionaries
    192                 if (it.hasNext() && (value == null || value instanceof List))
    193                 {
    194                     out.println();
    195                 }
    196             }
    197
    198             out.print(padding + "}");
    199
    200             // line feed if the dictionary is not in an array
    201             if (node.getParent() != null)
    202             {
    203                 out.println();
    204             }
    205         }
    206         else
    207         {
    208             // display the leaf value
    209             Object value = node.getValue();
    210             printValue(out, indentLevel, value);
    211         }
    212     }
    213
    214     /***
    215      * Append a value to the writer, indented according to a specific level.
    216      */
    217     private void printValue(PrintWriter out, int indentLevel, Object value)
    218     {
    219         String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
    220
    221         if (value instanceof List)
    222         {
    223             out.print("( ");
    224             Iterator it = ((List) value).iterator();
    225             while (it.hasNext())
    226             {
    227                 printValue(out, indentLevel + 1, it.next());
    228                 if (it.hasNext())
    229                 {
    230                     out.print(", ");
    231                 }
    232             }
    233             out.print(" )");
    234         }
    235         else if (value instanceof HierarchicalConfiguration)
    236         {
    237             printNode(out, indentLevel, ((HierarchicalConfiguration) value).getRoot());
    238         }
    239         else if (value instanceof Configuration)
    240         {
    241             // display a flat Configuration as a dictionary
    242             out.println();
    243             out.println(padding + "{");
    244
    245             Configuration config = (Configuration) value;
    246             Iterator it = config.getKeys();
    247             while (it.hasNext())
    248             {
    249                 String key = (String) it.next();
    250                 Node node = new Node(key);
    251                 node.setValue(config.getProperty(key));
    252
    253                 printNode(out, indentLevel + 1, node);
    254                 out.println(";");
    255             }
    256             out.println(padding + "}");
    257         }
    258         else if (value instanceof Map)
    259         {
    260             // display a Map as a dictionary
    261             Map map = (Map) value;
    262             printValue(out, indentLevel, new MapConfiguration(map));
    263         }
    264         else if (value instanceof byte[])
    265         {
    266             out.print("<" + new String(Hex.encodeHex((byte[]) value)) + ">");
    267         }
    268         else if (value != null)
    269         {
    270             out.print(quoteString(String.valueOf(value)));
    271         }
    272     }
    273
    274     /***
    275      * Quote the specified string if necessary, that's if the string contains:
    276      * <ul>
    277      *   <li>a space character (' ', '\t', '\r', '\n')</li>
    278      *   <li>a quote '"'</li>
    279      *   <li>special characters in plist files ('(', ')', '{', '}', '=', ';', ',')</li>
    280      * </ul>
    281      * Quotes within the string are escaped.
    282      *
    283      * <p>Examples:</p>
    284      * <ul>
    285      *   <li>abcd -> abcd</li>
    286      *   <li>ab cd -> "ab cd"</li>
    287      *   <li>foo"bar -> "foo\"bar"</li>
    288      *   <li>foo;bar -> "foo;bar"</li>
    289      * </ul>
    290      */
    291     String quoteString(String s)
    292     {
    293         if (s == null)
    294         {
    295             return null;
    296         }
    297
    298         if (s.indexOf(' ') != -1
    299                 || s.indexOf('\t') != -1
    300                 || s.indexOf('\r') != -1
    301                 || s.indexOf('\n') != -1
    302                 || s.indexOf('"') != -1
    303                 || s.indexOf('(') != -1
    304                 || s.indexOf(')') != -1
    305                 || s.indexOf('{') != -1
    306                 || s.indexOf('}') != -1
    307                 || s.indexOf('=') != -1
    308                 || s.indexOf(',') != -1
    309                 || s.indexOf(';') != -1)
    310         {
    311             s = StringUtils.replace(s, "\"", "//\"");
    312             s = "\"" + s + "\"";
    313         }
    314
    315         return s;
    316     }
    317 }