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