http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T6.java
----------------------------------------------------------------------
diff --git a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T6.java 
b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T6.java
deleted file mode 100755
index c34126c..0000000
--- a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T6.java
+++ /dev/null
@@ -1,38 +0,0 @@
-// 
***************************************************************************************************************************
-// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
-// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
-// * to you 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.juneau.xml.xml1b;
-
-import org.apache.juneau.annotation.*;
-import org.apache.juneau.xml.annotation.*;
-
-@Xml(prefix="foo",namespace="http://foo";)
-@Bean(sort=true)
-@SuppressWarnings("javadoc")
-public class T6 {
-
-       public int f1 = 1;
-
-       @Xml(prefix="bar",namespace="http://bar";) public int f2 = 2;
-
-       private int f3 = 3;
-       public int getF3() { return f3; }
-       public void setF3(int f3) { this.f3 = f3; }
-
-       private int f4 = 4;
-       @Xml(prefix="baz",namespace="http://baz";) public int getF4() { return 
f4; }
-       public void setF4(int f4) { this.f4 = f4; }
-
-       public boolean equals(T6 x) {
-               return x.f1 == f1 && x.f2 == f2 && x.f3 == f3 && x.f4 == f4;
-       }
-}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T7.java
----------------------------------------------------------------------
diff --git a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T7.java 
b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T7.java
deleted file mode 100755
index f2960aa..0000000
--- a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T7.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// 
***************************************************************************************************************************
-// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
-// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
-// * to you 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.juneau.xml.xml1b;
-
-import org.apache.juneau.annotation.*;
-import org.apache.juneau.xml.annotation.*;
-
-@Bean(sort=true)
-@SuppressWarnings("javadoc")
-public class T7 {
-
-       @BeanProperty(name="g1") public int f1 = 1;
-
-       @Xml(prefix="bar",namespace="http://bar";) @BeanProperty(name="g2") 
public int f2 = 2;
-
-       private int f3 = 3;
-       @BeanProperty(name="g3") public int getF3() { return f3; }
-       @BeanProperty(name="g3") public void setF3(int f3) { this.f3 = f3; }
-
-       private int f4 = 4;
-       @BeanProperty(name="g4") @Xml(prefix="baz",namespace="http://baz";) 
public int getF4() { return f4; }
-       @BeanProperty(name="g4") public void setF4(int f4) { this.f4 = f4; }
-
-       public boolean equals(T7 x) {
-               return x.f1 == f1 && x.f2 == f2 && x.f3 == f3 && x.f4 == f4;
-       }
-}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/package-info.java
----------------------------------------------------------------------
diff --git 
a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/package-info.java 
b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/package-info.java
deleted file mode 100755
index 306db1b..0000000
--- 
a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/package-info.java
+++ /dev/null
@@ -1,16 +0,0 @@
-// 
***************************************************************************************************************************
-// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
-// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
-// * to you 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.                                              *
-// 
***************************************************************************************************************************
-@XmlSchema(prefix="p1",namespace="http://p1";)
-package org.apache.juneau.xml.xml1b;
-import org.apache.juneau.xml.annotation.*;
-

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T8.java
----------------------------------------------------------------------
diff --git a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T8.java 
b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T8.java
deleted file mode 100755
index 0c383eb..0000000
--- a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T8.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// 
***************************************************************************************************************************
-// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
-// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
-// * to you 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.juneau.xml.xml1c;
-
-import org.apache.juneau.xml.annotation.*;
-
-@Xml(prefix="p2")
-@SuppressWarnings("javadoc")
-public class T8 {
-
-       public int f1 = 1;
-
-       @Xml(prefix="p1") public int f2 = 2;
-
-       @Xml(prefix="c1") public int f3 = 3;
-
-       @Xml(prefix="f1")
-       public int f4 = 4;
-
-       public boolean equals(T8 x) {
-               return x.f1 == f1 && x.f2 == f2 && x.f3 == f3 && x.f4 == f4;
-       }
-}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T9.java
----------------------------------------------------------------------
diff --git a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T9.java 
b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T9.java
deleted file mode 100755
index 7009626..0000000
--- a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T9.java
+++ /dev/null
@@ -1,23 +0,0 @@
-// 
***************************************************************************************************************************
-// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
-// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
-// * to you 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.juneau.xml.xml1c;
-
-@SuppressWarnings("javadoc")
-public class T9 {
-
-       public int f1 = 1;
-
-       public boolean equals(T9 x) {
-               return x.f1 == f1;
-       }
-}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/package-info.java
----------------------------------------------------------------------
diff --git 
a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/package-info.java 
b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/package-info.java
deleted file mode 100755
index e054388..0000000
--- 
a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/package-info.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// 
***************************************************************************************************************************
-// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
-// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
-// * to you 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.                                              *
-// 
***************************************************************************************************************************
-@XmlSchema(
-       prefix="p1",
-       xmlNs={
-               @XmlNs(prefix="p1",namespaceURI="http://p1";),
-               @XmlNs(prefix="p2",namespaceURI="http://p2";),
-               @XmlNs(prefix="p3",namespaceURI="http://p3(unused)"),
-               @XmlNs(prefix="c1",namespaceURI="http://c1";),
-               @XmlNs(prefix="f1",namespaceURI="http://f1";)
-       }
-)
-package org.apache.juneau.xml.xml1c;
-import org.apache.juneau.xml.annotation.*;
-

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core/juneau-config/.classpath
----------------------------------------------------------------------
diff --git a/juneau-core/juneau-config/.classpath 
b/juneau-core/juneau-config/.classpath
new file mode 100644
index 0000000..2fd4a04
--- /dev/null
+++ b/juneau-core/juneau-config/.classpath
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="src" output="target/classes" path="src/main/java">
+               <attributes>
+                       <attribute name="optional" value="true"/>
+                       <attribute name="maven.pomderived" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry kind="con" 
path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
+               <attributes>
+                       <attribute name="maven.pomderived" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry kind="src" output="target/test-classes" 
path="src/test/java">
+               <attributes>
+                       <attribute name="optional" value="true"/>
+                       <attribute name="maven.pomderived" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry kind="con" 
path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+               <attributes>
+                       <attribute name="maven.pomderived" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry kind="output" path="target/classes"/>
+</classpath>

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core/juneau-config/.gitignore
----------------------------------------------------------------------
diff --git a/juneau-core/juneau-config/.gitignore 
b/juneau-core/juneau-config/.gitignore
new file mode 100644
index 0000000..d274d47
--- /dev/null
+++ b/juneau-core/juneau-config/.gitignore
@@ -0,0 +1,3 @@
+/target/
+/.settings/
+/.DS_Store

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core/juneau-config/.project
----------------------------------------------------------------------
diff --git a/juneau-core/juneau-config/.project 
b/juneau-core/juneau-config/.project
new file mode 100644
index 0000000..23dc42c
--- /dev/null
+++ b/juneau-core/juneau-config/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>juneau-config</name>
+       <projects>
+       </projects>
+    <buildSpec>
+        <buildCommand>
+            <name>org.eclipse.jdt.core.javabuilder</name>
+            <arguments>
+            </arguments>
+        </buildCommand>
+        <buildCommand>
+            <name>edu.umd.cs.findbugs.plugin.eclipse.findbugsBuilder</name>
+            <arguments>
+            </arguments>
+        </buildCommand>
+        <buildCommand>
+            <name>org.eclipse.m2e.core.maven2Builder</name>
+            <arguments>
+            </arguments>
+        </buildCommand>
+    </buildSpec>
+    <natures>
+        <nature>org.eclipse.m2e.core.maven2Nature</nature>
+        <nature>org.eclipse.jdt.core.javanature</nature>
+        <nature>edu.umd.cs.findbugs.plugin.eclipse.findbugsNature</nature>
+    </natures>
+</projectDescription>

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core/juneau-config/pom.xml
----------------------------------------------------------------------
diff --git a/juneau-core/juneau-config/pom.xml 
b/juneau-core/juneau-config/pom.xml
new file mode 100644
index 0000000..516b07d
--- /dev/null
+++ b/juneau-core/juneau-config/pom.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 
***************************************************************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
+ * to you 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.  
                                            *
