1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 = <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
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
163 if (indentLevel > 0)
164 {
165 out.println();
166 }
167
168 out.println(padding + "{");
169
170
171 it = children.iterator();
172 while (it.hasNext())
173 {
174 Node child = (Node) it.next();
175
176 printNode(out, indentLevel + 1, child);
177
178
179 Object value = child.getValue();
180 if (value != null && !(value instanceof Map) && !(value instanceof Configuration))
181 {
182 out.println(";");
183 }
184
185
186 if (it.hasNext() && (value == null || value instanceof List))
187 {
188 out.println();
189 }
190 }
191
192 out.print(padding + "}");
193
194
195 if (node.getParent() != null)
196 {
197 out.println();
198 }
199 }
200 else
201 {
202
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
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
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 }