1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.configuration2.plist;
19
20 import java.io.PrintWriter;
21 import java.io.Reader;
22 import java.io.Writer;
23 import java.util.ArrayList;
24 import java.util.Calendar;
25 import java.util.Date;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.TimeZone;
31
32 import org.apache.commons.codec.binary.Hex;
33 import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
34 import org.apache.commons.configuration2.Configuration;
35 import org.apache.commons.configuration2.FileBasedConfiguration;
36 import org.apache.commons.configuration2.HierarchicalConfiguration;
37 import org.apache.commons.configuration2.ImmutableConfiguration;
38 import org.apache.commons.configuration2.MapConfiguration;
39 import org.apache.commons.configuration2.ex.ConfigurationException;
40 import org.apache.commons.configuration2.tree.ImmutableNode;
41 import org.apache.commons.configuration2.tree.InMemoryNodeModel;
42 import org.apache.commons.configuration2.tree.NodeHandler;
43 import org.apache.commons.lang3.StringUtils;
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 public class PropertyListConfiguration extends BaseHierarchicalConfiguration implements FileBasedConfiguration {
87
88
89
90
91
92 private abstract static class DateComponentParser {
93
94
95
96
97
98
99
100
101
102 protected void checkLength(final String s, final int index, final int length) throws ParseException {
103 final int len = s == null ? 0 : s.length();
104 if (index + length > len) {
105 throw new ParseException("Input string too short: " + s + ", index: " + index);
106 }
107 }
108
109
110
111
112
113
114
115
116 public abstract void formatComponent(StringBuilder buf, Calendar cal);
117
118
119
120
121
122
123
124
125 protected void padNum(final StringBuilder buf, final int num, final int length) {
126 buf.append(StringUtils.leftPad(String.valueOf(num), length, PAD_CHAR));
127 }
128
129
130
131
132
133
134
135
136
137
138 public abstract int parseComponent(String s, int index, Calendar cal) throws ParseException;
139 }
140
141
142
143
144
145 private static final class DateFieldParser extends DateComponentParser {
146
147 private final int calendarField;
148
149
150 private final int length;
151
152
153 private final int offset;
154
155
156
157
158
159
160
161 public DateFieldParser(final int calFld, final int len) {
162 this(calFld, len, 0);
163 }
164
165
166
167
168
169
170
171
172 public DateFieldParser(final int calFld, final int len, final int ofs) {
173 calendarField = calFld;
174 length = len;
175 offset = ofs;
176 }
177
178 @Override
179 public void formatComponent(final StringBuilder buf, final Calendar cal) {
180 padNum(buf, cal.get(calendarField) + offset, length);
181 }
182
183 @Override
184 public int parseComponent(final String s, final int index, final Calendar cal) throws ParseException {
185 checkLength(s, index, length);
186 try {
187 cal.set(calendarField, Integer.parseInt(s.substring(index, index + length)) - offset);
188 return length;
189 } catch (final NumberFormatException nfex) {
190 throw new ParseException("Invalid number: " + s + ", index " + index);
191 }
192 }
193 }
194
195
196
197
198 private static final class DateSeparatorParser extends DateComponentParser {
199
200 private final String separator;
201
202
203
204
205
206
207 public DateSeparatorParser(final String sep) {
208 separator = sep;
209 }
210
211 @Override
212 public void formatComponent(final StringBuilder buf, final Calendar cal) {
213 buf.append(separator);
214 }
215
216 @Override
217 public int parseComponent(final String s, final int index, final Calendar cal) throws ParseException {
218 checkLength(s, index, separator.length());
219 if (!s.startsWith(separator, index)) {
220 throw new ParseException("Invalid input: " + s + ", index " + index + ", expected " + separator);
221 }
222 return separator.length();
223 }
224 }
225
226
227
228
229 private static final class DateTimeZoneParser extends DateComponentParser {
230 @Override
231 public void formatComponent(final StringBuilder buf, final Calendar cal) {
232 final TimeZone tz = cal.getTimeZone();
233 int ofs = tz.getRawOffset() / MILLIS_PER_MINUTE;
234 if (ofs < 0) {
235 buf.append('-');
236 ofs = -ofs;
237 } else {
238 buf.append('+');
239 }
240 final int hour = ofs / MINUTES_PER_HOUR;
241 final int min = ofs % MINUTES_PER_HOUR;
242 padNum(buf, hour, 2);
243 padNum(buf, min, 2);
244 }
245
246 @Override
247 public int parseComponent(final String s, final int index, final Calendar cal) throws ParseException {
248 checkLength(s, index, TIME_ZONE_LENGTH);
249 final TimeZone tz = TimeZone.getTimeZone(TIME_ZONE_PREFIX + s.substring(index, index + TIME_ZONE_LENGTH));
250 cal.setTimeZone(tz);
251 return TIME_ZONE_LENGTH;
252 }
253 }
254
255
256 private static final DateComponentParser DATE_SEPARATOR_PARSER = new DateSeparatorParser("-");
257
258
259 private static final DateComponentParser TIME_SEPARATOR_PARSER = new DateSeparatorParser(":");
260
261
262 private static final DateComponentParser BLANK_SEPARATOR_PARSER = new DateSeparatorParser(" ");
263
264
265 private static final DateComponentParser[] DATE_PARSERS = {new DateSeparatorParser("<*D"), new DateFieldParser(Calendar.YEAR, 4), DATE_SEPARATOR_PARSER,
266 new DateFieldParser(Calendar.MONTH, 2, 1), DATE_SEPARATOR_PARSER, new DateFieldParser(Calendar.DATE, 2), BLANK_SEPARATOR_PARSER,
267 new DateFieldParser(Calendar.HOUR_OF_DAY, 2), TIME_SEPARATOR_PARSER, new DateFieldParser(Calendar.MINUTE, 2), TIME_SEPARATOR_PARSER,
268 new DateFieldParser(Calendar.SECOND, 2), BLANK_SEPARATOR_PARSER, new DateTimeZoneParser(), new DateSeparatorParser(">")};
269
270
271 private static final String TIME_ZONE_PREFIX = "GMT";
272
273
274 private static final int MILLIS_PER_MINUTE = 1000 * 60;
275
276
277 private static final int MINUTES_PER_HOUR = 60;
278
279
280 private static final int INDENT_SIZE = 4;
281
282
283 private static final int TIME_ZONE_LENGTH = 5;
284
285
286 private static final char PAD_CHAR = '0';
287
288
289
290
291
292
293
294 static String formatDate(final Calendar cal) {
295 final StringBuilder buf = new StringBuilder();
296
297 for (final DateComponentParser element : DATE_PARSERS) {
298 element.formatComponent(buf, cal);
299 }
300
301 return buf.toString();
302 }
303
304
305
306
307
308
309
310 static String formatDate(final Date date) {
311 final Calendar cal = Calendar.getInstance();
312 cal.setTime(date);
313 return formatDate(cal);
314 }
315
316
317
318
319
320
321
322
323 static Date parseDate(final String s) throws ParseException {
324 final Calendar cal = Calendar.getInstance();
325 cal.clear();
326 int index = 0;
327
328 for (final DateComponentParser parser : DATE_PARSERS) {
329 index += parser.parseComponent(s, index, cal);
330 }
331
332 return cal.getTime();
333 }
334
335
336
337
338
339
340
341
342 private static Map<String, Object> transformMap(final Map<?, ?> src) {
343 final Map<String, Object> dest = new HashMap<>();
344 src.forEach((k, v) -> {
345 if (k instanceof String) {
346 dest.put((String) k, v);
347 }
348 });
349 return dest;
350 }
351
352
353
354
355
356 public PropertyListConfiguration() {
357 }
358
359
360
361
362
363
364
365
366 public PropertyListConfiguration(final HierarchicalConfiguration<ImmutableNode> c) {
367 super(c);
368 }
369
370
371
372
373
374
375 PropertyListConfiguration(final ImmutableNode root) {
376 super(new InMemoryNodeModel(root));
377 }
378
379 @Override
380 protected void addPropertyInternal(final String key, final Object value) {
381 if (value instanceof byte[]) {
382 addPropertyDirect(key, value);
383 } else {
384 super.addPropertyInternal(key, value);
385 }
386 }
387
388
389
390
391 private void printNode(final PrintWriter out, final int indentLevel, final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
392 final String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
393
394 if (node.getNodeName() != null) {
395 out.print(padding + quoteString(node.getNodeName()) + " = ");
396 }
397
398 final List<ImmutableNode> children = new ArrayList<>(node.getChildren());
399 if (!children.isEmpty()) {
400
401 if (indentLevel > 0) {
402 out.println();
403 }
404
405 out.println(padding + "{");
406
407
408 final Iterator<ImmutableNode> it = children.iterator();
409 while (it.hasNext()) {
410 final ImmutableNode child = it.next();
411
412 printNode(out, indentLevel + 1, child, handler);
413
414
415 final Object value = child.getValue();
416 if (value != null && !(value instanceof Map) && !(value instanceof Configuration)) {
417 out.println(";");
418 }
419
420
421 if (it.hasNext() && (value == null || value instanceof List)) {
422 out.println();
423 }
424 }
425
426 out.print(padding + "}");
427
428
429 if (handler.getParent(node) != null) {
430 out.println();
431 }
432 } else if (node.getValue() == null) {
433 out.println();
434 out.print(padding + "{ };");
435
436
437 if (handler.getParent(node) != null) {
438 out.println();
439 }
440 } else {
441
442 final Object value = node.getValue();
443 printValue(out, indentLevel, value);
444 }
445 }
446
447
448
449
450 private void printValue(final PrintWriter out, final int indentLevel, final Object value) {
451 final String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
452 if (value instanceof List) {
453 out.print("( ");
454 final Iterator<?> it = ((List<?>) value).iterator();
455 while (it.hasNext()) {
456 printValue(out, indentLevel + 1, it.next());
457 if (it.hasNext()) {
458 out.print(", ");
459 }
460 }
461 out.print(" )");
462 } else if (value instanceof PropertyListConfiguration) {
463 final NodeHandler<ImmutableNode> handler = ((PropertyListConfiguration) value).getModel().getNodeHandler();
464 printNode(out, indentLevel, handler.getRootNode(), handler);
465 } else if (value instanceof ImmutableConfiguration) {
466
467 out.println();
468 out.println(padding + "{");
469 final ImmutableConfiguration config = (ImmutableConfiguration) value;
470 config.forEach((k, v) -> {
471 final ImmutableNode node = new ImmutableNode.Builder().name(k).value(v).create();
472 final InMemoryNodeModel tempModel = new InMemoryNodeModel(node);
473 printNode(out, indentLevel + 1, node, tempModel.getNodeHandler());
474 out.println(";");
475 });
476 out.println(padding + "}");
477 } else if (value instanceof Map) {
478
479 final Map<String, Object> map = transformMap((Map<?, ?>) value);
480 printValue(out, indentLevel, new MapConfiguration(map));
481 } else if (value instanceof byte[]) {
482 out.print("<" + new String(Hex.encodeHex((byte[]) value)) + ">");
483 } else if (value instanceof Date) {
484 out.print(formatDate((Date) value));
485 } else if (value != null) {
486 out.print(quoteString(String.valueOf(value)));
487 }
488 }
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509 String quoteString(String s) {
510 if (s == null) {
511 return null;
512 }
513
514 if (s.indexOf(' ') != -1 || s.indexOf('\t') != -1 || s.indexOf('\r') != -1 || s.indexOf('\n') != -1 || s.indexOf('"') != -1 || s.indexOf('(') != -1
515 || s.indexOf(')') != -1 || s.indexOf('{') != -1 || s.indexOf('}') != -1 || s.indexOf('=') != -1 || s.indexOf(',') != -1 || s.indexOf(';') != -1) {
516 s = s.replace("\"", "\\\"");
517 s = "\"" + s + "\"";
518 }
519
520 return s;
521 }
522
523 @Override
524 public void read(final Reader in) throws ConfigurationException {
525 final PropertyListParser parser = new PropertyListParser(in);
526 try {
527 final PropertyListConfiguration config = parser.parse();
528 getModel().setRootNode(config.getNodeModel().getNodeHandler().getRootNode());
529 } catch (final ParseException e) {
530 throw new ConfigurationException(e);
531 }
532 }
533
534 @Override
535 protected void setPropertyInternal(final String key, final Object value) {
536
537 if (value instanceof byte[]) {
538 setDetailEvents(false);
539 try {
540 clearProperty(key);
541 addPropertyDirect(key, value);
542 } finally {
543 setDetailEvents(true);
544 }
545 } else {
546 super.setPropertyInternal(key, value);
547 }
548 }
549
550 @Override
551 public void write(final Writer out) throws ConfigurationException {
552 final PrintWriter writer = new PrintWriter(out);
553 final NodeHandler<ImmutableNode> handler = getModel().getNodeHandler();
554 printNode(writer, 0, handler.getRootNode(), handler);
555 writer.flush();
556 }
557 }