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: 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
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
169 if (indentLevel > 0)
170 {
171 out.println();
172 }
173
174 out.println(padding + "{");
175
176
177 it = children.iterator();
178 while (it.hasNext())
179 {
180 Node child = (Node) it.next();
181
182 printNode(out, indentLevel + 1, child);
183
184
185 Object value = child.getValue();
186 if (value != null && !(value instanceof Map) && !(value instanceof Configuration))
187 {
188 out.println(";");
189 }
190
191
192 if (it.hasNext() && (value == null || value instanceof List))
193 {
194 out.println();
195 }
196 }
197
198 out.print(padding + "}");
199
200
201 if (node.getParent() != null)
202 {
203 out.println();
204 }
205 }
206 else
207 {
208
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
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
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 }