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.Calendar;
27 import java.util.Date;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.TimeZone;
33
34 import org.apache.commons.codec.binary.Hex;
35 import org.apache.commons.configuration.AbstractHierarchicalFileConfiguration;
36 import org.apache.commons.configuration.Configuration;
37 import org.apache.commons.configuration.ConfigurationException;
38 import org.apache.commons.configuration.HierarchicalConfiguration;
39 import org.apache.commons.configuration.MapConfiguration;
40 import org.apache.commons.configuration.tree.ConfigurationNode;
41 import org.apache.commons.lang.StringUtils;
42
43
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 public class PropertyListConfiguration extends AbstractHierarchicalFileConfiguration
86 {
87
88 private static final DateComponentParser DATE_SEPARATOR_PARSER = new DateSeparatorParser(
89 "-");
90
91
92 private static final DateComponentParser TIME_SEPARATOR_PARSER = new DateSeparatorParser(
93 ":");
94
95
96 private static final DateComponentParser BLANK_SEPARATOR_PARSER = new DateSeparatorParser(
97 " ");
98
99
100 private static final DateComponentParser[] DATE_PARSERS =
101 {new DateSeparatorParser("<*D"), new DateFieldParser(Calendar.YEAR, 4),
102 DATE_SEPARATOR_PARSER, new DateFieldParser(Calendar.MONTH, 2, 1),
103 DATE_SEPARATOR_PARSER, new DateFieldParser(Calendar.DATE, 2),
104 BLANK_SEPARATOR_PARSER,
105 new DateFieldParser(Calendar.HOUR_OF_DAY, 2),
106 TIME_SEPARATOR_PARSER, new DateFieldParser(Calendar.MINUTE, 2),
107 TIME_SEPARATOR_PARSER, new DateFieldParser(Calendar.SECOND, 2),
108 BLANK_SEPARATOR_PARSER, new DateTimeZoneParser(),
109 new DateSeparatorParser(">")};
110
111
112 private static final String TIME_ZONE_PREFIX = "GMT";
113
114
115 private static final long serialVersionUID = 3227248503779092127L;
116
117
118 private static final int MILLIS_PER_MINUTE = 1000 * 60;
119
120
121 private static final int MINUTES_PER_HOUR = 60;
122
123
124 private static final int INDENT_SIZE = 4;
125
126
127 private static final int TIME_ZONE_LENGTH = 5;
128
129
130 private static final char PAD_CHAR = '0';
131
132
133
134
135
136
137 public PropertyListConfiguration()
138 {
139 }
140
141
142
143
144
145
146
147
148 public PropertyListConfiguration(HierarchicalConfiguration c)
149 {
150 super(c);
151 }
152
153
154
155
156
157
158
159 public PropertyListConfiguration(String fileName) throws ConfigurationException
160 {
161 super(fileName);
162 }
163
164
165
166
167
168
169
170 public PropertyListConfiguration(File file) throws ConfigurationException
171 {
172 super(file);
173 }
174
175
176
177
178
179
180
181 public PropertyListConfiguration(URL url) throws ConfigurationException
182 {
183 super(url);
184 }
185
186 @Override
187 public void setProperty(String key, Object value)
188 {
189
190 if (value instanceof byte[])
191 {
192 fireEvent(EVENT_SET_PROPERTY, key, value, true);
193 setDetailEvents(false);
194 try
195 {
196 clearProperty(key);
197 addPropertyDirect(key, value);
198 }
199 finally
200 {
201 setDetailEvents(true);
202 }
203 fireEvent(EVENT_SET_PROPERTY, key, value, false);
204 }
205 else
206 {
207 super.setProperty(key, value);
208 }
209 }
210
211 @Override
212 public void addProperty(String key, Object value)
213 {
214 if (value instanceof byte[])
215 {
216 fireEvent(EVENT_ADD_PROPERTY, key, value, true);
217 addPropertyDirect(key, value);
218 fireEvent(EVENT_ADD_PROPERTY, key, value, false);
219 }
220 else
221 {
222 super.addProperty(key, value);
223 }
224 }
225
226 public void load(Reader in) throws ConfigurationException
227 {
228 PropertyListParser parser = new PropertyListParser(in);
229 try
230 {
231 HierarchicalConfiguration config = parser.parse();
232 setRoot(config.getRoot());
233 }
234 catch (ParseException e)
235 {
236 throw new ConfigurationException(e);
237 }
238 }
239
240 public void save(Writer out) throws ConfigurationException
241 {
242 PrintWriter writer = new PrintWriter(out);
243 printNode(writer, 0, getRoot());
244 writer.flush();
245 }
246
247
248
249
250 private void printNode(PrintWriter out, int indentLevel, ConfigurationNode node)
251 {
252 String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
253
254 if (node.getName() != null)
255 {
256 out.print(padding + quoteString(node.getName()) + " = ");
257 }
258
259 List<ConfigurationNode> children = new ArrayList<ConfigurationNode>(node.getChildren());
260 if (!children.isEmpty())
261 {
262
263 if (indentLevel > 0)
264 {
265 out.println();
266 }
267
268 out.println(padding + "{");
269
270
271 Iterator<ConfigurationNode> it = children.iterator();
272 while (it.hasNext())
273 {
274 ConfigurationNode child = it.next();
275
276 printNode(out, indentLevel + 1, child);
277
278
279 Object value = child.getValue();
280 if (value != null && !(value instanceof Map) && !(value instanceof Configuration))
281 {
282 out.println(";");
283 }
284
285
286 if (it.hasNext() && (value == null || value instanceof List))
287 {
288 out.println();
289 }
290 }
291
292 out.print(padding + "}");
293
294
295 if (node.getParentNode() != null)
296 {
297 out.println();
298 }
299 }
300 else if (node.getValue() == null)
301 {
302 out.println();
303 out.print(padding + "{ };");
304
305
306 if (node.getParentNode() != null)
307 {
308 out.println();
309 }
310 }
311 else
312 {
313
314 Object value = node.getValue();
315 printValue(out, indentLevel, value);
316 }
317 }
318
319
320
321
322 private void printValue(PrintWriter out, int indentLevel, Object value)
323 {
324 String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
325
326 if (value instanceof List)
327 {
328 out.print("( ");
329 Iterator<?> it = ((List<?>) value).iterator();
330 while (it.hasNext())
331 {
332 printValue(out, indentLevel + 1, it.next());
333 if (it.hasNext())
334 {
335 out.print(", ");
336 }
337 }
338 out.print(" )");
339 }
340 else if (value instanceof HierarchicalConfiguration)
341 {
342 printNode(out, indentLevel, ((HierarchicalConfiguration) value).getRoot());
343 }
344 else if (value instanceof Configuration)
345 {
346
347 out.println();
348 out.println(padding + "{");
349
350 Configuration config = (Configuration) value;
351 Iterator<String> it = config.getKeys();
352 while (it.hasNext())
353 {
354 String key = it.next();
355 Node node = new Node(key);
356 node.setValue(config.getProperty(key));
357
358 printNode(out, indentLevel + 1, node);
359 out.println(";");
360 }
361 out.println(padding + "}");
362 }
363 else if (value instanceof Map)
364 {
365
366 Map<String, Object> map = transformMap((Map<?, ?>) value);
367 printValue(out, indentLevel, new MapConfiguration(map));
368 }
369 else if (value instanceof byte[])
370 {
371 out.print("<" + new String(Hex.encodeHex((byte[]) value)) + ">");
372 }
373 else if (value instanceof Date)
374 {
375 out.print(formatDate((Date) value));
376 }
377 else if (value != null)
378 {
379 out.print(quoteString(String.valueOf(value)));
380 }
381 }
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400 String quoteString(String s)
401 {
402 if (s == null)
403 {
404 return null;
405 }
406
407 if (s.indexOf(' ') != -1
408 || s.indexOf('\t') != -1
409 || s.indexOf('\r') != -1
410 || s.indexOf('\n') != -1
411 || s.indexOf('"') != -1
412 || s.indexOf('(') != -1
413 || s.indexOf(')') != -1
414 || s.indexOf('{') != -1
415 || s.indexOf('}') != -1
416 || s.indexOf('=') != -1
417 || s.indexOf(',') != -1
418 || s.indexOf(';') != -1)
419 {
420 s = s.replaceAll("\"", "\\\\\\\"");
421 s = "\"" + s + "\"";
422 }
423
424 return s;
425 }
426
427
428
429
430
431
432
433
434
435 static Date parseDate(String s) throws ParseException
436 {
437 Calendar cal = Calendar.getInstance();
438 cal.clear();
439 int index = 0;
440
441 for (DateComponentParser parser : DATE_PARSERS)
442 {
443 index += parser.parseComponent(s, index, cal);
444 }
445
446 return cal.getTime();
447 }
448
449
450
451
452
453
454
455
456 static String formatDate(Calendar cal)
457 {
458 StringBuilder buf = new StringBuilder();
459
460 for (int i = 0; i < DATE_PARSERS.length; i++)
461 {
462 DATE_PARSERS[i].formatComponent(buf, cal);
463 }
464
465 return buf.toString();
466 }
467
468
469
470
471
472
473
474 static String formatDate(Date date)
475 {
476 Calendar cal = Calendar.getInstance();
477 cal.setTime(date);
478 return formatDate(cal);
479 }
480
481
482
483
484
485
486
487
488
489 private static Map<String, Object> transformMap(Map<?, ?> src)
490 {
491 Map<String, Object> dest = new HashMap<String, Object>();
492 for (Map.Entry<?, ?> e : src.entrySet())
493 {
494 if (e.getKey() instanceof String)
495 {
496 dest.put((String) e.getKey(), e.getValue());
497 }
498 }
499 return dest;
500 }
501
502
503
504
505
506
507
508
509 private abstract static class DateComponentParser
510 {
511
512
513
514
515
516
517
518
519
520 public abstract int parseComponent(String s, int index, Calendar cal)
521 throws ParseException;
522
523
524
525
526
527
528
529
530 public abstract void formatComponent(StringBuilder buf, Calendar cal);
531
532
533
534
535
536
537
538
539
540
541
542 protected void checkLength(String s, int index, int length)
543 throws ParseException
544 {
545 int len = (s == null) ? 0 : s.length();
546 if (index + length > len)
547 {
548 throw new ParseException("Input string too short: " + s
549 + ", index: " + index);
550 }
551 }
552
553
554
555
556
557
558
559
560
561 protected void padNum(StringBuilder buf, int num, int length)
562 {
563 buf.append(StringUtils.leftPad(String.valueOf(num), length,
564 PAD_CHAR));
565 }
566 }
567
568
569
570
571
572
573 private static class DateFieldParser extends DateComponentParser
574 {
575
576 private int calendarField;
577
578
579 private int length;
580
581
582 private int offset;
583
584
585
586
587
588
589
590 public DateFieldParser(int calFld, int len)
591 {
592 this(calFld, len, 0);
593 }
594
595
596
597
598
599
600
601
602
603 public DateFieldParser(int calFld, int len, int ofs)
604 {
605 calendarField = calFld;
606 length = len;
607 offset = ofs;
608 }
609
610 @Override
611 public void formatComponent(StringBuilder buf, Calendar cal)
612 {
613 padNum(buf, cal.get(calendarField) + offset, length);
614 }
615
616 @Override
617 public int parseComponent(String s, int index, Calendar cal)
618 throws ParseException
619 {
620 checkLength(s, index, length);
621 try
622 {
623 cal.set(calendarField, Integer.parseInt(s.substring(index,
624 index + length))
625 - offset);
626 return length;
627 }
628 catch (NumberFormatException nfex)
629 {
630 throw new ParseException("Invalid number: " + s + ", index "
631 + index);
632 }
633 }
634 }
635
636
637
638
639
640 private static class DateSeparatorParser extends DateComponentParser
641 {
642
643 private String separator;
644
645
646
647
648
649
650
651 public DateSeparatorParser(String sep)
652 {
653 separator = sep;
654 }
655
656 @Override
657 public void formatComponent(StringBuilder buf, Calendar cal)
658 {
659 buf.append(separator);
660 }
661
662 @Override
663 public int parseComponent(String s, int index, Calendar cal)
664 throws ParseException
665 {
666 checkLength(s, index, separator.length());
667 if (!s.startsWith(separator, index))
668 {
669 throw new ParseException("Invalid input: " + s + ", index "
670 + index + ", expected " + separator);
671 }
672 return separator.length();
673 }
674 }
675
676
677
678
679
680 private static class DateTimeZoneParser extends DateComponentParser
681 {
682 @Override
683 public void formatComponent(StringBuilder buf, Calendar cal)
684 {
685 TimeZone tz = cal.getTimeZone();
686 int ofs = tz.getRawOffset() / MILLIS_PER_MINUTE;
687 if (ofs < 0)
688 {
689 buf.append('-');
690 ofs = -ofs;
691 }
692 else
693 {
694 buf.append('+');
695 }
696 int hour = ofs / MINUTES_PER_HOUR;
697 int min = ofs % MINUTES_PER_HOUR;
698 padNum(buf, hour, 2);
699 padNum(buf, min, 2);
700 }
701
702 @Override
703 public int parseComponent(String s, int index, Calendar cal)
704 throws ParseException
705 {
706 checkLength(s, index, TIME_ZONE_LENGTH);
707 TimeZone tz = TimeZone.getTimeZone(TIME_ZONE_PREFIX
708 + s.substring(index, index + TIME_ZONE_LENGTH));
709 cal.setTimeZone(tz);
710 return TIME_ZONE_LENGTH;
711 }
712 }
713 }