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.math.BigDecimal;
25 import java.math.BigInteger;
26 import java.net.URL;
27 import java.text.DateFormat;
28 import java.text.ParseException;
29 import java.text.SimpleDateFormat;
30 import java.util.ArrayList;
31 import java.util.Calendar;
32 import java.util.Collection;
33 import java.util.Date;
34 import java.util.HashMap;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.TimeZone;
39
40 import javax.xml.parsers.SAXParser;
41 import javax.xml.parsers.SAXParserFactory;
42
43 import org.apache.commons.codec.binary.Base64;
44 import org.apache.commons.configuration.AbstractHierarchicalFileConfiguration;
45 import org.apache.commons.configuration.Configuration;
46 import org.apache.commons.configuration.ConfigurationException;
47 import org.apache.commons.configuration.HierarchicalConfiguration;
48 import org.apache.commons.configuration.MapConfiguration;
49 import org.apache.commons.configuration.tree.ConfigurationNode;
50 import org.apache.commons.lang.StringEscapeUtils;
51 import org.apache.commons.lang.StringUtils;
52 import org.xml.sax.Attributes;
53 import org.xml.sax.EntityResolver;
54 import org.xml.sax.InputSource;
55 import org.xml.sax.SAXException;
56 import org.xml.sax.helpers.DefaultHandler;
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124 public class XMLPropertyListConfiguration extends AbstractHierarchicalFileConfiguration
125 {
126
127
128
129 private static final long serialVersionUID = -3162063751042475985L;
130
131
132 private static final int INDENT_SIZE = 4;
133
134
135
136
137
138
139 public XMLPropertyListConfiguration()
140 {
141 initRoot();
142 }
143
144
145
146
147
148
149
150
151 public XMLPropertyListConfiguration(HierarchicalConfiguration configuration)
152 {
153 super(configuration);
154 }
155
156
157
158
159
160
161
162
163 public XMLPropertyListConfiguration(String fileName) throws ConfigurationException
164 {
165 super(fileName);
166 }
167
168
169
170
171
172
173
174 public XMLPropertyListConfiguration(File file) throws ConfigurationException
175 {
176 super(file);
177 }
178
179
180
181
182
183
184
185 public XMLPropertyListConfiguration(URL url) throws ConfigurationException
186 {
187 super(url);
188 }
189
190 @Override
191 public void setProperty(String key, Object value)
192 {
193
194 if (value instanceof byte[])
195 {
196 fireEvent(EVENT_SET_PROPERTY, key, value, true);
197 setDetailEvents(false);
198 try
199 {
200 clearProperty(key);
201 addPropertyDirect(key, value);
202 }
203 finally
204 {
205 setDetailEvents(true);
206 }
207 fireEvent(EVENT_SET_PROPERTY, key, value, false);
208 }
209 else
210 {
211 super.setProperty(key, value);
212 }
213 }
214
215 @Override
216 public void addProperty(String key, Object value)
217 {
218 if (value instanceof byte[])
219 {
220 fireEvent(EVENT_ADD_PROPERTY, key, value, true);
221 addPropertyDirect(key, value);
222 fireEvent(EVENT_ADD_PROPERTY, key, value, false);
223 }
224 else
225 {
226 super.addProperty(key, value);
227 }
228 }
229
230 public void load(Reader in) throws ConfigurationException
231 {
232
233
234
235 if (!(getRootNode() instanceof PListNode))
236 {
237 initRoot();
238 }
239
240
241 EntityResolver resolver = new EntityResolver()
242 {
243 public InputSource resolveEntity(String publicId, String systemId)
244 {
245 return new InputSource(getClass().getClassLoader().getResourceAsStream("PropertyList-1.0.dtd"));
246 }
247 };
248
249
250 XMLPropertyListHandler handler = new XMLPropertyListHandler(getRoot());
251 try
252 {
253 SAXParserFactory factory = SAXParserFactory.newInstance();
254 factory.setValidating(true);
255
256 SAXParser parser = factory.newSAXParser();
257 parser.getXMLReader().setEntityResolver(resolver);
258 parser.getXMLReader().setContentHandler(handler);
259 parser.getXMLReader().parse(new InputSource(in));
260 }
261 catch (Exception e)
262 {
263 throw new ConfigurationException("Unable to parse the configuration file", e);
264 }
265 }
266
267 public void save(Writer out) throws ConfigurationException
268 {
269 PrintWriter writer = new PrintWriter(out);
270
271 if (getEncoding() != null)
272 {
273 writer.println("<?xml version=\"1.0\" encoding=\"" + getEncoding() + "\"?>");
274 }
275 else
276 {
277 writer.println("<?xml version=\"1.0\"?>");
278 }
279
280 writer.println("<!DOCTYPE plist SYSTEM \"file://localhost/System/Library/DTDs/PropertyList.dtd\">");
281 writer.println("<plist version=\"1.0\">");
282
283 printNode(writer, 1, getRoot());
284
285 writer.println("</plist>");
286 writer.flush();
287 }
288
289
290
291
292 private void printNode(PrintWriter out, int indentLevel, ConfigurationNode node)
293 {
294 String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
295
296 if (node.getName() != null)
297 {
298 out.println(padding + "<key>" + StringEscapeUtils.escapeXml(node.getName()) + "</key>");
299 }
300
301 List<ConfigurationNode> children = node.getChildren();
302 if (!children.isEmpty())
303 {
304 out.println(padding + "<dict>");
305
306 Iterator<ConfigurationNode> it = children.iterator();
307 while (it.hasNext())
308 {
309 ConfigurationNode child = it.next();
310 printNode(out, indentLevel + 1, child);
311
312 if (it.hasNext())
313 {
314 out.println();
315 }
316 }
317
318 out.println(padding + "</dict>");
319 }
320 else if (node.getValue() == null)
321 {
322 out.println(padding + "<dict/>");
323 }
324 else
325 {
326 Object value = node.getValue();
327 printValue(out, indentLevel, value);
328 }
329 }
330
331
332
333
334 private void printValue(PrintWriter out, int indentLevel, Object value)
335 {
336 String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
337
338 if (value instanceof Date)
339 {
340 synchronized (PListNode.FORMAT)
341 {
342 out.println(padding + "<date>" + PListNode.FORMAT.format((Date) value) + "</date>");
343 }
344 }
345 else if (value instanceof Calendar)
346 {
347 printValue(out, indentLevel, ((Calendar) value).getTime());
348 }
349 else if (value instanceof Number)
350 {
351 if (value instanceof Double || value instanceof Float || value instanceof BigDecimal)
352 {
353 out.println(padding + "<real>" + value.toString() + "</real>");
354 }
355 else
356 {
357 out.println(padding + "<integer>" + value.toString() + "</integer>");
358 }
359 }
360 else if (value instanceof Boolean)
361 {
362 if (((Boolean) value).booleanValue())
363 {
364 out.println(padding + "<true/>");
365 }
366 else
367 {
368 out.println(padding + "<false/>");
369 }
370 }
371 else if (value instanceof List)
372 {
373 out.println(padding + "<array>");
374 Iterator<?> it = ((List<?>) value).iterator();
375 while (it.hasNext())
376 {
377 printValue(out, indentLevel + 1, it.next());
378 }
379 out.println(padding + "</array>");
380 }
381 else if (value instanceof HierarchicalConfiguration)
382 {
383 printNode(out, indentLevel, ((HierarchicalConfiguration) value).getRoot());
384 }
385 else if (value instanceof Configuration)
386 {
387
388 out.println(padding + "<dict>");
389
390 Configuration config = (Configuration) value;
391 Iterator<String> it = config.getKeys();
392 while (it.hasNext())
393 {
394
395 String key = it.next();
396 Node node = new Node(key);
397 node.setValue(config.getProperty(key));
398
399
400 printNode(out, indentLevel + 1, node);
401
402 if (it.hasNext())
403 {
404 out.println();
405 }
406 }
407 out.println(padding + "</dict>");
408 }
409 else if (value instanceof Map)
410 {
411
412 Map<String, Object> map = transformMap((Map<?, ?>) value);
413 printValue(out, indentLevel, new MapConfiguration(map));
414 }
415 else if (value instanceof byte[])
416 {
417 String base64 = new String(Base64.encodeBase64((byte[]) value));
418 out.println(padding + "<data>" + StringEscapeUtils.escapeXml(base64) + "</data>");
419 }
420 else if (value != null)
421 {
422 out.println(padding + "<string>" + StringEscapeUtils.escapeXml(String.valueOf(value)) + "</string>");
423 }
424 else
425 {
426 out.println(padding + "<string/>");
427 }
428 }
429
430
431
432
433 private void initRoot()
434 {
435 setRootNode(new PListNode());
436 }
437
438
439
440
441
442
443
444
445
446 private static Map<String, Object> transformMap(Map<?, ?> src)
447 {
448 Map<String, Object> dest = new HashMap<String, Object>();
449 for (Map.Entry<?, ?> e : src.entrySet())
450 {
451 if (e.getKey() instanceof String)
452 {
453 dest.put((String) e.getKey(), e.getValue());
454 }
455 }
456 return dest;
457 }
458
459
460
461
462 private class XMLPropertyListHandler extends DefaultHandler
463 {
464
465 private StringBuilder buffer = new StringBuilder();
466
467
468 private List<Node> stack = new ArrayList<Node>();
469
470 public XMLPropertyListHandler(Node root)
471 {
472 push(root);
473 }
474
475
476
477
478 private Node peek()
479 {
480 if (!stack.isEmpty())
481 {
482 return stack.get(stack.size() - 1);
483 }
484 else
485 {
486 return null;
487 }
488 }
489
490
491
492
493 private Node pop()
494 {
495 if (!stack.isEmpty())
496 {
497 return stack.remove(stack.size() - 1);
498 }
499 else
500 {
501 return null;
502 }
503 }
504
505
506
507
508 private void push(Node node)
509 {
510 stack.add(node);
511 }
512
513 @Override
514 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
515 {
516 if ("array".equals(qName))
517 {
518 push(new ArrayNode());
519 }
520 else if ("dict".equals(qName))
521 {
522 if (peek() instanceof ArrayNode)
523 {
524
525 XMLPropertyListConfiguration config = new XMLPropertyListConfiguration();
526
527
528 ArrayNode node = (ArrayNode) peek();
529 node.addValue(config);
530
531
532 push(config.getRoot());
533 }
534 }
535 }
536
537 @Override
538 public void endElement(String uri, String localName, String qName) throws SAXException
539 {
540 if ("key".equals(qName))
541 {
542
543 PListNode node = new PListNode();
544 node.setName(buffer.toString());
545 peek().addChild(node);
546 push(node);
547 }
548 else if ("dict".equals(qName))
549 {
550
551 pop();
552 }
553 else
554 {
555 if ("string".equals(qName))
556 {
557 ((PListNode) peek()).addValue(buffer.toString());
558 }
559 else if ("integer".equals(qName))
560 {
561 ((PListNode) peek()).addIntegerValue(buffer.toString());
562 }
563 else if ("real".equals(qName))
564 {
565 ((PListNode) peek()).addRealValue(buffer.toString());
566 }
567 else if ("true".equals(qName))
568 {
569 ((PListNode) peek()).addTrueValue();
570 }
571 else if ("false".equals(qName))
572 {
573 ((PListNode) peek()).addFalseValue();
574 }
575 else if ("data".equals(qName))
576 {
577 ((PListNode) peek()).addDataValue(buffer.toString());
578 }
579 else if ("date".equals(qName))
580 {
581 try
582 {
583 ((PListNode) peek()).addDateValue(buffer.toString());
584 }
585 catch (IllegalArgumentException iex)
586 {
587 getLogger().warn(
588 "Ignoring invalid date property " + buffer);
589 }
590 }
591 else if ("array".equals(qName))
592 {
593 ArrayNode array = (ArrayNode) pop();
594 ((PListNode) peek()).addList(array);
595 }
596
597
598
599 if (!(peek() instanceof ArrayNode))
600 {
601 pop();
602 }
603 }
604
605 buffer.setLength(0);
606 }
607
608 @Override
609 public void characters(char[] ch, int start, int length) throws SAXException
610 {
611 buffer.append(ch, start, length);
612 }
613 }
614
615
616
617
618
619
620 public static class PListNode extends Node
621 {
622
623
624
625 private static final long serialVersionUID = -7614060264754798317L;
626
627
628
629
630
631
632 private static final DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
633 static
634 {
635 FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
636 }
637
638
639
640
641
642
643 private static final DateFormat GNUSTEP_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
644
645
646
647
648
649
650
651
652
653 public void addValue(Object value)
654 {
655 if (getValue() == null)
656 {
657 setValue(value);
658 }
659 else if (getValue() instanceof Collection)
660 {
661
662 @SuppressWarnings("unchecked")
663 Collection<Object> collection = (Collection<Object>) getValue();
664 collection.add(value);
665 }
666 else
667 {
668 List<Object> list = new ArrayList<Object>();
669 list.add(getValue());
670 list.add(value);
671 setValue(list);
672 }
673 }
674
675
676
677
678
679
680
681 public void addDateValue(String value)
682 {
683 try
684 {
685 if (value.indexOf(' ') != -1)
686 {
687
688 synchronized (GNUSTEP_FORMAT)
689 {
690 addValue(GNUSTEP_FORMAT.parse(value));
691 }
692 }
693 else
694 {
695
696 synchronized (FORMAT)
697 {
698 addValue(FORMAT.parse(value));
699 }
700 }
701 }
702 catch (ParseException e)
703 {
704 throw new IllegalArgumentException(String.format(
705 "'%s' cannot be parsed to a date!", value), e);
706 }
707 }
708
709
710
711
712
713
714
715 public void addDataValue(String value)
716 {
717 addValue(Base64.decodeBase64(value.getBytes()));
718 }
719
720
721
722
723
724
725 public void addIntegerValue(String value)
726 {
727 addValue(new BigInteger(value));
728 }
729
730
731
732
733
734
735 public void addRealValue(String value)
736 {
737 addValue(new BigDecimal(value));
738 }
739
740
741
742
743 public void addTrueValue()
744 {
745 addValue(Boolean.TRUE);
746 }
747
748
749
750
751 public void addFalseValue()
752 {
753 addValue(Boolean.FALSE);
754 }
755
756
757
758
759
760
761 public void addList(ArrayNode node)
762 {
763 addValue(node.getValue());
764 }
765 }
766
767
768
769
770
771
772 public static class ArrayNode extends PListNode
773 {
774
775
776
777 private static final long serialVersionUID = 5586544306664205835L;
778
779
780 private List<Object> list = new ArrayList<Object>();
781
782
783
784
785
786
787 @Override
788 public void addValue(Object value)
789 {
790 list.add(value);
791 }
792
793
794
795
796
797
798 @Override
799 public Object getValue()
800 {
801 return list;
802 }
803 }
804 }