001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.commons.configuration.plist;
019:
020: import java.io.File;
021: import java.io.PrintWriter;
022: import java.io.Reader;
023: import java.io.Writer;
024: import java.net.URL;
025: import java.util.ArrayList;
026: import java.util.Iterator;
027: import java.util.List;
028: import java.util.Map;
029:
030: import org.apache.commons.codec.binary.Hex;
031: import org.apache.commons.configuration.AbstractHierarchicalFileConfiguration;
032: import org.apache.commons.configuration.Configuration;
033: import org.apache.commons.configuration.ConfigurationException;
034: import org.apache.commons.configuration.HierarchicalConfiguration;
035: import org.apache.commons.configuration.MapConfiguration;
036: import org.apache.commons.lang.StringUtils;
037:
038: /**
039: * NeXT / OpenStep style configuration.
040: * (http://developer.apple.com/documentation/Cocoa/Conceptual/PropertyLists/Concepts/OldStylePListsConcept.html)
041: *
042: * <p>Example:</p>
043: * <pre>
044: * {
045: * foo = "bar";
046: *
047: * array = ( value1, value2, value3 );
048: *
049: * data = <4f3e0145ab>;
050: *
051: * nested =
052: * {
053: * key1 = value1;
054: * key2 = value;
055: * nested =
056: * {
057: * foo = bar
058: * }
059: * }
060: * }
061: * </pre>
062: *
063: * @since 1.2
064: *
065: * @author Emmanuel Bourg
066: * @version $Revision: 492216 $, $Date: 2007-01-03 17:51:24 +0100 (Mi, 03 Jan 2007) $
067: */
068: public class PropertyListConfiguration extends
069: AbstractHierarchicalFileConfiguration {
070: /**
071: * The serial version UID.
072: */
073: private static final long serialVersionUID = 3227248503779092127L;
074:
075: /** Size of the indentation for the generated file. */
076: private static final int INDENT_SIZE = 4;
077:
078: /**
079: * Creates an empty PropertyListConfiguration object which can be
080: * used to synthesize a new plist file by adding values and
081: * then saving().
082: */
083: public PropertyListConfiguration() {
084: }
085:
086: /**
087: * Creates a new instance of <code>PropertyListConfiguration</code> and
088: * copies the content of the specified configuration into this object.
089: *
090: * @param c the configuration to copy
091: * @since 1.4
092: */
093: public PropertyListConfiguration(HierarchicalConfiguration c) {
094: super (c);
095: }
096:
097: /**
098: * Creates and loads the property list from the specified file.
099: *
100: * @param fileName The name of the plist file to load.
101: * @throws ConfigurationException Error while loading the plist file
102: */
103: public PropertyListConfiguration(String fileName)
104: throws ConfigurationException {
105: super (fileName);
106: }
107:
108: /**
109: * Creates and loads the property list from the specified file.
110: *
111: * @param file The plist file to load.
112: * @throws ConfigurationException Error while loading the plist file
113: */
114: public PropertyListConfiguration(File file)
115: throws ConfigurationException {
116: super (file);
117: }
118:
119: /**
120: * Creates and loads the property list from the specified URL.
121: *
122: * @param url The location of the plist file to load.
123: * @throws ConfigurationException Error while loading the plist file
124: */
125: public PropertyListConfiguration(URL url)
126: throws ConfigurationException {
127: super (url);
128: }
129:
130: public void load(Reader in) throws ConfigurationException {
131: PropertyListParser parser = new PropertyListParser(in);
132: try {
133:
134: HierarchicalConfiguration config = parser.parse();
135: setRoot(config.getRoot());
136: } catch (ParseException e) {
137: throw new ConfigurationException(e);
138: }
139: }
140:
141: public void save(Writer out) throws ConfigurationException {
142: PrintWriter writer = new PrintWriter(out);
143: printNode(writer, 0, getRoot());
144: writer.flush();
145: }
146:
147: /**
148: * Append a node to the writer, indented according to a specific level.
149: */
150: private void printNode(PrintWriter out, int indentLevel, Node node) {
151: String padding = StringUtils.repeat(" ", indentLevel
152: * INDENT_SIZE);
153:
154: if (node.getName() != null) {
155: out.print(padding + quoteString(node.getName()) + " = ");
156: }
157:
158: // get all non trivial nodes
159: List children = new ArrayList(node.getChildren());
160: Iterator it = children.iterator();
161: while (it.hasNext()) {
162: Node child = (Node) it.next();
163: if (child.getValue() == null
164: && (child.getChildren() == null || child
165: .getChildren().isEmpty())) {
166: it.remove();
167: }
168: }
169:
170: if (!children.isEmpty()) {
171: // skip a line, except for the root dictionary
172: if (indentLevel > 0) {
173: out.println();
174: }
175:
176: out.println(padding + "{");
177:
178: // display the children
179: it = children.iterator();
180: while (it.hasNext()) {
181: Node child = (Node) it.next();
182:
183: printNode(out, indentLevel + 1, child);
184:
185: // add a semi colon for elements that are not dictionaries
186: Object value = child.getValue();
187: if (value != null && !(value instanceof Map)
188: && !(value instanceof Configuration)) {
189: out.println(";");
190: }
191:
192: // skip a line after arrays and dictionaries
193: if (it.hasNext()
194: && (value == null || value instanceof List)) {
195: out.println();
196: }
197: }
198:
199: out.print(padding + "}");
200:
201: // line feed if the dictionary is not in an array
202: if (node.getParent() != null) {
203: out.println();
204: }
205: } else {
206: // display the leaf value
207: Object value = node.getValue();
208: printValue(out, indentLevel, value);
209: }
210: }
211:
212: /**
213: * Append a value to the writer, indented according to a specific level.
214: */
215: private void printValue(PrintWriter out, int indentLevel,
216: Object value) {
217: String padding = StringUtils.repeat(" ", indentLevel
218: * INDENT_SIZE);
219:
220: if (value instanceof List) {
221: out.print("( ");
222: Iterator it = ((List) value).iterator();
223: while (it.hasNext()) {
224: printValue(out, indentLevel + 1, it.next());
225: if (it.hasNext()) {
226: out.print(", ");
227: }
228: }
229: out.print(" )");
230: } else if (value instanceof HierarchicalConfiguration) {
231: printNode(out, indentLevel,
232: ((HierarchicalConfiguration) value).getRoot());
233: } else if (value instanceof Configuration) {
234: // display a flat Configuration as a dictionary
235: out.println();
236: out.println(padding + "{");
237:
238: Configuration config = (Configuration) value;
239: Iterator it = config.getKeys();
240: while (it.hasNext()) {
241: String key = (String) it.next();
242: Node node = new Node(key);
243: node.setValue(config.getProperty(key));
244:
245: printNode(out, indentLevel + 1, node);
246: out.println(";");
247: }
248: out.println(padding + "}");
249: } else if (value instanceof Map) {
250: // display a Map as a dictionary
251: Map map = (Map) value;
252: printValue(out, indentLevel, new MapConfiguration(map));
253: } else if (value instanceof byte[]) {
254: out.print("<" + new String(Hex.encodeHex((byte[]) value))
255: + ">");
256: } else if (value != null) {
257: out.print(quoteString(String.valueOf(value)));
258: }
259: }
260:
261: /**
262: * Quote the specified string if necessary, that's if the string contains:
263: * <ul>
264: * <li>a space character (' ', '\t', '\r', '\n')</li>
265: * <li>a quote '"'</li>
266: * <li>special characters in plist files ('(', ')', '{', '}', '=', ';', ',')</li>
267: * </ul>
268: * Quotes within the string are escaped.
269: *
270: * <p>Examples:</p>
271: * <ul>
272: * <li>abcd -> abcd</li>
273: * <li>ab cd -> "ab cd"</li>
274: * <li>foo"bar -> "foo\"bar"</li>
275: * <li>foo;bar -> "foo;bar"</li>
276: * </ul>
277: */
278: String quoteString(String s) {
279: if (s == null) {
280: return null;
281: }
282:
283: if (s.indexOf(' ') != -1 || s.indexOf('\t') != -1
284: || s.indexOf('\r') != -1 || s.indexOf('\n') != -1
285: || s.indexOf('"') != -1 || s.indexOf('(') != -1
286: || s.indexOf(')') != -1 || s.indexOf('{') != -1
287: || s.indexOf('}') != -1 || s.indexOf('=') != -1
288: || s.indexOf(',') != -1 || s.indexOf(';') != -1) {
289: s = StringUtils.replace(s, "\"", "\\\"");
290: s = "\"" + s + "\"";
291: }
292:
293: return s;
294: }
295: }
|