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: 492216 $, $Date: 2007-01-03 17:51:24 +0100 (Mi, 03 Jan 2007) $
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 a new instance of <code>PropertyListConfiguration</code> and
89       * copies the content of the specified configuration into this object.
90       *
91       * @param c the configuration to copy
92       * @since 1.4
93       */
94      public PropertyListConfiguration(HierarchicalConfiguration c)
95      {
96          super(c);
97      }
98  
99      /***
100      * Creates and loads the property list from the specified file.
101      *
102      * @param fileName The name of the plist file to load.
103      * @throws ConfigurationException Error while loading the plist file
104      */
105     public PropertyListConfiguration(String fileName) throws ConfigurationException
106     {
107         super(fileName);
108     }
109 
110     /***
111      * Creates and loads the property list from the specified file.
112      *
113      * @param file The plist file to load.
114      * @throws ConfigurationException Error while loading the plist file
115      */
116     public PropertyListConfiguration(File file) throws ConfigurationException
117     {
118         super(file);
119     }
120 
121     /***
122      * Creates and loads the property list from the specified URL.
123      *
124      * @param url The location of the plist file to load.
125      * @throws ConfigurationException Error while loading the plist file
126      */
127     public PropertyListConfiguration(URL url) throws ConfigurationException
128     {
129         super(url);
130     }
131 
132     public void load(Reader in) throws ConfigurationException
133     {
134         PropertyListParser parser = new PropertyListParser(in);
135         try
136         {
137 
138             HierarchicalConfiguration config = parser.parse();
139             setRoot(config.getRoot());
140         }
141         catch (ParseException e)
142         {
143             throw new ConfigurationException(e);
144         }
145     }
146 
147     public void save(Writer out) throws ConfigurationException
148     {
149         PrintWriter writer = new PrintWriter(out);
150         printNode(writer, 0, getRoot());
151         writer.flush();
152     }
153 
154     /***
155      * Append a node to the writer, indented according to a specific level.
156      */
157     private void printNode(PrintWriter out, int indentLevel, Node node)
158     {
159         String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
160 
161         if (node.getName() != null)
162         {
163             out.print(padding + quoteString(node.getName()) + " = ");
164         }
165 
166         // get all non trivial nodes
167         List children = new ArrayList(node.getChildren());
168         Iterator it = children.iterator();
169         while (it.hasNext())
170         {
171             Node child = (Node) it.next();
172             if (child.getValue() == null && (child.getChildren() == null || child.getChildren().isEmpty()))
173             {
174                 it.remove();
175             }
176         }
177 
178         if (!children.isEmpty())
179         {
180             // skip a line, except for the root dictionary
181             if (indentLevel > 0)
182             {
183                 out.println();
184             }
185 
186             out.println(padding + "{");
187 
188             // display the children
189             it = children.iterator();
190             while (it.hasNext())
191             {
192                 Node child = (Node) it.next();
193 
194                 printNode(out, indentLevel + 1, child);
195 
196                 // add a semi colon for elements that are not dictionaries
197                 Object value = child.getValue();
198                 if (value != null && !(value instanceof Map) && !(value instanceof Configuration))
199                 {
200                     out.println(";");
201                 }
202 
203                 // skip a line after arrays and dictionaries
204                 if (it.hasNext() && (value == null || value instanceof List))
205                 {
206                     out.println();
207                 }
208             }
209 
210             out.print(padding + "}");
211 
212             // line feed if the dictionary is not in an array
213             if (node.getParent() != null)
214             {
215                 out.println();
216             }
217         }
218         else
219         {
220             // display the leaf value
221             Object value = node.getValue();
222             printValue(out, indentLevel, value);
223         }
224     }
225 
226     /***
227      * Append a value to the writer, indented according to a specific level.
228      */
229     private void printValue(PrintWriter out, int indentLevel, Object value)
230     {
231         String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
232 
233         if (value instanceof List)
234         {
235             out.print("( ");
236             Iterator it = ((List) value).iterator();
237             while (it.hasNext())
238             {
239                 printValue(out, indentLevel + 1, it.next());
240                 if (it.hasNext())
241                 {
242                     out.print(", ");
243                 }
244             }
245             out.print(" )");
246         }
247         else if (value instanceof HierarchicalConfiguration)
248         {
249             printNode(out, indentLevel, ((HierarchicalConfiguration) value).getRoot());
250         }
251         else if (value instanceof Configuration)
252         {
253             // display a flat Configuration as a dictionary
254             out.println();
255             out.println(padding + "{");
256 
257             Configuration config = (Configuration) value;
258             Iterator it = config.getKeys();
259             while (it.hasNext())
260             {
261                 String key = (String) it.next();
262                 Node node = new Node(key);
263                 node.setValue(config.getProperty(key));
264 
265                 printNode(out, indentLevel + 1, node);
266                 out.println(";");
267             }
268             out.println(padding + "}");
269         }
270         else if (value instanceof Map)
271         {
272             // display a Map as a dictionary
273             Map map = (Map) value;
274             printValue(out, indentLevel, new MapConfiguration(map));
275         }
276         else if (value instanceof byte[])
277         {
278             out.print("<" + new String(Hex.encodeHex((byte[]) value)) + ">");
279         }
280         else if (value != null)
281         {
282             out.print(quoteString(String.valueOf(value)));
283         }
284     }
285 
286     /***
287      * Quote the specified string if necessary, that's if the string contains:
288      * <ul>
289      *   <li>a space character (' ', '\t', '\r', '\n')</li>
290      *   <li>a quote '"'</li>
291      *   <li>special characters in plist files ('(', ')', '{', '}', '=', ';', ',')</li>
292      * </ul>
293      * Quotes within the string are escaped.
294      *
295      * <p>Examples:</p>
296      * <ul>
297      *   <li>abcd -> abcd</li>
298      *   <li>ab cd -> "ab cd"</li>
299      *   <li>foo"bar -> "foo\"bar"</li>
300      *   <li>foo;bar -> "foo;bar"</li>
301      * </ul>
302      */
303     String quoteString(String s)
304     {
305         if (s == null)
306         {
307             return null;
308         }
309 
310         if (s.indexOf(' ') != -1
311                 || s.indexOf('\t') != -1
312                 || s.indexOf('\r') != -1
313                 || s.indexOf('\n') != -1
314                 || s.indexOf('"') != -1
315                 || s.indexOf('(') != -1
316                 || s.indexOf(')') != -1
317                 || s.indexOf('{') != -1
318                 || s.indexOf('}') != -1
319                 || s.indexOf('=') != -1
320                 || s.indexOf(',') != -1
321                 || s.indexOf(';') != -1)
322         {
323             s = StringUtils.replace(s, "\"", "//\"");
324             s = "\"" + s + "\"";
325         }
326 
327         return s;
328     }
329 }