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 }