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