+ 
***************************************************************************************************************************
+-->
+<project 
+       xmlns="http://maven.apache.org/POM/4.0.0";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+
+       <modelVersion>4.0.0</modelVersion>
+
+       <parent>
+               <groupId>org.apache.juneau</groupId>
+               <artifactId>juneau-core</artifactId>
+               <version>6.3.2-incubating-SNAPSHOT</version>
+       </parent>
+
+       <artifactId>juneau-config</artifactId>
+       <name>Apache Juneau Config File API</name>
+       <description>Dynamic configuration files.</description>
+       <packaging>bundle</packaging>
+
+       <dependencies>
+               <dependency>
+                       <groupId>org.apache.juneau</groupId>
+                       <artifactId>juneau-marshall</artifactId>
+                       <version>${project.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.apache.juneau</groupId>
+                       <artifactId>juneau-svl</artifactId>
+                       <version>${project.version}</version>
+               </dependency>
+       </dependencies>
+
+       <properties>
+               <!-- Skip javadoc generation since we generate them in the 
aggregate pom -->
+               <maven.javadoc.skip>true</maven.javadoc.skip>
+       </properties>
+
+       <build>
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.felix</groupId>
+                               <artifactId>maven-bundle-plugin</artifactId>
+                               <version>3.2.0</version>
+                               <extensions>true</extensions>
+                       </plugin>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-surefire-plugin</artifactId>
+                               <configuration>
+                                       <includes>
+                                               
<include>**/*Test.class</include>
+                                       </includes>
+                               </configuration>
+                       </plugin>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-source-plugin</artifactId>
+                               <executions>
+                                       <execution>
+                                               <id>attach-sources</id>
+                                               <phase>verify</phase>
+                                               <goals>
+                                                       <goal>jar-no-fork</goal>
+                                               </goals>
+                                       </execution>
+                               </executions>
+                       </plugin>
+                       <plugin>
+                               <groupId>org.jacoco</groupId>
+                               <artifactId>jacoco-maven-plugin</artifactId>
+                               <version>0.7.2.201409121644</version>
+                               <executions>
+                                       <execution>
+                                               <id>default-prepare-agent</id>
+                                               <goals>
+                                                       
<goal>prepare-agent</goal>
+                                               </goals>
+                                       </execution>
+                                       <execution>
+                                               <id>default-report</id>
+                                               <phase>prepare-package</phase>
+                                               <goals>
+                                                       <goal>report</goal>
+                                               </goals>
+                                       </execution>
+                               </executions>
+                       </plugin>
+               </plugins>
+       </build>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFile.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFile.java 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFile.java
new file mode 100644
index 0000000..5a956a5
--- /dev/null
+++ 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFile.java
@@ -0,0 +1,1215 @@
+// 
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
+// * to you 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.juneau.ini;
+
+import static java.lang.reflect.Modifier.*;
+import static org.apache.juneau.ini.ConfigFileFormat.*;
+import static org.apache.juneau.ini.ConfigUtils.*;
+import static org.apache.juneau.internal.ThrowableUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.internal.ArrayUtils.*;
+
+import java.beans.*;
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.svl.*;
+
+/**
+ * Implements the API for accessing the contents of a config file.
+ *
+ * <p>
+ * Refer to <a class='doclink' 
href='package-summary.html#TOC'>org.apache.juneau.ini</a> for usage information.
+ */
+public abstract class ConfigFile implements Map<String,Section> {
+
+       
//--------------------------------------------------------------------------------
+       // Abstract methods
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Retrieves an entry value from this config file.
+        *
+        * @param sectionName The section name.  Must not be <jk>null</jk>.
+        * @param sectionKey The section key.  Must not be <jk>null</jk>.
+        * @return The value, or the default value if the section or value 
doesn't exist.
+        */
+       public abstract String get(String sectionName, String sectionKey);
+
+       /**
+        * Sets an entry value in this config file.
+        *
+        * @param sectionName The section name.  Must not be <jk>null</jk>.
+        * @param sectionKey The section key.  Must not be <jk>null</jk>.
+        * @param value The new value.
+        * @param serializer
+        *      The serializer to use for serializing the object.
+        *      If <jk>null</jk>, then uses the predefined serializer on the 
config file.
+        * @param encoded If <jk>true</jk>, then encode the value using the 
encoder associated with this config file.
+        * @param newline If <jk>true</jk>, then put serialized output on a 
separate line from the key.
+        * @return The previous value, or <jk>null</jk> if the section or key 
did not previously exist.
+        * @throws SerializeException If the object value could not be 
converted to a JSON string for some reason.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract String put(String sectionName, String sectionKey, 
Object value, Serializer serializer,
+                       boolean encoded, boolean newline) throws 
SerializeException;
+
+       /**
+        * Identical to {@link #put(String, String, Object, Serializer, 
boolean, boolean)} except used when the value is a
+        * simple string to avoid having to catch a {@link SerializeException}.
+        *
+        * @param sectionName The section name.  Must not be <jk>null</jk>.
+        * @param sectionKey The section key.  Must not be <jk>null</jk>.
+        * @param value The new value.
+        * @param encoded
+        * @return The previous value, or <jk>null</jk> if the section or key 
did not previously exist.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract String put(String sectionName, String sectionKey, 
String value, boolean encoded);
+
+
+       /**
+        * Removes an entry from this config file.
+        *
+        * @param sectionName The section name.  Must not be <jk>null</jk>.
+        * @param sectionKey The section key.  Must not be <jk>null</jk>.
+        * @return The previous value, or <jk>null</jk> if the section or key 
did not previously exist.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract String remove(String sectionName, String sectionKey);
+
+       /**
+        * Returns the current set of keys in the specified section.
+        *
+        * @param sectionName The section name.  Must not be <jk>null</jk>.
+        * @return The list of keys in the specified section, or <jk>null</jk> 
if section does not exist.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract Set<String> getSectionKeys(String sectionName);
+
+       /**
+        * Reloads this config file object from the persisted file contents if 
the modified timestamp on the file has changed.
+        *
+        * @return This object (for method chaining).
+        * @throws IOException If file could not be read, or file is not 
associated with this object.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract ConfigFile loadIfModified() throws IOException;
+
+       /**
+        * Loads this config file object from the persisted file contents.
+        *
+        * @return This object (for method chaining).
+        * @throws IOException If file could not be read, or file is not 
associated with this object.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract ConfigFile load() throws IOException;
+
+       /**
+        * Loads this config file object from the specified reader.
+        *
+        * @param r The reader to read from.
+        * @return This object (for method chaining).
+        * @throws IOException If file could not be read, or file is not 
associated with this object.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract ConfigFile load(Reader r) throws IOException;
+
+       /**
+        * Adds arbitrary lines to the specified config file section.
+        *
+        * <p>
+        * The lines can be any of the following....
+        * <ul class='spaced-list'>
+        *      <li>
+        *              <js>"# comment"</js> - A comment line.
+        *      <li>
+        *              <js>"key=val"</js> - A key/value pair (equivalent to 
calling {@link #put(String,Object)}.
+        *      <li>
+        *              <js>" foobar "</js> - Anything else (interpreted as a 
comment).
+        * </ul>
+        *
+        * <p>
+        * If the section does not exist, it will automatically be created.
+        *
+        * @param section The name of the section to add lines to, or 
<jk>null</jk> to add to the beginning unnamed section.
+        * @param lines The lines to add to the section.
+        * @return This object (for method chaining).
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract ConfigFile addLines(String section, String...lines);
+
+       /**
+        * Adds header comments to the specified section.
+        *
+        * <p>
+        * Header comments are defined as lines that start with <jk>"#"</jk> 
immediately preceding a section header
+        * <jk>"[section]"</jk>.
+        * These are handled as part of the section itself instead of being 
interpreted as comments in the previous section.
+        *
+        * <p>
+        * Header comments can be of the following formats...
+        * <ul class='spaced-list'>
+        *      <li>
+        *              <js>"# comment"</js> - A comment line.
+        *      <li>
+        *              <js>"comment"</js> - Anything else (will automatically 
be prefixed with <js>"# "</js>).
+        * </ul>
+        *
+        * <p>
+        * If the section does not exist, it will automatically be created.
+        *
+        * @param section The name of the section to add lines to, or 
<jk>null</jk> to add to the default section.
+        * @param headerComments The comment lines to add to the section.
+        * @return This object (for method chaining).
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract ConfigFile addHeaderComments(String section, 
String...headerComments);
+
+       /**
+        * Removes any header comments from the specified section.
+        *
+        * @param section The name of the section to remove header comments 
from.
+        * @return This object (for method chaining).
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract ConfigFile clearHeaderComments(String section);
+
+       /**
+        * Returns the reusable bean session associated with this config file.
+        *
+        * <p>
+        * Used for performing simple datatype conversions.
+        *
+        * @return The reusable bean session associated with this config file.
+        */
+       protected abstract BeanSession getBeanSession();
+
+       /**
+        * Converts the specified object to a string.
+        *
+        * <p>
+        * The serialized output is identical to LAX JSON (JSON with unquoted 
attributes) except for the following
+        * exceptions:
+        * <ul>
+        *      <li>Top level strings are not quoted.
+        * </ul>
+        *
+        * @param o The object to serialize.
+        * @param serializer
+        *      The serializer to use for serializing the object.
+        *      If <jk>null</jk>, then uses the predefined serializer on the 
config file.
+        * @param newline If <jk>true</jk>, add a newline at the beginning of 
the value.
+        * @return The serialized object.
+        * @throws SerializeException
+        */
+       protected abstract String serialize(Object o, Serializer serializer, 
boolean newline) throws SerializeException;
+
+       /**
+        * Converts the specified string to an object of the specified type.
+        *
+        * @param s The string to parse.
+        * @param parser
+        *      The parser to use for parsing the object.
+        *      If <jk>null</jk>, then uses the predefined parser on the config 
file.
+        * @param type The data type to create.
+        * @param args The generic type arguments if the type is a {@link 
Collection} or {@link Map}
+        * @return The parsed object.
+        * @throws ParseException
+        */
+       protected abstract <T> T parse(String s, Parser parser, Type type, 
Type...args) throws ParseException;
+
+       /**
+        * Places a read lock on this config file.
+        */
+       protected abstract void readLock();
+
+       /**
+        * Removes the read lock on this config file.
+        */
+       protected abstract void readUnlock();
+
+
+       
//--------------------------------------------------------------------------------
+       // API methods
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Returns the specified value as a string from the config file.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param def The default value if the section or value does not exist.
+        * @return The value, or the default value if the section or value 
doesn't exist.
+        */
+       public final String getString(String key, String def) {
+               assertFieldNotNull(key, "key");
+               String s = get(getSectionName(key), getSectionKey(key));
+               return (StringUtils.isEmpty(s) && def != null ? def : s);
+       }
+
+       /**
+        * Removes an entry with the specified key.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @return The previous value, or <jk>null</jk> if the section or key 
did not previously exist.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public final String removeString(String key) {
+               assertFieldNotNull(key, "key");
+               return remove(getSectionName(key), getSectionKey(key));
+       }
+
+       /**
+        * Gets the entry with the specified key and converts it to the 
specified value.
+        *
+        * <p>
+        * The key can be in one of the following formats...
+        * <ul class='spaced-list'>
+        *      <li>
+        *              <js>"key"</js> - A value in the default section (i.e. 
defined above any <code>[section]</code> header).
+        *      <li>
+        *              <js>"section/key"</js> - A value from the specified 
section.
+        * </ul>
+        *
+        * <p>
+        * The type can be a simple type (e.g. beans, strings, numbers) or 
parameterized type (collections/maps).
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode'>
+        *      ConfigFile cf = <jk>new</jk> 
ConfigFileBuilder().build(<js>"MyConfig.cfg"</js>);
+        *
+        *      <jc>// Parse into a linked-list of strings.</jc>
+        *      List l = cf.getObject(<js>"MySection/myListOfStrings"</js>, 
LinkedList.<jk>class</jk>, String.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a linked-list of beans.</jc>
+        *      List l = cf.getObject(<js>"MySection/myListOfBeans"</js>, 
LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a linked-list of linked-lists of strings.</jc>
+        *      List l = cf.getObject(<js>"MySection/my2dListOfStrings"</js>, 
LinkedList.<jk>class</jk>,
+        *              LinkedList.<jk>class</jk>, String.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a map of string keys/values.</jc>
+        *      Map m = cf.getObject(<js>"MySection/myMap"</js>, 
TreeMap.<jk>class</jk>, String.<jk>class</jk>,
+        *              String.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a map containing string keys and values of 
lists containing beans.</jc>
+        *      Map m = cf.getObject(<js>"MySection/myMapOfListsOfBeans"</js>, 
TreeMap.<jk>class</jk>, String.<jk>class</jk>,
+        *              List.<jk>class</jk>, MyBean.<jk>class</jk>);
+        * </p>
+        *
+        * <p>
+        * <code>Collection</code> classes are assumed to be followed by zero 
or one objects indicating the element type.
+        *
+        * <p>
+        * <code>Map</code> classes are assumed to be followed by zero or two 
meta objects indicating the key and value
+        * types.
+        *
+        * <p>
+        * The array can be arbitrarily long to indicate arbitrarily complex 
data structures.
+        *
+        * <h5 class='section'>Notes:</h5>
+        * <ul>
+        *      <li>Use the {@link #getObject(String, Class)} method instead if 
you don't need a parameterized map/collection.
+        * </ul>
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param type
+        *      The object type to create.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType},
+        *              {@link GenericArrayType}
+        * @param args
+        *      The type arguments of the class if it's a collection or map.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType},
+        *      {@link GenericArrayType}
+        *      <br>Ignored if the main type is not a map or collection.
+        * @throws ParseException If parser could not parse the value or if a 
parser is not registered with this config file.
+        * @return The value, or <jk>null</jk> if the section or key does not 
exist.
+        */
+       public final <T> T getObject(String key, Type type, Type...args) throws 
ParseException {
+               return getObject(key, (Parser)null, type, args);
+       }
+
+       /**
+        * Same as {@link #getObject(String, Type, Type...)} but allows you to 
specify the parser to use to parse the value.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param parser
+        *      The parser to use for parsing the object.
+        *      If <jk>null</jk>, then uses the predefined parser on the config 
file.
+        * @param type
+        *      The object type to create.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType},
+        *      {@link GenericArrayType}
+        * @param args
+        *      The type arguments of the class if it's a collection or map.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType},
+        *      {@link GenericArrayType}
+        *      <br>Ignored if the main type is not a map or collection.
+        * @throws ParseException If parser could not parse the value or if a 
parser is not registered with this config file.
+        * @return The value, or <jk>null</jk> if the section or key does not 
exist.
+        */
+       public final <T> T getObject(String key, Parser parser, Type type, 
Type...args) throws ParseException {
+               assertFieldNotNull(key, "key");
+               assertFieldNotNull(type, "type");
+               return parse(getString(key), parser, type, args);
+       }
+
+       /**
+        * Same as {@link #getObject(String, Type, Type...)} except optimized 
for a non-parameterized class.
+        *
+        * <p>
+        * This is the preferred parse method for simple types since you don't 
need to cast the results.
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode'>
+        *      ConfigFile cf = <jk>new</jk> 
ConfigFileBuilder().build(<js>"MyConfig.cfg"</js>);
+        *
+        *      <jc>// Parse into a string.</jc>
+        *      String s = cf.getObject(<js>"MySection/mySimpleString"</js>, 
String.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a bean.</jc>
+        *      MyBean b = cf.getObject(<js>"MySection/myBean"</js>, 
MyBean.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a bean array.</jc>
+        *      MyBean[] b = cf.getObject(<js>"MySection/myBeanArray"</js>, 
MyBean[].<jk>class</jk>);
+        *
+        *      <jc>// Parse into a linked-list of objects.</jc>
+        *      List l = cf.getObject(<js>"MySection/myList"</js>, 
LinkedList.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a map of object keys/values.</jc>
+        *      Map m = cf.getObject(<js>"MySection/myMap"</js>, 
TreeMap.<jk>class</jk>);
+        * </p>
+        *
+        * @param <T> The class type of the object being created.
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param type The object type to create.
+        * @return The parsed object.
+        * @throws ParseException
+        *      If the input contains a syntax error or is malformed, or is not 
valid for the specified type.
+        * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for 
maps and collections.
+        */
+       public final <T> T getObject(String key, Class<T> type) throws 
ParseException {
+               return getObject(key, (Parser)null, type);
+       }
+
+       /**
+        * Same as {@link #getObject(String, Class)} but allows you to specify 
the parser to use to parse the value.
+        *
+        * @param <T> The class type of the object being created.
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param parser
+        *      The parser to use for parsing the object.
+        *      If <jk>null</jk>, then uses the predefined parser on the config 
file.
+        * @param type The object type to create.
+        * @return The parsed object.
+        * @throws ParseException
+        *      If the input contains a syntax error or is malformed, or is not 
valid for the specified type.
+        * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for 
maps and collections.
+        */
+       public final <T> T getObject(String key, Parser parser, Class<T> type) 
throws ParseException {
+               assertFieldNotNull(key, "key");
+               assertFieldNotNull(type, "c");
+               return parse(getString(key), parser, type);
+       }
+
+       /**
+        * Gets the entry with the specified key and converts it to the 
specified value.
+        *
+        * <p>
+        * Same as {@link #getObject(String, Class)}, but with a default value.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param def The default value if section or key does not exist.
+        * @param type The class to convert the value to.
+        * @throws ParseException If parser could not parse the value or if a 
parser is not registered with this config file.
+        * @return The value, or <jk>null</jk> if the section or key does not 
exist.
+        */
+       public final <T> T getObjectWithDefault(String key, T def, Class<T> 
type) throws ParseException {
+               return getObjectWithDefault(key, null, def, type);
+       }
+
+       /**
+        * Same as {@link #getObjectWithDefault(String, Object, Class)} but 
allows you to specify the parser to use to parse
+        * the value.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param parser
+        *      The parser to use for parsing the object.
+        *      If <jk>null</jk>, then uses the predefined parser on the config 
file.
+        * @param def The default value if section or key does not exist.
+        * @param type The class to convert the value to.
+        * @throws ParseException If parser could not parse the value or if a 
parser is not registered with this config file.
+        * @return The value, or <jk>null</jk> if the section or key does not 
exist.
+        */
+       public final <T> T getObjectWithDefault(String key, Parser parser, T 
def, Class<T> type) throws ParseException {
+               assertFieldNotNull(key, "key");
+               assertFieldNotNull(type, "c");
+               T t = parse(getString(key), parser, type);
+               return (t == null ? def : t);
+       }
+
+       /**
+        * Gets the entry with the specified key and converts it to the 
specified value.
+        *
+        * <p>
+        * Same as {@link #getObject(String, Type, Type...)}, but with a 
default value.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param def The default value if section or key does not exist.
+        * @param type
+        *      The object type to create.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType},
+        *      {@link GenericArrayType}
+        * @param args
+        *      The type arguments of the class if it's a collection or map.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType},
+        *      {@link GenericArrayType}
+        *      <br>Ignored if the main type is not a map or collection.
+        * @throws ParseException If parser could not parse the value or if a 
parser is not registered with this config file.
+        * @return The value, or <jk>null</jk> if the section or key does not 
exist.
+        */
+       public final <T> T getObjectWithDefault(String key, T def, Type type, 
Type...args) throws ParseException {
+               return getObjectWithDefault(key, null, def, type, args);
+       }
+
+       /**
+        * Same as {@link #getObjectWithDefault(String, Object, Type, Type...)} 
but allows you to specify the parser to use
+        * to parse the value.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param parser
+        *      The parser to use for parsing the object.
+        *      If <jk>null</jk>, then uses the predefined parser on the config 
file.
+        * @param def The default value if section or key does not exist.
+        * @param type
+        *      The object type to create.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType},
+        *      {@link GenericArrayType}
+        * @param args
+        *      The type arguments of the class if it's a collection or map.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType},
+        *      {@link GenericArrayType}
+        *      <br>Ignored if the main type is not a map or collection.
+        * @throws ParseException If parser could not parse the value or if a 
parser is not registered with this config file.
+        * @return The value, or <jk>null</jk> if the section or key does not 
exist.
+        */
+       public final <T> T getObjectWithDefault(String key, Parser parser, T 
def, Type type, Type...args) throws ParseException {
+               assertFieldNotNull(key, "key");
+               assertFieldNotNull(type, "type");
+               T t = parse(getString(key), parser, type, args);
+               return (t == null ? def : t);
+       }
+
+       /**
+        * Gets the entry with the specified key and converts it to the 
specified value.
+        *
+        * <p>
+        * Same as {@link #getObject(String, Class)}, but used when key is 
already broken into section/key.
+        *
+        * @param sectionName The section name.  Must not be <jk>null</jk>.
+        * @param sectionKey The section key.  Must not be <jk>null</jk>.
+        * @param c The class to convert the value to.
+        * @throws ParseException If parser could not parse the value or if a 
parser is not registered with this config file.
+        * @return The value, or the default value if the section or value 
doesn't exist.
+        */
+       public final <T> T getObject(String sectionName, String sectionKey, 
Class<T> c) throws ParseException {
+               return getObject(sectionName, sectionKey, null, c);
+       }
+
+       /**
+        * Same as {@link #getObject(String, String, Class)} but allows you to 
specify the parser to use to parse the value.
+        *
+        * @param sectionName The section name.  Must not be <jk>null</jk>.
+        * @param sectionKey The section key.  Must not be <jk>null</jk>.
+        * @param parser
+        *      The parser to use for parsing the object.
+        *      If <jk>null</jk>, then uses the predefined parser on the config 
file.
+        * @param c The class to convert the value to.
+        * @throws ParseException If parser could not parse the value or if a 
parser is not registered with this config file.
+        * @return The value, or the default value if the section or value 
doesn't exist.
+        */
+       public final <T> T getObject(String sectionName, String sectionKey, 
Parser parser, Class<T> c) throws ParseException {
+               assertFieldNotNull(sectionName, "sectionName");
+               assertFieldNotNull(sectionKey, "sectionKey");
+               return parse(get(sectionName, sectionKey), parser, c);
+       }
+
+       /**
+        * Gets the entry with the specified key and converts it to the 
specified value.
+        *
+        * <p>
+        * Same as {@link #getObject(String, Type, Type...)}, but used when key 
is already broken into section/key.
+        *
+        * @param sectionName The section name.  Must not be <jk>null</jk>.
+        * @param sectionKey The section key.  Must not be <jk>null</jk>.
+        * @param type
+        *      The object type to create.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType},
+        *      {@link GenericArrayType}
+        * @param args
+        *      The type arguments of the class if it's a collection or map.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType},
+        *      {@link GenericArrayType}
+        *      <br>Ignored if the main type is not a map or collection.
+        * @throws ParseException If parser could not parse the value or if a 
parser is not registered with this config file.
+        * @return The value, or <jk>null</jk> if the section or key does not 
exist.
+        */
+       public final <T> T getObject(String sectionName, String sectionKey, 
Type type, Type...args) throws ParseException {
+               return getObject(sectionName, sectionKey, null, type, args);
+       }
+
+       /**
+        * Same as {@link #getObject(String, String, Type, Type...)} but allows 
you to specify the parser to use to parse
+        * the value.
+        *
+        * @param sectionName The section name.  Must not be <jk>null</jk>.
+        * @param sectionKey The section key.  Must not be <jk>null</jk>.
+        * @param parser
+        *      The parser to use for parsing the object.
+        *      If <jk>null</jk>, then uses the predefined parser on the config 
file.
+        * @param type
+        *      The object type to create.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType},
+        *      {@link GenericArrayType}
+        * @param args
+        *      The type arguments of the class if it's a collection or map.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType},
+        *      {@link GenericArrayType}
+        *      <br>Ignored if the main type is not a map or collection.
+        * @throws ParseException If parser could not parse the value or if a 
parser is not registered with this config file.
+        * @return The value, or <jk>null</jk> if the section or key does not 
exist.
+        */
+       public final <T> T getObject(String sectionName, String sectionKey, 
Parser parser, Type type, Type...args)
+                       throws ParseException {
+               assertFieldNotNull(sectionName, "sectionName");
+               assertFieldNotNull(sectionKey, "sectionKey");
+               return parse(get(sectionName, sectionKey), parser, type, args);
+       }
+
+       /**
+        * Gets the entry with the specified key.
+        *
+        * <p>
+        * The key can be in one of the following formats...
+        * <ul class='spaced-list'>
+        *      <li>
+        *              <js>"key"</js> - A value in the default section (i.e. 
defined above any <code>[section]</code> header).
+        *      <li>
+        *              <js>"section/key"</js> - A value from the specified 
section.
+        * </ul>
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @return The value, or <jk>null</jk> if the section or key does not 
exist.
+        */
+       public final String getString(String key) {
+               return getString(key, null);
+       }
+
+       /**
+        * Gets the entry with the specified key, splits the value on commas, 
and returns the values as trimmed strings.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @return The value, or an empty list if the section or key does not 
exist.
+        */
+       public final String[] getStringArray(String key) {
+               return getStringArray(key, new String[0]);
+       }
+
+       /**
+        * Same as {@link #getStringArray(String)} but returns a default value 
if the value cannot be found.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param def The default value if section or key does not exist.
+        * @return The value, or an empty list if the section or key does not 
exist.
+        */
+       public final String[] getStringArray(String key, String[] def) {
+               String s = getString(key);
+               if (s == null)
+                       return def;
+               String[] r = StringUtils.isEmpty(s) ? new String[0] : split(s);
+               return r.length == 0 ? def : r;
+       }
+
+       /**
+        * Convenience method for getting int config values.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @return The value, or <code>0</code> if the section or key does not 
exist or cannot be parsed as an integer.
+        */
+       public final int getInt(String key) {
+               return getInt(key, 0);
+       }
+
+       /**
+        * Convenience method for getting int config values.
+        *
+        * <p>
+        * <js>"M"</js> and <js>"K"</js> can be used to identify millions and 
thousands.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <ul class='spaced-list'>
+        *      <li>
+        *              <code><js>"100K"</js> => 1024000</code>
+        *      <li>
+        *              <code><js>"100M"</js> => 104857600</code>
+        * </ul>
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param def The default value if config file or value does not exist.
+        * @return The value, or the default value if the section or key does 
not exist or cannot be parsed as an integer.
+        */
+       public final int getInt(String key, int def) {
+               String s = getString(key);
+               if (StringUtils.isEmpty(s))
+                       return def;
+               return parseIntWithSuffix(s);
+       }
+
+       /**
+        * Convenience method for getting boolean config values.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @return The value, or <jk>false</jk> if the section or key does not 
exist or cannot be parsed as a boolean.
+        */
+       public final boolean getBoolean(String key) {
+               return getBoolean(key, false);
+       }
+
+       /**
+        * Convenience method for getting boolean config values.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param def The default value if config file or value does not exist.
+        * @return The value, or the default value if the section or key does 
not exist or cannot be parsed as a boolean.
+        */
+       public final boolean getBoolean(String key, boolean def) {
+               String s = getString(key);
+               return StringUtils.isEmpty(s) ? def : Boolean.parseBoolean(s);
+       }
+
+       /**
+        * Adds or replaces an entry with the specified key with a POJO 
serialized to a string using the registered
+        * serializer.
+        *
+        * <p>
+        * Equivalent to calling <code>put(key, value, isEncoded(key))</code>.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param value The new value POJO.
+        * @return The previous value, or <jk>null</jk> if the section or key 
did not previously exist.
+        * @throws SerializeException
+        *      If serializer could not serialize the value or if a serializer 
is not registered with this config file.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public final String put(String key, Object value) throws 
SerializeException {
+               return put(key, value, null, isEncoded(key), false);
+       }
+
+       /**
+        * Same as {@link #put(String, Object)} but allows you to specify the 
serializer to use to serialize the value.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param value The new value POJO.
+        * @param serializer
+        *      The serializer to use for serializing the object.
+        *      If <jk>null</jk>, then uses the predefined serializer on the 
config file.
+        * @return The previous value, or <jk>null</jk> if the section or key 
did not previously exist.
+        * @throws SerializeException
+        *      If serializer could not serialize the value or if a serializer 
is not registered with this config file.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public final String put(String key, Object value, Serializer 
serializer) throws SerializeException {
+               return put(key, value, serializer, isEncoded(key), false);
+       }
+
+       /**
+        * Adds or replaces an entry with the specified key with the specified 
value.
+        *
+        * <p>
+        * The format of the entry depends on the data type of the value.
+        * <ul class='spaced-list'>
+        *      <li>
+        *              Simple types (<code>String</code>, <code>Number</code>, 
<code>Boolean</code>, primitives)
+        *              are serialized as plain strings.
+        *      <li>
+        *              Arrays and collections of simple types are serialized 
as comma-delimited lists of plain strings.
+        *      <li>
+        *              Other types (e.g. beans) are serialized using the 
serializer registered with this config file.
+        *      <li
+        *              >Arrays and collections of other types are serialized 
as comma-delimited lists of serialized strings of
+        *              each entry.
+        * </ul>
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param value The new value.
+        * @param encoded
+        *      If <jk>true</jk>, value is encoded by the registered encoder 
when the config file is persisted to disk.
+        * @return The previous value, or <jk>null</jk> if the section or key 
did not previously exist.
+        * @throws SerializeException
+        *      If serializer could not serialize the value or if a serializer 
is not registered with this config file.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public final String put(String key, Object value, boolean encoded) 
throws SerializeException {
+               return put(key, value, null, encoded, false);
+       }
+
+       /**
+        * Same as {@link #put(String, Object, boolean)} but allows you to 
specify the serializer to use to serialize the
+        * value.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @param value The new value.
+        * @param serializer
+        *      The serializer to use for serializing the object.
+        *      If <jk>null</jk>, then uses the predefined serializer on the 
config file.
+        * @param encoded
+        *      If <jk>true</jk>, value is encoded by the registered encoder 
when the config file is persisted to disk.
+        * @param newline If <jk>true</jk>, a newline is added to the beginning 
of the input.
+        * @return The previous value, or <jk>null</jk> if the section or key 
did not previously exist.
+        * @throws SerializeException
+        *      If serializer could not serialize the value or if a serializer 
is not registered with this config file.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public final String put(String key, Object value, Serializer 
serializer, boolean encoded, boolean newline)
+                       throws SerializeException {
+               assertFieldNotNull(key, "key");
+               return put(getSectionName(key), getSectionKey(key), 
serialize(value, serializer, newline), encoded);
+       }
+
+       /**
+        * Returns the specified section as a map of key/value pairs.
+        *
+        * @param sectionName The section name to retrieve.
+        * @return A map of the section, or <jk>null</jk> if the section was 
not found.
+        */
+       public final ObjectMap getSectionMap(String sectionName) {
+               readLock();
+               try {
+                       Set<String> keys = getSectionKeys(sectionName);
+                       if (keys == null)
+                               return null;
+                       ObjectMap m = new ObjectMap();
+                       for (String key : keys)
+                               m.put(key, get(sectionName, key));
+                       return m;
+               } finally {
+                       readUnlock();
+               }
+       }
+
+       /**
+        * Copies the entries in a section to the specified bean by calling the 
public setters on that bean.
+        *
+        * @param sectionName The section name to write from.
+        * @param bean The bean to set the properties on.
+        * @param ignoreUnknownProperties
+        *      If <jk>true</jk>, don't throw an {@link 
IllegalArgumentException} if this section contains a key that doesn't
+        *      correspond to a setter method.
+        * @param permittedPropertyTypes
+        *      If specified, only look for setters whose property types are 
those listed.
+        *      If not specified, use all setters.
+        * @return An object map of the changes made to the bean.
+        * @throws ParseException If parser was not set on this config file or 
invalid properties were found in the section.
+        * @throws IllegalArgumentException
+        * @throws IllegalAccessException
+        * @throws InvocationTargetException
+        */
+       public final ObjectMap writeProperties(String sectionName, Object bean, 
boolean ignoreUnknownProperties,
+                       Class<?>...permittedPropertyTypes) throws 
ParseException, IllegalArgumentException, IllegalAccessException,
+                       InvocationTargetException {
+               assertFieldNotNull(bean, "bean");
+               ObjectMap om = new ObjectMap();
+               readLock();
+               try {
+                       Set<String> keys = getSectionKeys(sectionName);
+                       if (keys == null)
+                               throw new IllegalArgumentException("Section not 
found");
+                       keys = new LinkedHashSet<String>(keys);
+                       for (Method m : bean.getClass().getMethods()) {
+                               int mod = m.getModifiers();
+                               if (isPublic(mod) && (!isStatic(mod)) && 
m.getName().startsWith("set") && m.getParameterTypes().length == 1) {
+                                       Class<?> pt = m.getParameterTypes()[0];
+                                       if (permittedPropertyTypes == null || 
permittedPropertyTypes.length == 0 || contains(pt, permittedPropertyTypes)) {
+                                               String propName = 
Introspector.decapitalize(m.getName().substring(3));
+                                               Object value = 
getObject(sectionName, propName, pt);
+                                               if (value != null) {
+                                                       m.invoke(bean, value);
+                                                       om.put(propName, value);
+                                                       keys.remove(propName);
+                                               }
+                                       }
+                               }
+                       }
+                       if (! (ignoreUnknownProperties || keys.isEmpty()))
+                               throw new ParseException("Invalid properties 
found in config file section ''{0}'': {1}", sectionName, keys);
+                       return om;
+               } finally {
+                       readUnlock();
+               }
+       }
+
+       /**
+        * Shortcut for calling <code>getSectionAsBean(sectionName, c, 
<jk>false</jk>)</code>.
+        *
+        * @param sectionName The section name to write from.
+        * @param c The bean class to create.
+        * @return A new bean instance.
+        * @throws ParseException
+        */
+       public final <T> T getSectionAsBean(String sectionName, Class<T>c) 
throws ParseException {
+               return getSectionAsBean(sectionName, c, false);
+       }
+
+       /**
+        * Converts this config file section to the specified bean instance.
+        *
+        * <p>
+        * Key/value pairs in the config file section get copied as bean 
property values to the specified bean class.
+        *
+        * <h6 class='figure'>Example config file</h6>
+        * <p class='bcode'>
+        *      <cs>[MyAddress]</cs>
+        *      <ck>name</ck> = <cv>John Smith</cv>
+        *      <ck>street</ck> = <cv>123 Main Street</cv>
+        *      <ck>city</ck> = <cv>Anywhere</cv>
+        *      <ck>state</ck> = <cv>NY</cv>
+        *      <ck>zip</ck> = <cv>12345</cv>
+        * </p>
+        *
+        * <h6 class='figure'>Example bean</h6>
+        * <p class='bcode'>
+        *      <jk>public class</jk> Address {
+        *              public String name, street, city;
+        *              public StateEnum state;
+        *              public int zip;
+        *      }
+        * </p>
+        *
+        * <h6 class='figure'>Example usage</h6>
+        * <p class='bcode'>
+        *      ConfigFile cf = <jk>new</jk> 
ConfigFileBuilder().build(<js>"MyConfig.cfg"</js>);
+        *      Address myAddress = cf.getSectionAsBean(<js>"MySection"</js>, 
Address.<jk>class</jk>);
+        * </p>
+        *
+        * @param sectionName The section name to write from.
+        * @param c The bean class to create.
+        * @param ignoreUnknownProperties
+        *      If <jk>false</jk>, throws a {@link ParseException} if the 
section contains an entry that isn't a bean property
+        *      name.
+        * @return A new bean instance.
+        * @throws ParseException
+        */
+       public final <T> T getSectionAsBean(String sectionName, Class<T> c, 
boolean ignoreUnknownProperties)
+                       throws ParseException {
+               assertFieldNotNull(c, "c");
+               readLock();
+               try {
+                       BeanMap<T> bm = getBeanSession().newBeanMap(c);
+                       for (String k : getSectionKeys(sectionName)) {
+                               BeanPropertyMeta bpm = bm.getPropertyMeta(k);
+                               if (bpm == null) {
+                                       if (! ignoreUnknownProperties)
+                                               throw new 
ParseException("Unknown property {0} encountered", k);
+                               } else {
+                                       bm.put(k, getObject(sectionName + '/' + 
k, bpm.getClassMeta().getInnerClass()));
+                               }
+                       }
+                       return bm.getBean();
+               } finally {
+                       readUnlock();
+               }
+       }
+
+       /**
+        * Wraps a config file section inside a Java interface so that values 
in the section can be read and
+        * write using getters and setters.
+        *
+        * <h6 class='figure'>Example config file</h6>
+        * <p class='bcode'>
+        *      <cs>[MySection]</cs>
+        *      <ck>string</ck> = <cv>foo</cv>
+        *      <ck>int</ck> = <cv>123</cv>
+        *      <ck>enum</ck> = <cv>ONE</cv>
+        *      <ck>bean</ck> = <cv>{foo:'bar',baz:123}</cv>
+        *      <ck>int3dArray</ck> = <cv>[[[123,null],null],null]</cv>
+        *      <ck>bean1d3dListMap</ck> = 
<cv>{key:[[[[{foo:'bar',baz:123}]]]]}</cv>
+        * </p>
+        *
+        * <h6 class='figure'>Example interface</h6>
+        * <p class='bcode'>
+        *      <jk>public interface</jk> MyConfigInterface {
+        *
+        *              String getString();
+        *              <jk>void</jk> setString(String x);
+        *
+        *              <jk>int</jk> getInt();
+        *              <jk>void</jk> setInt(<jk>int</jk> x);
+        *
+        *              MyEnum getEnum();
+        *              <jk>void</jk> setEnum(MyEnum x);
+        *
+        *              MyBean getBean();
+        *              <jk>void</jk> setBean(MyBean x);
+        *
+        *              <jk>int</jk>[][][] getInt3dArray();
+        *              <jk>void</jk> setInt3dArray(<jk>int</jk>[][][] x);
+        *
+        *              Map&lt;String,List&lt;MyBean[][][]&gt;&gt; 
getBean1d3dListMap();
+        *              <jk>void</jk> 
setBean1d3dListMap(Map&lt;String,List&lt;MyBean[][][]&gt;&gt; x);
+        *      }
+        * </p>
+        *
+        * <h6 class='figure'>Example usage</h6>
+        * <p class='bcode'>
+        *      ConfigFile cf = <jk>new</jk> 
ConfigFileBuilder().build(<js>"MyConfig.cfg"</js>);
+        *
+        *      MyConfigInterface ci = 
cf.getSectionAsInterface(<js>"MySection"</js>, 
MyConfigInterface.<jk>class</jk>);
+        *
+        *      <jk>int</jk> myInt = ci.getInt();
+        *
+        *      ci.setBean(<jk>new</jk> MyBean());
+        *
+        *      cf.save();
+        * </p>
+        *
+        * @param sectionName The section name to retrieve as an interface 
proxy.
+        * @param c The proxy interface class.
+        * @return The proxy interface.
+        */
+       @SuppressWarnings("unchecked")
+       public final <T> T getSectionAsInterface(final String sectionName, 
final Class<T> c) {
+               assertFieldNotNull(c, "c");
+
+               if (! c.isInterface())
+                       throw new UnsupportedOperationException("Class passed 
to getSectionAsInterface is not an interface.");
+
+               InvocationHandler h = new InvocationHandler() {
+
+                       @Override
+                       public Object invoke(Object proxy, Method method, 
Object[] args) throws Throwable {
+                               BeanInfo bi = Introspector.getBeanInfo(c, null);
+                               for (PropertyDescriptor pd : 
bi.getPropertyDescriptors()) {
+                                       Method rm = pd.getReadMethod(), wm = 
pd.getWriteMethod();
+                                       if (method.equals(rm))
+                                               return 
ConfigFile.this.getObject(sectionName, pd.getName(), rm.getGenericReturnType());
+                                       if (method.equals(wm))
+                                               return 
ConfigFile.this.put(sectionName, pd.getName(), args[0], null, false, false);
+                               }
+                               throw new 
UnsupportedOperationException("Unsupported interface method.  method=[ " + 
method + " ]");
+                       }
+               };
+
+               return (T)Proxy.newProxyInstance(c.getClassLoader(), new 
Class[] { c }, h);
+       }
+
+       /**
+        * Returns <jk>true</jk> if this section contains the specified key and 
the key has a non-blank value.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @return <jk>true</jk> if this section contains the specified key and 
the key has a non-blank value.
+        */
+       public final boolean containsNonEmptyValue(String key) {
+               return ! StringUtils.isEmpty(getString(key, null));
+       }
+
+       /**
+        * Gets the section with the specified name.
+        *
+        * @param name The section name.
+        * @return The section, or <jk>null</jk> if section does not exist.
+        */
+       protected abstract Section getSection(String name);
+
+       /**
+        * Gets the section with the specified name and optionally creates it 
if it's not there.
+        *
+        * @param name The section name.
+        * @param create Create the section if it's not there.
+        * @return The section, or <jk>null</jk> if section does not exist.
+        * @throws UnsupportedOperationException
+        *      If config file is read only and section doesn't exist and 
<code>create</code> is <jk>true</jk>.
+        */
+       protected abstract Section getSection(String name, boolean create);
+
+       /**
+        * Appends a section to this config file if it does not already exist.
+        *
+        * <p>
+        * Returns the existing section if it already exists.
+        *
+        * @param name The section name, or <jk>null</jk> for the default 
section.
+        * @return The appended or existing section.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract ConfigFile addSection(String name);
+
+       /**
+        * Creates or overwrites the specified section.
+        *
+        * @param name The section name, or <jk>null</jk> for the default 
section.
+        * @param contents The contents of the new section.
+        * @return The appended or existing section.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract ConfigFile setSection(String name, Map<String,String> 
contents);
+
+       /**
+        * Removes the section with the specified name.
+        *
+        * @param name The name of the section to remove, or <jk>null</jk> for 
the default section.
+        * @return The removed section, or <jk>null</jk> if named section does 
not exist.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract ConfigFile removeSection(String name);
+
+       /**
+        * Returns <jk>true</jk> if the encoding flag is set on the specified 
entry.
+        *
+        * @param key The key.  See {@link #getString(String)} for a 
description of the key.
+        * @return <jk>true</jk> if the encoding flag is set on the specified 
entry.
+        */
+       public abstract boolean isEncoded(String key);
+
+       /**
+        * Saves this config file to disk.
+        *
+        * @return This object (for method chaining).
+        * @throws IOException If a problem occurred trying to save file to 
disk, or file is not associated with this object.
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract ConfigFile save() throws IOException;
+
+       /**
+        * Saves this config file to the specified writer as an INI file.
+        *
+        * <p>
+        * The writer will automatically be closed.
+        *
+        * @param out The writer to send the output to.
+        * @return This object (for method chaining).
+        * @throws IOException If a problem occurred trying to send contents to 
the writer.
+        */
+       public final ConfigFile serializeTo(Writer out) throws IOException {
+               return serializeTo(out, INI);
+       }
+
+       /**
+        * Same as {@link #serializeTo(Writer)}, except allows you to 
explicitly specify a format.
+        *
+        * @param out The writer to send the output to.
+        * @param format The {@link ConfigFileFormat} of the output.
+        * @return This object (for method chaining).
+        * @throws IOException If a problem occurred trying to send contents to 
the writer.
+        */
+       public abstract ConfigFile serializeTo(Writer out, ConfigFileFormat 
format) throws IOException;
+
+       /**
+        * Add a listener to this config file to react to modification events.
+        *
+        * @param listener The new listener to add.
+        * @return This object (for method chaining).
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract ConfigFile addListener(ConfigFileListener listener);
+
+       /**
+        * Merges the contents of the specified config file into this config 
file.
+        *
+        * <p>
+        * Pretty much identical to just replacing this config file, but causes 
the
+        * {@link ConfigFileListener#onChange(ConfigFile, Set)} method to be 
invoked on differences between the file.
+        *
+        * @param cf The config file whose values should be copied into this 
config file.
+        * @return This object (for method chaining).
+        * @throws UnsupportedOperationException If config file is read only.
+        */
+       public abstract ConfigFile merge(ConfigFile cf);
+
+       /**
+        * Returns the config file contents as a string.
+        *
+        * <p>
+        * The contents of the string are the same as the contents that would 
be serialized to disk.
+        */
+       @Override /* Object */
+       public abstract String toString();
+
+       /**
+        * Returns a wrapped instance of this config file where calls to 
getters have their values first resolved by the
+        * specified {@link VarResolver}.
+        *
+        * @param vr The {@link VarResolver} for resolving variables in values.
+        * @return This config file wrapped in an instance of {@link 
ConfigFileWrapped}.
+        */
+       public abstract ConfigFile getResolving(VarResolver vr);
+
+       /**
+        * Returns a wrapped instance of this config file where calls to 
getters have their values first resolved by the
+        * specified {@link VarResolverSession}.
+        *
+        * @param vs The {@link VarResolverSession} for resolving variables in 
values.
+        * @return This config file wrapped in an instance of {@link 
ConfigFileWrapped}.
+        */
+       public abstract ConfigFile getResolving(VarResolverSession vs);
+
+       /**
+        * Returns a wrapped instance of this config file where calls to 
getters have their values first resolved by a
+        * default {@link VarResolver}.
+        *
+        * The default {@link VarResolver} is registered with the following 
{@link Var StringVars}:
+        * <ul class='spaced-list'>
+        *      <li>
+        *              <code>$S{key}</code>,<code>$S{key,default}</code> - 
System properties.
+        *      <li>
+        *              <code>$E{key}</code>,<code>$E{key,default}</code> - 
Environment variables.
+        *      <li>
+        *              <code>$C{key}</code>,<code>$C{key,default}</code> - 
Values in this configuration file.
+        * </ul>
+        *
+        * @return A new config file that resolves string variables.
+        */
+       public abstract ConfigFile getResolving();
+
+       /**
+        * Wraps this config file in a {@link Writable} interface that renders 
it as plain text.
+        *
+        * @return This config file wrapped in a {@link Writable}.
+        */
+       public abstract Writable toWritable();
+
+       /**
+        * @return The string var resolver associated with this config file.
+        */
+       protected VarResolver getVarResolver() {
+               // Only ConfigFileWrapped returns a value.
+               return null;
+       }
+
+       private static int parseIntWithSuffix(String s) {
+               assertFieldNotNull(s, "s");
+               int m = 1;
+               if (s.endsWith("M")) {
+                       m = 1024*1024;
+                       s = s.substring(0, s.length()-1).trim();
+               } else if (s.endsWith("K")) {
+                       m = 1024;
+                       s = s.substring(0, s.length()-1).trim();
+               }
+               return Integer.parseInt(s) * m;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFileBuilder.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFileBuilder.java
 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFileBuilder.java
new file mode 100644
index 0000000..63c6b38
--- /dev/null
+++ 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFileBuilder.java
@@ -0,0 +1,333 @@
+// 
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
+// * to you 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.juneau.ini;
+
+import static org.apache.juneau.ini.ConfigFileFormat.*;
+import static org.apache.juneau.internal.FileUtils.*;
+
+import java.io.*;
+import java.nio.charset.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.utils.*;
+
+/**
+ * Builder for creating instances of {@link ConfigFile ConfigFiles}.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bcode'>
+ *     ConfigFile cf = <jk>new</jk> 
ConfigFileBuilder().build(<js>"MyConfig.cfg"</js>);
+ *     String setting = cf.get(<js>"MySection/mysetting"</js>);
+ * </p>
+ */
+@SuppressWarnings("hiding")
+public class ConfigFileBuilder {
+
+       private WriterSerializer serializer = JsonSerializer.DEFAULT_LAX;
+       private ReaderParser parser = JsonParser.DEFAULT;
+       private Encoder encoder = new XorEncoder();
+       private boolean readOnly = false, createIfNotExists = false;
+       private Charset charset = Charset.defaultCharset();
+       private List<File> searchPaths = new AList<File>().append(new 
File("."));
+
+       /**
+        * Specify the encoder to use for encoded config file entries (e.g. 
<js>"mySecret*={...}"</js>).
+        *
+        * <p>
+        * The default value for this setting is an instance of {@link 
XorEncoder}.
+        *
+        * @param encoder The new value for this setting.
+        * @return This object (for method chaining).
+        */
+       public ConfigFileBuilder encoder(Encoder encoder) {
+               this.encoder = encoder;
+               return this;
+       }
+
+       /**
+        * Specify the serializer to use for serializing POJOs when using 
{@link ConfigFile#put(String, Object)}.
+        *
+        * <p>
+        * The default value for this setting is {@link 
JsonSerializer#DEFAULT_LAX}.
+        *
+        * @param serializer The new value for this setting.
+        * @return This object (for method chaining).
+        */
+       public ConfigFileBuilder serializer(WriterSerializer serializer) {
+               this.serializer = serializer;
+               return this;
+       }
+
+       /**
+        * Specify the parser to use for parsing POJOs when using {@link 
ConfigFile#getObject(String,Class)}.
+        *
+        * <p>
+        * The default value for this setting is {@link JsonParser#DEFAULT}
+        *
+        * @param parser The new value for this setting.
+        * @return This object (for method chaining).
+        */
+       public ConfigFileBuilder parser(ReaderParser parser) {
+               this.parser = parser;
+               return this;
+       }
+
+       /**
+        * Specify the config file character encoding.
+        *
+        * <p>
+        * The default value for this setting is {@link 
Charset#defaultCharset()}.
+        *
+        * @param charset The new value for this setting.
+        * @return This object (for method chaining).
+        */
+       public ConfigFileBuilder charset(Charset charset) {
+               this.charset = charset;
+               return this;
+       }
+
+       /**
+        * Specify the search paths for config files.
+        *
+        * <p>
+        * Can contain relative or absolute paths.
+        *
+        * <p>
+        * The default value for this setting is <code>[<js>"."</js>]</code>.
+        *
+        * @param searchPaths The new value for this setting.
+        * @return This object (for method chaining).
+        */
+       public ConfigFileBuilder paths(String...searchPaths) {
+               this.searchPaths = new LinkedList<File>();
+               for (String p : searchPaths)
+                       this.searchPaths.add(new File(p));
+               return this;
+       }
+
+       /**
+        * Make {@link ConfigFile ConfigFiles} read-only.
+        *
+        * <p>
+        * The default value of this setting is <jk>false</jk>.
+        *
+        * @return This object (for method chaining).
+        */
+       public ConfigFileBuilder readOnly() {
+               this.readOnly = true;
+               return this;
+       }
+
+       /**
+        * Create config files if they cannot be found on the file system.
+        *
+        * <p>
+        * The default value for this setting is <jk>false</jk>.
+        *
+        * @return This object (for method chaining).
+        */
+       public ConfigFileBuilder createIfNotExists() {
+               this.createIfNotExists = true;
+               return this;
+       }
+
+       /**
+        * Returns the config file with the specified absolute or relative path.
+        *
+        * @param path The absolute or relative path of the config file.
+        * @return The config file.
+        * @throws IOException If config file could not be parsed.
+        * @throws FileNotFoundException If config file could not be found.
+        */
+       public ConfigFile build(String path) throws IOException {
+               return new ConfigFileImpl(resolve(path), readOnly, encoder, 
serializer, parser, charset);
+       }
+
+       /**
+        * Create a new empty config file not backed by any file.
+        *
+        * @return A new config file.
+        * @throws IOException
+        */
+       public ConfigFile build() throws IOException {
+               return new ConfigFileImpl(null, false, encoder, serializer, 
parser, charset);
+       }
+
+       /**
+        * Create a new config file backed by the specified file.
+        *
+        * <p>
+        * This method is provided primarily for testing purposes.
+        *
+        * @param f The file to create a config file from.
+        * @return A new config file.
+        * @throws IOException
+        */
+       public ConfigFile build(File f) throws IOException {
+               return new ConfigFileImpl(f, false, encoder, serializer, 
parser, charset);
+       }
+
+       /**
+        * Create a new config file not backed by a file.
+        *
+        * @param r The reader containing an INI-formatted file to initialize 
the config file from.
+        * @return A new config file.
+        * @throws IOException
+        */
+       public ConfigFile build(Reader r) throws IOException {
+               return new ConfigFileImpl(null, false, encoder, serializer, 
parser, charset).load(r);
+       }
+
+       private File resolve(String path) throws IOException {
+
+               // Handle absolute file.
+               File f = new File(path);
+               if (f.isAbsolute()) {
+                       if (createIfNotExists)
+                               create(f);
+                       if (f.exists())
+                               return f;
+                       throw new FileNotFoundException("Could not find config 
file '"+path+"'");
+               }
+
+               if (searchPaths.isEmpty())
+                       throw new FileNotFoundException("No search paths 
specified in ConfigFileBuilder.");
+
+               // Handle paths relative to search paths.
+               for (File sf : searchPaths) {
+                       f = new File(sf.getAbsolutePath() + "/" + path);
+                       if (f.exists())
+                               return f;
+               }
+
+               if (createIfNotExists) {
+                       f = new File(searchPaths.get(0).getAbsolutePath() + "/" 
+ path);
+                       create(f);
+                       return f;
+               }
+
+               throw new FileNotFoundException("Could not find config file 
'"+path+"'");
+       }
+
+       /**
+        * Implements command-line features for working with INI configuration 
files.
+        *
+        * <p>
+        * Invoke as a normal Java program...
+        * <p class='bcode'>
+        *      java org.apache.juneau.ini.ConfigFileBuilder [args]
+        * </p>
+        *
+        * <p>
+        * Arguments can be any of the following...
+        * <ul class='spaced-list'>
+        *      <li>
+        *              No arguments
+        *              <br>Prints usage message.
+        *      <li>
+        *              <code>createBatchEnvFile -configfile &lt;configFile&gt; 
-envfile &lt;batchFile&gt; [-verbose]</code>
+        *              <br>Creates a batch file that will set each config file 
entry as an environment variable.
+        *              <br>Characters in the keys that are not valid as 
environment variable names (e.g. <js>'/'</js> and <js>'.'</js>)
+        *              will be converted to underscores.
+        *      <li>
+        *              <code>createShellEnvFile -configFile &lt;configFile&gt; 
-envFile &lt;configFile&gt; [-verbose]</code>
+        *              Creates a shell script that will set each config file 
entry as an environment variable.
+        *              <br>Characters in the keys that are not valid as 
environment variable names (e.g. <js>'/'</js> and <js>'.'</js>)
+        *              will be converted to underscores.
+        *      <li>
+        *              <code>setVals -configFile &lt;configFile&gt; -vals 
[var1=val1 [var2=val2...]] [-verbose]</code>
+        *              Sets values in config files.
+        * </ul>
+        *
+        * <p>
+        * For example, the following command will create the file 
<code>'MyConfig.bat'</code> from the contents of the
+        * file <code>'MyConfig.cfg'</code>.
+        * <p class='bcode'>
+        *      java org.apache.juneau.ini.ConfigFileBuilder createBatchEnvFile 
-configfile C:\foo\MyConfig.cfg
+        *              -batchfile C:\foo\MyConfig.bat
+        * </p>
+        *
+        * @param args Command-line arguments
+        */
+       public static void main(String[] args) {
+
+               Args a = new Args(args);
+               String command = a.getArg(0);
+               String configFile = a.getArg("configFile");
+               String envFile = a.getArg("envFile");
+               List<String> vals = a.getArgs("vals");
+
+               if (command == null || ! (command.equals("createBatchEnvFile") 
|| command.equals("createShellEnvFile") || command.equals("setVals")))
+                       printUsageAndExit();
+               else if (configFile.isEmpty())
+                       printUsageAndExit();
+               else if (command.equals("setVals") && vals.isEmpty())
+                       printUsageAndExit();
+               else if ((command.equals("createBatchEnvFile") || 
command.equals("createShellEnvFile")) && envFile.isEmpty())
+                       printUsageAndExit();
+               else {
+                       try {
+                               ConfigFile cf = new 
ConfigFileBuilder().build(configFile);
+
+                               if (command.equalsIgnoreCase("setVals")) {
+                                       for (String val : vals) {
+                                               String[] x = val.split("\\=");
+                                               if (x.length != 2)
+                                                       throw new 
FormattedRuntimeException(
+                                                               "Invalid format 
for value: ''{0}''.  Must be in the format 'key=value'",
+                                                               val
+                                                       );
+                                               cf.put(x[0], x[1]);
+                                       }
+                                       cf.save();
+                                       return;
+
+                               } else if 
(command.equalsIgnoreCase("createBatchEnvFile")) {
+                                       Writer fw = new OutputStreamWriter(new 
FileOutputStream(envFile), Charset.defaultCharset());
+                                       try {
+                                               cf.serializeTo(fw, BATCH);
+                                       } finally {
+                                               fw.close();
+                                       }
+                                       return;
+
+                               } else if 
(command.equalsIgnoreCase("createShellEnvFile")) {
+                                       Writer fw = new OutputStreamWriter(new 
FileOutputStream(envFile), Charset.defaultCharset());
+                                       try {
+                                               cf.serializeTo(fw, SHELL);
+                                       } finally {
+                                               fw.close();
+                                       }
+                                       return;
+                               }
+
+                       } catch (Exception e) {
+                               e.printStackTrace();
+                       }
+               }
+       }
+
+       private static void printUsageAndExit() {
+               System.err.println("---Usage---"); // NOT DEBUG
+               System.err.println("java -cp juneau.jar 
org.apache.juneau.ini.ConfigFile createBatchEnvFile -configFile <configFile> 
-envFile <envFile> [-verbose]"); // NOT DEBUG
+               System.err.println("java -cp juneau.jar 
org.apache.juneau.ini.ConfigFile createShellEnvFile -configFile <configFile> 
-envFile <envFile> [-verbose]"); // NOT DEBUG
+               System.err.println("java -cp juneau.jar 
org.apache.juneau.ini.ConfigFile setVals -configFile <configFile> -vals [var1 
val1 [var2 val2...]] [-verbose]"); // NOT DEBUG
+               int rc = Integer.getInteger("exit.2", 2);
+               if (rc != 0)
+                       System.exit(rc);
+       }
+}

Reply via email to