Added: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/SimpleCharStream.java URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/SimpleCharStream.java?rev=227018&view=auto ============================================================================== --- jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/SimpleCharStream.java (added) +++ jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/SimpleCharStream.java Tue Aug 2 07:43:20 2005 @@ -0,0 +1,401 @@ +/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 3.0 */
+package org.apache.commons.configuration.plist; + +/** + * An implementation of interface CharStream, where the stream is assumed to + * contain only ASCII characters (without unicode processing). + */ + +class SimpleCharStream +{ + public static final boolean staticFlag = false; + int bufsize; + int available; + int tokenBegin; + public int bufpos = -1; + protected int bufline[]; + protected int bufcolumn[]; + + protected int column = 0; + protected int line = 1; + + protected boolean prevCharIsCR = false; + protected boolean prevCharIsLF = false; + + protected java.io.Reader inputStream; + + protected char[] buffer; + protected int maxNextCharInd = 0; + protected int inBuf = 0; + + protected void ExpandBuff(boolean wrapAround) + { + char[] newbuffer = new char[bufsize + 2048]; + int newbufline[] = new int[bufsize + 2048]; + int newbufcolumn[] = new int[bufsize + 2048]; + + try + { + if (wrapAround) + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + System.arraycopy(buffer, 0, newbuffer, + bufsize - tokenBegin, bufpos); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos += (bufsize - tokenBegin)); + } + else + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos -= tokenBegin); + } + } + catch (Throwable t) + { + throw new Error(t.getMessage()); + } + + + bufsize += 2048; + available = bufsize; + tokenBegin = 0; + } + + protected void FillBuff() throws java.io.IOException + { + if (maxNextCharInd == available) + { + if (available == bufsize) + { + if (tokenBegin > 2048) + { + bufpos = maxNextCharInd = 0; + available = tokenBegin; + } + else if (tokenBegin < 0) + bufpos = maxNextCharInd = 0; + else + ExpandBuff(false); + } + else if (available > tokenBegin) + available = bufsize; + else if ((tokenBegin - available) < 2048) + ExpandBuff(true); + else + available = tokenBegin; + } + + int i; + try { + if ((i = inputStream.read(buffer, maxNextCharInd, + available - maxNextCharInd)) == -1) + { + inputStream.close(); + throw new java.io.IOException(); + } + else + maxNextCharInd += i; + return; + } + catch(java.io.IOException e) { + --bufpos; + backup(0); + if (tokenBegin == -1) + tokenBegin = bufpos; + throw e; + } + } + + public char BeginToken() throws java.io.IOException + { + tokenBegin = -1; + char c = readChar(); + tokenBegin = bufpos; + + return c; + } + + protected void UpdateLineColumn(char c) + { + column++; + + if (prevCharIsLF) + { + prevCharIsLF = false; + line += (column = 1); + } + else if (prevCharIsCR) + { + prevCharIsCR = false; + if (c == '\n') + { + prevCharIsLF = true; + } + else + line += (column = 1); + } + + switch (c) + { + case '\r' : + prevCharIsCR = true; + break; + case '\n' : + prevCharIsLF = true; + break; + case '\t' : + column--; + column += (8 - (column & 07)); + break; + default : + break; + } + + bufline[bufpos] = line; + bufcolumn[bufpos] = column; + } + + public char readChar() throws java.io.IOException + { + if (inBuf > 0) + { + --inBuf; + + if (++bufpos == bufsize) + bufpos = 0; + + return buffer[bufpos]; + } + + if (++bufpos >= maxNextCharInd) + FillBuff(); + + char c = buffer[bufpos]; + + UpdateLineColumn(c); + return (c); + } + + /** + * @deprecated + * @see #getEndColumn + */ + + public int getColumn() { + return bufcolumn[bufpos]; + } + + /** + * @deprecated + * @see #getEndLine + */ + + public int getLine() { + return bufline[bufpos]; + } + + public int getEndColumn() { + return bufcolumn[bufpos]; + } + + public int getEndLine() { + return bufline[bufpos]; + } + + public int getBeginColumn() { + return bufcolumn[tokenBegin]; + } + + public int getBeginLine() { + return bufline[tokenBegin]; + } + + public void backup(int amount) { + + inBuf += amount; + if ((bufpos -= amount) < 0) + bufpos += bufsize; + } + + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + + public SimpleCharStream(java.io.Reader dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + public SimpleCharStream(java.io.Reader dstream) + { + this(dstream, 1, 1, 4096); + } + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + if (buffer == null || buffersize != buffer.length) + { + available = bufsize = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + prevCharIsLF = prevCharIsCR = false; + tokenBegin = inBuf = maxNextCharInd = 0; + bufpos = -1; + } + + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + + public void ReInit(java.io.Reader dstream) + { + ReInit(dstream, 1, 1, 4096); + } + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + this(new java.io.InputStreamReader(dstream), startline, startcolumn, 4096); + } + + public SimpleCharStream(java.io.InputStream dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + public SimpleCharStream(java.io.InputStream dstream) + { + this(dstream, 1, 1, 4096); + } + + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, 4096); + } + + public void ReInit(java.io.InputStream dstream) + { + ReInit(dstream, 1, 1, 4096); + } + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + public String GetImage() + { + if (bufpos >= tokenBegin) + return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); + else + return new String(buffer, tokenBegin, bufsize - tokenBegin) + + new String(buffer, 0, bufpos + 1); + } + + public char[] GetSuffix(int len) + { + char[] ret = new char[len]; + + if ((bufpos + 1) >= len) + System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); + else + { + System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, + len - bufpos - 1); + System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); + } + + return ret; + } + + public void Done() + { + buffer = null; + bufline = null; + bufcolumn = null; + } + + /** + * Method to adjust line and column numbers for the start of a token. + */ + public void adjustBeginLineColumn(int newLine, int newCol) + { + int start = tokenBegin; + int len; + + if (bufpos >= tokenBegin) + { + len = bufpos - tokenBegin + inBuf + 1; + } + else + { + len = bufsize - tokenBegin + bufpos + 1 + inBuf; + } + + int i = 0, j = 0, k = 0; + int nextColDiff = 0, columnDiff = 0; + + while (i < len && + bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) + { + bufline[j] = newLine; + nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; + bufcolumn[j] = newCol + columnDiff; + columnDiff = nextColDiff; + i++; + } + + if (i < len) + { + bufline[j] = newLine++; + bufcolumn[j] = newCol + columnDiff; + + while (i++ < len) + { + if (bufline[j = start % bufsize] != bufline[++start % bufsize]) + bufline[j] = newLine++; + else + bufline[j] = newLine; + } + } + + line = bufline[j]; + column = bufcolumn[j]; + } + +} Added: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/Token.java URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/Token.java?rev=227018&view=auto ============================================================================== --- jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/Token.java (added) +++ jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/Token.java Tue Aug 2 07:43:20 2005 @@ -0,0 +1,81 @@ +/* Generated By:JavaCC: Do not edit this line. Token.java Version 3.0 */ +package org.apache.commons.configuration.plist; + +/** + * Describes the input token stream. + */ + +class Token { + + /** + * An integer that describes the kind of this token. This numbering + * system is determined by JavaCCParser, and a table of these numbers is + * stored in the file ...Constants.java. + */ + public int kind; + + /** + * beginLine and beginColumn describe the position of the first character + * of this token; endLine and endColumn describe the position of the + * last character of this token. + */ + public int beginLine, beginColumn, endLine, endColumn; + + /** + * The string image of the token. + */ + public String image; + + /** + * A reference to the next regular (non-special) token from the input + * stream. If this is the last token from the input stream, or if the + * token manager has not read tokens beyond this one, this field is + * set to null. This is true only if this token is also a regular + * token. Otherwise, see below for a description of the contents of + * this field. + */ + public Token next; + + /** + * This field is used to access special tokens that occur prior to this + * token, but after the immediately preceding regular (non-special) token. + * If there are no such special tokens, this field is set to null. + * When there are more than one such special token, this field refers + * to the last of these special tokens, which in turn refers to the next + * previous special token through its specialToken field, and so on + * until the first special token (whose specialToken field is null). + * The next fields of special tokens refer to other special tokens that + * immediately follow it (without an intervening regular token). If there + * is no such token, this field is null. + */ + public Token specialToken; + + /** + * Returns the image. + */ + public String toString() + { + return image; + } + + /** + * Returns a new Token object, by default. However, if you want, you + * can create and return subclass objects based on the value of ofKind. + * Simply add the cases to the switch for all those special cases. + * For example, if you have a subclass of Token called IDToken that + * you want to create if ofKind is ID, simlpy add something like : + * + * case MyParserConstants.ID : return new IDToken(); + * + * to the following switch statement. Then you can cast matchedToken + * variable to the appropriate type and use it in your lexical actions. + */ + public static final Token newToken(int ofKind) + { + switch(ofKind) + { + default : return new Token(); + } + } + +} Added: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/TokenMgrError.java URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/TokenMgrError.java?rev=227018&view=auto ============================================================================== --- jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/TokenMgrError.java (added) +++ jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/TokenMgrError.java Tue Aug 2 07:43:20 2005 @@ -0,0 +1,133 @@ +/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 3.0 */ +package org.apache.commons.configuration.plist; + +class TokenMgrError extends Error +{ + /* + * Ordinals for various reasons why an Error of this type can be thrown. + */ + + /** + * Lexical error occured. + */ + static final int LEXICAL_ERROR = 0; + + /** + * An attempt wass made to create a second instance of a static token manager. + */ + static final int STATIC_LEXER_ERROR = 1; + + /** + * Tried to change to an invalid lexical state. + */ + static final int INVALID_LEXICAL_STATE = 2; + + /** + * Detected (and bailed out of) an infinite loop in the token manager. + */ + static final int LOOP_DETECTED = 3; + + /** + * Indicates the reason why the exception is thrown. It will have + * one of the above 4 values. + */ + int errorCode; + + /** + * Replaces unprintable characters by their espaced (or unicode escaped) + * equivalents in the given string + */ + protected static final String addEscapes(String str) { + StringBuffer retval = new StringBuffer(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u" + s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + continue; + } + } + return retval.toString(); + } + + /** + * Returns a detailed message for the Error when it is thrown by the + * token manager to indicate a lexical error. + * Parameters : + * EOFSeen : indicates if EOF caused the lexicl error + * curLexState : lexical state in which this error occured + * errorLine : line number when the error occured + * errorColumn : column number when the error occured + * errorAfter : prefix that was seen before this error occured + * curchar : the offending character + * Note: You can customize the lexical error message by modifying this method. + */ + protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) { + return("Lexical error at line " + + errorLine + ", column " + + errorColumn + ". Encountered: " + + (EOFSeen ? "<EOF> " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") + + "after : \"" + addEscapes(errorAfter) + "\""); + } + + /** + * You can also modify the body of this method to customize your error messages. + * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not + * of end-users concern, so you can return something like : + * + * "Internal Error : Please file a bug report .... " + * + * from this method for such cases in the release version of your parser. + */ + public String getMessage() { + return super.getMessage(); + } + + /* + * Constructors of various flavors follow. + */ + + public TokenMgrError() { + } + + public TokenMgrError(String message, int reason) { + super(message); + errorCode = reason; + } + + public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) { + this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason); + } +} Added: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/XMLPropertyListConfiguration.java URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/XMLPropertyListConfiguration.java?rev=227018&view=auto ============================================================================== --- jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/XMLPropertyListConfiguration.java (added) +++ jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/plist/XMLPropertyListConfiguration.java Tue Aug 2 07:43:20 2005 @@ -0,0 +1,526 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.configuration.plist; + +import java.io.File; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.Writer; +import java.math.BigDecimal; +import java.net.URL; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.configuration.AbstractHierarchicalFileConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.HierarchicalConfiguration; +import org.apache.commons.configuration.MapConfiguration; +import org.apache.commons.digester.AbstractObjectCreationFactory; +import org.apache.commons.digester.Digester; +import org.apache.commons.digester.ObjectCreateRule; +import org.apache.commons.digester.SetNextRule; +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang.StringUtils; +import org.xml.sax.Attributes; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; + +/** + * Mac OS X configuration file (http://www.apple.com/DTDs/PropertyList-1.0.dtd). + * + * <p>Example:</p> + * <pre> + * <?xml version="1.0"?> + * <!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd"> + * <plist version="1.1"> + * <dict> + * <key>string</key> + * <string>value1</string> + * + * <key>integer</key> + * <integer>12345</integer> + * + * <key>real</key> + * <real>-123.45E-1</real> + * + * <key>boolean</key> + * <true/> + * + * <key>date</key> + * <date>2005-01-01T12:00:00-0700</date> + * + * <key>data</key> + * <data>RHJhY28gRG9ybWllbnMgTnVucXVhbSBUaXRpbGxhbmR1cw==</data> + * + * <key>array</key> + * <array> + * <string>value1</string> + * <string>value2</string> + * <string>value3</string> + * </array> + * + * <key>dictionnary</key> + * <dict> + * <key>key1</key> + * <string>value1</string> + * <key>key2</key> + * <string>value2</string> + * <key>key3</key> + * <string>value3</string> + * </dict> + * + * <key>nested</key> + * <dict> + * <key>node1</key> + * <dict> + * <key>node2</key> + * <dict> + * <key>node3</key> + * <string>value</string> + * </dict> + * </dict> + * </dict> + * + * </dict> + * </plist> + * </pre> + * + * @since 1.2 + * + * @author Emmanuel Bourg + * @version $Revision$, $Date$ + */ +public class XMLPropertyListConfiguration extends AbstractHierarchicalFileConfiguration +{ + private static final int INDENT_SIZE = 4; + + /** + * Creates an empty XMLPropertyListConfiguration object which can be + * used to synthesize a new plist file by adding values and + * then saving(). + */ + public XMLPropertyListConfiguration() { } + + /** + * Creates and loads the property list from the specified file. + * + * @param fileName The name of the plist file to load. + * @throws org.apache.commons.configuration.ConfigurationException Error while loading the plist file + */ + public XMLPropertyListConfiguration(String fileName) throws ConfigurationException + { + super(fileName); + } + + /** + * Creates and loads the property list from the specified file. + * + * @param file The plist file to load. + * @throws ConfigurationException Error while loading the plist file + */ + public XMLPropertyListConfiguration(File file) throws ConfigurationException + { + super(file); + } + + /** + * Creates and loads the property list from the specified URL. + * + * @param url The location of the plist file to load. + * @throws ConfigurationException Error while loading the plist file + */ + public XMLPropertyListConfiguration(URL url) throws ConfigurationException + { + super(url); + } + + public void load(Reader in) throws ConfigurationException + { + // set up the digester + Digester digester = new Digester(); + + // set up the DTD validation + digester.setEntityResolver(new EntityResolver() + { + public InputSource resolveEntity(String publicId, String systemId) + { + return new InputSource(getClass().getClassLoader().getResourceAsStream("PropertyList-1.0.dtd")); + } + }); + digester.setValidating(true); + + // dictionary rules + digester.addRule("*/key", new ObjectCreateRule(PListNode.class) + { + public void end() throws Exception + { + // leave the node on the stack to set the value + } + }); + + digester.addCallMethod("*/key", "setName", 0); + + digester.addRule("*/dict/string", new SetNextAndPopRule("addChild")); + digester.addRule("*/dict/data", new SetNextAndPopRule("addChild")); + digester.addRule("*/dict/integer", new SetNextAndPopRule("addChild")); + digester.addRule("*/dict/real", new SetNextAndPopRule("addChild")); + digester.addRule("*/dict/true", new SetNextAndPopRule("addChild")); + digester.addRule("*/dict/false", new SetNextAndPopRule("addChild")); + digester.addRule("*/dict/date", new SetNextAndPopRule("addChild")); + digester.addRule("*/dict/dict", new SetNextAndPopRule("addChild")); + + digester.addCallMethod("*/dict/string", "addValue", 0); + digester.addCallMethod("*/dict/data", "addDataValue", 0); + digester.addCallMethod("*/dict/integer", "addIntegerValue", 0); + digester.addCallMethod("*/dict/real", "addRealValue", 0); + digester.addCallMethod("*/dict/true", "addTrueValue"); + digester.addCallMethod("*/dict/false", "addFalseValue"); + digester.addCallMethod("*/dict/date", "addDateValue", 0); + + // rules for arrays + digester.addRule("*/dict/array", new SetNextAndPopRule("addChild")); + digester.addRule("*/dict/array", new ObjectCreateRule(ArrayNode.class)); + digester.addSetNext("*/dict/array", "addList"); + + digester.addRule("*/array/array", new ObjectCreateRule(ArrayNode.class)); + digester.addSetNext("*/array/array", "addList"); + + digester.addCallMethod("*/array/string", "addValue", 0); + digester.addCallMethod("*/array/data", "addDataValue", 0); + digester.addCallMethod("*/array/integer", "addIntegerValue", 0); + digester.addCallMethod("*/array/real", "addRealValue", 0); + digester.addCallMethod("*/array/true", "addTrueValue"); + digester.addCallMethod("*/array/false", "addFalseValue"); + digester.addCallMethod("*/array/date", "addDateValue", 0); + + // rule for a dictionary in an array + digester.addFactoryCreate("*/array/dict", new AbstractObjectCreationFactory() + { + public Object createObject(Attributes attributes) throws Exception + { + // create the configuration + XMLPropertyListConfiguration config = new XMLPropertyListConfiguration(); + + // add it to the ArrayNode + ArrayNode node = (ArrayNode) digester.peek(); + node.addValue(config); + + // push the root on the stack + return config.getRoot(); + } + }); + + // parse the file + digester.push(getRoot()); + try + { + digester.parse(in); + } + catch (Exception e) + { + throw new ConfigurationException("Unable to parse the configuration file", e); + } + } + + /** + * Digester rule that sets the object on the stack to the n-1 object + * and remove both of them from the stack. This rule is used to remove + * the configuration node from the stack once its value has been parsed. + */ + private class SetNextAndPopRule extends SetNextRule + { + public SetNextAndPopRule(String methodName) + { + super(methodName); + } + + public SetNextAndPopRule(String methodName, String paramType) + { + super(methodName, paramType); + } + + public void end(String namespace, String name) throws Exception + { + super.end(namespace, name); + digester.pop(); + } + } + + public void save(Writer out) throws ConfigurationException + { + PrintWriter writer = new PrintWriter(out); + + if (getEncoding() != null) + { + writer.println("<?xml version=\"1.0\" encoding=\"" + getEncoding() + "\"?>"); + } + else + { + writer.println("<?xml version=\"1.0\"?>"); + } + + writer.println("<!DOCTYPE plist SYSTEM \"file://localhost/System/Library/DTDs/PropertyList.dtd\">"); + writer.println("<plist version=\"1.1\">"); + + printNode(writer, 1, getRoot()); + + writer.println("</plist>"); + writer.flush(); + } + + /** + * Append a node to the writer, indented according to a specific level. + */ + private void printNode(PrintWriter out, int indentLevel, Node node) + { + String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE); + + if (node.getName() != null) + { + out.println(padding + "<key>" + StringEscapeUtils.escapeXml(node.getName()) + "</key>"); + } + + List children = node.getChildren(); + if (!children.isEmpty()) + { + out.println(padding + "<dict>"); + + Iterator it = children.iterator(); + while (it.hasNext()) + { + Node child = (Node) it.next(); + printNode(out, indentLevel + 1, child); + + if (it.hasNext()) + { + out.println(); + } + } + + out.println(padding + "</dict>"); + } + else + { + Object value = node.getValue(); + printValue(out, indentLevel, value); + } + } + + /** + * Append a value to the writer, indented according to a specific level. + */ + private void printValue(PrintWriter out, int indentLevel, Object value) + { + String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE); + + if (value instanceof Date) + { + out.println(padding + "<date>" + PListNode.format.format((Date) value) + "</date>"); + } + else if (value instanceof Calendar) + { + printValue(out, indentLevel, ((Calendar) value).getTime()); + } + else if (value instanceof Number) + { + if (value instanceof Double || value instanceof Float || value instanceof BigDecimal) + { + out.println(padding + "<real>" + value.toString() + "</real>"); + } + else + { + out.println(padding + "<integer>" + value.toString() + "</integer>"); + } + } + else if (value instanceof Boolean) + { + if (((Boolean) value).booleanValue()) + { + out.println(padding + "<true/>"); + } + else + { + out.println(padding + "<false/>"); + } + } + else if (value instanceof List) + { + out.println(padding + "<array>"); + Iterator it = ((List) value).iterator(); + while (it.hasNext()) + { + printValue(out, indentLevel + 1, it.next()); + } + out.println(padding + "</array>"); + } + else if (value instanceof HierarchicalConfiguration) + { + printNode(out, indentLevel, ((HierarchicalConfiguration) value).getRoot()); + } + else if (value instanceof Configuration) + { + // display a flat Configuration as a dictionary + out.println(padding + "<dict>"); + + Configuration config = (Configuration) value; + Iterator it = config.getKeys(); + while (it.hasNext()) + { + // create a node for each property + String key = (String) it.next(); + Node node = new Node(key); + node.setValue(config.getProperty(key)); + + // print the node + printNode(out, indentLevel + 1, node); + + if (it.hasNext()) + { + out.println(); + } + } + out.println(padding + "</dict>"); + } + else if (value instanceof Map) + { + // display a Map as a dictionary + Map map = (Map) value; + printValue(out, indentLevel, new MapConfiguration(map)); + } + else if (value instanceof byte[]) + { + String base64 = new String(Base64.encodeBase64((byte[]) value)); + out.println(padding + "<data>" + StringEscapeUtils.escapeXml(base64) + "</data>"); + } + else + { + out.println(padding + "<string>" + StringEscapeUtils.escapeXml(String.valueOf(value)) + "</string>"); + } + } + + + /** + * Node extension with addXXX methods to parse the typed data passed by Digester. + * <b>Do not use this class !</b> It is used internally by XMLPropertyConfiguration + * to parse the configuration file, it may be removed at any moment in the future. + */ + public static class PListNode extends Node + { + private static DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); + + public void addChild(Node child) + { + super.addChild(child); + } + + public void setName(String string) + { + super.setName(string); + } + + public void addValue(Object value) + { + if (getValue() == null) + { + setValue(value); + } + else if (getValue() instanceof List) + { + List list = (List) getValue(); + list.add(value); + } + else + { + List list = new ArrayList(); + list.add(getValue()); + list.add(value); + setValue(list); + } + } + + public void addDateValue(String value) + { + try + { + addValue(format.parse(value)); + } + catch (ParseException e) + { + e.printStackTrace(); + } + } + + public void addDataValue(String value) + { + addValue(Base64.decodeBase64(value.getBytes())); + } + + public void addIntegerValue(String value) + { + addValue(new Integer(value)); + } + + public void addRealValue(String value) + { + addValue(new Double(value)); + } + + public void addTrueValue() + { + addValue(Boolean.TRUE); + } + + public void addFalseValue() + { + addValue(Boolean.FALSE); + } + + public void addList(ArrayNode node) + { + addValue(node.getValue()); + } + } + + /** + * Container for array elements. <b>Do not use this class !</b> + * It is used internally by XMLPropertyConfiguration to parse the + * configuration file, it may be removed at any moment in the future. + */ + public static class ArrayNode extends PListNode + { + private List list = new ArrayList(); + + public void addValue(Object value) + { + list.add(value); + } + + public Object getValue() + { + return list; + } + } +} Added: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestPropertyListConfiguration.java URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestPropertyListConfiguration.java?rev=227018&view=auto ============================================================================== --- jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestPropertyListConfiguration.java (added) +++ jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestPropertyListConfiguration.java Tue Aug 2 07:43:20 2005 @@ -0,0 +1,204 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.configuration.plist; + +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; + +import junit.framework.TestCase; +import junitx.framework.ObjectAssert; +import junitx.framework.ArrayAssert; +import junitx.framework.ListAssert; +import org.apache.commons.configuration.Configuration; + +/** + * @author Emmanuel Bourg + * @version $Revision$, $Date$ + */ +public class TestPropertyListConfiguration extends TestCase +{ + private PropertyListConfiguration config; + + private String testProperties = new File("conf/test.plist").getAbsolutePath(); + + protected void setUp() throws Exception + { + config = new PropertyListConfiguration(); + config.setFileName(testProperties); + config.load(); + } + + public void testLoad() + { + assertFalse("the configuration is empty", config.isEmpty()); + } + + public void testString() + { + assertEquals("simple-string", "string1", config.getProperty("simple-string")); + } + + public void testQuotedString() + { + assertEquals("quoted-string", "string2", config.getProperty("quoted-string")); + assertEquals("quoted-string2", "this is a string", config.getProperty("quoted-string2")); + assertEquals("complex-string", "this is a \"complex\" string {(=,;)}", config.getProperty("complex-string")); + } + + public void testEmptyArray() + { + String key = "empty-array"; + assertNotNull("array null", config.getProperty(key)); + + List list = (List) config.getProperty(key); + assertTrue("array is not empty", list.isEmpty()); + } + + public void testArray() + { + String key = "array"; + assertNotNull("array null", config.getProperty(key)); + + List list = (List) config.getProperty(key); + assertFalse("array is empty", list.isEmpty()); + + assertEquals("1st value", "value1", list.get(0)); + assertEquals("2nd value", "value2", list.get(1)); + assertEquals("3rd value", "value3", list.get(2)); + } + + public void testNestedArrays() + { + String key = "nested-arrays"; + + Object array = config.getProperty(key); + + // root array + assertNotNull("array not found", array); + ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, array); + List list = config.getList(key); + + assertFalse("empty array", list.isEmpty()); + assertEquals("size", 2, list.size()); + + // 1st array + ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, list.get(0)); + List list1 = (List) list.get(0); + assertFalse("nested array 1 is empty", list1.isEmpty()); + assertEquals("size", 2, list1.size()); + assertEquals("1st element", "a", list1.get(0)); + assertEquals("2nd element", "b", list1.get(1)); + + // 2nd array + ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, list.get(1)); + List list2 = (List) list.get(1); + assertFalse("nested array 2 is empty", list2.isEmpty()); + assertEquals("size", 2, list2.size()); + assertEquals("1st element", "c", list2.get(0)); + assertEquals("2nd element", "d", list2.get(1)); + } + + public void testDictionary() + { + assertEquals("1st element in dictionary", "bar1", config.getProperty("dictionary.foo1")); + assertEquals("2nd element in dictionary", "bar2", config.getProperty("dictionary.foo2")); + } + + public void testDictionaryArray() + { + String key = "dictionary-array"; + + Object array = config.getProperty(key); + + // root array + assertNotNull("array not found", array); + ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, array); + List list = config.getList(key); + + assertFalse("empty array", list.isEmpty()); + assertEquals("size", 2, list.size()); + + // 1st dictionary + ObjectAssert.assertInstanceOf("the dict element is not parsed as a Configuration", Configuration.class, list.get(0)); + Configuration conf1 = (Configuration) list.get(0); + assertFalse("configuration 1 is empty", conf1.isEmpty()); + assertEquals("configuration element", "bar", conf1.getProperty("foo")); + + // 2nd dictionary + ObjectAssert.assertInstanceOf("the dict element is not parsed as a Configuration", Configuration.class, list.get(1)); + Configuration conf2 = (Configuration) list.get(1); + assertFalse("configuration 2 is empty", conf2.isEmpty()); + assertEquals("configuration element", "value", conf2.getProperty("key")); + } + + public void testNestedDictionaries() + { + assertEquals("nested property", "value", config.getString("nested-dictionaries.foo.bar.key")); + } + + public void testData() + { + ObjectAssert.assertInstanceOf("data", (new byte[0]).getClass(), config.getProperty("data")); + ArrayAssert.assertEquals("data", "foo bar".getBytes(), (byte[]) config.getProperty("data")); + } + + + public void testSave() throws Exception + { + File savedFile = new File("target/testsave.plist"); + + // remove the file previously saved if necessary + if (savedFile.exists()) + { + assertTrue(savedFile.delete()); + } + + // save the configuration + String filename = savedFile.getAbsolutePath(); + config.save(filename); + + assertTrue("The saved file doesn't exist", savedFile.exists()); + + // read the configuration and compare the properties + Configuration checkConfig = new PropertyListConfiguration(filename); + for (Iterator i = config.getKeys(); i.hasNext();) + { + String key = (String) i.next(); + assertTrue("The saved configuration doesn't contain the key '" + key + "'", checkConfig.containsKey(key)); + + Object value = checkConfig.getProperty(key); + if (value instanceof byte[]) + { + byte[] array = (byte[]) value; + ArrayAssert.assertEquals("Value of the '" + key + "' property", (byte[]) config.getProperty(key), array); + } + else if (value instanceof List) + { + List list1 = (List) value; + ListAssert.assertEquals("Value of the '" + key + "' property", (List) config.getProperty(key), list1); + } + else + { + assertEquals("Value of the '" + key + "' property", config.getProperty(key), checkConfig.getProperty(key)); + } + + } + } +} Added: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestXMLPropertyListConfiguration.java URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestXMLPropertyListConfiguration.java?rev=227018&view=auto ============================================================================== --- jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestXMLPropertyListConfiguration.java (added) +++ jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/plist/TestXMLPropertyListConfiguration.java Tue Aug 2 07:43:20 2005 @@ -0,0 +1,235 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.configuration.plist; + +import java.io.File; +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; + +import junit.framework.TestCase; +import junitx.framework.ObjectAssert; +import junitx.framework.ArrayAssert; +import junitx.framework.ListAssert; +import org.apache.commons.configuration.FileConfiguration; +import org.apache.commons.configuration.Configuration; + +/** + * @author Emmanuel Bourg + * @version $Revision$, $Date$ + */ +public class TestXMLPropertyListConfiguration extends TestCase +{ + private FileConfiguration config; + + protected void setUp() throws Exception + { + config = new XMLPropertyListConfiguration(); + config.setFileName("conf/test.plist.xml"); + config.load(); + } + + public void testString() throws Exception + { + assertEquals("'string' property", "value1", config.getString("string")); + } + + public void testInteger() throws Exception + { + assertEquals("'integer' property", 12345, config.getInt("integer")); + } + + public void testReal() throws Exception + { + assertEquals("'real' property", -12.345, config.getDouble("real"), 0); + } + + public void testBoolean() throws Exception + { + assertEquals("'boolean1' property", true, config.getBoolean("boolean1")); + assertEquals("'boolean2' property", false, config.getBoolean("boolean2")); + } + + public void testDictionary() + { + assertEquals("1st element", "value1", config.getProperty("dictionary.key1")); + assertEquals("2nd element", "value2", config.getProperty("dictionary.key2")); + assertEquals("3rd element", "value3", config.getProperty("dictionary.key3")); + } + + public void testSubset() + { + Configuration subset = config.subset("dictionary"); + Iterator keys = subset.getKeys(); + + String key = (String) keys.next(); + assertEquals("1st key", "key1", key); + assertEquals("1st value", "value1", subset.getString(key)); + + key = (String) keys.next(); + assertEquals("2nd key", "key2", key); + assertEquals("2nd value", "value2", subset.getString(key)); + + key = (String) keys.next(); + assertEquals("3rd key", "key3", key); + assertEquals("3rd value", "value3", subset.getString(key)); + + assertFalse("more than 3 properties founds", keys.hasNext()); + } + + public void testArray() + { + Object array = config.getProperty("array"); + + assertNotNull("array not found", array); + ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, array); + List list = config.getList("array"); + + assertFalse("empty array", list.isEmpty()); + assertEquals("size", 3, list.size()); + assertEquals("1st element", "value1", list.get(0)); + assertEquals("2nd element", "value2", list.get(1)); + assertEquals("3rd element", "value3", list.get(2)); + } + + public void testNestedArray() + { + String key = "nested-array"; + + Object array = config.getProperty(key); + + // root array + assertNotNull("array not found", array); + ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, array); + List list = config.getList(key); + + assertFalse("empty array", list.isEmpty()); + assertEquals("size", 2, list.size()); + + // 1st array + ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, list.get(0)); + List list1 = (List) list.get(0); + assertFalse("nested array 1 is empty", list1.isEmpty()); + assertEquals("size", 2, list1.size()); + assertEquals("1st element", "a", list1.get(0)); + assertEquals("2nd element", "b", list1.get(1)); + + // 2nd array + ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, list.get(1)); + List list2 = (List) list.get(1); + assertFalse("nested array 2 is empty", list2.isEmpty()); + assertEquals("size", 2, list2.size()); + assertEquals("1st element", "c", list2.get(0)); + assertEquals("2nd element", "d", list2.get(1)); + } + + public void testDictionaryArray() + { + String key = "dictionary-array"; + + Object array = config.getProperty(key); + + // root array + assertNotNull("array not found", array); + ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, array); + List list = config.getList(key); + + assertFalse("empty array", list.isEmpty()); + assertEquals("size", 2, list.size()); + + // 1st dictionary + ObjectAssert.assertInstanceOf("the dict element is not parsed as a Configuration", Configuration.class, list.get(0)); + Configuration conf1 = (Configuration) list.get(0); + assertFalse("configuration 1 is empty", conf1.isEmpty()); + assertEquals("configuration element", "bar", conf1.getProperty("foo")); + + // 2nd dictionary + ObjectAssert.assertInstanceOf("the dict element is not parsed as a Configuration", Configuration.class, list.get(1)); + Configuration conf2 = (Configuration) list.get(1); + assertFalse("configuration 2 is empty", conf2.isEmpty()); + assertEquals("configuration element", "value", conf2.getProperty("key")); + } + + public void testNested() + { + assertEquals("nested property", "value", config.getString("nested.node1.node2.node3")); + } + + public void testSave() throws Exception + { + File savedFile = new File("target/testsave.plist.xml"); + + // remove the file previously saved if necessary + if (savedFile.exists()) + { + assertTrue(savedFile.delete()); + } + + // add an array of strings to the configuration + /* + config.addProperty("string", "value1"); + List list = new ArrayList(); + for (int i = 1; i < 5; i++) + { + list.add("value" + i); + } + config.addProperty("newarray", list);*/ + // todo : investigate why the array structure of 'newarray' is lost in the saved file + + // add a map of strings + /* + Map map = new HashMap(); + map.put("foo", "bar"); + map.put("int", new Integer(123)); + config.addProperty("newmap", map); + */ + // todo : a Map added to a HierarchicalConfiguration should be decomposed as list of nodes + + // save the configuration + String filename = savedFile.getAbsolutePath(); + config.save(filename); + + assertTrue("The saved file doesn't exist", savedFile.exists()); + + // read the configuration and compare the properties + Configuration checkConfig = new XMLPropertyListConfiguration(filename); + for (Iterator i = config.getKeys(); i.hasNext();) + { + String key = (String) i.next(); + assertTrue("The saved configuration doesn't contain the key '" + key + "'", checkConfig.containsKey(key)); + + Object value = checkConfig.getProperty(key); + if (value instanceof byte[]) + { + byte[] array = (byte[]) value; + ArrayAssert.assertEquals("Value of the '" + key + "' property", (byte[]) config.getProperty(key), array); + } + else if (value instanceof List) + { + List list1 = (List) value; + ListAssert.assertEquals("Value of the '" + key + "' property", (List) config.getProperty(key), list1); + } + else + { + assertEquals("Value of the '" + key + "' property", config.getProperty(key), checkConfig.getProperty(key)); + } + + } + } +} Modified: jakarta/commons/proper/configuration/trunk/xdocs/changes.xml URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/xdocs/changes.xml?rev=227018&r1=227017&r2=227018&view=diff ============================================================================== --- jakarta/commons/proper/configuration/trunk/xdocs/changes.xml (original) +++ jakarta/commons/proper/configuration/trunk/xdocs/changes.xml Tue Aug 2 07:43:20 2005 @@ -23,6 +23,11 @@ <body> <release version="1.2-dev" date="in SVN"> + <action dev="ebourg" type="add" issue="32633"> + New configurations implementing the "property list" format used in + NeXT/OpenStep and its XML variant used in Mac OS X. + (PropertyListConfiguration and XMLPropertyListConfiguration) + </action> <action dev="oheger" type="update" issue="35938"> Resolved some issues with XMLConfiguration and properties containing the delimiter character. These properties are now correctly treated, --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]