1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 = <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
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
181 if (indentLevel > 0)
182 {
183 out.println();
184 }
185
186 out.println(padding + "{");
187
188
189 it = children.iterator();
190 while (it.hasNext())
191 {
192 Node child = (Node) it.next();
193
194 printNode(out, indentLevel + 1, child);
195
196
197 Object value = child.getValue();
198 if (value != null && !(value instanceof Map) && !(value instanceof Configuration))
199 {
200 out.println(";");
201 }
202
203
204 if (it.hasNext() && (value == null || value instanceof List))
205 {
206 out.println();
207 }
208 }
209
210 out.print(padding + "}");
211
212
213 if (node.getParent() != null)
214 {
215 out.println();
216 }
217 }
218 else
219 {
220
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
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
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 }