View Javadoc

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