[SCM] libini4j-java packaging branch, upstream, updated. upstream/0.4.1-1-gb2588f9

Andres Mejia ceros-guest at alioth.debian.org
Sat Jan 22 04:49:54 UTC 2011


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "libini4j-java packaging".

The branch, upstream has been updated
       via  b2588f9e9623697d60c47f28aff94a6b6b798cc6 (commit)
      from  b5b37f371023315c25194e17153c2770d61d4022 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
-----------------------------------------------------------------------

Summary of changes:
 NOTICE.txt                                         |    2 +-
 pom.xml                                            |  687 ++++++++++++
 .../ini4j/IniHandler.java => changes/ChangeLog.vm} |   20 +-
 .../OptionHandler.java => changes/ReleaseNotes.vm} |   16 +-
 src/changes/changes.xml                            |  374 +++++++
 src/changes/sitemap.vm                             |   69 ++
 src/conf/checkstyle.xml                            |  247 +++++
 src/conf/jalopy.xml                                | 1106 ++++++++++++++++++++
 src/conf/pmd.xml                                   |  298 ++++++
 .../addon/package.html => main/assembly/all.xml}   |   35 +-
 .../ini4j/package.html => main/assembly/bin.xml}   |   38 +-
 src/main/assembly/demo.xml                         |   52 +
 .../addon/package.html => main/assembly/src.xml}   |   34 +-
 .../java/org/ini4j/BasicMultiMap.java}             |   14 +-
 src/main/java/org/ini4j/BasicOptionMap.java        |  334 ++++++
 src/main/java/org/ini4j/BasicProfile.java          |  351 +++++++
 src/main/java/org/ini4j/BasicProfileSection.java   |  151 +++
 src/main/java/org/ini4j/BasicRegistry.java         |  105 ++
 src/main/java/org/ini4j/BasicRegistryKey.java      |   71 ++
 .../java/org/ini4j/CommentedMap.java}              |   12 +-
 src/main/java/org/ini4j/CommonMultiMap.java        |  134 +++
 src/main/java/org/ini4j/Config.java                |  388 +++++++
 .../java/org/ini4j}/ConfigParser.java              |   47 +-
 .../java/org/ini4j/Configurable.java}              |   10 +-
 src/main/java/org/ini4j/Ini.java                   |  185 ++++
 src/{ => main/java}/org/ini4j/IniPreferences.java  |   44 +-
 .../java}/org/ini4j/IniPreferencesFactory.java     |   10 +-
 .../org/ini4j/InvalidFileFormatException.java}     |   15 +-
 src/{ => main/java}/org/ini4j/MultiMap.java        |    0
 src/main/java/org/ini4j/OptionMap.java             |   57 +
 src/main/java/org/ini4j/Options.java               |  196 ++++
 src/main/java/org/ini4j/Persistable.java           |   50 +
 src/main/java/org/ini4j/Profile.java               |   66 ++
 src/main/java/org/ini4j/Reg.java                   |  314 ++++++
 src/main/java/org/ini4j/Registry.java              |  125 +++
 src/main/java/org/ini4j/Wini.java                  |   78 ++
 src/{ => main/java}/org/ini4j/package.html         |    0
 .../ini4j/spi/AbstractBeanInvocationHandler.java   |    4 +-
 .../java/org/ini4j/spi/AbstractFormatter.java}     |   96 +--
 .../java/org/ini4j/spi}/AbstractParser.java        |   71 +-
 .../java/org/ini4j/spi/AbstractProfileBuilder.java |  136 +++
 src/{ => main/java}/org/ini4j/spi/BeanAccess.java  |    0
 src/{ => main/java}/org/ini4j/spi/BeanTool.java    |   17 +-
 src/main/java/org/ini4j/spi/EscapeTool.java        |  194 ++++
 .../java/org/ini4j/spi/HandlerBase.java}           |    6 +-
 src/main/java/org/ini4j/spi/IniBuilder.java        |   54 +
 src/main/java/org/ini4j/spi/IniFormatter.java      |   66 ++
 .../java/org/ini4j/spi}/IniHandler.java            |    8 +-
 src/main/java/org/ini4j/spi/IniParser.java         |  137 +++
 src/main/java/org/ini4j/spi/IniSource.java         |  217 ++++
 src/main/java/org/ini4j/spi/OptionsBuilder.java    |  123 +++
 src/main/java/org/ini4j/spi/OptionsFormatter.java  |   49 +
 .../java/org/ini4j/spi/OptionsHandler.java}        |   13 +-
 .../java/org/ini4j/spi/OptionsParser.java}         |   34 +-
 src/main/java/org/ini4j/spi/RegBuilder.java        |   73 ++
 src/main/java/org/ini4j/spi/RegEscapeTool.java     |  283 +++++
 .../java}/org/ini4j/spi/ServiceFinder.java         |    4 +-
 .../java/org/ini4j/spi/TypeValuesPair.java}        |   23 +-
 .../org/ini4j/spi/UnicodeInputStreamReader.java    |  155 +++
 src/{ => main/java}/org/ini4j/spi/Warnings.java    |    0
 src/main/java/org/ini4j/spi/WinEscapeTool.java     |   68 ++
 src/{ => main/java}/org/ini4j/spi/package.html     |    0
 src/org/ini4j/Config.java                          |  216 ----
 src/org/ini4j/Ini.java                             |  422 --------
 src/org/ini4j/IniFile.java                         |  109 --
 src/org/ini4j/IniParser.java                       |  232 ----
 src/org/ini4j/IniPreferencesFactoryListener.java   |   72 --
 src/org/ini4j/IniSource.java                       |  168 ---
 src/org/ini4j/InvalidIniFormatException.java       |   48 -
 src/org/ini4j/OptionMapImpl.java                   |  193 ----
 src/org/ini4j/Options.java                         |  140 ---
 src/org/ini4j/PreferencesBean.java                 |   59 -
 src/org/ini4j/addon/FancyIniFormatter.java         |   57 -
 src/org/ini4j/addon/FancyIniParser.java            |  109 --
 src/org/ini4j/addon/PreferencesWrapper.java        |  204 ----
 src/org/ini4j/addon/StrictPreferences.java         |   76 --
 src/org/ini4j/spi/EscapeTool.java                  |  112 --
 src/org/ini4j/spi/XMLFormatter.java                |   89 --
 src/site/apt/design.apt                            |   68 ++
 src/site/apt/overview.apt                          |   46 +
 src/site/resources/.htaccess                       |   15 +
 src/site/resources/404.html                        |   42 +
 src/site/resources/css/site.css                    |  110 ++
 src/site/resources/favicon.ico                     |  Bin 0 -> 318 bytes
 src/site/resources/img/bugs.png                    |  Bin 0 -> 5726 bytes
 src/site/resources/img/community.png               |  Bin 0 -> 7506 bytes
 src/site/resources/img/docs.png                    |  Bin 0 -> 5572 bytes
 src/site/resources/img/download.png                |  Bin 0 -> 4323 bytes
 src/site/resources/img/feature.png                 |  Bin 0 -> 4185 bytes
 src/site/resources/img/fedora.png                  |  Bin 0 -> 802 bytes
 src/site/resources/img/forum.png                   |  Bin 0 -> 7077 bytes
 src/site/resources/img/ini4j-banner.png            |  Bin 0 -> 5125 bytes
 src/site/resources/img/ini4j.png                   |  Bin 0 -> 527 bytes
 src/site/resources/img/sample.png                  |  Bin 0 -> 4720 bytes
 src/site/resources/img/tutorial.png                |  Bin 0 -> 6166 bytes
 src/site/resources/img/ubuntu.png                  |  Bin 0 -> 2305 bytes
 src/site/resources/img/writer.png                  |  Bin 0 -> 7186 bytes
 src/site/resources/robots.txt                      |    2 +
 src/site/site.xml                                  |   97 ++
 src/site/xdoc/community.xml                        |   54 +
 src/site/xdoc/demo.xml                             |   39 +
 .../addon/package.html => site/xdoc/documents.xml} |   26 +-
 src/site/xdoc/download.xml                         |   70 ++
 .../addon/package.html => site/xdoc/features.xml}  |   26 +-
 src/site/xdoc/index.xml                            |   92 ++
 src/site/xdoc/search.xml                           |   44 +
 src/test/java/org/ini4j/BasicMultiMapTest.java     |  192 ++++
 .../java/org/ini4j/BasicOptionMapGate.java}        |   18 +-
 src/test/java/org/ini4j/BasicOptionMapTest.java    |  336 ++++++
 .../java/org/ini4j/BasicProfileSectionTest.java    |   94 ++
 src/test/java/org/ini4j/BasicProfileTest.java      |  449 ++++++++
 src/test/java/org/ini4j/BasicRegistryKeyTest.java  |   52 +
 src/test/java/org/ini4j/BasicRegistryTest.java     |   59 +
 src/test/java/org/ini4j/CommonMultiMapTest.java    |  113 ++
 src/test/java/org/ini4j/ConfigParserTest.java      |  490 +++++++++
 src/test/java/org/ini4j/ConfigTest.java            |  158 +++
 .../java/org/ini4j/Ini4jCase.java}                 |   29 +-
 .../java/org/ini4j/IniPreferencesFactoryTest.java  |  104 ++
 src/test/java/org/ini4j/IniPreferencesTest.java    |  231 ++++
 src/test/java/org/ini4j/IniTest.java               |  246 +++++
 src/test/java/org/ini4j/OptionsTest.java           |  247 +++++
 src/test/java/org/ini4j/RegTest.java               |  292 ++++++
 src/test/java/org/ini4j/WiniTest.java              |  132 +++
 src/test/java/org/ini4j/demo/Demo.java             |  262 +++++
 .../java/org/ini4j/demo/DemoApplet.java}           |   27 +-
 src/test/java/org/ini4j/demo/DemoMain.java         |   53 +
 src/test/java/org/ini4j/demo/DemoModel.java        |  146 +++
 src/test/java/org/ini4j/demo/help.txt              |   11 +
 src/test/java/org/ini4j/demo/ini-data.txt          |   26 +
 src/test/java/org/ini4j/demo/ini-tip.txt           |   15 +
 src/test/java/org/ini4j/demo/options-data.txt      |   33 +
 src/test/java/org/ini4j/demo/options-tip.txt       |   13 +
 src/test/java/org/ini4j/demo/reg-data.txt          |   53 +
 src/test/java/org/ini4j/demo/reg-tip.txt           |   15 +
 .../java/org/ini4j/sample/BeanEventSample.java     |   64 ++
 src/test/java/org/ini4j/sample/BeanSample.java     |   55 +
 src/test/java/org/ini4j/sample/DumpSample.java     |   57 +
 src/test/java/org/ini4j/sample/Dwarf.java          |   89 ++
 src/test/java/org/ini4j/sample/DwarfBean.java      |  173 +++
 src/test/java/org/ini4j/sample/Dwarfs.java         |   51 +
 src/test/java/org/ini4j/sample/DwarfsBean.java     |  107 ++
 src/test/java/org/ini4j/sample/FromSample.java     |   54 +
 src/test/java/org/ini4j/sample/IniSample.java      |   52 +
 src/test/java/org/ini4j/sample/ListenerSample.java |   76 ++
 src/test/java/org/ini4j/sample/NoImportSample.java |   55 +
 src/test/java/org/ini4j/sample/PyReadSample.java   |   52 +
 .../java/org/ini4j/sample/ReadPrimitiveSample.java |   55 +
 .../java/org/ini4j/sample/ReadStringSample.java    |   51 +
 src/test/java/org/ini4j/sample/StreamSample.java   |   55 +
 src/test/java/org/ini4j/sample/ToSample.java       |   54 +
 src/test/java/org/ini4j/sample/dwarfs-py.ini       |   90 ++
 src/test/java/org/ini4j/sample/dwarfs.ini          |   90 ++
 src/test/java/org/ini4j/sample/dwarfs.opt          |  100 ++
 src/test/java/org/ini4j/sample/dwarfs.reg          |  Bin 0 -> 8268 bytes
 .../java/org/ini4j/sample/package-info.java}       |   24 +-
 src/test/java/org/ini4j/sample/tale.ini            |   90 ++
 .../spi/AbstractBeanInvocationHandlerTest.java     |  273 +++++
 src/test/java/org/ini4j/spi/BeanToolTest.java      |  261 +++++
 src/test/java/org/ini4j/spi/EscapeToolTest.java    |   94 ++
 src/test/java/org/ini4j/spi/IniFormatterTest.java  |  232 ++++
 src/test/java/org/ini4j/spi/IniParserTest.java     |  272 +++++
 src/test/java/org/ini4j/spi/IniSourceTest.java     |  136 +++
 .../java/org/ini4j/spi/OptionsFormatterTest.java   |  235 +++++
 src/test/java/org/ini4j/spi/OptionsParserTest.java |  217 ++++
 .../java/org/ini4j/spi/RegEscapeToolTest.java}     |   40 +-
 src/test/java/org/ini4j/spi/ServiceFinderTest.java |   86 ++
 .../ini4j/spi/UnicodeInputStreamReaderTest.java    |  183 ++++
 src/test/java/org/ini4j/spi/WinEscapeToolTest.java |   97 ++
 src/test/java/org/ini4j/test/DwarfsData.java       |  227 ++++
 src/test/java/org/ini4j/test/Helper.java           |  349 ++++++
 src/test/java/org/ini4j/test/SampleRunnerTest.java |  350 +++++++
 src/test/java/org/ini4j/test/TaleData.java         |   51 +
 .../java/org/ini4j/tutorial/AbstractTutorial.java} |   14 +-
 src/test/java/org/ini4j/tutorial/BeanTutorial.java |  246 +++++
 src/test/java/org/ini4j/tutorial/IniTutorial.java  |  242 +++++
 .../java/org/ini4j/tutorial/OneMinuteTutorial.java |  135 +++
 src/test/java/org/ini4j/tutorial/OptTutorial.java  |  161 +++
 .../java/org/ini4j/tutorial/OptionMapTutorial.java |  154 +++
 .../java/org/ini4j/tutorial/PrefsTutorial.java     |   87 ++
 src/test/java/org/ini4j/tutorial/RegTutorial.java  |  142 +++
 .../ini4j/tutorial/WindowsRegistryTutorial.java    |  105 ++
 .../java/org/ini4j/tutorial/package-info.java}     |   23 +-
 .../META-INF/services/org.ini4j.BadConfig          |    2 +
 .../resources/META-INF/services/org.ini4j.Dummy    |    1 +
 .../resources/org/ini4j/addon/dwarfs-py-bad.ini    |   18 +
 src/test/resources/org/ini4j/addon/dwarfs-py.ini   |   82 ++
 src/test/resources/org/ini4j/mozilla.reg           |  Bin 0 -> 7620 bytes
 src/test/resources/org/ini4j/spi/UTF-16BE-BOM.ini  |  Bin 0 -> 52 bytes
 src/test/resources/org/ini4j/spi/UTF-16BE.ini      |  Bin 0 -> 50 bytes
 src/test/resources/org/ini4j/spi/UTF-16LE-BOM.ini  |  Bin 0 -> 52 bytes
 src/test/resources/org/ini4j/spi/UTF-16LE.ini      |  Bin 0 -> 50 bytes
 src/test/resources/org/ini4j/spi/UTF-32BE-BOM.ini  |  Bin 0 -> 104 bytes
 src/test/resources/org/ini4j/spi/UTF-32BE.ini      |  Bin 0 -> 100 bytes
 src/test/resources/org/ini4j/spi/UTF-32LE-BOM.ini  |  Bin 0 -> 104 bytes
 src/test/resources/org/ini4j/spi/UTF-32LE.ini      |  Bin 0 -> 100 bytes
 src/test/resources/org/ini4j/spi/UTF-8-BOM.ini     |    2 +
 src/test/resources/org/ini4j/spi/UTF-8.ini         |    2 +
 src/test/resources/org/ini4j/spi/include.txt       |   16 +
 src/test/resources/org/ini4j/spi/nested.txt        |    7 +
 src/test/resources/org/ini4j/spi/part1.txt         |   11 +
 src/test/resources/org/ini4j/spi/part2.txt         |    5 +
 201 files changed, 17889 insertions(+), 2662 deletions(-)

diff --git a/NOTICE.txt b/NOTICE.txt
index 3d5c0b8..5013504 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -10,4 +10,4 @@ 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.
+limitations under the License.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..b3116cb
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,687 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.ini4j</groupId>
+    <artifactId>ini4j</artifactId>
+    <version>0.5.2-SNAPSHOT</version>
+    <name>ini4j</name>
+    <description>Java API for handling configuration files in Windows .ini format. The library includes its own Map based API, Java Preferences API and Java Beans API for handling .ini files. Additionally, the library includes a feature rich (variable/macro substitution, multiply property values, etc) java.util.Properties replacement.</description>
+    <packaging>jar</packaging>
+    <url>http://www.ini4j.org</url>
+    <scm>
+        <connection>scm:svn:https://ini4j.svn.sourceforge.net/svnroot/ini4j/trunk</connection>
+        <developerConnection>scm:svn:https://ini4j.svn.sourceforge.net/svnroot/ini4j/trunk</developerConnection>
+    </scm>
+    <distributionManagement>
+        <repository>
+            <id>ini4j-release</id>
+            <name>[ini4j] releases on SourceForge</name>
+            <url>scp://shell.sourceforge.net/home/groups/i/in/ini4j/repository/release</url>
+        </repository>
+        <snapshotRepository>
+            <id>ini4j-snapshot</id>
+            <name>[ini4j] snapshots on SourceForge</name>
+            <url>scp://shell.sourceforge.net/home/groups/i/in/ini4j/repository/snapshot</url>
+        </snapshotRepository>
+        <site>
+            <id>ini4j-site</id>
+            <name>[ini4j] site on SourceForge</name>
+            <url>scp://shell.sourceforge.net/home/groups/i/in/ini4j/htdocs</url>
+        </site>
+    </distributionManagement>
+    <issueManagement>
+        <system>sourceforge</system>
+        <url>http://sourceforge.net/tracker2/?group_id=129580</url>
+    </issueManagement>
+    <licenses>
+        <license>
+            <name>Apache 2</name>
+            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+            <distribution>repo</distribution>
+        </license>
+    </licenses>
+    <developers>
+        <developer>
+            <id>ivan</id>
+            <name>Ivan Szkiba</name>
+            <email>szkiba at SourceForge</email>
+            <roles>
+                <role>architect</role>
+                <role>developer</role>
+            </roles>
+            <timezone>-1</timezone>
+        </developer>
+    </developers>
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.5</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>jetty</groupId>
+            <artifactId>jetty</artifactId>
+            <version>4.2.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <version>2.3</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymockclassextension</artifactId>
+            <version>2.3</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.beanshell</groupId>
+            <artifactId>bsh</artifactId>
+            <version>2.0b4</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.sf.retrotranslator</groupId>
+            <artifactId>retrotranslator-runtime</artifactId>
+            <version>1.2.4</version>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+    <properties>
+        <upload.url>scp://shell.sourceforge.net//home/frs/project/i/in/ini4j/uploads</upload.url>
+        <jdk14.home>/usr/local/j2sdk1.4.2_19</jdk14.home>
+    </properties>
+    <profiles>
+        <profile>
+            <id>windows</id>
+            <activation>
+                <os>
+                    <family>windows</family>
+                </os>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <systemProperties>
+                                <property>
+                                    <name>os.family</name>
+                                    <value>windows</value>
+                                </property>
+                            </systemProperties>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>release-candidate</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-checkstyle-plugin</artifactId>
+                        <version>2.3</version>
+                        <configuration>
+                            <configLocation>${basedir}/src/conf/checkstyle.xml</configLocation>
+                            <failOnViolation>true</failOnViolation>
+                            <consoleOutput>true</consoleOutput>
+                            <encoding>UTF-8</encoding>
+                            <skip>${check.skip}</skip>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <phase>verify</phase>
+                                <goals>
+                                    <goal>check</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-pmd-plugin</artifactId>
+                        <dependencies>
+                            <dependency>
+                                <groupId>pmd</groupId>
+                                <artifactId>pmd-jdk14</artifactId>
+                                <version>4.2.5</version>
+                            </dependency>
+                        </dependencies>
+                        <configuration>
+                            <failOnViolation>true</failOnViolation>
+                            <rulesets>
+                                <ruleset>${basedir}/src/conf/pmd.xml</ruleset>
+                            </rulesets>
+                            <skip>${check.skip}</skip>
+                            <verbose>true</verbose>
+                            <sourceEncoding>UTF-8</sourceEncoding>
+                            <targetJdk>1.5</targetJdk>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <phase>verify</phase>
+                                <goals>
+                                    <goal>check</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-dependency-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>copy-junit</id>
+                                <phase>process-classes</phase>
+                                <goals>
+                                    <goal>unpack</goal>
+                                </goals>
+                                <configuration>
+                                    <artifactItems>
+                                        <artifactItem>
+                                            <groupId>junit</groupId>
+                                            <artifactId>junit</artifactId>
+                                            <type>jar</type>
+                                            <overWrite>true</overWrite>
+                                        </artifactItem>
+                                        <artifactItem>
+                                            <groupId>org.easymock</groupId>
+                                            <artifactId>easymock</artifactId>
+                                            <type>jar</type>
+                                            <overWrite>true</overWrite>
+                                        </artifactItem>
+                                    </artifactItems>
+                                    <outputDirectory>${project.build.directory}/test-deps</outputDirectory>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>retrotranslator-maven-plugin</artifactId>
+                        <dependencies>
+                            <dependency>
+                                <groupId>net.sf.retrotranslator</groupId>
+                                <artifactId>retrotranslator-runtime</artifactId>
+                                <version>1.2.4</version>
+                            </dependency>
+                        </dependencies>
+                        <configuration>
+                            <embed>org.ini4j.jdk14</embed>
+                            <advanced>true</advanced>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <id>translate-classes</id>
+                                <goals>
+                                    <goal>translate</goal>
+                                </goals>
+                                <phase>process-classes</phase>
+                                <configuration>
+                                    <destdir>${project.build.directory}/classes-jdk14</destdir>
+                                    <filesets>
+                                        <fileset>
+                                            <directory>${project.build.outputDirectory}</directory>
+                                        </fileset>
+                                    </filesets>
+                                    <embed>org.ini4j.jdk14</embed>
+                                    <advanced>true</advanced>
+                                </configuration>
+                            </execution>
+                            <execution>
+                                <id>translate-test-classes</id>
+                                <goals>
+                                    <goal>translate</goal>
+                                </goals>
+                                <phase>process-test-classes</phase>
+                                <configuration>
+                                    <destdir>${project.build.directory}/test-classes-jdk14</destdir>
+                                    <filesets>
+                                        <fileset>
+                                            <directory>${project.build.testOutputDirectory}</directory>
+                                        </fileset>
+                                        <fileset>
+                                            <directory>${project.build.directory}/test-deps</directory>
+                                        </fileset>
+                                    </filesets>
+                                    <embed>org.ini4j.jdk14</embed>
+                                    <advanced>true</advanced>
+                                </configuration>
+                            </execution>
+                            <execution>
+                                <id>translate-project</id>
+                                <goals>
+                                    <goal>translate-project</goal>
+                                </goals>
+                                <configuration>
+                                    <classifier>jdk14</classifier>
+                                    <attach>true</attach>
+                                    <advanced>true</advanced>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-antrun-plugin</artifactId>
+                        <dependencies>
+                            <dependency>
+                                <groupId>org.apache.ant</groupId>
+                                <artifactId>ant-junit</artifactId>
+                                <version>1.7.0</version>
+                            </dependency>
+                        </dependencies>
+                        <executions>
+                            <execution>
+                                <id>test</id>
+                                <phase>test</phase>
+                                <goals>
+                                    <goal>run</goal>
+                                </goals>
+                                <configuration>
+                                    <tasks if="jdk14.home" unless="skip_jdk14_tests" >
+                                        <junit printsummary="on" haltonfailure="true" showoutput="true" fork="yes" forkMode="perBatch" jvm="${jdk14.home}/bin/java">
+                                            <sysproperty key="java.util.prefs.PreferencesFactory" value="org.ini4j.IniPreferencesFactory"/>
+                                            <sysproperty key="org.ini4j.prefs.user" value="org/ini4j/sample/dwarfs.ini"/>
+                                            <sysproperty key="org.ini4j.prefs.system" value="org/ini4j/sample/dwarfs.ini"/>
+                                            <sysproperty key="basedir" value="${basedir}"/>
+                                            <classpath>
+                                                <pathelement location="${basedir}/target/test-classes-jdk14"/>
+                                                <pathelement location="${basedir}/target/classes-jdk14"/>
+                                            </classpath>
+                                            <formatter type="plain" usefile="false" />
+                                            <batchtest>
+                                                <fileset dir="${basedir}/src/test/java">
+                                                    <include name="**/*Test.java"/>
+                                                    <exclude name="**/SampleRunnerTest.java"/>
+                                                </fileset>
+                                            </batchtest>
+                                        </junit>
+                                    </tasks>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-source-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>attach-sources</id>
+                                <goals>
+                                    <goal>jar</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-javadoc-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>attach-javadoc</id>
+                                <goals>
+                                    <goal>jar</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-changes-plugin</artifactId>
+                <inherited>false</inherited>
+                <configuration>
+                    <templateDirectory>src/changes</templateDirectory>
+                    <templateEncoding>UTF-8</templateEncoding>
+                    <outputDirectory>${project.build.directory}</outputDirectory>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>releasenotes</id>
+                        <goals>
+                            <goal>announcement-generate</goal>
+                        </goals>
+                        <phase>generate-resources</phase>
+                        <configuration>
+                            <template>ReleaseNotes.vm</template>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>changelog</id>
+                        <goals>
+                            <goal>announcement-generate</goal>
+                        </goals>
+                        <phase>generate-resources</phase>
+                        <configuration>
+                            <template>ChangeLog.vm</template>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>sitemap-create</id>
+                        <goals>
+                            <goal>announcement-generate</goal>
+                        </goals>
+                        <phase>pre-site</phase>
+                        <configuration>
+                            <template>sitemap.vm</template>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-antrun-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>sitemap-copy</id>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                        <phase>pre-site</phase>
+                        <configuration>
+                            <tasks>
+                                <property name="dir" value="${basedir}/target/generated-site/resources" />
+                                <mkdir dir="${dir}" />
+                                <copy file="target/sitemap.vm" tofile="${dir}/sitemap.xml" />
+                                <gzip src="${dir}/sitemap.xml" destfile="${dir}/sitemap.xml.gz" />
+                            </tasks>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.5</source>
+                    <target>1.5</target>
+                    <encoding>UTF-8</encoding>
+                    <compilerArgument>-Xlint</compilerArgument>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <configuration>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <systemProperties>
+                        <property>
+                            <name>java.util.prefs.PreferencesFactory</name>
+                            <value>org.ini4j.IniPreferencesFactory</value>
+                        </property>
+                        <property>
+                            <name>org.ini4j.prefs.user</name>
+                            <value>org/ini4j/sample/dwarfs.ini</value>
+                        </property>
+                        <property>
+                            <name>org.ini4j.prefs.system</name>
+                            <value>org/ini4j/sample/dwarfs.ini</value>
+                        </property>
+                    </systemProperties>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>zip</id>
+                        <phase>deploy</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <configuration>
+                            <descriptors>
+                                <descriptor>src/main/assembly/src.xml</descriptor>
+                                <descriptor>src/main/assembly/bin.xml</descriptor>
+                                <descriptor>src/main/assembly/all.xml</descriptor>
+                            </descriptors>
+                            <attach>false</attach>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>demo</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>target/generated-site/resources</outputDirectory>
+                            <finalName>ini4j-demo</finalName>
+                            <appendAssemblyId>false</appendAssemblyId>
+                            <descriptors>
+                                <descriptor>src/main/assembly/demo.xml</descriptor>
+                            </descriptors>
+                            <attach>false</attach>
+                            <archive>
+                                <manifest>
+                                    <mainClass>org.ini4j.demo.DemoMain</mainClass>
+                                </manifest>
+                            </archive>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>wagon-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>upload</id>
+                        <goals>
+                            <goal>upload</goal>
+                        </goals>
+                        <phase>deploy</phase>
+                        <configuration>
+                            <serverId>ini4j-upload</serverId>
+                            <fromDir>${project.build.directory}</fromDir>
+                            <includes>${project.build.finalName}-src*,${project.build.finalName}-bin*,${project.build.finalName}-all*</includes>
+                            <url>${upload.url}</url>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-site-plugin</artifactId>
+                <version>2.0</version>
+                <configuration>
+                    <generatedSiteDirectory>${basedir}/target/generated-site</generatedSiteDirectory>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>com.google.code.maven-license-plugin</groupId>
+                <artifactId>maven-license-plugin</artifactId>
+                <version>1.4.0</version>
+                <inherited>true</inherited>
+                <configuration>
+                    <aggregate>true</aggregate>
+                    <encoding>UTF-8</encoding>
+                    <useDefaultExcludes>true</useDefaultExcludes>
+                    <header>NOTICE.txt</header>
+                    <excludes>
+                        <exclude>**/META-INF/services/**</exclude>
+                        <exclude>**/*.txt</exclude>
+                        <exclude>**/*.apt</exclude>
+                        <exclude>**/*.reg</exclude>
+                        <exclude>**/.htaccess</exclude>
+                        <exclude>**/UTF*.ini</exclude>
+                    </excludes>
+                    <mapping>
+                        <java>SLASHSTAR_STYLE</java>
+                        <css>SLASHSTAR_STYLE</css>
+                        <js>SLASHSTAR_STYLE</js>
+                        <ini>SEMICOLON_STYLE</ini>
+                        <opt>SCRIPT_STYLE</opt>
+                    </mapping>
+                    <useDefaultMapping>true</useDefaultMapping>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>format</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>unpack</id>
+                        <phase>generate-resources</phase>
+                        <goals>
+                            <goal>unpack</goal>
+                        </goals>
+                        <configuration>
+                            <artifactItems>
+                                <artifactItem>
+                                    <groupId>org.beanshell</groupId>
+                                    <artifactId>bsh</artifactId>
+                                    <type>jar</type>
+                                    <overWrite>false</overWrite>
+                                    <outputDirectory>${project.build.directory}/test-classes</outputDirectory>
+                                </artifactItem>
+                            </artifactItems>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+
+        </plugins>
+        <testResources>
+            <testResource>
+                <directory>src/test/java</directory>
+                <includes>
+                    <include>**/*.ini</include>
+                    <include>**/*.opt</include>
+                    <include>**/*.reg</include>
+                    <include>**/*.properties</include>
+                    <include>**/*.xml</include>
+                    <include>**/*.txt</include>
+                </includes>
+            </testResource>
+            <testResource>
+                <directory>src/test/resources</directory>
+            </testResource>
+        </testResources>
+    </build>
+
+    <reporting>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jxr-plugin</artifactId>
+                <configuration>
+                    <inputEncoding>UTF-8</inputEncoding>
+                    <linkJavadoc>true</linkJavadoc>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <configuration>
+                    <charset>UTF-8</charset>
+                    <encoding>ISO-8859-1</encoding>
+                    <quiet>true</quiet>
+                    <noqualifier>java.*</noqualifier>
+                    <links>
+                        <link>http://java.sun.com/javase/6/docs/api/</link>
+                    </links>
+                    <keywords>true</keywords>
+                </configuration>
+                <reportSets>
+                    <reportSet>
+                        <reports>
+                            <report>javadoc</report>
+                        </reports>
+                    </reportSet>
+                </reportSets>
+
+            </plugin>
+
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>taglist-maven-plugin</artifactId>
+                <configuration>
+                    <encoding>UTF-8</encoding>
+                    <tags>
+                        <tag>TODO</tag>
+                        <tag>FIXME</tag>
+                        <tag>XXX</tag>
+                    </tags>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>cobertura-maven-plugin</artifactId>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-changes-plugin</artifactId>
+                <configuration>
+                    <templateEncoding>UTF-8</templateEncoding>
+                    <issueLinkTemplate>http://sourceforge.net/tracker2/?func=detail&amp;atid=715133&amp;group_id=129580&amp;aid=%ISSUE%</issueLinkTemplate>
+                </configuration>
+                <reportSets>
+                    <reportSet>
+                        <reports>
+                            <report>changes-report</report>
+                        </reports>
+                    </reportSet>
+                </reportSets>
+
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-project-info-reports-plugin</artifactId>
+                <reportSets>
+                    <reportSet>
+                        <reports>
+                            <report>index</report>
+                            <report>summary</report>
+                            <report>issue-tracking</report>
+                            <report>license</report>
+                            <report>project-team</report>
+                            <report>dependencies</report>
+                        </reports>
+                    </reportSet>
+                </reportSets>
+            </plugin>
+
+        </plugins>
+    </reporting>
+
+</project>
\ No newline at end of file
diff --git a/src/org/ini4j/IniHandler.java b/src/changes/ChangeLog.vm
similarity index 76%
copy from src/org/ini4j/IniHandler.java
copy to src/changes/ChangeLog.vm
index 9354f5c..4a602a6 100644
--- a/src/org/ini4j/IniHandler.java
+++ b/src/changes/ChangeLog.vm
@@ -1,4 +1,4 @@
-/*
+#*
  * Copyright 2005,2009 Ivan SZKIBA
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,16 +12,14 @@
  * 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.ini4j;
+ *#
+Change log
 
-public interface IniHandler extends OptionHandler
-{
-    void endIni();
+#foreach( $release in $releases )
+* $release.version ($release.dateRelease) - $release.description
 
-    void endSection();
+#foreach( $action in $release.actions )
+    * $action.action
+#end
 
-    void startIni();
-
-    void startSection(String sectionName);
-}
+#end
\ No newline at end of file
diff --git a/src/org/ini4j/OptionHandler.java b/src/changes/ReleaseNotes.vm
similarity index 79%
copy from src/org/ini4j/OptionHandler.java
copy to src/changes/ReleaseNotes.vm
index 57fb60f..ff50744 100644
--- a/src/org/ini4j/OptionHandler.java
+++ b/src/changes/ReleaseNotes.vm
@@ -1,4 +1,4 @@
-/*
+#*
  * Copyright 2005,2009 Ivan SZKIBA
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,10 +12,12 @@
  * 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.ini4j;
+ *#
+Release notes
+
+* $release.version ($release.dateRelease) - $release.description
+
+#foreach( $action in $release.actions )
+    * $action.action
+#end
 
-public interface OptionHandler
-{
-    void handleOption(String optionName, String optionValue);
-}
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
new file mode 100644
index 0000000..b75d152
--- /dev/null
+++ b/src/changes/changes.xml
@@ -0,0 +1,374 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright 2005,2009 Ivan SZKIBA
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<document>
+  <properties>
+    <title>[ini4j] Changes</title>
+  </properties>
+  <body>
+    <release version="0.5.2" date="2010-04-01" description="Bugfix release">
+      <action type="fix" dev="szkiba" issue="2908623" due-to="Markus">
+      Backport to java 1.4 fixed. There were a lot of problem. Now all junit tests
+      are run both on current java and on 1.4 jvm. This way backported version
+      is junit tested (on real 1.4 jvm) as the normal version.
+      </action>
+      <action type="fix" dev="szkiba" issue="2778337" >
+      Removed UnicodeLittle/UnicodeBig charset references because these break
+      the backported version.
+      </action>
+      <action type="fix" dev="szkiba" issue="2866427" due-to="Lilian Valero">
+      Add BOM (byte order mark) support. Workaround on java BOM/UTF8 problem.
+      </action>
+      <action type="fix" dev="szkiba" issue="2817399" >
+      Fixed typo in Options Tutorial.
+      </action>
+      <action type="fix" dev="szkiba" issue="2817403" dua-to="Gary Pampara">
+      Multiply macro substituton per value fixed. There were a bug in
+      variable/macro substitution if more than one macro used in one option value.
+      </action>
+      <action type="fix" dev="szkiba" issue="2971952" due-to="René Krell">
+      Configurable comment handling. The header comment can be disable/enable via
+      Config.setHeaderComment(boolean). Also the whole comment handling can be
+      disable/enable via Config.setComment(boolean).
+      </action>
+    </release>
+    <release version="0.5.1" date="2009-04-08" description="Maintenance release">
+      <action type="fix" dev="szkiba">
+      Removed java.util.NavigableMap reference from code, becasue it is break
+      java 1.5 compatibility.
+      </action>
+      <action type="add" dev="szkiba">
+      Applet support: [ini4j] now able to run in applet environment.
+      </action>
+      <action type="add" dev="szkiba">
+      Demo applet: now project site contains a small demo applet to check
+      [ini4j] interactive mode from browser.
+      </action>
+    </release>
+    <release version="0.5.0" date="2009-04-02" description="Subsection handling, comment support, Windows registry handling">
+      <action type="add" dev="szkiba">
+      Read/write Windows registry. The new Reg class is able to read/write
+      windows registry. This class is very similar to Ini class, has same data
+      model.
+      </action>
+      <action type="add" dev="szkiba">
+      Support for .REG (windows regedit) format. The file format is similar to .ini,
+      and now the org.ini4j.Reg class can handle it.
+      </action>
+      <action type="add" dev="szkiba">
+      Tree data model for ections. Now sections handled as a tree, with subsections.
+      It is based on section names. With a separator ('/' or '\') sectios can
+      organized into trees (like in .REG format).
+      </action>
+      <action type="update" dev="szkiba">
+      Improve project web site.
+      </action>
+      <action type="add" dev="szkiba">
+      Bean property mapping with upper case first letter in key names (aka Mozilla .ini files)
+      (Feature request #2706511)
+      </action>
+      <action type="add" dev="szkiba">
+      Comment support for Ini and Options. There is a header comment, section
+      comment and option comment. Comments at beginig of file are header
+      comments. Continous comment block before section is a section comment,
+      continous comment bleck before option is an option comment.
+      </action>
+      <action type="update" dev="szkiba">
+      Refactor Ini class, extract Profile interface and BasicProfile superclass.
+      </action>
+      <action type="add" dev="szkiba">
+      Comfortable access methods for OptionBundle and OptionMap.
+      </action>
+      <action type="update" dev="szkiba">
+      Code cleanup, remove deprecated methods, classes.
+      </action>
+      <action type="update" dev="szkiba">
+      Backslash at end of line support: join with next line.
+      </action>
+    </release>
+    <release version="0.4.1" date="2009-03-11" description="Python like ConfigParser Options as java.util.Properties replacement">
+      <action type="add" dev="szkiba">
+      Added Options as replacement for java.util.Properties. Options has a lot
+      of useful features, like: ${} macro handling, multi value
+      handling, bean marshalling/unmarshalling etc.
+      </action>
+      <action type="add" dev="szkiba">
+      Python ConfigParser interface for .ini files.
+      </action>
+      <action type="update" dev="szkiba">
+      Cleanup code, use PMD+checkstyle.
+      </action>
+      <action type="fix" dev="szkiba">
+      Cleanup deprecated warnings from test code.
+      </action>
+      <action type="fix" dev="szkiba" issue="2623266" due-to="Karl Heinz Marbaise">
+      Skip "class" property in Ini.Section#from method.
+      </action>
+      <action type="fix" dev="szkiba" issue="2623274" due-to="Karl Heinz Marbaise">
+      Added Issue Tracking to Project Information on maven generated site.
+      </action>
+      <action type="fix" dev="szkiba">
+      Fix BeanTool bug: ${} macros an dindexed properties not handled properly in inject().
+      </action>
+    </release>
+
+    <release version="0.4.0" date="2009-02-17" description="Refactoring and new features">
+      <action type="add" dev="szkiba">
+      New from() and to() methods in Ini.Section for
+      marshalling/unmarshalling bean properties.
+      </action>
+
+      <action type="add" dev="szkiba">
+      Indexed property access in bean interafce for multi value options and
+      sections.
+      </action>
+
+      <action type="add" dev="szkiba">
+      Configurable multi value handling both for options and sections.
+      </action>
+
+      <action type="add" dev="szkiba">
+      Configurable escape handling. Now possible to handle windows path
+      values (caontains backslases).
+      </action>
+
+      <action type="update" dev="szkiba">
+      Migrate junit test to 4.x version.
+      </action>
+
+      <action type="update" dev="szkiba">
+      Added MuliMap datatype: multiple value for same key. It used by Ini and
+      Ini.Section classes. 
+      </action>
+
+      <action type="update" dev="szkiba">
+      Move FancyIniParser and FancyIniFormatter functionality to IniParser and
+      IniFormatter. Make them configurable (enable/disable features via Config
+      object).
+      </action>
+
+      <action type="update" dev="szkiba">
+      Make bean manipulation (BeanTool) and escaping (EscapeTool) replaceable
+      via standard services mechanism (/META-INF/services/XXX)
+      </action>
+
+      <action type="add" dev="szkiba">
+      Internal changes: migrating changelog.apt to changes.xml, generating text
+      version of ChangeLog and Releasenotes from changes.xml
+      </action>
+
+      <action type="add" dev="szkiba">
+      Internal changes: use wagon plugin for helping SourceForge release
+      upload, use license plugin to keep up to date file headers.
+      </action>
+    </release>
+
+    <release version="0.3.3" date="2009-02-05" description="Bugfixes">
+      <action type="fix" dev="szkiba" issue="2060122" due-to="Julien MARBACH">
+      ini4j now support files with # in the name
+      </action>
+      <action type="fix" dev="szkiba" issue="2120766" due-to="Karl Heinz Marbaise and Gert van Valkenhoef">
+      (Java 1.5 Support) now ini4j compiled for jdk 1.5 by default (and
+      translated to 1.4)
+      </action>
+      <action type="update" dev="szkiba">
+      SVN rename. TRUNK and TAGS renamed to lowercase, to use conventional names
+      </action>
+      <action type="fix" dev="szkiba" issue="1918502">
+      Write Ini file: skip blanks around " = ":
+      Separated IniFormatter class format the .ini file (and XMLFormatter).
+      This class can instantiate using factory pattern (internally use
+      standard jar service locator pattern), and caller can override default
+      IniFormatter implementation. See FancyIniFormatter in addons package,
+      which is by default do not put space around '='.
+      </action>
+      <action type="fix" dev="szkiba" issue="1897071" due-to="Walter Podesser">
+      Ini.store with null values:
+      By default keys with empty (null) values now skipped, but in
+      FancyIniFormatter empty values printed as empty string.
+      </action>
+    </release>
+
+    <release version="0.3.2" date="2008-02-17" description="Minor bugfixes">
+      <action type="fix" dev="szkiba">
+      Fix file close problem: in some case IniFile and IniParser left files
+      open. This cause file deletion problem on some OS.
+      </action>
+      <action type="fix" dev="szkiba">
+      Fix Unicode escape problem: \u sequences now generated correctly
+      (4 character with leading zeros)
+      </action>
+    </release>
+
+    <release version="0.3.1" date="2008-02-11" description="Maven repository support">
+      <action type="add" dev="szkiba">
+      Added maven repository support: http://www.ini4j.org/repository/release
+      and http://www.ini4j.org/repository/snapshot
+      </action>
+    </release>
+
+    <release version="0.3.0" date="2008-02-10" description="Migrating to SVN and Maven">
+      <action type="update" dev="szkiba">
+      Migration: CVS to SVN
+      </action>
+      <action type="update" dev="szkiba">Migration: Ant to Maven</action>
+    </release>
+
+    <release version="0.2.6" date="2005-11-29" description="Include handling">
+      <action type="update" dev="szkiba">
+      Refactored code for customization: Ini, IniPreferences, IniParser now
+      also handle URL as input. It makes possible to create C like #include
+      directive at IniParser level, when using URL as input source.
+      </action>
+      <action type="add" dev="szkiba">
+      Added include feature to FancyIniParser: &lt;location&gt; mean: '&lt;'
+      and '&gt;' token, location is either relative or absolute URL of include
+      file. Unlimited level of includes allowed. Relative URLs only allowed,
+      when container file loaded via URL (not via stream or reader).
+      </action>
+      <action type="add" dev="szkiba">
+      IniFile got getMode() and getFile() accessor methods.
+      </action>
+      <action type="add" dev="szkiba">
+      PreferencesWrapper class in addon package to support decorator design
+      pattern for java.util.Preferences objects.
+      </action>
+      <action type="add" dev="szkiba">
+      StrictPreferences decorator class, which has getXXX methods without
+      default parameter, and throws java.util.NoSuchElementException on missing
+      values instead of returning default value.
+      </action>
+    </release>
+
+    <release version="0.2.5" date="2005-11-26" description="Backport to JDK 1.4">
+      <action type="fix" dev="szkiba">
+      As per many programmers request, finally [ini4j] backported to JDK 1.4.
+      There is a ini4j-compat.jar and retroweaver-rt*.jar in distribution
+      directory. These 2 jar need for JDK 1.4 to use [ini4j]. Every JUnit test
+      run successfully, so I'm sure that [ini4j] work correctly with JDK 1.4
+      (JUnit test coverage ~ 98% :)
+
+      I like retroweaver tool, so I'd like to mentoin here:
+      retroweaver.sorceforge.net and forked project (I used for ini4j)
+      retroweaver-ng.sourceforge.net
+      </action>
+    </release>
+
+    <release version="0.2.4" date="2005-11-25" description="Replaceable parser via factory pattern">
+      <action type="fix" dev="szkiba">removed some private constructor (cosmetic change)</action>
+      <action type="add" dev="szkiba">
+      Added factory design pattern for IniParser class: it is
+      possible now to change IniParser implementation at runtime:
+      either "org.ini4j.IniParser" system property or
+      META-INF/services/org.ini4j.IniParser file is the configuration point.
+      This is conform with JAR specifications service definition.
+
+      Thanx this to James Ahlborn and Steve Buschman. They are suggested some
+      changes in IniParser. I disagree with the changes, but I made the
+      possibility of changing IniParser without change Ini4j source :)
+      </action>
+      <action type="add" dev="szkiba">added org.ini4j.addon package for optional classes.</action>
+      <action type="add" dev="szkiba">
+      added org.ini4j.addon.FancyIniParser as alternate parser with following
+      features:
+      - option without = operator mean empty option
+      - empty section names [] mean empty string as section name
+      - first section tag ([name]) is optional, by default it is same as []
+      - section and/or option names may be converted to lowercase while parsing
+      </action>
+    </release>
+
+    <release version="0.2.3" date="2005-02-18" description="Minor bugfixes">
+      <action type="fix" dev="szkiba">
+      fixed xhml bugs in documentation
+      </action>
+      <action type="fix" dev="szkiba">
+      set java.util.prefs.PreferencesFactory property in
+      IniPreferencesFactoryListener
+      </action>
+      <action type="fix" dev="szkiba">
+      fix AbstractBeanInvocationHandler#getProperty(), now it returns zero()
+      if getPropertySpi() returns null
+      </action>
+      <action type="add" dev="szkiba">
+      hasProperty method in AbstractBeanInvocationHandler: if interface has
+      hasXXX method, it will return true if bean has XXX property.
+      </action>
+    </release>
+
+    <release version="0.2.2" date="2005-02-13" description="Webapp support">
+      <action type="fix" dev="szkiba">
+      fixed logo png in CVS
+      </action>
+      <action type="add" dev="szkiba">
+      support for webapps: IniPreferencesFactoryListener
+      webapp can use /WEB-INF/user.ini and /ERB-INF/system.ini
+      as user and system root preferences
+      </action>
+    </release>
+
+    <release version="0.2.1" date="2005-02-12" description="Import to CVS, org.ini4j in package names">
+      <action type="add" dev="szkiba">
+      checked in to CVS CVSROOT: :ext:cvs.sourceforge.net/cvsroot/ini4j/main
+      </action>
+      <action type="update" dev="szkiba">
+      documentation reorganization: footer navigation bar added
+      </action>
+      <action type="add" dev="szkiba">
+      ini4j.org domain registered, java package names modified to org.ini4j
+      </action>
+      <action type="update" dev="szkiba">
+      JUnit tests improved
+      </action>
+      <action type="add" dev="szkiba">
+      Clover code coverage tool used
+      </action>
+      <action type="fix" dev="szkiba">
+      documentation English cleaned
+      </action>
+    </release>
+
+    <release version="0.1.1" date="2005-02-05" description="Bean interface and variable substitution">
+      <action type="add" dev="szkiba">
+      Java Beans style interface for sections and for ini too. You should pass
+      interface to Ini.Section.to() (or Ini.to()) and you get a Java object
+      implements passed interface on top of Ini.section (or Ini). Not only setXX
+      and getXX works well, but property change listeners and vetoable change
+      listeners also supported.
+      </action>
+      <action type="add" dev="szkiba">
+      Variable substitution for option values. You can use ${section/option}
+      style substitution expressions in any option value.
+      </action>
+      <action type="update" dev="szkiba">
+      Reorganize XML parsing: now IniParser has parseXML method, which
+      simply translate SAX callbacks to IniHandler callbacks
+      </action>
+      <action type="add" dev="szkiba">
+      Create Convert class for unicode escape conversion: escape and unsecape
+      methods.
+      </action>
+    </release>
+
+    <release version="0.1.0" date="2005-01-30" description="First release">
+      <action type="add" dev="szkiba">
+      Initial version
+      </action>
+    </release>
+  </body>
+</document>
diff --git a/src/changes/sitemap.vm b/src/changes/sitemap.vm
new file mode 100644
index 0000000..e560aea
--- /dev/null
+++ b/src/changes/sitemap.vm
@@ -0,0 +1,69 @@
+#*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *#<?xml version="1.0" encoding="UTF-8"?>
+<urlset  xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
+   xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'
+   xsi:schemaLocation='http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd'>
+
+    <url>
+        <loc>http://ini4j.sourceforge.net/</loc>
+        <lastmod>$release.dateRelease</lastmod>
+        <priority>0.9</priority>
+        <changefreq>monthly</changefreq>
+    </url>
+
+    <url>
+        <loc>http://ini4j.sourceforge.net/tutorial/index.html</loc>
+        <lastmod>$release.dateRelease</lastmod>
+        <priority>0.8</priority>
+        <changefreq>monthly</changefreq>
+    </url>
+
+    <url>
+        <loc>http://ini4j.sourceforge.net/sample/index.html</loc>
+        <lastmod>$release.dateRelease</lastmod>
+        <priority>0.8</priority>
+        <changefreq>monthly</changefreq>
+    </url>
+
+    <url>
+        <loc>http://ini4j.sourceforge.net/download.html</loc>
+        <lastmod>$release.dateRelease</lastmod>
+        <priority>0.8</priority>
+        <changefreq>monthly</changefreq>
+    </url>
+
+    <url>
+        <loc>http://ini4j.sourceforge.net/apidocs/index.html</loc>
+        <lastmod>$release.dateRelease</lastmod>
+        <priority>0.7</priority>
+        <changefreq>monthly</changefreq>
+    </url>
+
+    <url>
+        <loc>http://ini4j.sourceforge.net/cobertura/index.html</loc>
+        <lastmod>$release.dateRelease</lastmod>
+        <priority>0.6</priority>
+        <changefreq>monthly</changefreq>
+    </url>
+
+    <url>
+        <loc>http://ini4j.sourceforge.net/design.html</loc>
+        <lastmod>$release.dateRelease</lastmod>
+        <priority>0.6</priority>
+        <changefreq>monthly</changefreq>
+    </url>
+
+</urlset>
diff --git a/src/conf/checkstyle.xml b/src/conf/checkstyle.xml
new file mode 100644
index 0000000..772122c
--- /dev/null
+++ b/src/conf/checkstyle.xml
@@ -0,0 +1,247 @@
+<?xml version="1.0"?>
+<!--
+
+    Copyright 2005,2009 Ivan SZKIBA
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<!DOCTYPE module PUBLIC
+    "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
+    "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+
+<module name="Checker">
+
+    <!-- Checks that a package.html file exists for each package.     -->
+    <!-- See http://checkstyle.sf.net/config_javadoc.html#PackageHtml -->
+    <module name="PackageHtml"/>
+
+    <!-- Checks whether files end with a new line.                        -->
+    <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->
+    <module name="NewlineAtEndOfFile"/>
+
+    <!-- Checks that property files contain the same keys.         -->
+    <!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
+    <module name="Translation"/>
+
+    <module name="SuppressionCommentFilter"/>
+
+    <module name="TreeWalker">
+        <module name="FileContentsHolder"/>
+        <!-- Checks for Javadoc comments.                     -->
+        <!-- See http://checkstyle.sf.net/config_javadoc.html -->
+        <!--
+        <module name="JavadocMethod">
+            <property name="scope" value="protected"/>
+        </module>
+        <module name="JavadocType">
+            <property name="scope" value="protected"/>
+        </module>
+        <module name="JavadocVariable">
+            <property name="scope" value="protected"/>
+        </module>
+        <module name="JavadocStyle">
+            <property name="scope" value="protected"/>
+        </module>
+        -->
+
+        <!-- Checks for Naming Conventions.                  -->
+        <!-- See http://checkstyle.sf.net/config_naming.html -->
+        <module name="ConstantName">
+            <property name="format" value="(^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$)|(^_[a-z][a-zA-Z0-9]*$)"/>
+        </module>
+        <module name="LocalFinalVariableName"/>
+        <module name="LocalVariableName"/>
+        <module name="MemberName">
+            <property name="applyToPrivate" value="false"/>
+        </module>
+        <module name="MemberName">
+            <property name="format" value="^_[a-z][a-zA-Z0-9]*$"/>
+            <property name="applyToPublic" value="false"/>
+            <property name="applyToProtected " value="false"/>
+            <property name="applyToPackage" value="false"/>
+        </module>
+        <module name="MethodName"/>
+        <module name="PackageName"/>
+        <module name="ParameterName"/>
+        <module name="StaticVariableName">
+            <property name="format" value="^_?[a-z][a-zA-Z]*$"/>
+        </module>
+        <module name="TypeName">
+            <property name="format" value="^[A-Z][a-zA-Z]*$"/>
+        </module>
+
+
+        <!-- Checks for Headers                                -->
+        <!-- See http://checkstyle.sf.net/config_header.html   -->
+        <!-- <module name="Header">                            -->
+            <!-- The follow property value demonstrates the ability     -->
+            <!-- to have access to ANT properties. In this case it uses -->
+            <!-- the ${basedir} property to allow Checkstyle to be run  -->
+            <!-- from any directory within a project. See property      -->
+            <!-- expansion,                                             -->
+            <!-- http://checkstyle.sf.net/config.html#properties        -->
+            <!-- <property                                              -->
+            <!--     name="headerFile"                                  -->
+            <!--     value="${basedir}/java.header"/>                   -->
+        <!-- </module> -->
+
+        <!-- Following interprets the header file as regular expressions. -->
+        <!-- <module name="RegexpHeader"/>                                -->
+
+
+        <!-- Checks for imports                              -->
+        <!-- See http://checkstyle.sf.net/config_import.html -->
+        <module name="AvoidStarImport"/>
+        <module name="IllegalImport"/> <!-- defaults to sun.* packages -->
+        <module name="RedundantImport"/>
+        <module name="UnusedImports"/>
+
+
+        <!-- Checks for Size Violations.                    -->
+        <!-- See http://checkstyle.sf.net/config_sizes.html -->
+        <module name="FileLength"/>
+        <module name="LineLength">
+            <property name="max" value="200"/>
+        </module>
+        <module name="AnonInnerLength">
+            <property name="max" value="30"/>
+        </module>
+        <module name="MethodLength"/>
+        <module name="ParameterNumber"/>
+
+
+        <!-- Checks for whitespace                               -->
+        <!-- See http://checkstyle.sf.net/config_whitespace.html -->
+        <module name="EmptyForIteratorPad"/>
+        <module name="MethodParamPad"/>
+        <module name="NoWhitespaceAfter">
+            <property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS, UNARY_PLUS"/>
+        </module>
+        <module name="NoWhitespaceBefore"/>
+        <module name="OperatorWrap"/>
+        <module name="ParenPad"/>
+        <module name="TypecastParenPad"/>
+        <module name="TabCharacter"/>
+        <module name="WhitespaceAfter"/>
+        <module name="WhitespaceAround">
+            <property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, EQUAL, GE, GT, LAND, LCURLY, LE, LITERAL_ASSERT, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR, SR_ASSIGN, STAR, STAR_ASSIGN, TYPE_EXTENSION_AND"/>
+        </module>
+
+
+        <!-- Modifier Checks                                    -->
+        <!-- See http://checkstyle.sf.net/config_modifiers.html -->
+        <module name="ModifierOrder"/>
+        <module name="RedundantModifier"/>
+
+
+        <!-- Checks for blocks. You know, those {}'s         -->
+        <!-- See http://checkstyle.sf.net/config_blocks.html -->
+        <module name="AvoidNestedBlocks"/>
+        <module name="EmptyBlock"/>
+        <module name="LeftCurly">
+            <property name="option" value="nl"/>
+        </module>
+        <module name="NeedBraces"/>
+        <module name="RightCurly">
+            <property name="option" value="alone"/>
+        </module>
+
+
+        <!-- Checks for common coding problems               -->
+        <!-- See http://checkstyle.sf.net/config_coding.html -->
+        <!-- <module name="AvoidInlineConditionals"/> -->
+        <module name="DoubleCheckedLocking"/>
+        <module name="EmptyStatement"/>
+        <module name="EqualsHashCode"/>
+        <module name="HiddenField"/>
+        <module name="IllegalInstantiation"/>
+        <module name="InnerAssignment"/>
+        <module name="MagicNumber"/>
+        <module name="MissingSwitchDefault"/>
+        <!--
+        <module name="RedundantThrows">
+          <property name="allowUnchecked" value="true"/>
+        </module>
+        -->
+        <module name="SimplifyBooleanExpression"/>
+        <module name="SimplifyBooleanReturn"/>
+        <module name="ModifiedControlVariable"/>
+        <module name="StringLiteralEquality"/>
+        <module name="NestedIfDepth">
+            <property name="max" value="3"/>
+        </module>
+        <module name="NestedTryDepth">
+            <property name="max" value="2"/>
+        </module>
+        <module name="SuperClone"/>
+        <module name="IllegalCatch">
+            <property name="illegalClassNames" value="java.lang.Throwable, java.lang.RuntimeException"/>
+        </module>
+        <module name="IllegalThrows"/>
+        <module name="PackageDeclaration"/>
+        <module name="JUnitTestCase"/>
+        <module name="ReturnCount"/>
+        <module name="IllegalType"/>
+        <module name="ParameterAssignment"/>
+        <module name="ExplicitInitialization"/>
+        <module name="DefaultComesLast"/>
+        <module name="FallThrough"/>
+        <module name="MultipleVariableDeclarations"/>
+
+        <!-- Checks for class design                         -->
+        <!-- See http://checkstyle.sf.net/config_design.html -->
+        <!-- <module name="DesignForExtension"/> -->
+        <module name="FinalClass"/>
+        <module name="HideUtilityClassConstructor"/>
+        <module name="InterfaceIsType"/>
+        <module name="VisibilityModifier">
+            <property name="protectedAllowed" value="true"/>
+        </module>
+
+
+        <!-- Miscellaneous other checks.                   -->
+        <!-- See http://checkstyle.sf.net/config_misc.html -->
+        <module name="ArrayTypeStyle"/>
+        <!-- <module name="FinalParameters"/> -->
+        <module name="GenericIllegalRegexp">
+            <property name="format" value="\s+$"/>
+            <property name="message" value="Line has trailing spaces."/>
+        </module>
+        <module name="GenericIllegalRegexp">
+            <property name="format" value="//NOPMD"/>
+            <property name="message" value="Use the SuppressWarnings annotation"/>
+        </module>
+        <module name="TodoComment"/>
+        <module name="UpperEll"/>
+
+        <module name="UncommentedMain"/>
+        <module name="Indentation"/>
+
+
+
+        <!-- Metrics checks.                   -->
+        <!-- See http://checkstyle.sourceforge.net/config_metrics.html -->
+        <module name="CyclomaticComplexity"/>
+        <module name="CyclomaticComplexity"/>
+        <module name="NPathComplexity"/>
+        <module name="JavaNCSS"/>
+        <module name="BooleanExpressionComplexity">
+          <property name="max" value="5"/>
+        </module>
+
+    </module>
+    
+<!--  founds license headers :)  <module name="StrictDuplicateCode"/> -->
+
+</module>
diff --git a/src/conf/jalopy.xml b/src/conf/jalopy.xml
new file mode 100644
index 0000000..7de7847
--- /dev/null
+++ b/src/conf/jalopy.xml
@@ -0,0 +1,1106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright 2005,2009 Ivan SZKIBA
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<jalopy vendor="TRIEMAX">
+    <profile name="ini4j">
+  <html>
+   <output>
+    <blankLines>
+     <keepUpTo>2</keepUpTo>
+    </blankLines>
+    <indent>
+     <size>2</size>
+    </indent>
+    <wrap>
+     <attributes>never</attributes>
+     <keepBreak>
+      <tags>false</tags>
+      <text>false</text>
+     </keepBreak>
+     <size>80</size>
+     <text>necessary</text>
+    </wrap>
+   </output>
+  </html>
+        <internal>
+            <version>36</version>
+        </internal>
+        <java>
+            <general>
+                <compliance>
+                    <version>15</version>
+                </compliance>
+            </general>
+            <inspector>
+                <enable>true</enable>
+                <naming>
+                    <classes>
+                        <abstract>[A-Z][a-zA-Z0-9]+</abstract>
+                        <general>[A-Z][a-zA-Z0-9]+</general>
+                        <inner>[A-Z][a-zA-Z0-9]+</inner>
+                    </classes>
+                    <fields>
+                        <default>[a-z]\w+</default>
+                        <defaultFinal>[a-z]\w+</defaultFinal>
+                        <defaultStatic>[a-z]\w+</defaultStatic>
+                        <defaultStaticFinal>[a-zA-Z]\w+</defaultStaticFinal>
+                        <private>_[a-z]\w+</private>
+                        <privateFinal>[a-z]\w+</privateFinal>
+                        <privateStatic>[a-z]\w+</privateStatic>
+                        <privateStaticFinal>[a-zA-Z]\w+</privateStaticFinal>
+                        <protected>[a-z]\w+</protected>
+                        <protectedFinal>[a-z]\w+</protectedFinal>
+                        <protectedStatic>[a-z]\w+</protectedStatic>
+                        <protectedStaticFinal>[a-zA-Z]\w+</protectedStaticFinal>
+                        <public>[a-z]\w+</public>
+                        <publicFinal>[a-z]\w+</publicFinal>
+                        <publicStatic>[a-z]\w+</publicStatic>
+                        <publicStaticFinal>[A-Z0-9_]+</publicStaticFinal>
+                    </fields>
+                    <interfaces>[A-Z][a-zA-Z0-9]+</interfaces>
+                    <labels>\w+</labels>
+                    <methods>
+                        <default>[a-z]\w+</default>
+                        <defaultStatic>[a-z]\w+</defaultStatic>
+                        <defaultStaticFinal>[a-z]\w+</defaultStaticFinal>
+                        <private>[a-z]\w+</private>
+                        <privateStatic>[a-z]\w+</privateStatic>
+                        <privateStaticFinal>[a-z]\w+</privateStaticFinal>
+                        <protected>[a-z]\w+</protected>
+                        <protectedStatic>[a-z]\w+</protectedStatic>
+                        <protectedStaticFinal>[a-z]\w+</protectedStaticFinal>
+                        <public>[a-z]\w+</public>
+                        <publicStatic>[a-z]\w+</publicStatic>
+                        <publicStaticFinal>[a-z]\w+</publicStaticFinal>
+                    </methods>
+                    <packages>[a-z]+(?:\.[a-z]+)*</packages>
+                    <parameters>
+                        <default>[a-z]\w+</default>
+                        <exception>\w+</exception>
+                        <final>[a-z]\w+</final>
+                    </parameters>
+                    <variables>
+                        <local>[a-z]\w*</local>
+                        <localFinal>[a-z]\w*</localFinal>
+                        <loop>[a-z]\w*</loop>
+                    </variables>
+                </naming>
+                <tips>
+                    <addCommentForCollections>false</addCommentForCollections>
+                    <adhereToNamingConvention>false</adhereToNamingConvention>
+                    <alwaysOverrideHashCode>false</alwaysOverrideHashCode>
+                    <avoidThreadGroups>false</avoidThreadGroups>
+                    <avoidVariableShadowing>false</avoidVariableShadowing>
+                    <declareCollectionComment>false</declareCollectionComment>
+                    <dontIgnoreExceptions>false</dontIgnoreExceptions>
+                    <dontSubstituteObjectEquals>false</dontSubstituteObjectEquals>
+                    <exceedLineLength>false</exceedLineLength>
+                    <exceedLineLengthDisableWithinPragma>false</exceedLineLengthDisableWithinPragma>
+                    <neverDeclareException>false</neverDeclareException>
+                    <neverDeclareThrowable>false</neverDeclareThrowable>
+                    <neverInvokeWaitOutsideLoop>false</neverInvokeWaitOutsideLoop>
+                    <neverReturnZeroArrays>false</neverReturnZeroArrays>
+                    <neverUseEmptyFinally>false</neverUseEmptyFinally>
+                    <obeyContractEquals>false</obeyContractEquals>
+                    <overrideToString>false</overrideToString>
+                    <referToObjectsByInterface>false</referToObjectsByInterface>
+                    <replaceStructureWithClass>false</replaceStructureWithClass>
+                    <stringLiterallI18n>false</stringLiterallI18n>
+                    <useInterfaceOnlyForTypes>false</useInterfaceOnlyForTypes>
+                    <wrongCollectionComment>false</wrongCollectionComment>
+                </tips>
+            </inspector>
+            <logging>
+                <enable>false</enable>
+                <file>logs\run.log</file>
+                <type>log</type>
+            </logging>
+            <messages>
+                <autoShow>true</autoShow>
+                <priority>
+                    <general>30000</general>
+                    <inspector>30000</inspector>
+                    <parser>30000</parser>
+                    <parserJavadoc>30000</parserJavadoc>
+                    <printer>30000</printer>
+                    <printerJavadoc>30000</printerJavadoc>
+                    <progress>30000</progress>
+                    <transform>30000</transform>
+                </priority>
+                <showStackTrace>true</showStackTrace>
+            </messages>
+            <printer>
+                <alignment>
+                    <arrayInit>false</arrayInit>
+                    <methodCallChain>false</methodCallChain>
+                    <nestedMethodCallChain>false</nestedMethodCallChain>
+                    <parameterMethodDeclaration>false</parameterMethodDeclaration>
+                    <ternaryOperator>true</ternaryOperator>
+                    <variableAssignment>false</variableAssignment>
+                    <variableIdentifier>false</variableIdentifier>
+                </alignment>
+                <annotation>
+                    <insert>false</insert>
+                    <patterns></patterns>
+                </annotation>
+                <blanklines>
+                    <abstractNone>false</abstractNone>
+                    <after>
+                        <annotation>1</annotation>
+                        <assign>1</assign>
+                        <block>1</block>
+                        <braceLeft>-1</braceLeft>
+                        <braceLeftClass>-1</braceLeftClass>
+                        <braceLeftMethod>-1</braceLeftMethod>
+                        <braceLeftNewline>-1</braceLeftNewline>
+                        <class>1</class>
+                        <comment>
+                            <separator>1</separator>
+                        </comment>
+                        <declaration>1</declaration>
+                        <enum>1</enum>
+                        <footer>1</footer>
+                        <header>0</header>
+                        <imports>1</imports>
+                        <interface>1</interface>
+                        <method>1</method>
+                        <package>1</package>
+                        <sqlj>1</sqlj>
+                    </after>
+                    <before>
+                        <annotation>0</annotation>
+                        <block>0</block>
+                        <braceRight>-1</braceRight>
+                        <caseBlock>1</caseBlock>
+                        <class>0</class>
+                        <comment>
+                            <javadoc>1</javadoc>
+                            <multiline>1</multiline>
+                            <separator>1</separator>
+                            <singleline>1</singleline>
+                        </comment>
+                        <controlStatement>1</controlStatement>
+                        <declaration>0</declaration>
+                        <enum>0</enum>
+                        <footer>1</footer>
+                        <header>0</header>
+                        <interface>0</interface>
+                        <section>1</section>
+                        <sqlj>1</sqlj>
+                    </before>
+                    <headerKeepUpTo>2</headerKeepUpTo>
+                    <ignoreSwitchBlock>true</ignoreSwitchBlock>
+                    <ignoreSwitchBreak>true</ignoreSwitchBreak>
+                    <keepUpTo>0</keepUpTo>
+                </blanklines>
+                <braces>
+                    <anonInner>
+                        <indentSizeBeforeLeft>1</indentSizeBeforeLeft>
+                        <indentSizeBeforeRight>0</indentSizeBeforeRight>
+                        <lineBreakLeft>false</lineBreakLeft>
+                    </anonInner>
+                    <array>
+                        <indentSizeBeforeLeft>1</indentSizeBeforeLeft>
+                        <indentSizeBeforeRight>0</indentSizeBeforeRight>
+                        <lineBreakLeft>false</lineBreakLeft>
+                    </array>
+                    <catch>
+                        <indentSizeAfterRight>1</indentSizeAfterRight>
+                        <indentSizeBeforeLeft>1</indentSizeBeforeLeft>
+                        <indentSizeBeforeRight>0</indentSizeBeforeRight>
+                        <lineBreakLeft>false</lineBreakLeft>
+                        <lineBreakRight>false</lineBreakRight>
+                    </catch>
+                    <class>
+                        <indentSizeBeforeLeft>1</indentSizeBeforeLeft>
+                        <indentSizeBeforeRight>0</indentSizeBeforeRight>
+                        <lineBreakLeft>false</lineBreakLeft>
+                    </class>
+                    <comment>
+                        <class>false</class>
+                        <ctor>false</ctor>
+                        <for>false</for>
+                        <ifElse>false</ifElse>
+                        <interface>false</interface>
+                        <method>false</method>
+                        <switch>false</switch>
+                        <synchronized>false</synchronized>
+                        <threshold>25</threshold>
+                        <tryCatchFinally>false</tryCatchFinally>
+                        <while>false</while>
+                    </comment>
+                    <compact>
+                        <arrayInit>true</arrayInit>
+                        <ctor>false</ctor>
+                        <else>false</else>
+                        <elseIf>false</elseIf>
+                        <enum>true</enum>
+                        <enumConstant>false</enumConstant>
+                        <if>false</if>
+                        <method>false</method>
+                        <singleIf>false</singleIf>
+                        <throwAndReturn>false</throwAndReturn>
+                    </compact>
+                    <empty>
+                        <cuddle>false</cuddle>
+                        <insertStatement>true</insertStatement>
+                        <obeyBraceStyle>false</obeyBraceStyle>
+                        <obeyBraceStyleArray>false</obeyBraceStyleArray>
+                    </empty>
+                    <enum>
+                        <indentSizeBeforeLeft>1</indentSizeBeforeLeft>
+                        <indentSizeBeforeRight>0</indentSizeBeforeRight>
+                        <lineBreakLeft>false</lineBreakLeft>
+                    </enum>
+                    <globalStyle>true</globalStyle>
+                    <initializer>
+                        <indentSizeBeforeLeft>1</indentSizeBeforeLeft>
+                        <indentSizeBeforeRight>0</indentSizeBeforeRight>
+                        <lineBreakLeft>false</lineBreakLeft>
+                    </initializer>
+                    <insert>
+                        <doWhile>true</doWhile>
+                        <for>true</for>
+                        <ifElse>true</ifElse>
+      <singleIf>true</singleIf>
+                        <singleLine>false</singleLine>
+                        <switch>false</switch>
+                        <while>true</while>
+                    </insert>
+                    <method>
+                        <indentSizeBeforeLeft>1</indentSizeBeforeLeft>
+                        <indentSizeBeforeRight>0</indentSizeBeforeRight>
+                        <lineBreakLeft>false</lineBreakLeft>
+                    </method>
+                    <remove>
+                        <block>true</block>
+                        <doWhile>false</doWhile>
+                        <for>false</for>
+                        <ifElse>false</ifElse>
+      <singleIf>false</singleIf>
+                        <switch>false</switch>
+                        <while>false</while>
+                    </remove>
+                    <statement>
+                        <indentSizeAfterRight>1</indentSizeAfterRight>
+                        <indentSizeBeforeLeft>1</indentSizeBeforeLeft>
+                        <indentSizeBeforeRight>0</indentSizeBeforeRight>
+                        <lineBreakLeft>false</lineBreakLeft>
+                        <lineBreakRight>false</lineBreakRight>
+                    </statement>
+                    <treatDifferent>
+                        <methodClass>false</methodClass>
+                        <methodClassIfWrapped>false</methodClassIfWrapped>
+                        <statementWrapped>false</statementWrapped>
+                    </treatDifferent>
+                </braces>
+                <chunks>
+                    <blankLines>true</blankLines>
+                    <comments>true</comments>
+                    <wrap>false</wrap>
+                </chunks>
+                <comments>
+                    <format>
+                        <multiLine>false</multiLine>
+                        <singleLine>false</singleLine>
+                    </format>
+                    <javadoc>
+      <blankLines>
+       <xdoclet>-1</xdoclet>
+      </blankLines>
+                        <blockTagsShort>false</blockTagsShort>
+                        <check>
+                            <description>
+                                <enabled>false</enabled>
+                                <firstSentence>false</firstSentence>
+                                <onlyWhenGenerate>false</onlyWhenGenerate>
+                                <onlyWhenNoSee>false</onlyWhenNoSee>
+                                <onlyWhenParamOrReturn>false</onlyWhenParamOrReturn>
+                                <useReturnDescription>false</useReturnDescription>
+                            </description>
+                            <ignoreRuntimeExceptions>false</ignoreRuntimeExceptions>
+                            <innerclass>false</innerclass>
+                            <keepThrowsTags>false</keepThrowsTags>
+                            <suppressWhenSee>false</suppressWhenSee>
+                            <tags>
+                                <enabled>false</enabled>
+                                <missingDescription>false</missingDescription>
+                                <missingTags>false</missingTags>
+                                <onlyWhenGenerate>false</onlyWhenGenerate>
+                                <onlyWhenParamOrReturn>false</onlyWhenParamOrReturn>
+                                <useDescription>false</useDescription>
+                            </tags>
+                            <throwsTags>false</throwsTags>
+                        </check>
+                        <classShort>false</classShort>
+      <compact>
+       <attribute>false</attribute>
+      </compact>
+                        <enumShort>false</enumShort>
+                        <failOnError>false</failOnError>
+                        <fieldsShort>true</fieldsShort>
+                        <generate>
+                            <bean>true</bean>
+                            <class>0</class>
+                            <constructor>0</constructor>
+                            <enabled>false</enabled>
+                            <excludeOverride>false</excludeOverride>
+                            <exclusions></exclusions>
+                            <field>0</field>
+                            <formatBeanProperty>true</formatBeanProperty>
+                            <method>0</method>
+                            <seeTag>false</seeTag>
+                            <useExisting>false</useExisting>
+                        </generate>
+                        <html>
+                            <indent>false</indent>
+                            <verify>false</verify>
+                        </html>
+                        <methodsShort>false</methodsShort>
+                        <normalizeWhitespace>true</normalizeWhitespace>
+                        <parseComments>false</parseComments>
+                        <removePreformattedStar>true</removePreformattedStar>
+                        <tags>
+                            <align>true</align>
+                            <alignAttributes>false</alignAttributes>
+                            <custom>
+                                <html></html>
+                                <inline></inline>
+                                <standard></standard>
+                                <xdoclet></xdoclet>
+                            </custom>
+                            <group>true</group>
+                            <indent>true</indent>
+                            <innerSpacing>2</innerSpacing>
+                            <methodTypeParams>false</methodTypeParams>
+                            <order>@param^param|@return^return|@throws^throws|@author^common|@version^common|@see^common|@since^common|@serial^common|@serialData^common|@serialField^common|@deprecated^common|@custom^common|@beaninfo^none|@@attribute^none|@invalid^common|@todo^common|@xdoclet^none|@jalopy.group^none|@call-tree^none</order>
+                            <removeMisused>false</removeMisused>
+                            <sort>false</sort>
+                            <sortAttributes>false</sortAttributes>
+       <sortXDoclet>false</sortXDoclet>
+                            <spelling>false</spelling>
+                        </tags>
+                        <templates>
+                            <class><![CDATA[/**
+ * DOCUMENT ME!
+ *
+                            */]]></class>
+                            <constructor>
+                                <bottom><![CDATA[ */]]></bottom>
+                                <exception><![CDATA[ * @throws $exception.type$ DOCUMENT ME!]]></exception>
+                                <param><![CDATA[ * @param $param.name$ DOCUMENT ME!]]></param>
+                                <top><![CDATA[/**
+                                * Creates a new $class.name$ object.]]></top>
+                            </constructor>
+                            <enum><![CDATA[/**
+ * DOCUMENT ME!
+ *
+                            */]]></enum>
+                            <getter>
+                                <bottom><![CDATA[ */]]></bottom>
+                                <exception><![CDATA[ * @throws $exception.type$ DOCUMENT ME!]]></exception>
+                                <param><![CDATA[ * @param $param.name$ DOCUMENT ME!]]></param>
+                                <return><![CDATA[ * @return DOCUMENT ME!]]></return>
+                                <top><![CDATA[/**
+                                * DOCUMENT ME!]]></top>
+                            </getter>
+                            <interface><![CDATA[/**
+ * DOCUMENT ME!
+ *
+                            */]]></interface>
+                            <method>
+                                <bottom><![CDATA[ */]]></bottom>
+                                <exception><![CDATA[ * @throws $exception.type$ DOCUMENT ME!]]></exception>
+                                <param><![CDATA[ * @param $param.name$ DOCUMENT ME!]]></param>
+                                <return><![CDATA[ * @return DOCUMENT ME!]]></return>
+                                <top><![CDATA[/**
+                                * DOCUMENT ME!]]></top>
+                            </method>
+                            <setter>
+                                <bottom><![CDATA[ */]]></bottom>
+                                <exception><![CDATA[ * @throws $exception.type$ DOCUMENT ME!]]></exception>
+                                <param><![CDATA[ * @param $param.name$ DOCUMENT ME!]]></param>
+                                <top><![CDATA[/**
+                                * DOCUMENT ME!]]></top>
+                            </setter>
+                            <variable>/** DOCUMENT ME! */</variable>
+                        </templates>
+                        <transform>false</transform>
+                        <wrap>
+                            <after>
+                                <class>false</class>
+                                <delim>
+                                    <class>true</class>
+                                    <ctor>true</ctor>
+                                    <field>false</field>
+                                    <interface>true</interface>
+                                    <method>true</method>
+                                </delim>
+                                <enumConstant>true</enumConstant>
+                                <throws>false</throws>
+                            </after>
+                            <inline>true</inline>
+                            <keepNewline>false</keepNewline>
+                            <lineLength>80</lineLength>
+                        </wrap>
+                    </javadoc>
+                    <keepFirstColumnAsIs>false</keepFirstColumnAsIs>
+                    <moveAfterBrace>-1</moveAfterBrace>
+                    <reflow>
+                        <multiLine>false</multiLine>
+                        <singeLine>false</singeLine>
+                    </reflow>
+                    <remove>
+                        <javadoc>
+                            <all>true</all>
+                            <enable>false</enable>
+                            <pattern></pattern>
+                        </javadoc>
+                        <multiLine>
+                            <all>true</all>
+                            <enable>false</enable>
+                            <pattern></pattern>
+                        </multiLine>
+                        <singleLine>
+                            <all>true</all>
+                            <enable>false</enable>
+                            <pattern></pattern>
+                        </singleLine>
+                    </remove>
+                    <separator>
+                        <fillCharacter>-</fillCharacter>
+                        <insert>false</insert>
+                        <insertAccess>false</insertAccess>
+                        <insertMethodRecursive>false</insertMethodRecursive>
+                        <insertMethods>false</insertMethods>
+                        <insertRecursive>false</insertRecursive>
+                        <length>80</length>
+                        <methodTemplate>//~ ${fill.character}*</methodTemplate>
+                        <template>//~ ${description} ${fill.character}*</template>
+                        <text>
+                            <annotation>Annotations</annotation>
+                            <class>Inner Classes</class>
+                            <constructor>Constructors</constructor>
+                            <enum>Enums</enum>
+                            <enumConstant>Enum constants</enumConstant>
+                            <field>Instance fields</field>
+                            <initializer>Instance initializers</initializer>
+                            <interface>Inner Interfaces</interface>
+                            <method>Methods</method>
+                            <ordinary>Ordinary methods</ordinary>
+                            <static>Static fields/initializers</static>
+                        </text>
+                    </separator>
+                    <wrap>
+                        <lineLength>140</lineLength>
+                        <minSpace>20</minSpace>
+                        <multiLine>false</multiLine>
+                        <singleLine>false</singleLine>
+                    </wrap>
+                </comments>
+                <environment></environment>
+                <footer>
+                    <keepTags>false</keepTags>
+                    <keys></keys>
+                    <override>true</override>
+                    <smartMode>0</smartMode>
+                    <text></text>
+                    <use>false</use>
+                </footer>
+                <header>
+                    <keepTags>false</keepTags>
+                    <keys></keys>
+                    <override>true</override>
+                    <smartMode>0</smartMode>
+                    <text></text>
+                    <use>false</use>
+                </header>
+                <imports>
+                    <classRepositoryDirectory>repository</classRepositoryDirectory>
+                    <grouping>
+                        <defaultDepth>3</defaultDepth>
+                        <packages>*:0|gnu:2|java:2|javax:2</packages>
+                        <static>none</static>
+                    </grouping>
+                    <policy>expand</policy>
+                    <sort>true</sort>
+                    <useCustomImplementation>false</useCustomImplementation>
+                </imports>
+                <indentation>
+                    <align>
+                        <anonInnerClass>true</anonInnerClass>
+                        <arrayInitializers>false</arrayInitializers>
+                        <assert>false</assert>
+                        <endlineComment>false</endlineComment>
+                        <enumConstant>false</enumConstant>
+                        <parenthesesRight>false</parenthesesRight>
+                    </align>
+                    <arrayInit>standard</arrayInit>
+                    <blockBody>true</blockBody>
+                    <callArgs>standard</callArgs>
+                    <caseBody>true</caseBody>
+                    <caseFromSwitch>true</caseFromSwitch>
+                    <classBody>true</classBody>
+                    <continuation>
+                        <block>true</block>
+                        <operator>true</operator>
+                        <parameter>false</parameter>
+                        <return>true</return>
+                    </continuation>
+                    <declParams>standard</declParams>
+                    <dottedExpr>false</dottedExpr>
+                    <firstColumnComments>false</firstColumnComments>
+                    <general>standard</general>
+                    <increaseHotSpots>true</increaseHotSpots>
+                    <label>false</label>
+                    <methodBody>true</methodBody>
+                    <sizes>
+                        <bigGapIndent>8</bigGapIndent>
+                        <braceCuddled>1</braceCuddled>
+                        <braceLeft>0</braceLeft>
+                        <braceRight>0</braceRight>
+                        <braceRightAfter>0</braceRightAfter>
+                        <continuation>2</continuation>
+                        <deep>55</deep>
+                        <extends>-1</extends>
+                        <general>4</general>
+                        <implements>-1</implements>
+                        <leading>0</leading>
+                        <tabs>8</tabs>
+                        <throws>-1</throws>
+                        <trailingComment>2</trailingComment>
+                    </sizes>
+                    <tabs>
+                        <comments>false</comments>
+                        <enable>false</enable>
+                        <onlyLeading>false</onlyLeading>
+                    </tabs>
+                    <ternaryOperand>false</ternaryOperand>
+                </indentation>
+                <misc>
+                    <arrayBracketsAfterIdent>false</arrayBracketsAfterIdent>
+                    <booleanGetter>^(is)[A-Z]\w+</booleanGetter>
+                    <callTree>false</callTree>
+                    <insertExpressionParentheses>true</insertExpressionParentheses>
+                    <insertLoggingConditional>false</insertLoggingConditional>
+                    <insertOverride>false</insertOverride>
+                    <insertParen>
+                        <return>false</return>
+                        <throw>false</throw>
+                    </insertParen>
+                    <insertTrailingNewline>true</insertTrailingNewline>
+                    <insertUID>false</insertUID>
+                    <javadocOnly>false</javadocOnly>
+                    <keepSameLine>
+                        <else>false</else>
+                        <elseIf>false</elseIf>
+                        <if>false</if>
+                        <singleIf>false</singleIf>
+                        <throwAndReturn>false</throwAndReturn>
+                    </keepSameLine>
+                    <removeRedundantModifier>true</removeRedundantModifier>
+                    <requireBeanPropertyField>false</requireBeanPropertyField>
+                    <splitMultiVariables>false</splitMultiVariables>
+                </misc>
+                <parser>
+                    <stripQualification>false</stripQualification>
+                </parser>
+                <searchAndReplace>
+                    <javadoc>false</javadoc>
+                    <multiLine>false</multiLine>
+                    <patterns></patterns>
+                    <singleLine>false</singleLine>
+                    <stringLiteral>false</stringLiteral>
+                </searchAndReplace>
+                <sorting>
+                    <declaration>
+                        <bean>
+                            <enable>true</enable>
+                            <keepTogether>true</keepTogether>
+                            <order>isFoo|getFoo|setFoo|foo</order>
+                        </bean>
+                        <class>
+                            <enable>true</enable>
+                            <level>true</level>
+                            <name>true</name>
+                            <order>level|name</order>
+                        </class>
+                        <constructor>
+                            <copy>false</copy>
+                            <count>true</count>
+                            <default>false</default>
+                            <enable>true</enable>
+                            <level>true</level>
+                            <order>level|count|default|copy</order>
+                        </constructor>
+                        <enable>true</enable>
+                        <enum>
+                            <enable>true</enable>
+                            <level>true</level>
+                            <name>true</name>
+                            <order>level|name</order>
+                        </enum>
+                        <enumConstant>
+                            <count>true</count>
+                            <enable>false</enable>
+                            <name>true</name>
+                            <order>name|count</order>
+                        </enumConstant>
+                        <field>
+                            <enable>true</enable>
+                            <level>true</level>
+                            <name>true</name>
+                            <order>level|name|type</order>
+                            <type>true</type>
+                        </field>
+                        <interface>
+                            <enable>true</enable>
+                            <level>true</level>
+                            <name>true</name>
+                            <order>level|name</order>
+                        </interface>
+                        <method>
+                            <bean>true</bean>
+                            <count>true</count>
+                            <custom>true</custom>
+                            <enable>true</enable>
+                            <level>true</level>
+                            <name>true</name>
+                            <order>level|bean|regex|name|count|custom</order>
+                            <regex>
+                                <enable>true</enable>
+                                <order><![CDATA[begin\w+
+end\w+
+test\w+
+                                js.et_\w+]]></order>
+                            </regex>
+                        </method>
+                        <modifier>
+                            <abstract>true</abstract>
+                            <final>false</final>
+                            <friendly>true</friendly>
+                            <native>false</native>
+                            <order>public|protected|friendly|private|abstract|static|final|native</order>
+                            <private>true</private>
+                            <protected>true</protected>
+                            <public>true</public>
+                            <static>true</static>
+                        </modifier>
+                        <order>static|enum|enumConstant|field|initializer|constructor|method|interface|class|annotation</order>
+                    </declaration>
+                    <modifier>
+                        <annotation>false</annotation>
+                        <enable>false</enable>
+                        <order>annotation|public|protected|private|abstract|static|final|synchronized|transient|volatile|native|strictfp</order>
+                    </modifier>
+                </sorting>
+                <whitespace>
+                    <after>
+                        <ampersand>
+                            <typeParam>true</typeParam>
+                        </ampersand>
+                        <colon>
+                            <assert>true</assert>
+                            <conditional>true</conditional>
+                            <for>true</for>
+                            <label>true</label>
+                        </colon>
+                        <comma>
+                            <annotationArrayInit>true</annotationArrayInit>
+                            <annotationMember>true</annotationMember>
+                            <arrayInit>true</arrayInit>
+                            <creatorCall>true</creatorCall>
+                            <ctorCall>true</ctorCall>
+                            <ctorDecl>true</ctorDecl>
+                            <enumConstant>true</enumConstant>
+                            <enumConstantArgument>true</enumConstantArgument>
+                            <extends>true</extends>
+                            <forInit>true</forInit>
+                            <forUpdate>true</forUpdate>
+                            <implements>true</implements>
+                            <methodCall>true</methodCall>
+                            <methodDecl>true</methodDecl>
+                            <multiField>true</multiField>
+                            <multiVar>true</multiVar>
+                            <throwsCtor>true</throwsCtor>
+                            <throwsMethod>true</throwsMethod>
+                            <typeArgument>true</typeArgument>
+                            <typeParam>true</typeParam>
+                            <typeRef>true</typeRef>
+                        </comma>
+                        <ellipsis>true</ellipsis>
+                        <lbrace>
+                            <annotation>true</annotation>
+                            <arrayInit>true</arrayInit>
+                            <compactedBraces>true</compactedBraces>
+                        </lbrace>
+                        <lbracket>
+                            <arrayAccess>false</arrayAccess>
+                            <arrayCreator>false</arrayCreator>
+                            <typeArgument>false</typeArgument>
+                            <typeParam>false</typeParam>
+                            <typeReference>false</typeReference>
+                        </lbracket>
+                        <lparen>
+                            <annotationMember>false</annotationMember>
+                            <catch>false</catch>
+                            <creatorCall>false</creatorCall>
+                            <ctorCall>false</ctorCall>
+                            <ctorDecl>false</ctorDecl>
+                            <enumConstant>false</enumConstant>
+                            <expr>false</expr>
+                            <for>false</for>
+                            <if>false</if>
+                            <methodCall>false</methodCall>
+                            <methodDecl>false</methodDecl>
+                            <return>false</return>
+                            <switch>false</switch>
+                            <synchronized>false</synchronized>
+                            <throw>false</throw>
+                            <typeCast>false</typeCast>
+                            <while>false</while>
+                        </lparen>
+                        <operator>
+                            <assign>
+                                true
+                                <annotation>true</annotation>
+                            </assign>
+                            <bitwise>true</bitwise>
+                            <logical>true</logical>
+                            <math>true</math>
+                            <prefix>false</prefix>
+                            <relational>true</relational>
+                            <shift>true</shift>
+                            <stringConcat>true</stringConcat>
+                            <unary>false</unary>
+                        </operator>
+                        <question>
+                            <conditional>true</conditional>
+                            <typeArgument>false</typeArgument>
+                            <typeParam>false</typeParam>
+                        </question>
+                        <rbracket>
+                            <typeArgument>false</typeArgument>
+                            <typeParam>false</typeParam>
+                        </rbracket>
+                        <semicolon>1</semicolon>
+                        <typeCast>true</typeCast>
+                    </after>
+                    <before>
+                        <ampersand>
+                            <typeParam>true</typeParam>
+                        </ampersand>
+                        <bracketsTypes>false</bracketsTypes>
+                        <colon>
+                            <assert>true</assert>
+                            <case>false</case>
+                            <conditional>true</conditional>
+                            <for>true</for>
+                            <label>false</label>
+                        </colon>
+                        <comma>
+                            <annotationArrayInit>false</annotationArrayInit>
+                            <annotationMember>false</annotationMember>
+                            <arrayInit>false</arrayInit>
+                            <creatorCall>false</creatorCall>
+                            <ctorCall>false</ctorCall>
+                            <ctorDecl>false</ctorDecl>
+                            <enumConstant>false</enumConstant>
+                            <enumConstantArgument>false</enumConstantArgument>
+                            <extends>false</extends>
+                            <forInit>false</forInit>
+                            <forUpdate>false</forUpdate>
+                            <implements>false</implements>
+                            <methodCall>false</methodCall>
+                            <methodDecl>false</methodDecl>
+                            <multiField>false</multiField>
+                            <multiVar>false</multiVar>
+                            <throwsCtor>false</throwsCtor>
+                            <throwsMethod>false</throwsMethod>
+                            <typeArgument>false</typeArgument>
+                            <typeParam>false</typeParam>
+                            <typeRef>false</typeRef>
+                        </comma>
+                        <ellipsis>false</ellipsis>
+                        <lbrace>
+                            <arrayInit>true</arrayInit>
+                            <compactedBraces>true</compactedBraces>
+                        </lbrace>
+                        <lbracket>
+                            <arrayAccess>false</arrayAccess>
+                            <arrayCreator>false</arrayCreator>
+                            <arrayDeclaration>false</arrayDeclaration>
+                            <typeArgument>false</typeArgument>
+                            <typeParam>false</typeParam>
+                            <typeReference>false</typeReference>
+                        </lbracket>
+                        <lparen>
+                            <return>true</return>
+                            <throw>true</throw>
+                        </lparen>
+                        <operator>
+                            <assign>
+                                true
+                                <annotation>true</annotation>
+                            </assign>
+                            <bitwise>true</bitwise>
+                            <logical>true</logical>
+                            <math>true</math>
+                            <not>false</not>
+                            <postfix>false</postfix>
+                            <relational>true</relational>
+                            <shift>true</shift>
+                            <stringConcat>true</stringConcat>
+                        </operator>
+                        <parentheses>
+                            <annotationMember>false</annotationMember>
+                            <annotationTypeMember>false</annotationTypeMember>
+                            <catch>true</catch>
+                            <creator>false</creator>
+                            <ctorCall>false</ctorCall>
+                            <ctorDecl>false</ctorDecl>
+                            <enumConstant>false</enumConstant>
+                            <for>true</for>
+                            <if>true</if>
+                            <methodCall>false</methodCall>
+                            <methodDeclaration>false</methodDeclaration>
+                            <switch>true</switch>
+                            <synchronized>true</synchronized>
+                            <while>true</while>
+                        </parentheses>
+                        <question>
+                            <conditional>true</conditional>
+                            <typeArgument>false</typeArgument>
+                            <typeParam>false</typeParam>
+                        </question>
+                        <rbrace>
+                            <annotation>true</annotation>
+                            <arrayInit>true</arrayInit>
+                            <compactedBraces>true</compactedBraces>
+                        </rbrace>
+                        <rbracket>
+                            <arrayAccess>false</arrayAccess>
+                            <arrayCreator>false</arrayCreator>
+                            <typeArgument>false</typeArgument>
+                            <typeParam>false</typeParam>
+                            <typeReference>false</typeReference>
+                        </rbracket>
+                        <rparen>
+                            <annotationMember>false</annotationMember>
+                            <catch>false</catch>
+                            <creatorCall>false</creatorCall>
+                            <ctorCall>false</ctorCall>
+                            <ctorDecl>false</ctorDecl>
+                            <enumConstant>false</enumConstant>
+                            <expr>false</expr>
+                            <for>false</for>
+                            <if>false</if>
+                            <methodCall>false</methodCall>
+                            <methodDecl>false</methodDecl>
+                            <return>false</return>
+                            <switch>false</switch>
+                            <synchronized>false</synchronized>
+                            <throw>false</throw>
+                            <typeCast>false</typeCast>
+                            <while>false</while>
+                        </rparen>
+                        <semi>
+                            <for>false</for>
+                        </semi>
+                    </before>
+                    <compact>
+                        <brackets>false</brackets>
+                        <parentheses>false</parentheses>
+                    </compact>
+                    <emptyBraces>
+                        <arrayInit>false</arrayInit>
+                        <decl>true</decl>
+                    </emptyBraces>
+                    <emptyBracket>
+                        <arrayCreator>false</arrayCreator>
+                        <arrayDeclaration>false</arrayDeclaration>
+                    </emptyBracket>
+                    <emptyParen>
+                        <annotationTypeMember>false</annotationTypeMember>
+                        <creatorCall>false</creatorCall>
+                        <ctorCall>false</ctorCall>
+                        <ctorDecl>false</ctorDecl>
+                        <emptyEnumParen>false</emptyEnumParen>
+                        <methodCall>false</methodCall>
+                        <methodDecl>false</methodDecl>
+                    </emptyParen>
+                </whitespace>
+                <wrapping>
+                    <allWhenFirst>
+                        <operator>false</operator>
+                    </allWhenFirst>
+                    <always>
+                        <after>
+                            <annotationMember>true</annotationMember>
+                            <arrayElement>0</arrayElement>
+                            <assignRegistry>false</assignRegistry>
+                            <braceRight>true</braceRight>
+                            <conditional>false</conditional>
+                            <extendsTypes>false</extendsTypes>
+                            <implementsTypes>false</implementsTypes>
+                            <label>true</label>
+                            <markerAnnotation>false</markerAnnotation>
+                            <methodCallChained>false</methodCallChained>
+                            <multiDeclarator>false</multiDeclarator>
+                            <multiType>false</multiType>
+                            <nestedMethodCallChained>false</nestedMethodCallChained>
+                            <ternaryOperator>
+                                <first>false</first>
+                                <second>false</second>
+                            </ternaryOperator>
+                            <throwsTypes>false</throwsTypes>
+                        </after>
+                        <before>
+                            <braceLeft>true</braceLeft>
+                            <declaration>false</declaration>
+                            <extends>false</extends>
+                            <fieldName>false</fieldName>
+                            <implements>false</implements>
+                            <methodName>false</methodName>
+                            <throws>false</throws>
+                        </before>
+                        <parameter>
+                            <methodCall>false</methodCall>
+                            <methodCallNested>false</methodCallNested>
+                            <methodDeclaration>false</methodDeclaration>
+                        </parameter>
+                    </always>
+                    <general>
+                        <avoidBareLeftParen>true</avoidBareLeftParen>
+                        <beforeOperator>true</beforeOperator>
+                        <concatOnlyWhenBothLiteral>false</concatOnlyWhenBothLiteral>
+                        <disable>
+                            <complexExpr>false</complexExpr>
+                        </disable>
+                        <enable>true</enable>
+                        <keepLinebreak>false</keepLinebreak>
+                        <keepLinebreakArray>false</keepLinebreakArray>
+                        <keepLinebreakCallArgs>false</keepLinebreakCallArgs>
+                        <keepLinebreakConcat>false</keepLinebreakConcat>
+                        <keepLinebreakDeclParams>false</keepLinebreakDeclParams>
+                        <lineLength>140</lineLength>
+                        <strictlyObeyKeepLinebreak>false</strictlyObeyKeepLinebreak>
+                    </general>
+                    <never>
+                        <chainedIndexOperator>false</chainedIndexOperator>
+                        <chainedMethodCall>false</chainedMethodCall>
+                        <expression>true</expression>
+                        <qualifier>false</qualifier>
+                    </never>
+                    <ondemand>
+                        <after>
+                            <argument>false</argument>
+                            <assignment>false</assignment>
+                            <leftParenthesis>false</leftParenthesis>
+                            <markerAnnotation>true</markerAnnotation>
+                            <multivar>
+                                <declarator>false</declarator>
+                                <type>false</type>
+                            </multivar>
+                            <param>false</param>
+                            <parameter>false</parameter>
+                            <return>false</return>
+                            <types>
+                                <extends>false</extends>
+                                <implements>false</implements>
+                                <throws>false</throws>
+                            </types>
+                        </after>
+                        <argsLower>false</argsLower>
+                        <before>
+                            <extends>false</extends>
+                            <fieldName>false</fieldName>
+                            <implements>false</implements>
+                            <methodName>false</methodName>
+                            <rightParenthesis>false</rightParenthesis>
+                            <ternaryColon>false</ternaryColon>
+                            <throws>false</throws>
+                        </before>
+                        <groupingParentheses>false</groupingParentheses>
+                        <imports>false</imports>
+                        <indexOperator>true</indexOperator>
+                        <operator>false</operator>
+                        <stringLiteral>false</stringLiteral>
+                        <typeParameter>false</typeParameter>
+                    </ondemand>
+                    <preferLowerLevel>
+                        <methodCallAssignments>false</methodCallAssignments>
+                    </preferLowerLevel>
+                </wrapping>
+            </printer>
+        </java>
+        <shared>
+            <backup>
+                <directory>bak</directory>
+                <level>1</level>
+            </backup>
+            <convention>
+                <description>[ini4j] Java Coding Convention</description>
+                <local></local>
+                <location></location>
+                <name>ini4j</name>
+                <synchronize>false</synchronize>
+            </convention>
+            <exclusions></exclusions>
+            <fileTypes><![CDATA[Java
+*.java
+*.sqlj
+---
+            ]]></fileTypes>
+            <history>
+                <location></location>
+                <method>none</method>
+            </history>
+            <locale>
+                <country>US</country>
+                <datePattern>yyyy-MM-dd</datePattern>
+                <language>en</language>
+                <timePattern>hh:mm</timePattern>
+            </locale>
+            <misc>
+    <checkout>false</checkout>
+    <encoding>UTF-8</encoding>
+    <forceEncoding>false</forceEncoding>
+                <forceFormatting>false</forceFormatting>
+                <forceLineSeparator>false</forceLineSeparator>
+                <formatOnCheckin>false</formatOnCheckin>
+                <formatOnGeneration>true</formatOnGeneration>
+                <formatOnLoad>false</formatOnLoad>
+                <formatOnSave>true</formatOnSave>
+                <keepEditorState>false</keepEditorState>
+                <lineSeparator>AUTO</lineSeparator>
+                <testFormatting>false</testFormatting>
+                <threadCount>1</threadCount>
+                <useEditorPreview>false</useEditorPreview>
+            </misc>
+            <project>
+                <autoSwitch>true</autoSwitch>
+            </project>
+            <repository>
+                <failOnError>false</failOnError>
+                <warnOnError>true</warnOnError>
+            </repository>
+        </shared>
+    </profile>
+</jalopy>
+
diff --git a/src/conf/pmd.xml b/src/conf/pmd.xml
new file mode 100644
index 0000000..29997f2
--- /dev/null
+++ b/src/conf/pmd.xml
@@ -0,0 +1,298 @@
+<?xml version="1.0"?>
+<!--
+
+    Copyright 2005,2009 Ivan SZKIBA
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<ruleset name="Custom ruleset"
+         xmlns="http://pmd.sf.net/ruleset/1.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
+         xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
+    
+    <description>
+        Coding checks for [ini4j]
+    </description>
+
+
+<!--    <rule ref="rulesets/design.xml/AbstractClassWithoutAbstractMethod"/> -->
+    <rule ref="rulesets/design.xml/AbstractClassWithoutAnyMethod"/>
+    <rule ref="rulesets/naming.xml/AbstractNaming"/>
+<!--    <rule ref="rulesets/design.xml/AccessorClassGeneration"/> -->
+    <rule ref="rulesets/optimizations.xml/AddEmptyString"/>
+    <rule ref="rulesets/strings.xml/AppendCharacterWithChar"/>
+    <rule ref="rulesets/sunsecure.xml/ArrayIsStoredDirectly"/>
+    <rule ref="rulesets/controversial.xml/AssignmentInOperand"/>
+    <rule ref="rulesets/design.xml/AssignmentToNonFinalStatic"/>
+    <rule ref="rulesets/controversial.xml/AtLeastOneConstructor"/>
+    <rule ref="rulesets/controversial.xml/AvoidAccessibilityAlteration"/>
+    <rule ref="rulesets/optimizations.xml/AvoidArrayLoops"/>
+    <rule ref="rulesets/migrating.xml/AvoidAssertAsIdentifier"/>
+    <rule ref="rulesets/finalizers.xml/AvoidCallingFinalize"/>
+    <rule ref="rulesets/strictexception.xml/AvoidCatchingNPE"/>
+    <rule ref="rulesets/strictexception.xml/AvoidCatchingThrowable"/>
+    <rule ref="rulesets/design.xml/AvoidConstantsInterface"/>
+    <rule ref="rulesets/basic.xml/AvoidDecimalLiteralsInBigDecimalConstructor"/>
+    <rule ref="rulesets/design.xml/AvoidDeeplyNestedIfStmts">
+        <properties>
+            <property name="problemDepth" value="4"/>
+        </properties>
+    </rule>
+    <rule ref="rulesets/naming.xml/AvoidDollarSigns"/>
+    <rule ref="rulesets/strings.xml/AvoidDuplicateLiterals"/>
+    <rule ref="rulesets/migrating.xml/AvoidEnumAsIdentifier"/>
+    <rule ref="rulesets/naming.xml/AvoidFieldNameMatchingMethodName"/>
+    <rule ref="rulesets/naming.xml/AvoidFieldNameMatchingTypeName"/>
+    <rule ref="rulesets/controversial.xml/AvoidFinalLocalVariable"/>
+    <rule ref="rulesets/design.xml/AvoidInstanceofChecksInCatchClause"/>
+<!-- some case it is really needed  <rule ref="rulesets/optimizations.xml/AvoidInstantiatingObjectsInLoops"/> -->
+    <rule ref="rulesets/basic.xml/AvoidMultipleUnaryOperators"/>
+    <rule ref="rulesets/logging-java.xml/AvoidPrintStackTrace"/>
+    <rule ref="rulesets/design.xml/AvoidProtectedFieldInFinalClass"/>
+    <rule ref="rulesets/design.xml/AvoidReassigningParameters"/>
+    <rule ref="rulesets/strictexception.xml/AvoidRethrowingException"/>
+    <rule ref="rulesets/strings.xml/AvoidStringBufferField"/>
+<!--    <rule ref="rulesets/design.xml/AvoidSynchronizedAtMethodLevel"/> -->
+    <rule ref="rulesets/basic.xml/AvoidThreadGroup"/>
+    <rule ref="rulesets/strictexception.xml/AvoidThrowingNewInstanceOfSameException"/>
+    <rule ref="rulesets/strictexception.xml/AvoidThrowingNullPointerException"/>
+    <rule ref="rulesets/strictexception.xml/AvoidThrowingRawExceptionTypes"/>
+    <rule ref="rulesets/basic.xml/AvoidUsingHardCodedIP"/>
+    <rule ref="rulesets/controversial.xml/AvoidUsingNativeCode"/>
+    <rule ref="rulesets/basic.xml/AvoidUsingOctalValues"/>
+<!--    <rule ref="rulesets/controversial.xml/AvoidUsingShortType"/> -->
+    <rule ref="rulesets/controversial.xml/AvoidUsingVolatile"/>
+    <rule ref="rulesets/design.xml/BadComparison"/>
+<!-- bug for logical properties: isXXX not recognized as accessor  <rule ref="rulesets/javabeans.xml/BeanMembersShouldSerialize"/> -->
+    <rule ref="rulesets/basic.xml/BigIntegerInstantiation"/>
+    <rule ref="rulesets/naming.xml/BooleanGetMethodName"/>
+    <rule ref="rulesets/basic.xml/BooleanInstantiation"/>
+    <rule ref="rulesets/controversial.xml/BooleanInversion"/>
+    <rule ref="rulesets/basic.xml/BrokenNullCheck"/>
+    <rule ref="rulesets/migrating.xml/ByteInstantiation"/>
+    <rule ref="rulesets/android.xml/CallSuperFirst"/>
+<!--    <rule ref="rulesets/controversial.xml/CallSuperInConstructor"/> -->
+    <rule ref="rulesets/android.xml/CallSuperLast"/>
+    <rule ref="rulesets/basic.xml/CheckResultSet"/>
+    <rule ref="rulesets/basic.xml/ClassCastExceptionWithToArray"/>
+    <rule ref="rulesets/naming.xml/ClassNamingConventions"/>
+    <rule ref="rulesets/design.xml/ClassWithOnlyPrivateConstructorsShouldBeFinal"/>
+    <rule ref="rulesets/clone.xml/CloneMethodMustImplementCloneable"/>
+    <rule ref="rulesets/typeresolution.xml/CloneMethodMustImplementCloneable"/>
+<!--    <rule ref="rulesets/clone.xml/CloneThrowsCloneNotSupportedException"/> -->
+    <rule ref="rulesets/design.xml/CloseResource"/>
+    <rule ref="rulesets/basic.xml/CollapsibleIfStatements"/>
+    <rule ref="rulesets/design.xml/CompareObjectsWithEquals"/>
+    <rule ref="rulesets/design.xml/ConfusingTernary"/>
+    <rule ref="rulesets/strings.xml/ConsecutiveLiteralAppends"/>
+<!--    <rule ref="rulesets/design.xml/ConstructorCallsOverridableMethod"/> -->
+    <rule ref="rulesets/coupling.xml/CouplingBetweenObjects"/>
+    <rule ref="rulesets/codesize.xml/CyclomaticComplexity">
+        <properties>
+            <property name="reportLevel" value="11"/>
+        </properties>
+    </rule>
+<!--    <rule ref="rulesets/controversial.xml/DataflowAnomalyAnalysis"/> -->
+    <rule ref="rulesets/design.xml/DefaultLabelNotLastInSwitchStmt"/>
+<!--  used for junit testing  <rule ref="rulesets/controversial.xml/DefaultPackage"/> -->
+    <rule ref="rulesets/controversial.xml/DoNotCallGarbageCollectionExplicitly"/>
+    <rule ref="rulesets/j2ee.xml/DoNotCallSystemExit"/>
+    <rule ref="rulesets/strictexception.xml/DoNotExtendJavaLangError"/>
+    <rule ref="rulesets/strictexception.xml/DoNotThrowExceptionInFinally"/>
+    <rule ref="rulesets/j2ee.xml/DoNotUseThreads"/>
+    <rule ref="rulesets/imports.xml/DontImportJavaLang"/>
+    <rule ref="rulesets/controversial.xml/DontImportSun"/>
+    <rule ref="rulesets/basic-jsf.xml/DontNestJsfInJstlIteration"/>
+    <rule ref="rulesets/basic.xml/DoubleCheckedLocking"/>
+    <rule ref="rulesets/imports.xml/DuplicateImports"/>
+    <rule ref="rulesets/basic.xml/EmptyCatchBlock"/>
+    <rule ref="rulesets/finalizers.xml/EmptyFinalizer"/>
+    <rule ref="rulesets/basic.xml/EmptyFinallyBlock"/>
+    <rule ref="rulesets/basic.xml/EmptyIfStmt"/>
+    <rule ref="rulesets/basic.xml/EmptyInitializer"/>
+    <rule ref="rulesets/design.xml/EmptyMethodInAbstractClassShouldBeAbstract"/>
+    <rule ref="rulesets/basic.xml/EmptyStatementNotInLoop"/>
+    <rule ref="rulesets/basic.xml/EmptyStaticInitializer"/>
+    <rule ref="rulesets/basic.xml/EmptySwitchStatements"/>
+    <rule ref="rulesets/basic.xml/EmptySynchronizedBlock"/>
+    <rule ref="rulesets/basic.xml/EmptyTryBlock"/>
+    <rule ref="rulesets/basic.xml/EmptyWhileStmt"/>
+    <rule ref="rulesets/design.xml/EqualsNull"/>
+    <rule ref="rulesets/strictexception.xml/ExceptionAsFlowControl"/>
+    <rule ref="rulesets/codesize.xml/ExcessiveClassLength"/>
+    <rule ref="rulesets/coupling.xml/ExcessiveImports"/>
+    <rule ref="rulesets/codesize.xml/ExcessiveMethodLength"/>
+    <rule ref="rulesets/codesize.xml/ExcessiveParameterList"/>
+    <rule ref="rulesets/codesize.xml/ExcessivePublicCount"/>
+    <rule ref="rulesets/design.xml/FinalFieldCouldBeStatic"/>
+    <rule ref="rulesets/finalizers.xml/FinalizeDoesNotCallSuperFinalize"/>
+    <rule ref="rulesets/finalizers.xml/FinalizeOnlyCallsSuperFinalize"/>
+    <rule ref="rulesets/finalizers.xml/FinalizeOverloaded"/>
+    <rule ref="rulesets/finalizers.xml/FinalizeShouldBeProtected"/>
+    <rule ref="rulesets/basic.xml/ForLoopShouldBeWhileLoop"/>
+    <rule ref="rulesets/braces.xml/ForLoopsMustUseBraces"/>
+    <rule ref="rulesets/design.xml/IdempotentOperations"/>
+    <rule ref="rulesets/braces.xml/IfElseStmtsMustUseBraces"/>
+    <rule ref="rulesets/braces.xml/IfStmtsMustUseBraces"/>
+    <rule ref="rulesets/design.xml/ImmutableField"/>
+    <rule ref="rulesets/imports.xml/ImportFromSamePackage"/>
+    <rule ref="rulesets/strings.xml/InefficientEmptyStringCheck"/>
+    <rule ref="rulesets/strings.xml/InefficientStringBuffering"/>
+    <rule ref="rulesets/design.xml/InstantiationToGetClass"/>
+    <rule ref="rulesets/strings.xml/InsufficientStringBufferDeclaration"/>
+    <rule ref="rulesets/migrating.xml/IntegerInstantiation"/>
+    <rule ref="rulesets/basic.xml/JumbledIncrementer"/>
+    <rule ref="rulesets/migrating.xml/JUnit4SuitesShouldUseSuiteAnnotation"/>
+    <rule ref="rulesets/migrating.xml/JUnit4TestShouldUseAfterAnnotation"/>
+    <rule ref="rulesets/migrating.xml/JUnit4TestShouldUseBeforeAnnotation"/>
+    <rule ref="rulesets/migrating.xml/JUnit4TestShouldUseTestAnnotation"/>
+    <rule ref="rulesets/junit.xml/JUnitAssertionsShouldIncludeMessage"/>
+    <rule ref="rulesets/junit.xml/JUnitSpelling"/>
+    <rule ref="rulesets/junit.xml/JUnitStaticSuite"/>
+    <rule ref="rulesets/junit.xml/JUnitTestsShouldIncludeAssert"/>
+    <rule ref="rulesets/migrating.xml/JUnitUseExpected"/>
+    <rule ref="rulesets/j2ee.xml/LocalHomeNamingConvention"/>
+    <rule ref="rulesets/j2ee.xml/LocalInterfaceSessionNamingConvention"/>
+<!--    <rule ref="rulesets/optimizations.xml/LocalVariableCouldBeFinal"/> -->
+    <rule ref="rulesets/logging-java.xml/LoggerIsNotStaticFinal"/>
+    <rule ref="rulesets/migrating.xml/LongInstantiation"/>
+    <rule ref="rulesets/naming.xml/LongVariable">
+        <properties>
+            <property name="minimum" value="48"/>
+        </properties>
+    </rule>
+    <rule ref="rulesets/coupling.xml/LooseCoupling"/>
+    <rule ref="rulesets/typeresolution.xml/LooseCoupling"/>
+    <rule ref="rulesets/j2ee.xml/MDBAndSessionBeanNamingConvention"/>
+<!--    <rule ref="rulesets/optimizations.xml/MethodArgumentCouldBeFinal"/> -->
+    <rule ref="rulesets/naming.xml/MethodNamingConventions"/>
+<!--  static final empty array is a problem ? hmmm  <rule ref="rulesets/sunsecure.xml/MethodReturnsInternalArray"/> -->
+    <rule ref="rulesets/naming.xml/MethodWithSameNameAsEnclosingClass"/>
+    <rule ref="rulesets/naming.xml/MisleadingVariableName"/>
+    <rule ref="rulesets/basic.xml/MisplacedNullCheck"/>
+    <rule ref="rulesets/design.xml/MissingBreakInSwitch"/>
+    <rule ref="rulesets/javabeans.xml/MissingSerialVersionUID"/>
+    <rule ref="rulesets/design.xml/MissingStaticMethodInNonInstantiatableClass"/>
+    <rule ref="rulesets/logging-java.xml/MoreThanOneLogger"/>
+    <rule ref="rulesets/codesize.xml/NcssConstructorCount"/>
+    <rule ref="rulesets/codesize.xml/NcssMethodCount"/>
+    <rule ref="rulesets/codesize.xml/NcssTypeCount"/>
+    <rule ref="rulesets/design.xml/NonCaseLabelInSwitchStatement"/>
+    <rule ref="rulesets/design.xml/NonStaticInitializer"/>
+    <rule ref="rulesets/design.xml/NonThreadSafeSingleton"/>
+    <rule ref="rulesets/naming.xml/NoPackage"/>
+    <rule ref="rulesets/codesize.xml/NPathComplexity"/>
+<!--     <rule ref="rulesets/controversial.xml/NullAssignment"/> -->
+    <rule ref="rulesets/controversial.xml/OnlyOneReturn"/>
+    <rule ref="rulesets/design.xml/OptimizableToArrayCall"/>
+    <rule ref="rulesets/basic.xml/OverrideBothEqualsAndHashcode"/>
+    <rule ref="rulesets/naming.xml/PackageCase"/>
+    <rule ref="rulesets/design.xml/PositionLiteralsFirstInComparisons"/>
+    <rule ref="rulesets/design.xml/PreserveStackTrace"/>
+    <rule ref="rulesets/clone.xml/ProperCloneImplementation"/>
+    <rule ref="rulesets/logging-jakarta-commons.xml/ProperLogger"/>
+    <rule ref="rulesets/android.xml/ProtectLogD"/>
+    <rule ref="rulesets/android.xml/ProtectLogV"/>
+    <rule ref="rulesets/j2ee.xml/RemoteInterfaceNamingConvention"/>
+    <rule ref="rulesets/j2ee.xml/RemoteSessionInterfaceNamingConvention"/>
+    <rule ref="rulesets/migrating.xml/ReplaceEnumerationWithIterator"/>
+    <rule ref="rulesets/migrating.xml/ReplaceHashtableWithMap"/>
+    <rule ref="rulesets/migrating.xml/ReplaceVectorWithList"/>
+    <rule ref="rulesets/design.xml/ReturnEmptyArrayRatherThanNull"/>
+    <rule ref="rulesets/basic.xml/ReturnFromFinallyBlock"/>
+    <rule ref="rulesets/migrating.xml/ShortInstantiation"/>
+<!--    <rule ref="rulesets/naming.xml/ShortMethodName"/>
+    <rule ref="rulesets/naming.xml/ShortVariable"/> -->
+    <rule ref="rulesets/strictexception.xml/SignatureDeclareThrowsException"/>
+    <rule ref="rulesets/typeresolution.xml/SignatureDeclareThrowsException"/>
+    <rule ref="rulesets/design.xml/SimpleDateFormatNeedsLocale"/>
+    <rule ref="rulesets/junit.xml/SimplifyBooleanAssertion"/>
+    <rule ref="rulesets/design.xml/SimplifyBooleanExpressions"/>
+    <rule ref="rulesets/design.xml/SimplifyBooleanReturns"/>
+    <rule ref="rulesets/design.xml/SimplifyConditional"/>
+    <rule ref="rulesets/optimizations.xml/SimplifyStartsWith"/>
+    <rule ref="rulesets/design.xml/SingularField"/>
+    <rule ref="rulesets/j2ee.xml/StaticEJBFieldShouldBeFinal"/>
+    <rule ref="rulesets/strings.xml/StringBufferInstantiationWithChar"/>
+    <rule ref="rulesets/strings.xml/StringInstantiation"/>
+    <rule ref="rulesets/strings.xml/StringToString"/>
+    <rule ref="rulesets/naming.xml/SuspiciousConstantFieldName"/>
+    <rule ref="rulesets/naming.xml/SuspiciousEqualsMethodName"/>
+    <rule ref="rulesets/naming.xml/SuspiciousHashcodeMethodName"/>
+    <rule ref="rulesets/controversial.xml/SuspiciousOctalEscape"/>
+    <rule ref="rulesets/design.xml/SwitchDensity"/>
+    <rule ref="rulesets/design.xml/SwitchStmtsShouldHaveDefault"/>
+    <rule ref="rulesets/logging-java.xml/SystemPrintln"/>
+    <rule ref="rulesets/junit.xml/TestClassWithoutTestCases"/>
+    <rule ref="rulesets/design.xml/TooFewBranchesForASwitchStatement"/>
+    <rule ref="rulesets/codesize.xml/TooManyFields">
+        <properties>
+            <property name="maxfields" value="24"/>
+        </properties>
+    </rule>
+    <rule ref="rulesets/codesize.xml/TooManyMethods">
+        <properties>
+            <property name="maxmethods" value="32"/>
+        </properties>
+    </rule>
+    <rule ref="rulesets/imports.xml/TooManyStaticImports"/>
+    <rule ref="rulesets/design.xml/UncommentedEmptyConstructor"/>
+    <rule ref="rulesets/design.xml/UncommentedEmptyMethod"/>
+    <rule ref="rulesets/basic.xml/UnconditionalIfStatement"/>
+    <rule ref="rulesets/junit.xml/UnnecessaryBooleanAssertion"/>
+    <rule ref="rulesets/strings.xml/UnnecessaryCaseChange"/>
+    <rule ref="rulesets/controversial.xml/UnnecessaryConstructor"/>
+    <rule ref="rulesets/basic.xml/UnnecessaryConversionTemporary"/>
+    <rule ref="rulesets/basic.xml/UnnecessaryFinalModifier"/>
+    <rule ref="rulesets/design.xml/UnnecessaryLocalBeforeReturn"/>
+    <rule ref="rulesets/controversial.xml/UnnecessaryParentheses"/>
+    <rule ref="rulesets/basic.xml/UnnecessaryReturn"/>
+    <rule ref="rulesets/optimizations.xml/UnnecessaryWrapperObjectCreation"/>
+    <rule ref="rulesets/design.xml/UnsynchronizedStaticDateFormatter"/>
+    <rule ref="rulesets/unusedcode.xml/UnusedFormalParameter"/>
+    <rule ref="rulesets/imports.xml/UnusedImports"/>
+    <rule ref="rulesets/typeresolution.xml/UnusedImports"/>
+    <rule ref="rulesets/unusedcode.xml/UnusedLocalVariable"/>
+    <rule ref="rulesets/controversial.xml/UnusedModifier"/>
+    <rule ref="rulesets/basic.xml/UnusedNullCheckInEquals"/>
+    <rule ref="rulesets/unusedcode.xml/UnusedPrivateField"/>
+    <rule ref="rulesets/unusedcode.xml/UnusedPrivateMethod"/>
+    <rule ref="rulesets/optimizations.xml/UseArrayListInsteadOfVector"/>
+    <rule ref="rulesets/optimizations.xml/UseArraysAsList"/>
+    <rule ref="rulesets/junit.xml/UseAssertEqualsInsteadOfAssertTrue"/>
+    <rule ref="rulesets/junit.xml/UseAssertNullInsteadOfAssertTrue"/>
+    <rule ref="rulesets/junit.xml/UseAssertSameInsteadOfAssertTrue"/>
+    <rule ref="rulesets/design.xml/UseCollectionIsEmpty"/>
+    <rule ref="rulesets/logging-jakarta-commons.xml/UseCorrectExceptionLogging"/>
+    <rule ref="rulesets/strings.xml/UseEqualsToCompareStrings"/>
+    <rule ref="rulesets/strings.xml/UseIndexOfChar"/>
+    <rule ref="rulesets/basic.xml/UselessOperationOnImmutable"/>
+    <rule ref="rulesets/basic.xml/UselessOverridingMethod"/>
+    <rule ref="rulesets/strings.xml/UselessStringValueOf"/>
+    <rule ref="rulesets/design.xml/UseLocaleWithCaseConversions"/>
+    <rule ref="rulesets/design.xml/UseNotifyAllInsteadOfNotify"/>
+    <rule ref="rulesets/j2ee.xml/UseProperClassLoader"/>
+    <rule ref="rulesets/design.xml/UseSingleton"/>
+    <rule ref="rulesets/optimizations.xml/UseStringBufferForStringAppends"/>
+    <rule ref="rulesets/strings.xml/UseStringBufferLength"/>
+    <rule ref="rulesets/naming.xml/VariableNamingConventions">
+        <properties>
+            <property name="memberPrefix" value="_"/>
+            <property name="staticPrefix" value="_"/>
+        </properties>
+    </rule>
+    <rule ref="rulesets/braces.xml/WhileLoopsMustUseBraces"/>
+
+</ruleset>
diff --git a/src/org/ini4j/addon/package.html b/src/main/assembly/all.xml
similarity index 54%
copy from src/org/ini4j/addon/package.html
copy to src/main/assembly/all.xml
index 2108c61..0f9cf0f 100644
--- a/src/org/ini4j/addon/package.html
+++ b/src/main/assembly/all.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0"?>
 <!--
 
     Copyright 2005,2009 Ivan SZKIBA
@@ -16,15 +15,25 @@
     limitations under the License.
 
 -->
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-  <head>
-    <title>[ini4j] addon package</title>
-  </head>
-  <body>
-  <h1>[ini4j] addon package</h1>
-  <p>This package contains optional [ini4j] classes.
-  </p>
-  </body>
-</html>
+<assembly>
+  <id>all</id>
+  <formats>
+    <format>zip</format>
+  </formats>
+  <fileSets>
+    <fileSet>
+      <includes>
+        <include>README*</include>
+        <include>LICENSE*</include>
+        <include>NOTICE*</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>${project.build.directory}</directory>
+      <outputDirectory></outputDirectory>
+      <includes>
+        <include>${project.artifactId}-*.jar</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+</assembly>
diff --git a/src/org/ini4j/package.html b/src/main/assembly/bin.xml
similarity index 50%
copy from src/org/ini4j/package.html
copy to src/main/assembly/bin.xml
index 0b42f7f..01060c0 100644
--- a/src/org/ini4j/package.html
+++ b/src/main/assembly/bin.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0"?>
 <!--
 
     Copyright 2005,2009 Ivan SZKIBA
@@ -16,17 +15,26 @@
     limitations under the License.
 
 -->
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-  <head>
-    <title>[ini4j] main package</title>
-  </head>
-  <body>
-  <h1>[ini4j] main package</h1>
-  <p>This package contains [ini4j] API. This is a very small library, so
-    no interface and implementation separation neccessary. This is why [ini4j]
-    has only one package.
-  </p>
-  </body>
-</html>
+<assembly>
+  <id>bin</id>
+  <formats>
+    <format>zip</format>
+  </formats>
+  <fileSets>
+    <fileSet>
+      <includes>
+        <include>README*</include>
+        <include>LICENSE*</include>
+        <include>NOTICE*</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>${project.build.directory}</directory>
+      <outputDirectory></outputDirectory>
+      <includes>
+        <include>${project.artifactId}-${project.version}.jar</include>
+        <include>${project.artifactId}-${project.version}-jdk14.jar</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+</assembly>
diff --git a/src/main/assembly/demo.xml b/src/main/assembly/demo.xml
new file mode 100644
index 0000000..c918088
--- /dev/null
+++ b/src/main/assembly/demo.xml
@@ -0,0 +1,52 @@
+<!--
+
+    Copyright 2005,2009 Ivan SZKIBA
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<assembly>
+    <id>demo</id>
+    <formats>
+        <format>jar</format>
+    </formats>
+    <includeBaseDirectory>false</includeBaseDirectory>
+    <fileSets>
+        <fileSet>
+            <includes>
+                <include>README*</include>
+                <include>LICENSE*</include>
+                <include>NOTICE*</include>
+            </includes>
+            <useDefaultExcludes>true</useDefaultExcludes>
+        </fileSet>
+        <fileSet>
+            <directory>target/test-classes</directory>
+            <outputDirectory></outputDirectory>
+            <includes>
+                <include>bsh/**</include>
+                <include>org/ini4j/demo/*</include>
+                <include>org/ini4j/sample/Dwarf*</include>
+            </includes>
+            <useDefaultExcludes>true</useDefaultExcludes>
+        </fileSet>
+        <fileSet>
+            <directory>target/classes</directory>
+            <outputDirectory></outputDirectory>
+            <includes>
+                <include>org/ini4j/**</include>
+            </includes>
+            <useDefaultExcludes>true</useDefaultExcludes>
+        </fileSet>
+    </fileSets>
+</assembly>
diff --git a/src/org/ini4j/addon/package.html b/src/main/assembly/src.xml
similarity index 55%
copy from src/org/ini4j/addon/package.html
copy to src/main/assembly/src.xml
index 2108c61..12b3619 100644
--- a/src/org/ini4j/addon/package.html
+++ b/src/main/assembly/src.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0"?>
 <!--
 
     Copyright 2005,2009 Ivan SZKIBA
@@ -16,15 +15,24 @@
     limitations under the License.
 
 -->
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-  <head>
-    <title>[ini4j] addon package</title>
-  </head>
-  <body>
-  <h1>[ini4j] addon package</h1>
-  <p>This package contains optional [ini4j] classes.
-  </p>
-  </body>
-</html>
+<assembly>
+  <id>src</id>
+  <formats>
+    <format>zip</format>
+  </formats>
+  <fileSets>
+    <fileSet>
+      <includes>
+        <include>README*</include>
+        <include>LICENSE*</include>
+        <include>NOTICE*</include>
+        <include>pom.xml</include>
+      </includes>
+      <useDefaultExcludes>true</useDefaultExcludes>
+    </fileSet>
+    <fileSet>
+      <directory>src</directory>
+      <useDefaultExcludes>true</useDefaultExcludes>
+    </fileSet>
+  </fileSets>
+</assembly>
diff --git a/src/org/ini4j/MultiMapImpl.java b/src/main/java/org/ini4j/BasicMultiMap.java
similarity index 94%
rename from src/org/ini4j/MultiMapImpl.java
rename to src/main/java/org/ini4j/BasicMultiMap.java
index fa4e6ad..74a9a99 100644
--- a/src/org/ini4j/MultiMapImpl.java
+++ b/src/main/java/org/ini4j/BasicMultiMap.java
@@ -17,6 +17,8 @@ package org.ini4j;
 
 import org.ini4j.spi.Warnings;
 
+import java.io.Serializable;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
@@ -26,16 +28,17 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 
-public class MultiMapImpl<K, V> implements MultiMap<K, V>
+public class BasicMultiMap<K, V> implements MultiMap<K, V>, Serializable
 {
+    private static final long serialVersionUID = 4716749660560043989L;
     private final Map<K, List<V>> _impl;
 
-    public MultiMapImpl()
+    public BasicMultiMap()
     {
         this(new LinkedHashMap<K, List<V>>());
     }
 
-    public MultiMapImpl(Map<K, List<V>> impl)
+    public BasicMultiMap(Map<K, List<V>> impl)
     {
         _impl = impl;
     }
@@ -206,6 +209,11 @@ public class MultiMapImpl<K, V> implements MultiMap<K, V>
         return _impl.size();
     }
 
+    @Override public String toString()
+    {
+        return _impl.toString();
+    }
+
     @Override public Collection<V> values()
     {
         List<V> all = new ArrayList<V>(_impl.size());
diff --git a/src/main/java/org/ini4j/BasicOptionMap.java b/src/main/java/org/ini4j/BasicOptionMap.java
new file mode 100644
index 0000000..3bb1595
--- /dev/null
+++ b/src/main/java/org/ini4j/BasicOptionMap.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.spi.BeanAccess;
+import org.ini4j.spi.BeanTool;
+import org.ini4j.spi.Warnings;
+
+import java.lang.reflect.Array;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class BasicOptionMap extends CommonMultiMap<String, String> implements OptionMap
+{
+    private static final char SUBST_CHAR = '$';
+    private static final String SYSTEM_PROPERTY_PREFIX = "@prop/";
+    private static final String ENVIRONMENT_PREFIX = "@env/";
+    private static final int SYSTEM_PROPERTY_PREFIX_LEN = SYSTEM_PROPERTY_PREFIX.length();
+    private static final int ENVIRONMENT_PREFIX_LEN = ENVIRONMENT_PREFIX.length();
+    private static final Pattern EXPRESSION = Pattern.compile("(?<!\\\\)\\$\\{(([^\\[]+)(\\[([0-9]+)\\])?)\\}");
+    private static final int G_OPTION = 2;
+    private static final int G_INDEX = 4;
+    private static final long serialVersionUID = 325469712293707584L;
+    private BeanAccess _defaultBeanAccess;
+    private final boolean _propertyFirstUpper;
+
+    public BasicOptionMap()
+    {
+        this(false);
+    }
+
+    public BasicOptionMap(boolean propertyFirstUpper)
+    {
+        _propertyFirstUpper = propertyFirstUpper;
+    }
+
+    @Override @SuppressWarnings(Warnings.UNCHECKED)
+    public <T> T getAll(Object key, Class<T> clazz)
+    {
+        requireArray(clazz);
+        T value;
+
+        value = (T) Array.newInstance(clazz.getComponentType(), length(key));
+        for (int i = 0; i < length(key); i++)
+        {
+            Array.set(value, i, BeanTool.getInstance().parse(get(key, i), clazz.getComponentType()));
+        }
+
+        return value;
+    }
+
+    @Override public void add(String key, Object value)
+    {
+        super.add(key, ((value == null) || (value instanceof String)) ? (String) value : String.valueOf(value));
+    }
+
+    @Override public void add(String key, Object value, int index)
+    {
+        super.add(key, ((value == null) || (value instanceof String)) ? (String) value : String.valueOf(value), index);
+    }
+
+    @Override public <T> T as(Class<T> clazz)
+    {
+        return BeanTool.getInstance().proxy(clazz, getDefaultBeanAccess());
+    }
+
+    @Override public <T> T as(Class<T> clazz, String keyPrefix)
+    {
+        return BeanTool.getInstance().proxy(clazz, newBeanAccess(keyPrefix));
+    }
+
+    @Override public String fetch(Object key)
+    {
+        int len = length(key);
+
+        return (len == 0) ? null : fetch(key, len - 1);
+    }
+
+    @Override public String fetch(Object key, int index)
+    {
+        String value = get(key, index);
+
+        if ((value != null) && (value.indexOf(SUBST_CHAR) >= 0))
+        {
+            StringBuilder buffer = new StringBuilder(value);
+
+            resolve(buffer);
+            value = buffer.toString();
+        }
+
+        return value;
+    }
+
+    @Override public <T> T fetch(Object key, Class<T> clazz)
+    {
+        return BeanTool.getInstance().parse(fetch(key), clazz);
+    }
+
+    @Override public <T> T fetch(Object key, int index, Class<T> clazz)
+    {
+        return BeanTool.getInstance().parse(fetch(key, index), clazz);
+    }
+
+    @Override @SuppressWarnings(Warnings.UNCHECKED)
+    public <T> T fetchAll(Object key, Class<T> clazz)
+    {
+        requireArray(clazz);
+        T value;
+
+        value = (T) Array.newInstance(clazz.getComponentType(), length(key));
+        for (int i = 0; i < length(key); i++)
+        {
+            Array.set(value, i, BeanTool.getInstance().parse(fetch(key, i), clazz.getComponentType()));
+        }
+
+        return value;
+    }
+
+    @Override public void from(Object bean)
+    {
+        BeanTool.getInstance().inject(getDefaultBeanAccess(), bean);
+    }
+
+    @Override public void from(Object bean, String keyPrefix)
+    {
+        BeanTool.getInstance().inject(newBeanAccess(keyPrefix), bean);
+    }
+
+    @Override public <T> T get(Object key, Class<T> clazz)
+    {
+        return BeanTool.getInstance().parse(get(key), clazz);
+    }
+
+    @Override public <T> T get(Object key, int index, Class<T> clazz)
+    {
+        return BeanTool.getInstance().parse(get(key, index), clazz);
+    }
+
+    @Override public String put(String key, Object value)
+    {
+        return super.put(key, ((value == null) || (value instanceof String)) ? (String) value : String.valueOf(value));
+    }
+
+    @Override public String put(String key, Object value, int index)
+    {
+        return super.put(key, ((value == null) || (value instanceof String)) ? (String) value : String.valueOf(value), index);
+    }
+
+    @Override public void putAll(String key, Object value)
+    {
+        if (value != null)
+        {
+            requireArray(value.getClass());
+        }
+
+        remove(key);
+        if (value != null)
+        {
+            int n = Array.getLength(value);
+
+            for (int i = 0; i < n; i++)
+            {
+                add(key, Array.get(value, i));
+            }
+        }
+    }
+
+    @Override public void to(Object bean)
+    {
+        BeanTool.getInstance().inject(bean, getDefaultBeanAccess());
+    }
+
+    @Override public void to(Object bean, String keyPrefix)
+    {
+        BeanTool.getInstance().inject(bean, newBeanAccess(keyPrefix));
+    }
+
+    synchronized BeanAccess getDefaultBeanAccess()
+    {
+        if (_defaultBeanAccess == null)
+        {
+            _defaultBeanAccess = newBeanAccess();
+        }
+
+        return _defaultBeanAccess;
+    }
+
+    boolean isPropertyFirstUpper()
+    {
+        return _propertyFirstUpper;
+    }
+
+    BeanAccess newBeanAccess()
+    {
+        return new Access();
+    }
+
+    BeanAccess newBeanAccess(String propertyNamePrefix)
+    {
+        return new Access(propertyNamePrefix);
+    }
+
+    void resolve(StringBuilder buffer)
+    {
+        Matcher m = EXPRESSION.matcher(buffer);
+
+        while (m.find())
+        {
+            String name = m.group(G_OPTION);
+            int index = (m.group(G_INDEX) == null) ? -1 : Integer.parseInt(m.group(G_INDEX));
+            String value;
+
+            if (name.startsWith(ENVIRONMENT_PREFIX))
+            {
+                value = Config.getEnvironment(name.substring(ENVIRONMENT_PREFIX_LEN));
+            }
+            else if (name.startsWith(SYSTEM_PROPERTY_PREFIX))
+            {
+                value = Config.getSystemProperty(name.substring(SYSTEM_PROPERTY_PREFIX_LEN));
+            }
+            else
+            {
+                value = (index == -1) ? fetch(name) : fetch(name, index);
+            }
+
+            if (value != null)
+            {
+                buffer.replace(m.start(), m.end(), value);
+                m.reset(buffer);
+            }
+        }
+    }
+
+    private void requireArray(Class clazz)
+    {
+        if (!clazz.isArray())
+        {
+            throw new IllegalArgumentException("Array required");
+        }
+    }
+
+    class Access implements BeanAccess
+    {
+        private final String _prefix;
+
+        Access()
+        {
+            this(null);
+        }
+
+        Access(String prefix)
+        {
+            _prefix = prefix;
+        }
+
+        @Override public void propAdd(String propertyName, String value)
+        {
+            add(transform(propertyName), value);
+        }
+
+        @Override public String propDel(String propertyName)
+        {
+            return remove(transform(propertyName));
+        }
+
+        @Override public String propGet(String propertyName)
+        {
+            return fetch(transform(propertyName));
+        }
+
+        @Override public String propGet(String propertyName, int index)
+        {
+            return fetch(transform(propertyName), index);
+        }
+
+        @Override public int propLength(String propertyName)
+        {
+            return length(transform(propertyName));
+        }
+
+        @Override public String propSet(String propertyName, String value)
+        {
+            return put(transform(propertyName), value);
+        }
+
+        @Override public String propSet(String propertyName, String value, int index)
+        {
+            return put(transform(propertyName), value, index);
+        }
+
+        private String transform(String orig)
+        {
+            String ret = orig;
+
+            if (((_prefix != null) || isPropertyFirstUpper()) && (orig != null))
+            {
+                StringBuilder buff = new StringBuilder();
+
+                if (_prefix != null)
+                {
+                    buff.append(_prefix);
+                }
+
+                if (isPropertyFirstUpper())
+                {
+                    buff.append(Character.toUpperCase(orig.charAt(0)));
+                    buff.append(orig.substring(1));
+                }
+                else
+                {
+                    buff.append(orig);
+                }
+
+                ret = buff.toString();
+            }
+
+            return ret;
+        }
+    }
+}
diff --git a/src/main/java/org/ini4j/BasicProfile.java b/src/main/java/org/ini4j/BasicProfile.java
new file mode 100644
index 0000000..e1b9cae
--- /dev/null
+++ b/src/main/java/org/ini4j/BasicProfile.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.spi.AbstractBeanInvocationHandler;
+import org.ini4j.spi.BeanTool;
+import org.ini4j.spi.IniHandler;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Proxy;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class BasicProfile extends CommonMultiMap<String, Profile.Section> implements Profile
+{
+    private static final String SECTION_SYSTEM_PROPERTIES = "@prop";
+    private static final String SECTION_ENVIRONMENT = "@env";
+    private static final Pattern EXPRESSION = Pattern.compile(
+            "(?<!\\\\)\\$\\{(([^\\[\\}]+)(\\[([0-9]+)\\])?/)?([^\\[^/\\}]+)(\\[(([0-9]+))\\])?\\}");
+    private static final int G_SECTION = 2;
+    private static final int G_SECTION_IDX = 4;
+    private static final int G_OPTION = 5;
+    private static final int G_OPTION_IDX = 7;
+    private static final long serialVersionUID = -1817521505004015256L;
+    private String _comment;
+    private final boolean _propertyFirstUpper;
+    private final boolean _treeMode;
+
+    public BasicProfile()
+    {
+        this(false, false);
+    }
+
+    public BasicProfile(boolean treeMode, boolean propertyFirstUpper)
+    {
+        _treeMode = treeMode;
+        _propertyFirstUpper = propertyFirstUpper;
+    }
+
+    @Override public String getComment()
+    {
+        return _comment;
+    }
+
+    @Override public void setComment(String value)
+    {
+        _comment = value;
+    }
+
+    @Override public Section add(String name)
+    {
+        if (isTreeMode())
+        {
+            int idx = name.lastIndexOf(getPathSeparator());
+
+            if (idx > 0)
+            {
+                String parent = name.substring(0, idx);
+
+                if (!containsKey(parent))
+                {
+                    add(parent);
+                }
+            }
+        }
+
+        Section section = newSection(name);
+
+        add(name, section);
+
+        return section;
+    }
+
+    @Override public void add(String section, String option, Object value)
+    {
+        getOrAdd(section).add(option, value);
+    }
+
+    @Override public <T> T as(Class<T> clazz)
+    {
+        return as(clazz, null);
+    }
+
+    @Override public <T> T as(Class<T> clazz, String prefix)
+    {
+        return clazz.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { clazz },
+                    new BeanInvocationHandler(prefix)));
+    }
+
+    @Override public String fetch(Object sectionName, Object optionName)
+    {
+        Section sec = get(sectionName);
+
+        return (sec == null) ? null : sec.fetch(optionName);
+    }
+
+    @Override public <T> T fetch(Object sectionName, Object optionName, Class<T> clazz)
+    {
+        Section sec = get(sectionName);
+
+        return (sec == null) ? BeanTool.getInstance().zero(clazz) : sec.fetch(optionName, clazz);
+    }
+
+    @Override public String get(Object sectionName, Object optionName)
+    {
+        Section sec = get(sectionName);
+
+        return (sec == null) ? null : sec.get(optionName);
+    }
+
+    @Override public <T> T get(Object sectionName, Object optionName, Class<T> clazz)
+    {
+        Section sec = get(sectionName);
+
+        return (sec == null) ? BeanTool.getInstance().zero(clazz) : sec.get(optionName, clazz);
+    }
+
+    @Override public String put(String sectionName, String optionName, Object value)
+    {
+        return getOrAdd(sectionName).put(optionName, value);
+    }
+
+    @Override public Section remove(Section section)
+    {
+        return remove((Object) section.getName());
+    }
+
+    @Override public String remove(Object sectionName, Object optionName)
+    {
+        Section sec = get(sectionName);
+
+        return (sec == null) ? null : sec.remove(optionName);
+    }
+
+    boolean isTreeMode()
+    {
+        return _treeMode;
+    }
+
+    char getPathSeparator()
+    {
+        return PATH_SEPARATOR;
+    }
+
+    boolean isPropertyFirstUpper()
+    {
+        return _propertyFirstUpper;
+    }
+
+    Section newSection(String name)
+    {
+        return new BasicProfileSection(this, name);
+    }
+
+    void resolve(StringBuilder buffer, Section owner)
+    {
+        Matcher m = EXPRESSION.matcher(buffer);
+
+        while (m.find())
+        {
+            String sectionName = m.group(G_SECTION);
+            String optionName = m.group(G_OPTION);
+            int optionIndex = parseOptionIndex(m);
+            Section section = parseSection(m, owner);
+            String value = null;
+
+            if (SECTION_ENVIRONMENT.equals(sectionName))
+            {
+                value = Config.getEnvironment(optionName);
+            }
+            else if (SECTION_SYSTEM_PROPERTIES.equals(sectionName))
+            {
+                value = Config.getSystemProperty(optionName);
+            }
+            else if (section != null)
+            {
+                value = (optionIndex == -1) ? section.fetch(optionName) : section.fetch(optionName, optionIndex);
+            }
+
+            if (value != null)
+            {
+                buffer.replace(m.start(), m.end(), value);
+                m.reset(buffer);
+            }
+        }
+    }
+
+    void store(IniHandler formatter)
+    {
+        formatter.startIni();
+        store(formatter, getComment());
+        for (Ini.Section s : values())
+        {
+            store(formatter, s);
+        }
+
+        formatter.endIni();
+    }
+
+    void store(IniHandler formatter, Section s)
+    {
+        store(formatter, getComment(s.getName()));
+        formatter.startSection(s.getName());
+        for (String name : s.keySet())
+        {
+            store(formatter, s, name);
+        }
+
+        formatter.endSection();
+    }
+
+    void store(IniHandler formatter, String comment)
+    {
+        formatter.handleComment(comment);
+    }
+
+    void store(IniHandler formatter, Section section, String option)
+    {
+        store(formatter, section.getComment(option));
+        int n = section.length(option);
+
+        for (int i = 0; i < n; i++)
+        {
+            store(formatter, section, option, i);
+        }
+    }
+
+    void store(IniHandler formatter, Section section, String option, int index)
+    {
+        formatter.handleOption(option, section.get(option, index));
+    }
+
+    private Section getOrAdd(String sectionName)
+    {
+        Section section = get(sectionName);
+
+        return ((section == null)) ? add(sectionName) : section;
+    }
+
+    private int parseOptionIndex(Matcher m)
+    {
+        return (m.group(G_OPTION_IDX) == null) ? -1 : Integer.parseInt(m.group(G_OPTION_IDX));
+    }
+
+    private Section parseSection(Matcher m, Section owner)
+    {
+        String sectionName = m.group(G_SECTION);
+        int sectionIndex = parseSectionIndex(m);
+
+        return (sectionName == null) ? owner : ((sectionIndex == -1) ? get(sectionName) : get(sectionName, sectionIndex));
+    }
+
+    private int parseSectionIndex(Matcher m)
+    {
+        return (m.group(G_SECTION_IDX) == null) ? -1 : Integer.parseInt(m.group(G_SECTION_IDX));
+    }
+
+    private final class BeanInvocationHandler extends AbstractBeanInvocationHandler
+    {
+        private final String _prefix;
+
+        private BeanInvocationHandler(String prefix)
+        {
+            _prefix = prefix;
+        }
+
+        @Override protected Object getPropertySpi(String property, Class<?> clazz)
+        {
+            String key = transform(property);
+            Object o = null;
+
+            if (containsKey(key))
+            {
+                if (clazz.isArray())
+                {
+                    o = Array.newInstance(clazz.getComponentType(), length(key));
+                    for (int i = 0; i < length(key); i++)
+                    {
+                        Array.set(o, i, get(key, i).as(clazz.getComponentType()));
+                    }
+                }
+                else
+                {
+                    o = get(key).as(clazz);
+                }
+            }
+
+            return o;
+        }
+
+        @Override protected void setPropertySpi(String property, Object value, Class<?> clazz)
+        {
+            String key = transform(property);
+
+            remove(key);
+            if (value != null)
+            {
+                if (clazz.isArray())
+                {
+                    for (int i = 0; i < Array.getLength(value); i++)
+                    {
+                        Section sec = add(key);
+
+                        sec.from(Array.get(value, i));
+                    }
+                }
+                else
+                {
+                    Section sec = add(key);
+
+                    sec.from(value);
+                }
+            }
+        }
+
+        @Override protected boolean hasPropertySpi(String property)
+        {
+            return containsKey(transform(property));
+        }
+
+        String transform(String property)
+        {
+            String ret = (_prefix == null) ? property : (_prefix + property);
+
+            if (isPropertyFirstUpper())
+            {
+                StringBuilder buff = new StringBuilder();
+
+                buff.append(Character.toUpperCase(property.charAt(0)));
+                buff.append(property.substring(1));
+                ret = buff.toString();
+            }
+
+            return ret;
+        }
+    }
+}
diff --git a/src/main/java/org/ini4j/BasicProfileSection.java b/src/main/java/org/ini4j/BasicProfileSection.java
new file mode 100644
index 0000000..18f513e
--- /dev/null
+++ b/src/main/java/org/ini4j/BasicProfileSection.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+class BasicProfileSection extends BasicOptionMap implements Profile.Section
+{
+    private static final long serialVersionUID = 985800697957194374L;
+    private static final String[] EMPTY_STRING_ARRAY = {};
+    private static final char REGEXP_ESCAPE_CHAR = '\\';
+    private final Pattern _childPattern;
+    private final String _name;
+    private final BasicProfile _profile;
+
+    protected BasicProfileSection(BasicProfile profile, String name)
+    {
+        _profile = profile;
+        _name = name;
+        _childPattern = newChildPattern(name);
+    }
+
+    @Override public Profile.Section getChild(String key)
+    {
+        return _profile.get(childName(key));
+    }
+
+    @Override public String getName()
+    {
+        return _name;
+    }
+
+    @Override public Profile.Section getParent()
+    {
+        Profile.Section ret = null;
+        int idx = _name.lastIndexOf(_profile.getPathSeparator());
+
+        if (idx >= 0)
+        {
+            String name = _name.substring(0, idx);
+
+            ret = _profile.get(name);
+        }
+
+        return ret;
+    }
+
+    @Override public String getSimpleName()
+    {
+        int idx = _name.lastIndexOf(_profile.getPathSeparator());
+
+        return (idx < 0) ? _name : _name.substring(idx + 1);
+    }
+
+    @Override public Profile.Section addChild(String key)
+    {
+        String name = childName(key);
+
+        return _profile.add(name);
+    }
+
+    @Override public String[] childrenNames()
+    {
+        List<String> names = new ArrayList<String>();
+
+        for (String key : _profile.keySet())
+        {
+            if (_childPattern.matcher(key).matches())
+            {
+                names.add(key.substring(_name.length() + 1));
+            }
+        }
+
+        return names.toArray(EMPTY_STRING_ARRAY);
+    }
+
+    @Override public Profile.Section lookup(String... parts)
+    {
+        StringBuilder buff = new StringBuilder();
+
+        for (String part : parts)
+        {
+            if (buff.length() != 0)
+            {
+                buff.append(_profile.getPathSeparator());
+            }
+
+            buff.append(part);
+        }
+
+        return _profile.get(childName(buff.toString()));
+    }
+
+    @Override public void removeChild(String key)
+    {
+        String name = childName(key);
+
+        _profile.remove(name);
+    }
+
+    @Override boolean isPropertyFirstUpper()
+    {
+        return _profile.isPropertyFirstUpper();
+    }
+
+    @Override void resolve(StringBuilder buffer)
+    {
+        _profile.resolve(buffer, this);
+    }
+
+    private String childName(String key)
+    {
+        StringBuilder buff = new StringBuilder(_name);
+
+        buff.append(_profile.getPathSeparator());
+        buff.append(key);
+
+        return buff.toString();
+    }
+
+    private Pattern newChildPattern(String name)
+    {
+        StringBuilder buff = new StringBuilder();
+
+        buff.append('^');
+        buff.append(Pattern.quote(name));
+        buff.append(REGEXP_ESCAPE_CHAR);
+        buff.append(_profile.getPathSeparator());
+        buff.append("[^");
+        buff.append(REGEXP_ESCAPE_CHAR);
+        buff.append(_profile.getPathSeparator());
+        buff.append("]+$");
+
+        return Pattern.compile(buff.toString());
+    }
+}
diff --git a/src/main/java/org/ini4j/BasicRegistry.java b/src/main/java/org/ini4j/BasicRegistry.java
new file mode 100644
index 0000000..5395d6b
--- /dev/null
+++ b/src/main/java/org/ini4j/BasicRegistry.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.Profile.Section;
+
+import org.ini4j.spi.IniHandler;
+import org.ini4j.spi.RegEscapeTool;
+import org.ini4j.spi.TypeValuesPair;
+
+public class BasicRegistry extends BasicProfile implements Registry
+{
+    private static final long serialVersionUID = -6432826330714504802L;
+    private String _version;
+
+    public BasicRegistry()
+    {
+        _version = VERSION;
+    }
+
+    @Override public String getVersion()
+    {
+        return _version;
+    }
+
+    @Override public void setVersion(String value)
+    {
+        _version = value;
+    }
+
+    @Override public Key add(String name)
+    {
+        return (Key) super.add(name);
+    }
+
+    @Override public Key get(Object key)
+    {
+        return (Key) super.get(key);
+    }
+
+    @Override public Key get(Object key, int index)
+    {
+        return (Key) super.get(key, index);
+    }
+
+    @Override public Key put(String key, Section value)
+    {
+        return (Key) super.put(key, value);
+    }
+
+    @Override public Key put(String key, Section value, int index)
+    {
+        return (Key) super.put(key, value, index);
+    }
+
+    @Override public Key remove(Section section)
+    {
+        return (Key) super.remove(section);
+    }
+
+    @Override public Key remove(Object key)
+    {
+        return (Key) super.remove(key);
+    }
+
+    @Override public Key remove(Object key, int index)
+    {
+        return (Key) super.remove(key, index);
+    }
+
+    @Override Key newSection(String name)
+    {
+        return new BasicRegistryKey(this, name);
+    }
+
+    @Override void store(IniHandler formatter, Section section, String option)
+    {
+        store(formatter, section.getComment(option));
+        Type type = ((Key) section).getType(option, Type.REG_SZ);
+        String rawName = option.equals(Key.DEFAULT_NAME) ? option : RegEscapeTool.getInstance().quote(option);
+        String[] values = new String[section.length(option)];
+
+        for (int i = 0; i < values.length; i++)
+        {
+            values[i] = section.get(option, i);
+        }
+
+        String rawValue = RegEscapeTool.getInstance().encode(new TypeValuesPair(type, values));
+
+        formatter.handleOption(rawName, rawValue);
+    }
+}
diff --git a/src/main/java/org/ini4j/BasicRegistryKey.java b/src/main/java/org/ini4j/BasicRegistryKey.java
new file mode 100644
index 0000000..ea74de2
--- /dev/null
+++ b/src/main/java/org/ini4j/BasicRegistryKey.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.Registry.Key;
+
+class BasicRegistryKey extends BasicProfileSection implements Registry.Key
+{
+    private static final long serialVersionUID = -1390060044244350928L;
+    private static final String META_TYPE = "type";
+
+    public BasicRegistryKey(BasicRegistry registry, String name)
+    {
+        super(registry, name);
+    }
+
+    @Override public Key getChild(String key)
+    {
+        return (Key) super.getChild(key);
+    }
+
+    @Override public Key getParent()
+    {
+        return (Key) super.getParent();
+    }
+
+    @Override public Registry.Type getType(Object key)
+    {
+        return (Registry.Type) getMeta(META_TYPE, key);
+    }
+
+    @Override public Registry.Type getType(Object key, Registry.Type defaultType)
+    {
+        Registry.Type type = getType(key);
+
+        return (type == null) ? defaultType : type;
+    }
+
+    @Override public Key addChild(String key)
+    {
+        return (Key) super.addChild(key);
+    }
+
+    @Override public Key lookup(String... path)
+    {
+        return (Key) super.lookup(path);
+    }
+
+    @Override public Registry.Type putType(String key, Registry.Type type)
+    {
+        return (Registry.Type) putMeta(META_TYPE, key, type);
+    }
+
+    @Override public Registry.Type removeType(Object key)
+    {
+        return (Registry.Type) removeMeta(META_TYPE, key);
+    }
+}
diff --git a/src/org/ini4j/IniHandler.java b/src/main/java/org/ini4j/CommentedMap.java
similarity index 76%
copy from src/org/ini4j/IniHandler.java
copy to src/main/java/org/ini4j/CommentedMap.java
index 9354f5c..a35ccd4 100644
--- a/src/org/ini4j/IniHandler.java
+++ b/src/main/java/org/ini4j/CommentedMap.java
@@ -15,13 +15,13 @@
  */
 package org.ini4j;
 
-public interface IniHandler extends OptionHandler
-{
-    void endIni();
+import java.util.Map;
 
-    void endSection();
+public interface CommentedMap<K, V> extends Map<K, V>
+{
+    String getComment(Object key);
 
-    void startIni();
+    String putComment(K key, String comment);
 
-    void startSection(String sectionName);
+    String removeComment(Object key);
 }
diff --git a/src/main/java/org/ini4j/CommonMultiMap.java b/src/main/java/org/ini4j/CommonMultiMap.java
new file mode 100644
index 0000000..7ac6171
--- /dev/null
+++ b/src/main/java/org/ini4j/CommonMultiMap.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+public class CommonMultiMap<K, V> extends BasicMultiMap<K, V> implements CommentedMap<K, V>
+{
+    private static final long serialVersionUID = 3012579878005541746L;
+    private static final String SEPARATOR = ";#;";
+    private static final String FIRST_CATEGORY = "";
+    private static final String LAST_CATEGORY = "zzzzzzzzzzzzzzzzzzzzzz";
+    private static final String META_COMMENT = "comment";
+    private SortedMap<String, Object> _meta;
+
+    @Override public String getComment(Object key)
+    {
+        return (String) getMeta(META_COMMENT, key);
+    }
+
+    @Override public void clear()
+    {
+        super.clear();
+        if (_meta != null)
+        {
+            _meta.clear();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override public void putAll(Map<? extends K, ? extends V> map)
+    {
+        super.putAll(map);
+        if (map instanceof CommonMultiMap)
+        {
+            Map<String, String> meta = ((CommonMultiMap) map)._meta;
+
+            if (meta != null)
+            {
+                meta().putAll(meta);
+            }
+        }
+    }
+
+    @Override public String putComment(K key, String comment)
+    {
+        return (String) putMeta(META_COMMENT, key, comment);
+    }
+
+    @Override public V remove(Object key)
+    {
+        V ret = super.remove(key);
+
+        removeMeta(key);
+
+        return ret;
+    }
+
+    @Override public V remove(Object key, int index)
+    {
+        V ret = super.remove(key, index);
+
+        if (length(key) == 0)
+        {
+            removeMeta(key);
+        }
+
+        return ret;
+    }
+
+    @Override public String removeComment(Object key)
+    {
+        return (String) removeMeta(META_COMMENT, key);
+    }
+
+    Object getMeta(String category, Object key)
+    {
+        return (_meta == null) ? null : _meta.get(makeKey(category, key));
+    }
+
+    Object putMeta(String category, K key, Object value)
+    {
+        return meta().put(makeKey(category, key), value);
+    }
+
+    void removeMeta(Object key)
+    {
+        if (_meta != null)
+        {
+            _meta.subMap(makeKey(FIRST_CATEGORY, key), makeKey(LAST_CATEGORY, key)).clear();
+        }
+    }
+
+    Object removeMeta(String category, Object key)
+    {
+        return (_meta == null) ? null : _meta.remove(makeKey(category, key));
+    }
+
+    private String makeKey(String category, Object key)
+    {
+        StringBuilder buff = new StringBuilder();
+
+        buff.append(String.valueOf(key));
+        buff.append(SEPARATOR);
+        buff.append(category);
+
+        return buff.toString();
+    }
+
+    private Map<String, Object> meta()
+    {
+        if (_meta == null)
+        {
+            _meta = new TreeMap<String, Object>();
+        }
+
+        return _meta;
+    }
+}
diff --git a/src/main/java/org/ini4j/Config.java b/src/main/java/org/ini4j/Config.java
new file mode 100644
index 0000000..f53d88f
--- /dev/null
+++ b/src/main/java/org/ini4j/Config.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import java.io.Serializable;
+
+import java.nio.charset.Charset;
+
+ at SuppressWarnings("PMD.ExcessivePublicCount")
+public class Config implements Cloneable, Serializable
+{
+    public static final String KEY_PREFIX = "org.ini4j.config.";
+    public static final String PROP_EMPTY_OPTION = "emptyOption";
+    public static final String PROP_EMPTY_SECTION = "emptySection";
+    public static final String PROP_GLOBAL_SECTION = "globalSection";
+    public static final String PROP_GLOBAL_SECTION_NAME = "globalSectionName";
+    public static final String PROP_INCLUDE = "include";
+    public static final String PROP_LOWER_CASE_OPTION = "lowerCaseOption";
+    public static final String PROP_LOWER_CASE_SECTION = "lowerCaseSection";
+    public static final String PROP_MULTI_OPTION = "multiOption";
+    public static final String PROP_MULTI_SECTION = "multiSection";
+    public static final String PROP_STRICT_OPERATOR = "strictOperator";
+    public static final String PROP_UNNAMED_SECTION = "unnamedSection";
+    public static final String PROP_ESCAPE = "escape";
+    public static final String PROP_PATH_SEPARATOR = "pathSeparator";
+    public static final String PROP_TREE = "tree";
+    public static final String PROP_PROPERTY_FIRST_UPPER = "propertyFirstUpper";
+    public static final String PROP_FILE_ENCODING = "fileEncoding";
+    public static final String PROP_LINE_SEPARATOR = "lineSeparator";
+    public static final String PROP_COMMENT = "comment";
+    public static final String PROP_HEADER_COMMENT = "headerComment";
+    public static final boolean DEFAULT_EMPTY_OPTION = false;
+    public static final boolean DEFAULT_EMPTY_SECTION = false;
+    public static final boolean DEFAULT_GLOBAL_SECTION = false;
+    public static final String DEFAULT_GLOBAL_SECTION_NAME = "?";
+    public static final boolean DEFAULT_INCLUDE = false;
+    public static final boolean DEFAULT_LOWER_CASE_OPTION = false;
+    public static final boolean DEFAULT_LOWER_CASE_SECTION = false;
+    public static final boolean DEFAULT_MULTI_OPTION = true;
+    public static final boolean DEFAULT_MULTI_SECTION = false;
+    public static final boolean DEFAULT_STRICT_OPERATOR = false;
+    public static final boolean DEFAULT_UNNAMED_SECTION = false;
+    public static final boolean DEFAULT_ESCAPE = true;
+    public static final boolean DEFAULT_TREE = true;
+    public static final boolean DEFAULT_PROPERTY_FIRST_UPPER = false;
+    public static final boolean DEFAULT_COMMENT = true;
+    public static final boolean DEFAULT_HEADER_COMMENT = true;
+    public static final char DEFAULT_PATH_SEPARATOR = '/';
+    public static final String DEFAULT_LINE_SEPARATOR = getSystemProperty("line.separator", "\n");
+    public static final Charset DEFAULT_FILE_ENCODING = Charset.forName("UTF-8");
+    private static final Config GLOBAL = new Config();
+    private static final long serialVersionUID = 2865793267410367814L;
+    private boolean _comment;
+    private boolean _emptyOption;
+    private boolean _emptySection;
+    private boolean _escape;
+    private Charset _fileEncoding;
+    private boolean _globalSection;
+    private String _globalSectionName;
+    private boolean _headerComment;
+    private boolean _include;
+    private String _lineSeparator;
+    private boolean _lowerCaseOption;
+    private boolean _lowerCaseSection;
+    private boolean _multiOption;
+    private boolean _multiSection;
+    private char _pathSeparator;
+    private boolean _propertyFirstUpper;
+    private boolean _strictOperator;
+    private boolean _tree;
+    private boolean _unnamedSection;
+
+    public Config()
+    {
+        reset();
+    }
+
+    public static String getEnvironment(String name)
+    {
+        return getEnvironment(name, null);
+    }
+
+    public static String getEnvironment(String name, String defaultValue)
+    {
+        String value;
+
+        try
+        {
+            value = System.getenv(name);
+        }
+        catch (SecurityException x)
+        {
+            value = null;
+        }
+
+        return (value == null) ? defaultValue : value;
+    }
+
+    public static Config getGlobal()
+    {
+        return GLOBAL;
+    }
+
+    public static String getSystemProperty(String name)
+    {
+        return getSystemProperty(name, null);
+    }
+
+    public static String getSystemProperty(String name, String defaultValue)
+    {
+        String value;
+
+        try
+        {
+            value = System.getProperty(name);
+        }
+        catch (SecurityException x)
+        {
+            value = null;
+        }
+
+        return (value == null) ? defaultValue : value;
+    }
+
+    public void setComment(boolean value)
+    {
+        _comment = value;
+    }
+
+    public boolean isEscape()
+    {
+        return _escape;
+    }
+
+    public boolean isInclude()
+    {
+        return _include;
+    }
+
+    public boolean isTree()
+    {
+        return _tree;
+    }
+
+    public void setEmptyOption(boolean value)
+    {
+        _emptyOption = value;
+    }
+
+    public void setEmptySection(boolean value)
+    {
+        _emptySection = value;
+    }
+
+    public void setEscape(boolean value)
+    {
+        _escape = value;
+    }
+
+    public Charset getFileEncoding()
+    {
+        return _fileEncoding;
+    }
+
+    public void setFileEncoding(Charset value)
+    {
+        _fileEncoding = value;
+    }
+
+    public void setGlobalSection(boolean value)
+    {
+        _globalSection = value;
+    }
+
+    public String getGlobalSectionName()
+    {
+        return _globalSectionName;
+    }
+
+    public void setGlobalSectionName(String value)
+    {
+        _globalSectionName = value;
+    }
+
+    public void setHeaderComment(boolean value)
+    {
+        _headerComment = value;
+    }
+
+    public void setInclude(boolean value)
+    {
+        _include = value;
+    }
+
+    public String getLineSeparator()
+    {
+        return _lineSeparator;
+    }
+
+    public void setLineSeparator(String value)
+    {
+        _lineSeparator = value;
+    }
+
+    public void setLowerCaseOption(boolean value)
+    {
+        _lowerCaseOption = value;
+    }
+
+    public void setLowerCaseSection(boolean value)
+    {
+        _lowerCaseSection = value;
+    }
+
+    public void setMultiOption(boolean value)
+    {
+        _multiOption = value;
+    }
+
+    public void setMultiSection(boolean value)
+    {
+        _multiSection = value;
+    }
+
+    public boolean isEmptyOption()
+    {
+        return _emptyOption;
+    }
+
+    public boolean isEmptySection()
+    {
+        return _emptySection;
+    }
+
+    public boolean isGlobalSection()
+    {
+        return _globalSection;
+    }
+
+    public boolean isLowerCaseOption()
+    {
+        return _lowerCaseOption;
+    }
+
+    public boolean isLowerCaseSection()
+    {
+        return _lowerCaseSection;
+    }
+
+    public boolean isMultiOption()
+    {
+        return _multiOption;
+    }
+
+    public boolean isMultiSection()
+    {
+        return _multiSection;
+    }
+
+    public boolean isUnnamedSection()
+    {
+        return _unnamedSection;
+    }
+
+    public char getPathSeparator()
+    {
+        return _pathSeparator;
+    }
+
+    public void setPathSeparator(char value)
+    {
+        _pathSeparator = value;
+    }
+
+    public void setPropertyFirstUpper(boolean value)
+    {
+        _propertyFirstUpper = value;
+    }
+
+    public boolean isPropertyFirstUpper()
+    {
+        return _propertyFirstUpper;
+    }
+
+    public boolean isStrictOperator()
+    {
+        return _strictOperator;
+    }
+
+    public void setStrictOperator(boolean value)
+    {
+        _strictOperator = value;
+    }
+
+    public boolean isComment()
+    {
+        return _comment;
+    }
+
+    public boolean isHeaderComment()
+    {
+        return _headerComment;
+    }
+
+    public void setTree(boolean value)
+    {
+        _tree = value;
+    }
+
+    public void setUnnamedSection(boolean value)
+    {
+        _unnamedSection = value;
+    }
+
+    @Override public Config clone()
+    {
+        try
+        {
+            return (Config) super.clone();
+        }
+        catch (CloneNotSupportedException x)
+        {
+            throw new AssertionError(x);
+        }
+    }
+
+    public final void reset()
+    {
+        _emptyOption = getBoolean(PROP_EMPTY_OPTION, DEFAULT_EMPTY_OPTION);
+        _emptySection = getBoolean(PROP_EMPTY_SECTION, DEFAULT_EMPTY_SECTION);
+        _globalSection = getBoolean(PROP_GLOBAL_SECTION, DEFAULT_GLOBAL_SECTION);
+        _globalSectionName = getString(PROP_GLOBAL_SECTION_NAME, DEFAULT_GLOBAL_SECTION_NAME);
+        _include = getBoolean(PROP_INCLUDE, DEFAULT_INCLUDE);
+        _lowerCaseOption = getBoolean(PROP_LOWER_CASE_OPTION, DEFAULT_LOWER_CASE_OPTION);
+        _lowerCaseSection = getBoolean(PROP_LOWER_CASE_SECTION, DEFAULT_LOWER_CASE_SECTION);
+        _multiOption = getBoolean(PROP_MULTI_OPTION, DEFAULT_MULTI_OPTION);
+        _multiSection = getBoolean(PROP_MULTI_SECTION, DEFAULT_MULTI_SECTION);
+        _strictOperator = getBoolean(PROP_STRICT_OPERATOR, DEFAULT_STRICT_OPERATOR);
+        _unnamedSection = getBoolean(PROP_UNNAMED_SECTION, DEFAULT_UNNAMED_SECTION);
+        _escape = getBoolean(PROP_ESCAPE, DEFAULT_ESCAPE);
+        _pathSeparator = getChar(PROP_PATH_SEPARATOR, DEFAULT_PATH_SEPARATOR);
+        _tree = getBoolean(PROP_TREE, DEFAULT_TREE);
+        _propertyFirstUpper = getBoolean(PROP_PROPERTY_FIRST_UPPER, DEFAULT_PROPERTY_FIRST_UPPER);
+        _lineSeparator = getString(PROP_LINE_SEPARATOR, DEFAULT_LINE_SEPARATOR);
+        _fileEncoding = getCharset(PROP_FILE_ENCODING, DEFAULT_FILE_ENCODING);
+        _comment = getBoolean(PROP_COMMENT, DEFAULT_COMMENT);
+        _headerComment = getBoolean(PROP_HEADER_COMMENT, DEFAULT_HEADER_COMMENT);
+    }
+
+    private boolean getBoolean(String name, boolean defaultValue)
+    {
+        String value = getSystemProperty(KEY_PREFIX + name);
+
+        return (value == null) ? defaultValue : Boolean.parseBoolean(value);
+    }
+
+    private char getChar(String name, char defaultValue)
+    {
+        String value = getSystemProperty(KEY_PREFIX + name);
+
+        return (value == null) ? defaultValue : value.charAt(0);
+    }
+
+    private Charset getCharset(String name, Charset defaultValue)
+    {
+        String value = getSystemProperty(KEY_PREFIX + name);
+
+        return (value == null) ? defaultValue : Charset.forName(value);
+    }
+
+    private String getString(String name, String defaultValue)
+    {
+        return getSystemProperty(KEY_PREFIX + name, defaultValue);
+    }
+}
diff --git a/src/org/ini4j/addon/ConfigParser.java b/src/main/java/org/ini4j/ConfigParser.java
similarity index 92%
rename from src/org/ini4j/addon/ConfigParser.java
rename to src/main/java/org/ini4j/ConfigParser.java
index 0265d63..83c76de 100644
--- a/src/org/ini4j/addon/ConfigParser.java
+++ b/src/main/java/org/ini4j/ConfigParser.java
@@ -13,13 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.ini4j.addon;
-
-import org.ini4j.Config;
-import org.ini4j.Ini;
-import org.ini4j.IniHandler;
-import org.ini4j.InvalidIniFormatException;
+package org.ini4j;
 
+import org.ini4j.spi.IniHandler;
 import org.ini4j.spi.Warnings;
 
 import java.io.File;
@@ -29,6 +25,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.Reader;
+import java.io.Serializable;
 import java.io.Writer;
 
 import java.net.URL;
@@ -41,8 +38,9 @@ import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public class ConfigParser
+public class ConfigParser implements Serializable
 {
+    private static final long serialVersionUID = 9118857036229164353L;
     private PyIni _ini;
 
     @SuppressWarnings(Warnings.UNCHECKED)
@@ -65,7 +63,8 @@ public class ConfigParser
         {
             ret = true;
         }
-        else if ("0".equalsIgnoreCase(value) || "no".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value) || "off".equalsIgnoreCase(value))
+        else if ("0".equalsIgnoreCase(value) || "no".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)
+              || "off".equalsIgnoreCase(value))
         {
             ret = false;
         }
@@ -128,8 +127,8 @@ public class ConfigParser
         return get(section, option, raw, Collections.EMPTY_MAP);
     }
 
-    public String get(String sectionName, String optionName, boolean raw, Map<String, String> variables) throws NoSectionException, NoOptionException,
-        InterpolationException
+    public String get(String sectionName, String optionName, boolean raw, Map<String, String> variables) throws NoSectionException,
+        NoOptionException, InterpolationException
     {
         String value = requireOption(sectionName, optionName);
 
@@ -160,7 +159,8 @@ public class ConfigParser
     }
 
     @SuppressWarnings(Warnings.UNCHECKED)
-    public List<Map.Entry<String, String>> items(String sectionName, boolean raw) throws NoSectionException, InterpolationMissingOptionException
+    public List<Map.Entry<String, String>> items(String sectionName, boolean raw) throws NoSectionException,
+        InterpolationMissingOptionException
     {
         return items(sectionName, raw, Collections.EMPTY_MAP);
     }
@@ -208,7 +208,7 @@ public class ConfigParser
         {
             _ini.load(reader);
         }
-        catch (InvalidIniFormatException x)
+        catch (InvalidFileFormatException x)
         {
             throw new ParsingException(x);
         }
@@ -220,7 +220,7 @@ public class ConfigParser
         {
             _ini.load(url);
         }
-        catch (InvalidIniFormatException x)
+        catch (InvalidFileFormatException x)
         {
             throw new ParsingException(x);
         }
@@ -232,7 +232,7 @@ public class ConfigParser
         {
             _ini.load(new FileReader(file));
         }
-        catch (InvalidIniFormatException x)
+        catch (InvalidFileFormatException x)
         {
             throw new ParsingException(x);
         }
@@ -244,7 +244,7 @@ public class ConfigParser
         {
             _ini.load(stream);
         }
-        catch (InvalidIniFormatException x)
+        catch (InvalidFileFormatException x)
         {
             throw new ParsingException(x);
         }
@@ -303,7 +303,7 @@ public class ConfigParser
         _ini.store(new FileWriter(file));
     }
 
-    protected PyIni getIni()
+    protected Ini getIni()
     {
         return _ini;
     }
@@ -406,16 +406,18 @@ public class ConfigParser
 
         private ParsingException(Throwable cause)
         {
-            super(cause.getMessage(), cause);
+            super(cause.getMessage());
+            initCause(cause);
         }
     }
 
-    protected static class PyIni extends Ini
+    static class PyIni extends Ini
     {
         private static final char SUBST_CHAR = '%';
         private static final Pattern EXPRESSION = Pattern.compile("(?<!\\\\)\\%\\(([^\\)]+)\\)");
         private static final int G_OPTION = 1;
         protected static final String DEFAULT_SECTION_NAME = "DEFAULT";
+        private static final long serialVersionUID = -7152857626328996122L;
         private final Map<String, String> _defaults;
         private Ini.Section _defaultSection;
 
@@ -450,7 +452,7 @@ public class ConfigParser
             {
                 if (_defaultSection == null)
                 {
-                    _defaultSection = new Ini.Section(name);
+                    _defaultSection = newSection(name);
                 }
 
                 section = _defaultSection;
@@ -473,7 +475,8 @@ public class ConfigParser
             return _defaultSection;
         }
 
-        protected String fetch(Ini.Section section, String optionName, Map<String, String> variables) throws InterpolationMissingOptionException
+        protected String fetch(Ini.Section section, String optionName, Map<String, String> variables)
+            throws InterpolationMissingOptionException
         {
             String value = section.get(optionName);
 
@@ -522,7 +525,7 @@ public class ConfigParser
             }
         }
 
-        @Override protected void store(IniHandler formatter) throws IOException
+        @Override protected void store(IniHandler formatter)
         {
             formatter.startIni();
             if (_defaultSection != null)
@@ -538,7 +541,7 @@ public class ConfigParser
             formatter.endIni();
         }
 
-        protected void store(IniHandler formatter, Section section) throws IOException
+        @Override protected void store(IniHandler formatter, Section section)
         {
             formatter.startSection(section.getName());
             for (String name : section.keySet())
diff --git a/src/org/ini4j/IniHandler.java b/src/main/java/org/ini4j/Configurable.java
similarity index 79%
copy from src/org/ini4j/IniHandler.java
copy to src/main/java/org/ini4j/Configurable.java
index 9354f5c..b0ec2bc 100644
--- a/src/org/ini4j/IniHandler.java
+++ b/src/main/java/org/ini4j/Configurable.java
@@ -15,13 +15,9 @@
  */
 package org.ini4j;
 
-public interface IniHandler extends OptionHandler
+public interface Configurable
 {
-    void endIni();
+    Config getConfig();
 
-    void endSection();
-
-    void startIni();
-
-    void startSection(String sectionName);
+    void setConfig(Config value);
 }
diff --git a/src/main/java/org/ini4j/Ini.java b/src/main/java/org/ini4j/Ini.java
new file mode 100644
index 0000000..39562a2
--- /dev/null
+++ b/src/main/java/org/ini4j/Ini.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.spi.IniBuilder;
+import org.ini4j.spi.IniFormatter;
+import org.ini4j.spi.IniHandler;
+import org.ini4j.spi.IniParser;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+
+import java.net.URL;
+
+public class Ini extends BasicProfile implements Persistable, Configurable
+{
+    private static final long serialVersionUID = -6029486578113700585L;
+    private Config _config;
+    private File _file;
+
+    public Ini()
+    {
+        _config = Config.getGlobal();
+    }
+
+    public Ini(Reader input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        load(input);
+    }
+
+    public Ini(InputStream input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        load(input);
+    }
+
+    public Ini(URL input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        load(input);
+    }
+
+    public Ini(File input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        _file = input;
+        load();
+    }
+
+    @Override public Config getConfig()
+    {
+        return _config;
+    }
+
+    @Override public void setConfig(Config value)
+    {
+        _config = value;
+    }
+
+    @Override public File getFile()
+    {
+        return _file;
+    }
+
+    @Override public void setFile(File value)
+    {
+        _file = value;
+    }
+
+    @Override public void load() throws IOException, InvalidFileFormatException
+    {
+        if (_file == null)
+        {
+            throw new FileNotFoundException();
+        }
+
+        load(_file);
+    }
+
+    @Override public void load(InputStream input) throws IOException, InvalidFileFormatException
+    {
+        load(new InputStreamReader(input, getConfig().getFileEncoding()));
+    }
+
+    @Override public void load(Reader input) throws IOException, InvalidFileFormatException
+    {
+        IniParser.newInstance(getConfig()).parse(input, newBuilder());
+    }
+
+    @Override public void load(File input) throws IOException, InvalidFileFormatException
+    {
+        load(input.toURI().toURL());
+    }
+
+    @Override public void load(URL input) throws IOException, InvalidFileFormatException
+    {
+        IniParser.newInstance(getConfig()).parse(input, newBuilder());
+    }
+
+    @Override public void store() throws IOException
+    {
+        if (_file == null)
+        {
+            throw new FileNotFoundException();
+        }
+
+        store(_file);
+    }
+
+    @Override public void store(OutputStream output) throws IOException
+    {
+        store(new OutputStreamWriter(output, getConfig().getFileEncoding()));
+    }
+
+    @Override public void store(Writer output) throws IOException
+    {
+        store(IniFormatter.newInstance(output, getConfig()));
+    }
+
+    @Override public void store(File output) throws IOException
+    {
+        OutputStream stream = new FileOutputStream(output);
+
+        store(stream);
+        stream.close();
+    }
+
+    protected IniHandler newBuilder()
+    {
+        return IniBuilder.newInstance(this);
+    }
+
+    @Override protected void store(IniHandler formatter, Profile.Section section)
+    {
+        if (getConfig().isEmptySection() || (section.size() != 0))
+        {
+            super.store(formatter, section);
+        }
+    }
+
+    @Override protected void store(IniHandler formatter, Profile.Section section, String option, int index)
+    {
+        if (getConfig().isMultiOption() || (index == (section.length(option) - 1)))
+        {
+            super.store(formatter, section, option, index);
+        }
+    }
+
+    @Override boolean isTreeMode()
+    {
+        return getConfig().isTree();
+    }
+
+    @Override char getPathSeparator()
+    {
+        return getConfig().getPathSeparator();
+    }
+
+    @Override boolean isPropertyFirstUpper()
+    {
+        return getConfig().isPropertyFirstUpper();
+    }
+}
diff --git a/src/org/ini4j/IniPreferences.java b/src/main/java/org/ini4j/IniPreferences.java
similarity index 92%
rename from src/org/ini4j/IniPreferences.java
rename to src/main/java/org/ini4j/IniPreferences.java
index 47ff8b9..1cfe275 100644
--- a/src/org/ini4j/IniPreferences.java
+++ b/src/main/java/org/ini4j/IniPreferences.java
@@ -21,6 +21,8 @@ import java.io.Reader;
 
 import java.net.URL;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.prefs.AbstractPreferences;
 import java.util.prefs.BackingStoreException;
 
@@ -52,9 +54,9 @@ public class IniPreferences extends AbstractPreferences
      *
      * @param input the <code>Reader</code> containing <code>Ini</code> data
      * @throws IOException if an I/O error occured
-     * @throws InvalidIniFormatException if <code>Ini</code> parsing error occured
+     * @throws InvalidFileFormatException if <code>Ini</code> parsing error occured
      */
-    public IniPreferences(Reader input) throws IOException, InvalidIniFormatException
+    public IniPreferences(Reader input) throws IOException, InvalidFileFormatException
     {
         super(null, "");
         _ini = new Ini(input);
@@ -68,9 +70,9 @@ public class IniPreferences extends AbstractPreferences
      *
      * @param input the <code>InputStream</code> containing <code>Ini</code> data
      * @throws IOException if an I/O error occured
-     * @throws InvalidIniFormatException if <code>Ini</code> parsing error occured
+     * @throws InvalidFileFormatException if <code>Ini</code> parsing error occured
      */
-    public IniPreferences(InputStream input) throws IOException, InvalidIniFormatException
+    public IniPreferences(InputStream input) throws IOException, InvalidFileFormatException
     {
         super(null, "");
         _ini = new Ini(input);
@@ -84,9 +86,9 @@ public class IniPreferences extends AbstractPreferences
      *
      * @param input the <code>URL</code> containing <code>Ini</code> data
      * @throws IOException if an I/O error occured
-     * @throws InvalidIniFormatException if <code>Ini</code> parsing error occured
+     * @throws InvalidFileFormatException if <code>Ini</code> parsing error occured
      */
-    public IniPreferences(URL input) throws IOException, InvalidIniFormatException
+    public IniPreferences(URL input) throws IOException, InvalidFileFormatException
     {
         super(null, "");
         _ini = new Ini(input);
@@ -125,7 +127,17 @@ public class IniPreferences extends AbstractPreferences
      */
     @Override protected String[] childrenNamesSpi() throws BackingStoreException
     {
-        return _ini.keySet().toArray(EMPTY);
+        List<String> names = new ArrayList<String>();
+
+        for (String name : _ini.keySet())
+        {
+            if (name.indexOf(_ini.getPathSeparator()) < 0)
+            {
+                names.add(name);
+            }
+        }
+
+        return names.toArray(EMPTY);
     }
 
     /**
@@ -239,9 +251,9 @@ public class IniPreferences extends AbstractPreferences
          * @parem section underlaying Ini.Section instance
          * @param isNew indicate is this a new node or already existing one
          */
-        SectionPreferences(IniPreferences parent, Ini.Section section, boolean isNew)
+        SectionPreferences(AbstractPreferences parent, Ini.Section section, boolean isNew)
         {
-            super(parent, section.getName());
+            super(parent, section.getSimpleName());
             _section = section;
             newNode = isNew;
         }
@@ -294,7 +306,7 @@ public class IniPreferences extends AbstractPreferences
          */
         @Override protected String[] childrenNamesSpi() throws BackingStoreException
         {
-            return EMPTY;
+            return _section.childrenNames();
         }
 
         /**
@@ -307,9 +319,17 @@ public class IniPreferences extends AbstractPreferences
          * @param name child name
          * @return child node
          */
-        @Override protected IniPreferences childSpi(String name) throws UnsupportedOperationException
+        @Override protected SectionPreferences childSpi(String name) throws UnsupportedOperationException
         {
-            throw new UnsupportedOperationException();
+            Ini.Section child = _section.getChild(name);
+            boolean isNew = child == null;
+
+            if (isNew)
+            {
+                child = _section.addChild(name);
+            }
+
+            return new SectionPreferences(this, child, isNew);
         }
 
         /**
diff --git a/src/org/ini4j/IniPreferencesFactory.java b/src/main/java/org/ini4j/IniPreferencesFactory.java
similarity index 90%
rename from src/org/ini4j/IniPreferencesFactory.java
rename to src/main/java/org/ini4j/IniPreferencesFactory.java
index 10fbadd..905ea34 100644
--- a/src/org/ini4j/IniPreferencesFactory.java
+++ b/src/main/java/org/ini4j/IniPreferencesFactory.java
@@ -52,9 +52,9 @@ public class IniPreferencesFactory implements PreferencesFactory
         return _user;
     }
 
-    protected String getIniLocation(String key)
+    String getIniLocation(String key)
     {
-        String location = System.getProperty(key);
+        String location = Config.getSystemProperty(key);
 
         if (location == null)
         {
@@ -74,7 +74,7 @@ public class IniPreferencesFactory implements PreferencesFactory
         return location;
     }
 
-    protected URL getResource(String location) throws IllegalArgumentException
+    URL getResource(String location) throws IllegalArgumentException
     {
         try
         {
@@ -98,7 +98,7 @@ public class IniPreferencesFactory implements PreferencesFactory
         }
     }
 
-    protected InputStream getResourceAsStream(String location) throws IllegalArgumentException
+    InputStream getResourceAsStream(String location) throws IllegalArgumentException
     {
         try
         {
@@ -110,7 +110,7 @@ public class IniPreferencesFactory implements PreferencesFactory
         }
     }
 
-    protected Preferences newIniPreferences(String key)
+    Preferences newIniPreferences(String key)
     {
         Ini ini = new Ini();
         String location = getIniLocation(key);
diff --git a/src/org/ini4j/IniHandler.java b/src/main/java/org/ini4j/InvalidFileFormatException.java
similarity index 71%
copy from src/org/ini4j/IniHandler.java
copy to src/main/java/org/ini4j/InvalidFileFormatException.java
index 9354f5c..43e87ee 100644
--- a/src/org/ini4j/IniHandler.java
+++ b/src/main/java/org/ini4j/InvalidFileFormatException.java
@@ -15,13 +15,14 @@
  */
 package org.ini4j;
 
-public interface IniHandler extends OptionHandler
-{
-    void endIni();
-
-    void endSection();
+import java.io.IOException;
 
-    void startIni();
+public class InvalidFileFormatException extends IOException
+{
+    private static final long serialVersionUID = -4354616830804732309L;
 
-    void startSection(String sectionName);
+    public InvalidFileFormatException(String message)
+    {
+        super(message);
+    }
 }
diff --git a/src/org/ini4j/MultiMap.java b/src/main/java/org/ini4j/MultiMap.java
similarity index 100%
rename from src/org/ini4j/MultiMap.java
rename to src/main/java/org/ini4j/MultiMap.java
diff --git a/src/main/java/org/ini4j/OptionMap.java b/src/main/java/org/ini4j/OptionMap.java
new file mode 100644
index 0000000..df37459
--- /dev/null
+++ b/src/main/java/org/ini4j/OptionMap.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+public interface OptionMap extends MultiMap<String, String>, CommentedMap<String, String>
+{
+    <T> T getAll(Object key, Class<T> clazz);
+
+    void add(String key, Object value);
+
+    void add(String key, Object value, int index);
+
+    <T> T as(Class<T> clazz);
+
+    <T> T as(Class<T> clazz, String keyPrefix);
+
+    String fetch(Object key);
+
+    String fetch(Object key, int index);
+
+    <T> T fetch(Object key, Class<T> clazz);
+
+    <T> T fetch(Object key, int index, Class<T> clazz);
+
+    <T> T fetchAll(Object key, Class<T> clazz);
+
+    void from(Object bean);
+
+    void from(Object bean, String keyPrefix);
+
+    <T> T get(Object key, Class<T> clazz);
+
+    <T> T get(Object key, int index, Class<T> clazz);
+
+    String put(String key, Object value);
+
+    String put(String key, Object value, int index);
+
+    void putAll(String key, Object value);
+
+    void to(Object bean);
+
+    void to(Object bean, String keyPrefix);
+}
diff --git a/src/main/java/org/ini4j/Options.java b/src/main/java/org/ini4j/Options.java
new file mode 100644
index 0000000..226046f
--- /dev/null
+++ b/src/main/java/org/ini4j/Options.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.spi.OptionsBuilder;
+import org.ini4j.spi.OptionsFormatter;
+import org.ini4j.spi.OptionsHandler;
+import org.ini4j.spi.OptionsParser;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+
+import java.net.URL;
+
+public class Options extends BasicOptionMap implements Persistable, Configurable
+{
+    private static final long serialVersionUID = -1119753444859181822L;
+    private String _comment;
+    private Config _config;
+    private File _file;
+
+    public Options()
+    {
+        _config = Config.getGlobal().clone();
+        _config.setEmptyOption(true);
+    }
+
+    public Options(Reader input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        load(input);
+    }
+
+    public Options(InputStream input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        load(input);
+    }
+
+    public Options(URL input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        load(input);
+    }
+
+    public Options(File input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        _file = input;
+        load();
+    }
+
+    public String getComment()
+    {
+        return _comment;
+    }
+
+    public void setComment(String value)
+    {
+        _comment = value;
+    }
+
+    @Override public Config getConfig()
+    {
+        return _config;
+    }
+
+    @Override public void setConfig(Config value)
+    {
+        _config = value;
+    }
+
+    @Override public File getFile()
+    {
+        return _file;
+    }
+
+    @Override public void setFile(File value)
+    {
+        _file = value;
+    }
+
+    @Override public void load() throws IOException, InvalidFileFormatException
+    {
+        if (_file == null)
+        {
+            throw new FileNotFoundException();
+        }
+
+        load(_file);
+    }
+
+    @Override public void load(InputStream input) throws IOException, InvalidFileFormatException
+    {
+        load(new InputStreamReader(input, getConfig().getFileEncoding()));
+    }
+
+    @Override public void load(Reader input) throws IOException, InvalidFileFormatException
+    {
+        OptionsParser.newInstance(getConfig()).parse(input, newBuilder());
+    }
+
+    @Override public void load(URL input) throws IOException, InvalidFileFormatException
+    {
+        OptionsParser.newInstance(getConfig()).parse(input, newBuilder());
+    }
+
+    @Override public void load(File input) throws IOException, InvalidFileFormatException
+    {
+        load(input.toURI().toURL());
+    }
+
+    @Override public void store() throws IOException
+    {
+        if (_file == null)
+        {
+            throw new FileNotFoundException();
+        }
+
+        store(_file);
+    }
+
+    @Override public void store(OutputStream output) throws IOException
+    {
+        store(new OutputStreamWriter(output, getConfig().getFileEncoding()));
+    }
+
+    @Override public void store(Writer output) throws IOException
+    {
+        store(OptionsFormatter.newInstance(output, getConfig()));
+    }
+
+    @Override public void store(File output) throws IOException
+    {
+        OutputStream stream = new FileOutputStream(output);
+
+        store(stream);
+        stream.close();
+    }
+
+    protected OptionsHandler newBuilder()
+    {
+        return OptionsBuilder.newInstance(this);
+    }
+
+    protected void store(OptionsHandler formatter) throws IOException
+    {
+        formatter.startOptions();
+        storeComment(formatter, _comment);
+        for (String name : keySet())
+        {
+            storeComment(formatter, getComment(name));
+            int n = getConfig().isMultiOption() ? length(name) : 1;
+
+            for (int i = 0; i < n; i++)
+            {
+                String value = get(name, i);
+
+                formatter.handleOption(name, value);
+            }
+        }
+
+        formatter.endOptions();
+    }
+
+    @Override boolean isPropertyFirstUpper()
+    {
+        return getConfig().isPropertyFirstUpper();
+    }
+
+    private void storeComment(OptionsHandler formatter, String comment)
+    {
+        formatter.handleComment(comment);
+    }
+}
diff --git a/src/main/java/org/ini4j/Persistable.java b/src/main/java/org/ini4j/Persistable.java
new file mode 100644
index 0000000..9a5a8d3
--- /dev/null
+++ b/src/main/java/org/ini4j/Persistable.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+
+import java.net.URL;
+
+public interface Persistable
+{
+    File getFile();
+
+    void setFile(File value);
+
+    void load() throws IOException, InvalidFileFormatException;
+
+    void load(InputStream input) throws IOException, InvalidFileFormatException;
+
+    void load(Reader input) throws IOException, InvalidFileFormatException;
+
+    void load(File input) throws IOException, InvalidFileFormatException;
+
+    void load(URL input) throws IOException, InvalidFileFormatException;
+
+    void store() throws IOException;
+
+    void store(OutputStream output) throws IOException;
+
+    void store(Writer output) throws IOException;
+
+    void store(File output) throws IOException;
+}
diff --git a/src/main/java/org/ini4j/Profile.java b/src/main/java/org/ini4j/Profile.java
new file mode 100644
index 0000000..49c31f7
--- /dev/null
+++ b/src/main/java/org/ini4j/Profile.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+public interface Profile extends MultiMap<String, Profile.Section>, CommentedMap<String, Profile.Section>
+{
+    char PATH_SEPARATOR = '/';
+
+    String getComment();
+
+    void setComment(String value);
+
+    Section add(String sectionName);
+
+    void add(String sectionName, String optionName, Object value);
+
+    <T> T as(Class<T> clazz);
+
+    <T> T as(Class<T> clazz, String prefix);
+
+    String fetch(Object sectionName, Object optionName);
+
+    <T> T fetch(Object sectionName, Object optionName, Class<T> clazz);
+
+    String get(Object sectionName, Object optionName);
+
+    <T> T get(Object sectionName, Object optionName, Class<T> clazz);
+
+    String put(String sectionName, String optionName, Object value);
+
+    Section remove(Profile.Section section);
+
+    String remove(Object sectionName, Object optionName);
+
+    interface Section extends OptionMap
+    {
+        Section getChild(String key);
+
+        String getName();
+
+        Section getParent();
+
+        String getSimpleName();
+
+        Section addChild(String key);
+
+        String[] childrenNames();
+
+        Section lookup(String... path);
+
+        void removeChild(String key);
+    }
+}
diff --git a/src/main/java/org/ini4j/Reg.java b/src/main/java/org/ini4j/Reg.java
new file mode 100644
index 0000000..fa643a0
--- /dev/null
+++ b/src/main/java/org/ini4j/Reg.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.spi.IniFormatter;
+import org.ini4j.spi.IniHandler;
+import org.ini4j.spi.IniParser;
+import org.ini4j.spi.RegBuilder;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+
+import java.net.URL;
+
+public class Reg extends BasicRegistry implements Registry, Persistable, Configurable
+{
+    private static final long serialVersionUID = -1485602876922985912L;
+    protected static final String DEFAULT_SUFFIX = ".reg";
+    protected static final String TMP_PREFIX = "reg-";
+    private static final int STDERR_BUFF_SIZE = 8192;
+    private static final String PROP_OS_NAME = "os.name";
+    private static final boolean WINDOWS = Config.getSystemProperty(PROP_OS_NAME, "Unknown").startsWith("Windows");
+    private static final char CR = '\r';
+    private static final char LF = '\n';
+    private Config _config;
+    private File _file;
+
+    public Reg()
+    {
+        Config cfg = Config.getGlobal().clone();
+
+        cfg.setEscape(false);
+        cfg.setGlobalSection(false);
+        cfg.setEmptyOption(true);
+        cfg.setMultiOption(true);
+        cfg.setStrictOperator(true);
+        cfg.setEmptySection(true);
+        cfg.setPathSeparator(KEY_SEPARATOR);
+        cfg.setFileEncoding(FILE_ENCODING);
+        cfg.setLineSeparator(LINE_SEPARATOR);
+        _config = cfg;
+    }
+
+    public Reg(String registryKey) throws IOException
+    {
+        this();
+        read(registryKey);
+    }
+
+    public Reg(File input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        _file = input;
+        load();
+    }
+
+    public Reg(URL input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        load(input);
+    }
+
+    public Reg(InputStream input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        load(input);
+    }
+
+    public Reg(Reader input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        load(input);
+    }
+
+    public static boolean isWindows()
+    {
+        return WINDOWS;
+    }
+
+    @Override public Config getConfig()
+    {
+        return _config;
+    }
+
+    public void setConfig(Config value)
+    {
+        _config = value;
+    }
+
+    @Override public File getFile()
+    {
+        return _file;
+    }
+
+    @Override public void setFile(File value)
+    {
+        _file = value;
+    }
+
+    @Override public void load() throws IOException, InvalidFileFormatException
+    {
+        if (_file == null)
+        {
+            throw new FileNotFoundException();
+        }
+
+        load(_file);
+    }
+
+    @Override public void load(InputStream input) throws IOException, InvalidFileFormatException
+    {
+        load(new InputStreamReader(input, getConfig().getFileEncoding()));
+    }
+
+    @Override public void load(URL input) throws IOException, InvalidFileFormatException
+    {
+        load(new InputStreamReader(input.openStream(), getConfig().getFileEncoding()));
+    }
+
+    @Override public void load(Reader input) throws IOException, InvalidFileFormatException
+    {
+        int newline = 2;
+        StringBuilder buff = new StringBuilder();
+
+        for (int c = input.read(); c != -1; c = input.read())
+        {
+            if (c == LF)
+            {
+                newline--;
+                if (newline == 0)
+                {
+                    break;
+                }
+            }
+            else if ((c != CR) && (newline != 1))
+            {
+                buff.append((char) c);
+            }
+        }
+
+        if (buff.length() == 0)
+        {
+            throw new InvalidFileFormatException("Missing version header");
+        }
+
+        if (!buff.toString().equals(getVersion()))
+        {
+            throw new InvalidFileFormatException("Unsupported version: " + buff.toString());
+        }
+
+        IniParser.newInstance(getConfig()).parse(input, newBuilder());
+    }
+
+    @Override public void load(File input) throws IOException, InvalidFileFormatException
+    {
+        load(input.toURI().toURL());
+    }
+
+    public void read(String registryKey) throws IOException
+    {
+        File tmp = createTempFile();
+
+        try
+        {
+            regExport(registryKey, tmp);
+            load(tmp);
+        }
+        finally
+        {
+            tmp.delete();
+        }
+    }
+
+    @Override public void store() throws IOException
+    {
+        if (_file == null)
+        {
+            throw new FileNotFoundException();
+        }
+
+        store(_file);
+    }
+
+    @Override public void store(OutputStream output) throws IOException
+    {
+        store(new OutputStreamWriter(output, getConfig().getFileEncoding()));
+    }
+
+    @Override public void store(Writer output) throws IOException
+    {
+        output.write(getVersion());
+        output.write(getConfig().getLineSeparator());
+        output.write(getConfig().getLineSeparator());
+        store(IniFormatter.newInstance(output, getConfig()));
+    }
+
+    @Override public void store(File output) throws IOException
+    {
+        OutputStream stream = new FileOutputStream(output);
+
+        store(stream);
+        stream.close();
+    }
+
+    public void write() throws IOException
+    {
+        File tmp = createTempFile();
+
+        try
+        {
+            store(tmp);
+            regImport(tmp);
+        }
+        finally
+        {
+            tmp.delete();
+        }
+    }
+
+    protected IniHandler newBuilder()
+    {
+        return RegBuilder.newInstance(this);
+    }
+
+    @Override boolean isTreeMode()
+    {
+        return getConfig().isTree();
+    }
+
+    @Override char getPathSeparator()
+    {
+        return getConfig().getPathSeparator();
+    }
+
+    @Override boolean isPropertyFirstUpper()
+    {
+        return getConfig().isPropertyFirstUpper();
+    }
+
+    void exec(String[] args) throws IOException
+    {
+        Process proc = Runtime.getRuntime().exec(args);
+
+        try
+        {
+            int status = proc.waitFor();
+
+            if (status != 0)
+            {
+                Reader in = new InputStreamReader(proc.getErrorStream());
+                char[] buff = new char[STDERR_BUFF_SIZE];
+                int n = in.read(buff);
+
+                in.close();
+                throw new IOException(new String(buff, 0, n).trim());
+            }
+        }
+        catch (InterruptedException x)
+        {
+            throw (IOException) (new InterruptedIOException().initCause(x));
+        }
+    }
+
+    private File createTempFile() throws IOException
+    {
+        File ret = File.createTempFile(TMP_PREFIX, DEFAULT_SUFFIX);
+
+        ret.deleteOnExit();
+
+        return ret;
+    }
+
+    private void regExport(String registryKey, File file) throws IOException
+    {
+        requireWindows();
+        exec(new String[] { "cmd", "/c", "reg", "export", registryKey, file.getAbsolutePath() });
+    }
+
+    private void regImport(File file) throws IOException
+    {
+        requireWindows();
+        exec(new String[] { "cmd", "/c", "reg", "import", file.getAbsolutePath() });
+    }
+
+    private void requireWindows()
+    {
+        if (!WINDOWS)
+        {
+            throw new UnsupportedOperationException("Unsupported operating system or runtime environment");
+        }
+    }
+}
diff --git a/src/main/java/org/ini4j/Registry.java b/src/main/java/org/ini4j/Registry.java
new file mode 100644
index 0000000..0839642
--- /dev/null
+++ b/src/main/java/org/ini4j/Registry.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import java.nio.charset.Charset;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public interface Registry extends Profile
+{
+    enum Hive
+    {
+        HKEY_CLASSES_ROOT,
+        HKEY_CURRENT_CONFIG,
+        HKEY_CURRENT_USER,
+        HKEY_LOCAL_MACHINE,
+        HKEY_USERS;
+    }
+
+    // TODO handle delete operations with special Type
+    enum Type
+    {
+        REG_NONE("hex(0)"),
+        REG_SZ(""),
+        REG_EXPAND_SZ("hex(2)"),
+        REG_BINARY("hex"),
+        REG_DWORD("dword"),
+        REG_DWORD_BIG_ENDIAN("hex(5)"),
+        REG_LINK("hex(6)"),
+        REG_MULTI_SZ("hex(7)"),
+        REG_RESOURCE_LIST("hex(8)"),
+        REG_FULL_RESOURCE_DESCRIPTOR("hex(9)"),
+        REG_RESOURCE_REQUIREMENTS_LIST("hex(a)"),
+        REG_QWORD("hex(b)");
+        private static final Map<String, Type> MAPPING;
+
+        static
+        {
+            MAPPING = new HashMap<String, Type>();
+            for (Type t : values())
+            {
+                MAPPING.put(t.toString(), t);
+            }
+        }
+
+        public static final char SEPARATOR_CHAR = ':';
+        public static final String SEPARATOR = String.valueOf(SEPARATOR_CHAR);
+        public static final char REMOVE_CHAR = '-';
+        public static final String REMOVE = String.valueOf(REMOVE_CHAR);
+        private final String _prefix;
+
+        private Type(String prefix)
+        {
+            _prefix = prefix;
+        }
+
+        public static Type fromString(String str)
+        {
+            return MAPPING.get(str);
+        }
+
+        @Override public String toString()
+        {
+            return _prefix;
+        }
+    }
+
+    char ESCAPE_CHAR = '\\';
+    Charset FILE_ENCODING = Charset.forName("UnicodeLittle");
+    char KEY_SEPARATOR = '\\';
+    String LINE_SEPARATOR = "\r\n";
+    char TYPE_SEPARATOR = ':';
+    String VERSION = "Windows Registry Editor Version 5.00";
+
+    String getVersion();
+
+    void setVersion(String value);
+
+    @Override Key get(Object key);
+
+    @Override Key get(Object key, int index);
+
+    @Override Key put(String key, Section value);
+
+    @Override Key put(String key, Section value, int index);
+
+    @Override Key remove(Object key);
+
+    @Override Key remove(Object key, int index);
+
+    interface Key extends Section
+    {
+        String DEFAULT_NAME = "@";
+
+        @Override Key getChild(String key);
+
+        @Override Key getParent();
+
+        Type getType(Object key);
+
+        Type getType(Object key, Type defaulType);
+
+        @Override Key addChild(String key);
+
+        @Override Key lookup(String... path);
+
+        Type putType(String key, Type type);
+
+        Type removeType(Object key);
+    }
+}
diff --git a/src/main/java/org/ini4j/Wini.java b/src/main/java/org/ini4j/Wini.java
new file mode 100644
index 0000000..30c3e20
--- /dev/null
+++ b/src/main/java/org/ini4j/Wini.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.spi.WinEscapeTool;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+import java.net.URL;
+
+public class Wini extends Ini
+{
+    private static final long serialVersionUID = -2781377824232440728L;
+    public static final char PATH_SEPARATOR = '\\';
+
+    public Wini()
+    {
+        Config cfg = Config.getGlobal().clone();
+
+        cfg.setEscape(false);
+        cfg.setGlobalSection(true);
+        cfg.setEmptyOption(true);
+        cfg.setMultiOption(false);
+        cfg.setPathSeparator(PATH_SEPARATOR);
+        setConfig(cfg);
+    }
+
+    public Wini(File input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        setFile(input);
+        load();
+    }
+
+    public Wini(URL input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        load(input);
+    }
+
+    public Wini(InputStream input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        load(input);
+    }
+
+    public Wini(Reader input) throws IOException, InvalidFileFormatException
+    {
+        this();
+        load(input);
+    }
+
+    public String escape(String value)
+    {
+        return WinEscapeTool.getInstance().escape(value);
+    }
+
+    public String unescape(String value)
+    {
+        return WinEscapeTool.getInstance().unescape(value);
+    }
+}
diff --git a/src/org/ini4j/package.html b/src/main/java/org/ini4j/package.html
similarity index 100%
rename from src/org/ini4j/package.html
rename to src/main/java/org/ini4j/package.html
diff --git a/src/org/ini4j/spi/AbstractBeanInvocationHandler.java b/src/main/java/org/ini4j/spi/AbstractBeanInvocationHandler.java
similarity index 98%
rename from src/org/ini4j/spi/AbstractBeanInvocationHandler.java
rename to src/main/java/org/ini4j/spi/AbstractBeanInvocationHandler.java
index 99ae665..7703cf7 100644
--- a/src/org/ini4j/spi/AbstractBeanInvocationHandler.java
+++ b/src/main/java/org/ini4j/spi/AbstractBeanInvocationHandler.java
@@ -262,7 +262,7 @@ public abstract class AbstractBeanInvocationHandler implements InvocationHandler
         return ret;
     }
 
-    protected Object parse(String value, Class clazz) throws IllegalArgumentException
+    protected Object parse(String value, Class<?> clazz) throws IllegalArgumentException
     {
         return BeanTool.getInstance().parse(value, clazz);
     }
@@ -283,7 +283,7 @@ public abstract class AbstractBeanInvocationHandler implements InvocationHandler
         }
     }
 
-    protected Object zero(Class clazz)
+    protected Object zero(Class<?> clazz)
     {
         return BeanTool.getInstance().zero(clazz);
     }
diff --git a/src/org/ini4j/spi/IniFormatter.java b/src/main/java/org/ini4j/spi/AbstractFormatter.java
similarity index 54%
rename from src/org/ini4j/spi/IniFormatter.java
rename to src/main/java/org/ini4j/spi/AbstractFormatter.java
index bc51169..d4048aa 100644
--- a/src/org/ini4j/spi/IniFormatter.java
+++ b/src/main/java/org/ini4j/spi/AbstractFormatter.java
@@ -16,62 +16,36 @@
 package org.ini4j.spi;
 
 import org.ini4j.Config;
-import org.ini4j.IniHandler;
-import org.ini4j.IniParser;
 
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
-import java.io.Writer;
 
-public class IniFormatter implements IniHandler
+abstract class AbstractFormatter implements HandlerBase
 {
     private static final char OPERATOR = '=';
+    private static final char COMMENT = '#';
     private static final char SPACE = ' ';
     private Config _config = Config.getGlobal();
+    private boolean _header = true;
     private PrintWriter _output;
 
-    public static IniFormatter newInstance(Writer out)
+    @Override public void handleComment(String comment)
     {
-        IniFormatter instance = newInstance();
-
-        instance.setOutput(new PrintWriter(out));
-
-        return instance;
-    }
-
-    public static IniFormatter newInstance(OutputStream out)
-    {
-        return newInstance(new OutputStreamWriter(out));
-    }
-
-    public static IniFormatter newInstance(Writer out, Config config)
-    {
-        IniFormatter instance = newInstance(out);
-
-        instance.setConfig(config);
-
-        return instance;
-    }
-
-    public static IniFormatter newInstance(OutputStream out, Config config)
-    {
-        return newInstance(new OutputStreamWriter(out), config);
-    }
-
-    public Config getConfig()
-    {
-        return _config;
-    }
+        if (getConfig().isComment() && (!_header || getConfig().isHeaderComment()) && (comment != null) && (comment.length() != 0))
+        {
+            for (String line : comment.split(getConfig().getLineSeparator()))
+            {
+                getOutput().print(COMMENT);
+                getOutput().print(line);
+                getOutput().print(getConfig().getLineSeparator());
+            }
 
-    @Override public void endIni()
-    {
-        getOutput().flush();
-    }
+            if (_header)
+            {
+                getOutput().print(getConfig().getLineSeparator());
+            }
+        }
 
-    @Override public void endSection()
-    {
-        getOutput().println();
+        _header = false;
     }
 
     @Override public void handleOption(String optionName, String optionValue)
@@ -80,18 +54,18 @@ public class IniFormatter implements IniHandler
         {
             if (getConfig().isEmptyOption() || (optionValue != null))
             {
-                getOutput().print(escape(optionName));
+                getOutput().print(escapeFilter(optionName));
                 getOutput().print(OPERATOR);
             }
 
             if (optionValue != null)
             {
-                getOutput().print(escape(optionValue));
+                getOutput().print(escapeFilter(optionValue));
             }
 
             if (getConfig().isEmptyOption() || (optionValue != null))
             {
-                getOutput().println();
+                getOutput().print(getConfig().getLineSeparator());
             }
         }
         else
@@ -100,30 +74,21 @@ public class IniFormatter implements IniHandler
 
             if (value != null)
             {
-                getOutput().print(escape(optionName));
+                getOutput().print(escapeFilter(optionName));
                 getOutput().print(SPACE);
                 getOutput().print(OPERATOR);
                 getOutput().print(SPACE);
-                getOutput().println(escape(value));
+                getOutput().print(escapeFilter(value));
+                getOutput().print(getConfig().getLineSeparator());
             }
         }
-    }
 
-    @Override public void startIni()
-    {
-        assert true;
-    }
-
-    @Override public void startSection(String sectionName)
-    {
-        getOutput().print(IniParser.SECTION_BEGIN);
-        getOutput().print(escape(sectionName));
-        getOutput().println(IniParser.SECTION_END);
+        setHeader(false);
     }
 
-    protected static IniFormatter newInstance()
+    protected Config getConfig()
     {
-        return ServiceFinder.findService(IniFormatter.class);
+        return _config;
     }
 
     protected void setConfig(Config value)
@@ -141,7 +106,12 @@ public class IniFormatter implements IniHandler
         _output = value;
     }
 
-    protected String escape(String input)
+    void setHeader(boolean value)
+    {
+        _header = value;
+    }
+
+    String escapeFilter(String input)
     {
         return getConfig().isEscape() ? EscapeTool.getInstance().escape(input) : input;
     }
diff --git a/src/org/ini4j/AbstractParser.java b/src/main/java/org/ini4j/spi/AbstractParser.java
similarity index 68%
rename from src/org/ini4j/AbstractParser.java
rename to src/main/java/org/ini4j/spi/AbstractParser.java
index 55a467b..c162af6 100644
--- a/src/org/ini4j/AbstractParser.java
+++ b/src/main/java/org/ini4j/spi/AbstractParser.java
@@ -13,9 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.ini4j;
+package org.ini4j.spi;
 
-import org.ini4j.spi.EscapeTool;
+import org.ini4j.Config;
+import org.ini4j.InvalidFileFormatException;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -25,7 +26,7 @@ import java.net.URL;
 
 import java.util.Locale;
 
-public abstract class AbstractParser
+abstract class AbstractParser
 {
     private final String _comments;
     private Config _config = Config.getGlobal();
@@ -37,39 +38,37 @@ public abstract class AbstractParser
         _comments = comments;
     }
 
-    public void setConfig(Config value)
-    {
-        _config = value;
-    }
-
     protected Config getConfig()
     {
         return _config;
     }
 
-    protected int indexOfOperator(String line)
+    protected void setConfig(Config value)
     {
-        int idx = -1;
+        _config = value;
+    }
 
-        for (char c : _operators.toCharArray())
-        {
-            int index = line.indexOf(c);
+    protected void parseError(String line, int lineNumber) throws InvalidFileFormatException
+    {
+        throw new InvalidFileFormatException("parse error (at line: " + lineNumber + "): " + line);
+    }
 
-            if ((index >= 0) && ((idx == -1) || (index < idx)))
-            {
-                idx = index;
-            }
-        }
+    IniSource newIniSource(InputStream input, HandlerBase handler)
+    {
+        return new IniSource(input, handler, _comments, getConfig());
+    }
 
-        return idx;
+    IniSource newIniSource(Reader input, HandlerBase handler)
+    {
+        return new IniSource(input, handler, _comments, getConfig());
     }
 
-    protected void parseError(String line, int lineNumber) throws InvalidIniFormatException
+    IniSource newIniSource(URL input, HandlerBase handler) throws IOException
     {
-        throw new InvalidIniFormatException("parse error (at line: " + lineNumber + "): " + line);
+        return new IniSource(input, handler, _comments, getConfig());
     }
 
-    protected void parseOptionLine(String line, OptionHandler handler, int lineNumber) throws InvalidIniFormatException
+    void parseOptionLine(String line, HandlerBase handler, int lineNumber) throws InvalidFileFormatException
     {
         int idx = indexOfOperator(line);
         String name = null;
@@ -88,8 +87,8 @@ public abstract class AbstractParser
         }
         else
         {
-            name = unescape(line.substring(0, idx)).trim();
-            value = unescape(line.substring(idx + 1)).trim();
+            name = unescapeFilter(line.substring(0, idx)).trim();
+            value = unescapeFilter(line.substring(idx + 1)).trim();
         }
 
         if (name.length() == 0)
@@ -105,23 +104,25 @@ public abstract class AbstractParser
         handler.handleOption(name, value);
     }
 
-    protected String unescape(String line)
+    String unescapeFilter(String line)
     {
         return getConfig().isEscape() ? EscapeTool.getInstance().unescape(line) : line;
     }
 
-    IniSource newIniSource(InputStream input)
+    private int indexOfOperator(String line)
     {
-        return new IniSource(input, getConfig().isInclude(), _comments);
-    }
+        int idx = -1;
 
-    IniSource newIniSource(Reader input)
-    {
-        return new IniSource(input, getConfig().isInclude(), _comments);
-    }
+        for (char c : _operators.toCharArray())
+        {
+            int index = line.indexOf(c);
 
-    IniSource newIniSource(URL input) throws IOException
-    {
-        return new IniSource(input, getConfig().isInclude(), _comments);
+            if ((index >= 0) && ((idx == -1) || (index < idx)))
+            {
+                idx = index;
+            }
+        }
+
+        return idx;
     }
 }
diff --git a/src/main/java/org/ini4j/spi/AbstractProfileBuilder.java b/src/main/java/org/ini4j/spi/AbstractProfileBuilder.java
new file mode 100644
index 0000000..badc263
--- /dev/null
+++ b/src/main/java/org/ini4j/spi/AbstractProfileBuilder.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.ini4j.CommentedMap;
+import org.ini4j.Config;
+import org.ini4j.Ini;
+import org.ini4j.Profile;
+
+abstract class AbstractProfileBuilder implements IniHandler
+{
+    private Profile.Section _currentSection;
+    private boolean _header;
+    private String _lastComment;
+
+    @Override public void endIni()
+    {
+
+        // comment only .ini files....
+        if ((_lastComment != null) && _header)
+        {
+            setHeaderComment();
+        }
+    }
+
+    @Override public void endSection()
+    {
+        _currentSection = null;
+    }
+
+    @Override public void handleComment(String comment)
+    {
+        if ((_lastComment != null) && _header)
+        {
+            _header = false;
+            setHeaderComment();
+        }
+
+        _lastComment = comment;
+    }
+
+    @Override public void handleOption(String name, String value)
+    {
+        _header = false;
+        if (getConfig().isMultiOption())
+        {
+            _currentSection.add(name, value);
+        }
+        else
+        {
+            _currentSection.put(name, value);
+        }
+
+        if (_lastComment != null)
+        {
+            putComment(_currentSection, name);
+            _lastComment = null;
+        }
+    }
+
+    @Override public void startIni()
+    {
+        if (getConfig().isHeaderComment())
+        {
+            _header = true;
+        }
+    }
+
+    @Override public void startSection(String sectionName)
+    {
+        if (getConfig().isMultiSection())
+        {
+            _currentSection = getProfile().add(sectionName);
+        }
+        else
+        {
+            Ini.Section s = getProfile().get(sectionName);
+
+            _currentSection = (s == null) ? getProfile().add(sectionName) : s;
+        }
+
+        if (_lastComment != null)
+        {
+            if (_header)
+            {
+                setHeaderComment();
+            }
+            else
+            {
+                putComment(getProfile(), sectionName);
+            }
+
+            _lastComment = null;
+        }
+
+        _header = false;
+    }
+
+    abstract Config getConfig();
+
+    abstract Profile getProfile();
+
+    Profile.Section getCurrentSection()
+    {
+        return _currentSection;
+    }
+
+    private void setHeaderComment()
+    {
+        if (getConfig().isComment())
+        {
+            getProfile().setComment(_lastComment);
+        }
+    }
+
+    private void putComment(CommentedMap<String, ?> map, String key)
+    {
+        if (getConfig().isComment())
+        {
+            map.putComment(key, _lastComment);
+        }
+    }
+}
diff --git a/src/org/ini4j/spi/BeanAccess.java b/src/main/java/org/ini4j/spi/BeanAccess.java
similarity index 100%
rename from src/org/ini4j/spi/BeanAccess.java
rename to src/main/java/org/ini4j/spi/BeanAccess.java
diff --git a/src/org/ini4j/spi/BeanTool.java b/src/main/java/org/ini4j/spi/BeanTool.java
similarity index 94%
rename from src/org/ini4j/spi/BeanTool.java
rename to src/main/java/org/ini4j/spi/BeanTool.java
index a9a48cd..05d8be7 100644
--- a/src/org/ini4j/spi/BeanTool.java
+++ b/src/main/java/org/ini4j/spi/BeanTool.java
@@ -32,7 +32,7 @@ import java.util.TimeZone;
 
 public class BeanTool
 {
-    protected static final String PARSE_METHOD = "valueOf";
+    private static final String PARSE_METHOD = "valueOf";
     private static final BeanTool INSTANCE = ServiceFinder.findService(BeanTool.class);
 
     public static final BeanTool getInstance()
@@ -49,7 +49,6 @@ public class BeanTool
                 Method method = pd.getWriteMethod();
                 String name = pd.getName();
 
-//                if ((method != null) && props.containsKey(name))
                 if ((method != null) && (props.propLength(name) != 0))
                 {
                     Object value;
@@ -72,7 +71,8 @@ public class BeanTool
             }
             catch (Exception x)
             {
-                throw new IllegalArgumentException("Failed to set property: " + pd.getDisplayName(), x);
+                throw (IllegalArgumentException) (new IllegalArgumentException("Failed to set property: " + pd.getDisplayName()).initCause(
+                        x));
             }
         }
     }
@@ -119,7 +119,8 @@ public class BeanTool
         }
     }
 
-    public Object parse(String value, Class clazz) throws IllegalArgumentException
+    @SuppressWarnings("unchecked")
+    public <T> T parse(String value, Class<T> clazz) throws IllegalArgumentException
     {
         if (clazz == null)
         {
@@ -152,7 +153,7 @@ public class BeanTool
             }
         }
 
-        return o;
+        return (T) o;
     }
 
     public <T> T proxy(Class<T> clazz, BeanAccess props)
@@ -161,7 +162,8 @@ public class BeanTool
                     new BeanInvocationHandler(props)));
     }
 
-    public Object zero(Class clazz)
+    @SuppressWarnings("unchecked")
+    public <T> T zero(Class<T> clazz)
     {
         Object o = null;
 
@@ -201,7 +203,7 @@ public class BeanTool
             }
         }
 
-        return o;
+        return (T) o;
     }
 
     @SuppressWarnings(Warnings.UNCHECKED)
@@ -234,6 +236,7 @@ public class BeanTool
             else
             {
 
+                // TODO handle constructor with String arg as converter from String
                 // look for "valueOf" converter method
                 Method parser = clazz.getMethod(PARSE_METHOD, new Class[] { String.class });
 
diff --git a/src/main/java/org/ini4j/spi/EscapeTool.java b/src/main/java/org/ini4j/spi/EscapeTool.java
new file mode 100644
index 0000000..4309f3d
--- /dev/null
+++ b/src/main/java/org/ini4j/spi/EscapeTool.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+public class EscapeTool
+{
+    private static final String ESCAPE_LETTERS = "\\tnfbr";
+    private static final String ESCAPEABLE_CHARS = "\\\t\n\f\b\r";
+    private static final char ESCAPE_CHAR = '\\';
+    static final char[] HEX = "0123456789abcdef".toCharArray();
+    private static final EscapeTool INSTANCE = ServiceFinder.findService(EscapeTool.class);
+    private static final char ASCII_MIN = 0x20;
+    private static final char ASCII_MAX = 0x7e;
+    static final int HEX_DIGIT_MASK = 0x0f;
+    static final int HEX_DIGIT_3_OFFSET = 4;
+    static final int HEX_DIGIT_2_OFFSET = 8;
+    static final int HEX_DIGIT_1_OFFSET = 12;
+    static final int HEX_RADIX = 16;
+    private static final int UNICODE_HEX_DIGITS = 4;
+    static final char DOUBLE_QUOTE = '"';
+
+    public static EscapeTool getInstance()
+    {
+        return INSTANCE;
+    }
+
+    public String escape(String line)
+    {
+        int len = line.length();
+        StringBuilder buffer = new StringBuilder(len * 2);
+
+        for (int i = 0; i < len; i++)
+        {
+            char c = line.charAt(i);
+            int idx = ESCAPEABLE_CHARS.indexOf(c);
+
+            if (idx >= 0)
+            {
+                buffer.append(ESCAPE_CHAR);
+                buffer.append(ESCAPE_LETTERS.charAt(idx));
+            }
+            else
+            {
+                if ((c < ASCII_MIN) || (c > ASCII_MAX))
+                {
+                    escapeBinary(buffer, c);
+                }
+                else
+                {
+                    buffer.append(c);
+                }
+            }
+        }
+
+        return buffer.toString();
+    }
+
+    public String quote(String value)
+    {
+        String ret = value;
+
+        if ((value != null) && (value.length() != 0))
+        {
+            StringBuilder buff = new StringBuilder();
+
+            buff.append(DOUBLE_QUOTE);
+            for (int i = 0; i < value.length(); i++)
+            {
+                char c = value.charAt(i);
+
+                if ((c == ESCAPE_CHAR) || (c == DOUBLE_QUOTE))
+                {
+                    buff.append(ESCAPE_CHAR);
+                }
+
+                buff.append(c);
+            }
+
+            buff.append(DOUBLE_QUOTE);
+            ret = buff.toString();
+        }
+
+        return ret;
+    }
+
+    public String unescape(String line)
+    {
+        int n = line.length();
+        StringBuilder buffer = new StringBuilder(n);
+        int i = 0;
+
+        while (i < n)
+        {
+            char c = line.charAt(i++);
+
+            if (c == ESCAPE_CHAR)
+            {
+                c = line.charAt(i++);
+                int next = unescapeBinary(buffer, c, line, i);
+
+                if (next == i)
+                {
+                    int idx = ESCAPE_LETTERS.indexOf(c);
+
+                    if (idx >= 0)
+                    {
+                        c = ESCAPEABLE_CHARS.charAt(idx);
+                    }
+
+                    buffer.append(c);
+                }
+                else
+                {
+                    i = next;
+                }
+            }
+            else
+            {
+                buffer.append(c);
+            }
+        }
+
+        return buffer.toString();
+    }
+
+    public String unquote(String value)
+    {
+        StringBuilder buff = new StringBuilder();
+        boolean escape = false;
+
+        for (int i = 1; i < (value.length() - 1); i++)
+        {
+            char c = value.charAt(i);
+
+            if (c == ESCAPE_CHAR)
+            {
+                if (!escape)
+                {
+                    escape = true;
+
+                    continue;
+                }
+
+                escape = false;
+            }
+
+            buff.append(c);
+        }
+
+        return buff.toString();
+    }
+
+    void escapeBinary(StringBuilder buff, char c)
+    {
+        buff.append("\\u");
+        buff.append(HEX[(c >>> HEX_DIGIT_1_OFFSET) & HEX_DIGIT_MASK]);
+        buff.append(HEX[(c >>> HEX_DIGIT_2_OFFSET) & HEX_DIGIT_MASK]);
+        buff.append(HEX[(c >>> HEX_DIGIT_3_OFFSET) & HEX_DIGIT_MASK]);
+        buff.append(HEX[c & HEX_DIGIT_MASK]);
+    }
+
+    int unescapeBinary(StringBuilder buff, char escapeType, String line, int index)
+    {
+        int ret = index;
+
+        if (escapeType == 'u')
+        {
+            try
+            {
+                buff.append((char) Integer.parseInt(line.substring(index, index + UNICODE_HEX_DIGITS), HEX_RADIX));
+                ret = index + UNICODE_HEX_DIGITS;
+            }
+            catch (Exception x)
+            {
+                throw new IllegalArgumentException("Malformed \\uxxxx encoding.", x);
+            }
+        }
+
+        return ret;
+    }
+}
diff --git a/src/org/ini4j/OptionHandler.java b/src/main/java/org/ini4j/spi/HandlerBase.java
similarity index 88%
rename from src/org/ini4j/OptionHandler.java
rename to src/main/java/org/ini4j/spi/HandlerBase.java
index 57fb60f..30813b8 100644
--- a/src/org/ini4j/OptionHandler.java
+++ b/src/main/java/org/ini4j/spi/HandlerBase.java
@@ -13,9 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.ini4j;
+package org.ini4j.spi;
 
-public interface OptionHandler
+interface HandlerBase
 {
+    void handleComment(String comment);
+
     void handleOption(String optionName, String optionValue);
 }
diff --git a/src/main/java/org/ini4j/spi/IniBuilder.java b/src/main/java/org/ini4j/spi/IniBuilder.java
new file mode 100644
index 0000000..f54b794
--- /dev/null
+++ b/src/main/java/org/ini4j/spi/IniBuilder.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.ini4j.Config;
+import org.ini4j.Ini;
+import org.ini4j.Profile;
+
+public class IniBuilder extends AbstractProfileBuilder implements IniHandler
+{
+    private Ini _ini;
+
+    public static IniBuilder newInstance(Ini ini)
+    {
+        IniBuilder instance = newInstance();
+
+        instance.setIni(ini);
+
+        return instance;
+    }
+
+    public void setIni(Ini value)
+    {
+        _ini = value;
+    }
+
+    @Override Config getConfig()
+    {
+        return _ini.getConfig();
+    }
+
+    @Override Profile getProfile()
+    {
+        return _ini;
+    }
+
+    private static IniBuilder newInstance()
+    {
+        return ServiceFinder.findService(IniBuilder.class);
+    }
+}
diff --git a/src/main/java/org/ini4j/spi/IniFormatter.java b/src/main/java/org/ini4j/spi/IniFormatter.java
new file mode 100644
index 0000000..da11d71
--- /dev/null
+++ b/src/main/java/org/ini4j/spi/IniFormatter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.ini4j.Config;
+
+import java.io.PrintWriter;
+import java.io.Writer;
+
+public class IniFormatter extends AbstractFormatter implements IniHandler
+{
+    public static IniFormatter newInstance(Writer out, Config config)
+    {
+        IniFormatter instance = newInstance();
+
+        instance.setOutput((out instanceof PrintWriter) ? (PrintWriter) out : new PrintWriter(out));
+        instance.setConfig(config);
+
+        return instance;
+    }
+
+    @Override public void endIni()
+    {
+        getOutput().flush();
+    }
+
+    @Override public void endSection()
+    {
+        getOutput().print(getConfig().getLineSeparator());
+    }
+
+    @Override public void startIni()
+    {
+        assert true;
+    }
+
+    @Override public void startSection(String sectionName)
+    {
+        setHeader(false);
+        if (!getConfig().isGlobalSection() || !sectionName.equals(getConfig().getGlobalSectionName()))
+        {
+            getOutput().print(IniParser.SECTION_BEGIN);
+            getOutput().print(escapeFilter(sectionName));
+            getOutput().print(IniParser.SECTION_END);
+            getOutput().print(getConfig().getLineSeparator());
+        }
+    }
+
+    private static IniFormatter newInstance()
+    {
+        return ServiceFinder.findService(IniFormatter.class);
+    }
+}
diff --git a/src/org/ini4j/IniHandler.java b/src/main/java/org/ini4j/spi/IniHandler.java
similarity index 78%
copy from src/org/ini4j/IniHandler.java
copy to src/main/java/org/ini4j/spi/IniHandler.java
index 9354f5c..83208e6 100644
--- a/src/org/ini4j/IniHandler.java
+++ b/src/main/java/org/ini4j/spi/IniHandler.java
@@ -13,14 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.ini4j;
+package org.ini4j.spi;
 
-public interface IniHandler extends OptionHandler
+public interface IniHandler extends HandlerBase
 {
     void endIni();
 
     void endSection();
 
+    @Override void handleComment(String comment);
+
+    @Override void handleOption(String optionName, String optionValue);
+
     void startIni();
 
     void startSection(String sectionName);
diff --git a/src/main/java/org/ini4j/spi/IniParser.java b/src/main/java/org/ini4j/spi/IniParser.java
new file mode 100644
index 0000000..f0c6841
--- /dev/null
+++ b/src/main/java/org/ini4j/spi/IniParser.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.ini4j.Config;
+import org.ini4j.InvalidFileFormatException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+import java.net.URL;
+
+import java.util.Locale;
+
+public class IniParser extends AbstractParser
+{
+    private static final String COMMENTS = ";#";
+    private static final String OPERATORS = ":=";
+    static final char SECTION_BEGIN = '[';
+    static final char SECTION_END = ']';
+
+    public IniParser()
+    {
+        super(OPERATORS, COMMENTS);
+    }
+
+    public static IniParser newInstance()
+    {
+        return ServiceFinder.findService(IniParser.class);
+    }
+
+    public static IniParser newInstance(Config config)
+    {
+        IniParser instance = newInstance();
+
+        instance.setConfig(config);
+
+        return instance;
+    }
+
+    public void parse(InputStream input, IniHandler handler) throws IOException, InvalidFileFormatException
+    {
+        parse(newIniSource(input, handler), handler);
+    }
+
+    public void parse(Reader input, IniHandler handler) throws IOException, InvalidFileFormatException
+    {
+        parse(newIniSource(input, handler), handler);
+    }
+
+    public void parse(URL input, IniHandler handler) throws IOException, InvalidFileFormatException
+    {
+        parse(newIniSource(input, handler), handler);
+    }
+
+    private void parse(IniSource source, IniHandler handler) throws IOException, InvalidFileFormatException
+    {
+        handler.startIni();
+        String sectionName = null;
+
+        for (String line = source.readLine(); line != null; line = source.readLine())
+        {
+            if (line.charAt(0) == SECTION_BEGIN)
+            {
+                if (sectionName != null)
+                {
+                    handler.endSection();
+                }
+
+                sectionName = parseSectionLine(line, source, handler);
+            }
+            else
+            {
+                if (sectionName == null)
+                {
+                    if (getConfig().isGlobalSection())
+                    {
+                        sectionName = getConfig().getGlobalSectionName();
+                        handler.startSection(sectionName);
+                    }
+                    else
+                    {
+                        parseError(line, source.getLineNumber());
+                    }
+                }
+
+                parseOptionLine(line, handler, source.getLineNumber());
+            }
+        }
+
+        if (sectionName != null)
+        {
+            handler.endSection();
+        }
+
+        handler.endIni();
+    }
+
+    private String parseSectionLine(String line, IniSource source, IniHandler handler) throws InvalidFileFormatException
+    {
+        String sectionName;
+
+        if (line.charAt(line.length() - 1) != SECTION_END)
+        {
+            parseError(line, source.getLineNumber());
+        }
+
+        sectionName = unescapeFilter(line.substring(1, line.length() - 1).trim());
+        if ((sectionName.length() == 0) && !getConfig().isUnnamedSection())
+        {
+            parseError(line, source.getLineNumber());
+        }
+
+        if (getConfig().isLowerCaseSection())
+        {
+            sectionName = sectionName.toLowerCase(Locale.getDefault());
+        }
+
+        handler.startSection(sectionName);
+
+        return sectionName;
+    }
+}
diff --git a/src/main/java/org/ini4j/spi/IniSource.java b/src/main/java/org/ini4j/spi/IniSource.java
new file mode 100644
index 0000000..abcd235
--- /dev/null
+++ b/src/main/java/org/ini4j/spi/IniSource.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.ini4j.Config;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.LineNumberReader;
+import java.io.Reader;
+
+import java.net.URL;
+
+class IniSource
+{
+    public static final char INCLUDE_BEGIN = '<';
+    public static final char INCLUDE_END = '>';
+    public static final char INCLUDE_OPTIONAL = '?';
+    private static final char ESCAPE_CHAR = '\\';
+    private URL _base;
+    private IniSource _chain;
+    private final String _commentChars;
+    private final Config _config;
+    private final HandlerBase _handler;
+    private final LineNumberReader _reader;
+
+    IniSource(InputStream input, HandlerBase handler, String comments, Config config)
+    {
+        this(new UnicodeInputStreamReader(input, config.getFileEncoding()), handler, comments, config);
+    }
+
+    IniSource(Reader input, HandlerBase handler, String comments, Config config)
+    {
+        _reader = new LineNumberReader(input);
+        _handler = handler;
+        _commentChars = comments;
+        _config = config;
+    }
+
+    IniSource(URL input, HandlerBase handler, String comments, Config config) throws IOException
+    {
+        this(new UnicodeInputStreamReader(input.openStream(), config.getFileEncoding()), handler, comments, config);
+        _base = input;
+    }
+
+    int getLineNumber()
+    {
+        int ret;
+
+        if (_chain == null)
+        {
+            ret = _reader.getLineNumber();
+        }
+        else
+        {
+            ret = _chain.getLineNumber();
+        }
+
+        return ret;
+    }
+
+    String readLine() throws IOException
+    {
+        String line;
+
+        if (_chain == null)
+        {
+            line = readLineLocal();
+        }
+        else
+        {
+            line = _chain.readLine();
+            if (line == null)
+            {
+                _chain = null;
+                line = readLine();
+            }
+        }
+
+        return line;
+    }
+
+    private void close() throws IOException
+    {
+        _reader.close();
+    }
+
+    private void handleComment(StringBuilder buff)
+    {
+        if (buff.length() != 0)
+        {
+            buff.deleteCharAt(buff.length() - 1);
+            _handler.handleComment(buff.toString());
+            buff.delete(0, buff.length());
+        }
+    }
+
+    private String handleInclude(String input) throws IOException
+    {
+        String line = input;
+
+        if (_config.isInclude() && (line.length() > 2) && (line.charAt(0) == INCLUDE_BEGIN)
+              && (line.charAt(line.length() - 1) == INCLUDE_END))
+        {
+            line = line.substring(1, line.length() - 1).trim();
+            boolean optional = line.charAt(0) == INCLUDE_OPTIONAL;
+
+            if (optional)
+            {
+                line = line.substring(1).trim();
+            }
+
+            URL loc = (_base == null) ? new URL(line) : new URL(_base, line);
+
+            if (optional)
+            {
+                try
+                {
+                    _chain = new IniSource(loc, _handler, _commentChars, _config);
+                }
+                catch (IOException x)
+                {
+                    assert true;
+                }
+                finally
+                {
+                    line = readLine();
+                }
+            }
+            else
+            {
+                _chain = new IniSource(loc, _handler, _commentChars, _config);
+                line = readLine();
+            }
+        }
+
+        return line;
+    }
+
+    private String readLineLocal() throws IOException
+    {
+        String line = readLineSkipComments();
+
+        if (line == null)
+        {
+            close();
+        }
+        else
+        {
+            line = handleInclude(line);
+        }
+
+        return line;
+    }
+
+    private String readLineSkipComments() throws IOException
+    {
+        String line;
+        StringBuilder comment = new StringBuilder();
+        StringBuilder buff = new StringBuilder();
+
+        for (line = _reader.readLine(); line != null; line = _reader.readLine())
+        {
+            line = line.trim();
+            if (line.length() == 0)
+            {
+                handleComment(comment);
+            }
+            else if ((_commentChars.indexOf(line.charAt(0)) >= 0) && (buff.length() == 0))
+            {
+                comment.append(line.substring(1));
+                comment.append(_config.getLineSeparator());
+            }
+            else
+            {
+                handleComment(comment);
+                int escapeCount = 0;
+
+                for (int i = line.length() - 1; (i >= 0) && (line.charAt(i) == ESCAPE_CHAR); i--)
+                {
+                    escapeCount++;
+                }
+
+                if ((escapeCount & 1) == 0)
+                {
+                    buff.append(line);
+                    line = buff.toString();
+
+                    break;
+                }
+
+                buff.append(line.subSequence(0, line.length() - 1));
+            }
+        }
+
+        // handle end comments
+        if ((line == null) && (comment.length() != 0))
+        {
+            handleComment(comment);
+        }
+
+        return line;
+    }
+}
diff --git a/src/main/java/org/ini4j/spi/OptionsBuilder.java b/src/main/java/org/ini4j/spi/OptionsBuilder.java
new file mode 100644
index 0000000..5dcb4d4
--- /dev/null
+++ b/src/main/java/org/ini4j/spi/OptionsBuilder.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.ini4j.Config;
+import org.ini4j.Options;
+
+public class OptionsBuilder implements OptionsHandler
+{
+    private boolean _header;
+    private String _lastComment;
+    private Options _options;
+
+    public static OptionsBuilder newInstance(Options opts)
+    {
+        OptionsBuilder instance = newInstance();
+
+        instance.setOptions(opts);
+
+        return instance;
+    }
+
+    public void setOptions(Options value)
+    {
+        _options = value;
+    }
+
+    @Override public void endOptions()
+    {
+
+        // comment only .opt file ...
+        if ((_lastComment != null) && _header)
+        {
+            setHeaderComment();
+        }
+    }
+
+    @Override public void handleComment(String comment)
+    {
+        if ((_lastComment != null) && _header)
+        {
+            setHeaderComment();
+            _header = false;
+        }
+
+        _lastComment = comment;
+    }
+
+    @Override public void handleOption(String name, String value)
+    {
+        if (getConfig().isMultiOption())
+        {
+            _options.add(name, value);
+        }
+        else
+        {
+            _options.put(name, value);
+        }
+
+        if (_lastComment != null)
+        {
+            if (_header)
+            {
+                setHeaderComment();
+            }
+            else
+            {
+                putComment(name);
+            }
+
+            _lastComment = null;
+        }
+
+        _header = false;
+    }
+
+    @Override public void startOptions()
+    {
+        if (getConfig().isHeaderComment())
+        {
+            _header = true;
+        }
+    }
+
+    protected static OptionsBuilder newInstance()
+    {
+        return ServiceFinder.findService(OptionsBuilder.class);
+    }
+
+    private Config getConfig()
+    {
+        return _options.getConfig();
+    }
+
+    private void setHeaderComment()
+    {
+        if (getConfig().isComment())
+        {
+            _options.setComment(_lastComment);
+        }
+    }
+
+    private void putComment(String key)
+    {
+        if (getConfig().isComment())
+        {
+            _options.putComment(key, _lastComment);
+        }
+    }
+}
diff --git a/src/main/java/org/ini4j/spi/OptionsFormatter.java b/src/main/java/org/ini4j/spi/OptionsFormatter.java
new file mode 100644
index 0000000..aa858ed
--- /dev/null
+++ b/src/main/java/org/ini4j/spi/OptionsFormatter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.ini4j.Config;
+
+import java.io.PrintWriter;
+import java.io.Writer;
+
+public class OptionsFormatter extends AbstractFormatter implements OptionsHandler
+{
+    public static OptionsFormatter newInstance(Writer out, Config config)
+    {
+        OptionsFormatter instance = newInstance();
+
+        instance.setOutput((out instanceof PrintWriter) ? (PrintWriter) out : new PrintWriter(out));
+        instance.setConfig(config);
+
+        return instance;
+    }
+
+    public void endOptions()
+    {
+        getOutput().flush();
+    }
+
+    public void startOptions()
+    {
+        assert true;
+    }
+
+    private static OptionsFormatter newInstance()
+    {
+        return ServiceFinder.findService(OptionsFormatter.class);
+    }
+}
diff --git a/src/org/ini4j/spi/Warnings.java b/src/main/java/org/ini4j/spi/OptionsHandler.java
similarity index 73%
copy from src/org/ini4j/spi/Warnings.java
copy to src/main/java/org/ini4j/spi/OptionsHandler.java
index 84de3d7..3987e5c 100644
--- a/src/org/ini4j/spi/Warnings.java
+++ b/src/main/java/org/ini4j/spi/OptionsHandler.java
@@ -15,12 +15,13 @@
  */
 package org.ini4j.spi;
 
-public final class Warnings
+public interface OptionsHandler extends HandlerBase
 {
-    public static final String UNCHECKED = "unchecked";
+    void endOptions();
 
-    private Warnings()
-    {
-        assert true;
-    }
+    @Override void handleComment(String comment);
+
+    @Override void handleOption(String optionName, String optionValue);
+
+    void startOptions();
 }
diff --git a/src/org/ini4j/OptionParser.java b/src/main/java/org/ini4j/spi/OptionsParser.java
similarity index 52%
rename from src/org/ini4j/OptionParser.java
rename to src/main/java/org/ini4j/spi/OptionsParser.java
index 9f04e08..7a5fb0f 100644
--- a/src/org/ini4j/OptionParser.java
+++ b/src/main/java/org/ini4j/spi/OptionsParser.java
@@ -13,9 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.ini4j;
+package org.ini4j.spi;
 
-import org.ini4j.spi.ServiceFinder;
+import org.ini4j.Config;
+import org.ini4j.InvalidFileFormatException;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -23,50 +24,53 @@ import java.io.Reader;
 
 import java.net.URL;
 
-public class OptionParser extends AbstractParser
+public class OptionsParser extends AbstractParser
 {
     private static final String COMMENTS = "!#";
     private static final String OPERATORS = ":=";
 
-    public OptionParser()
+    public OptionsParser()
     {
         super(OPERATORS, COMMENTS);
     }
 
-    public static OptionParser newInstance()
+    public static OptionsParser newInstance()
     {
-        return ServiceFinder.findService(OptionParser.class);
+        return ServiceFinder.findService(OptionsParser.class);
     }
 
-    public static OptionParser newInstance(Config config)
+    public static OptionsParser newInstance(Config config)
     {
-        OptionParser instance = newInstance();
+        OptionsParser instance = newInstance();
 
         instance.setConfig(config);
 
         return instance;
     }
 
-    public void parse(InputStream input, OptionHandler handler) throws IOException, InvalidIniFormatException
+    public void parse(InputStream input, OptionsHandler handler) throws IOException, InvalidFileFormatException
     {
-        parse(newIniSource(input), handler);
+        parse(newIniSource(input, handler), handler);
     }
 
-    public void parse(Reader input, OptionHandler handler) throws IOException, InvalidIniFormatException
+    public void parse(Reader input, OptionsHandler handler) throws IOException, InvalidFileFormatException
     {
-        parse(newIniSource(input), handler);
+        parse(newIniSource(input, handler), handler);
     }
 
-    public void parse(URL input, OptionHandler handler) throws IOException, InvalidIniFormatException
+    public void parse(URL input, OptionsHandler handler) throws IOException, InvalidFileFormatException
     {
-        parse(newIniSource(input), handler);
+        parse(newIniSource(input, handler), handler);
     }
 
-    private void parse(IniSource source, OptionHandler handler) throws IOException, InvalidIniFormatException
+    private void parse(IniSource source, OptionsHandler handler) throws IOException, InvalidFileFormatException
     {
+        handler.startOptions();
         for (String line = source.readLine(); line != null; line = source.readLine())
         {
             parseOptionLine(line, handler, source.getLineNumber());
         }
+
+        handler.endOptions();
     }
 }
diff --git a/src/main/java/org/ini4j/spi/RegBuilder.java b/src/main/java/org/ini4j/spi/RegBuilder.java
new file mode 100644
index 0000000..0684bf4
--- /dev/null
+++ b/src/main/java/org/ini4j/spi/RegBuilder.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.ini4j.Config;
+import org.ini4j.Profile;
+import org.ini4j.Reg;
+
+import org.ini4j.Registry.Key;
+import org.ini4j.Registry.Type;
+
+public class RegBuilder extends AbstractProfileBuilder
+{
+    private Reg _reg;
+
+    public static RegBuilder newInstance(Reg reg)
+    {
+        RegBuilder instance = newInstance();
+
+        instance.setReg(reg);
+
+        return instance;
+    }
+
+    public void setReg(Reg value)
+    {
+        _reg = value;
+    }
+
+    @Override public void handleOption(String rawName, String rawValue)
+    {
+        String name = (rawName.charAt(0) == EscapeTool.DOUBLE_QUOTE) ? RegEscapeTool.getInstance().unquote(rawName) : rawName;
+        TypeValuesPair tv = RegEscapeTool.getInstance().decode(rawValue);
+
+        if (tv.getType() != Type.REG_SZ)
+        {
+            ((Key) getCurrentSection()).putType(name, tv.getType());
+        }
+
+        for (String value : tv.getValues())
+        {
+            super.handleOption(name, value);
+        }
+    }
+
+    @Override Config getConfig()
+    {
+        return _reg.getConfig();
+    }
+
+    @Override Profile getProfile()
+    {
+        return _reg;
+    }
+
+    private static RegBuilder newInstance()
+    {
+        return ServiceFinder.findService(RegBuilder.class);
+    }
+}
diff --git a/src/main/java/org/ini4j/spi/RegEscapeTool.java b/src/main/java/org/ini4j/spi/RegEscapeTool.java
new file mode 100644
index 0000000..ce8429f
--- /dev/null
+++ b/src/main/java/org/ini4j/spi/RegEscapeTool.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.ini4j.Registry;
+
+import org.ini4j.Registry.Type;
+
+import java.io.UnsupportedEncodingException;
+
+import java.nio.charset.Charset;
+
+import java.util.Arrays;
+
+public class RegEscapeTool extends EscapeTool
+{
+    private static final RegEscapeTool INSTANCE = ServiceFinder.findService(RegEscapeTool.class);
+    private static final Charset HEX_CHARSET = Charset.forName("UTF-16LE");
+    private static final int LOWER_DIGIT = 0x0f;
+    private static final int UPPER_DIGIT = 0xf0;
+    private static final int DIGIT_SIZE = 4;
+
+    public static final RegEscapeTool getInstance()
+    {
+        return INSTANCE;
+    }
+
+    public TypeValuesPair decode(String raw)
+    {
+        Type type = type(raw);
+        String value = (type == Type.REG_SZ) ? unquote(raw) : raw.substring(type.toString().length() + 1);
+        String[] values;
+
+        switch (type)
+        {
+
+            case REG_EXPAND_SZ:
+            case REG_MULTI_SZ:
+                value = bytes2string(binary(value));
+                break;
+
+            case REG_DWORD:
+                value = String.valueOf(Long.parseLong(value, HEX_RADIX));
+                break;
+
+            case REG_SZ:
+                break;
+
+            default:
+                break;
+        }
+
+        if (type == Type.REG_MULTI_SZ)
+        {
+            values = splitMulti(value);
+        }
+        else
+        {
+            values = new String[] { value };
+        }
+
+        return new TypeValuesPair(type, values);
+    }
+
+    public String encode(TypeValuesPair data)
+    {
+        String ret = null;
+
+        if (data.getType() == Type.REG_SZ)
+        {
+            ret = quote(data.getValues()[0]);
+        }
+        else if (data.getValues()[0] != null)
+        {
+            ret = encode(data.getType(), data.getValues());
+        }
+
+        return ret;
+    }
+
+    byte[] binary(String value)
+    {
+        byte[] bytes = new byte[value.length()];
+        int idx = 0;
+        int shift = DIGIT_SIZE;
+
+        for (int i = 0; i < value.length(); i++)
+        {
+            char c = value.charAt(i);
+
+            if (Character.isWhitespace(c))
+            {
+                continue;
+            }
+
+            if (c == ',')
+            {
+                idx++;
+                shift = DIGIT_SIZE;
+            }
+            else
+            {
+                int digit = Character.digit(c, HEX_RADIX);
+
+                if (digit >= 0)
+                {
+                    bytes[idx] |= digit << shift;
+                    shift = 0;
+                }
+            }
+        }
+
+        return Arrays.copyOfRange(bytes, 0, idx + 1);
+    }
+
+    String encode(Type type, String[] values)
+    {
+        StringBuilder buff = new StringBuilder();
+
+        buff.append(type.toString());
+        buff.append(Type.SEPARATOR_CHAR);
+        switch (type)
+        {
+
+            case REG_EXPAND_SZ:
+                buff.append(hexadecimal(values[0]));
+                break;
+
+            case REG_DWORD:
+                buff.append(String.format("%08x", Long.parseLong(values[0])));
+                break;
+
+            case REG_MULTI_SZ:
+                int n = values.length;
+
+                for (int i = 0; i < n; i++)
+                {
+                    buff.append(hexadecimal(values[i]));
+                    buff.append(',');
+                }
+
+                buff.append("00,00");
+                break;
+
+            default:
+                buff.append(values[0]);
+                break;
+        }
+
+        return buff.toString();
+    }
+
+    String hexadecimal(String value)
+    {
+        StringBuilder buff = new StringBuilder();
+
+        if ((value != null) && (value.length() != 0))
+        {
+            byte[] bytes = string2bytes(value);
+
+            for (int i = 0; i < bytes.length; i++)
+            {
+                buff.append(Character.forDigit((bytes[i] & UPPER_DIGIT) >> DIGIT_SIZE, HEX_RADIX));
+                buff.append(Character.forDigit(bytes[i] & LOWER_DIGIT, HEX_RADIX));
+                buff.append(',');
+            }
+
+            buff.append("00,00");
+        }
+
+        return buff.toString();
+    }
+
+    Registry.Type type(String raw)
+    {
+        Registry.Type type;
+
+        if (raw.charAt(0) == DOUBLE_QUOTE)
+        {
+            type = Registry.Type.REG_SZ;
+        }
+        else
+        {
+            int idx = raw.indexOf(Registry.TYPE_SEPARATOR);
+
+            type = (idx < 0) ? Registry.Type.REG_SZ : Registry.Type.fromString(raw.substring(0, idx));
+        }
+
+        return type;
+    }
+
+    // XXX Java 1.4 compatibility hack
+    private String bytes2string(byte[] bytes)
+    {
+        String str;
+
+        try
+        {
+            str = new String(bytes, 0, bytes.length - 2, HEX_CHARSET);
+        }
+        catch (NoSuchMethodError x)
+        {
+            try
+            {
+                str = new String(bytes, 0, bytes.length, HEX_CHARSET.name());
+            }
+            catch (UnsupportedEncodingException ex)
+            {
+                throw new IllegalStateException(ex);
+            }
+        }
+
+        return str;
+    }
+
+    private String[] splitMulti(String value)
+    {
+        int len = value.length();
+        int start;
+        int end;
+        int n = 0;
+
+        start = 0;
+        for (end = value.indexOf(0, start); end >= 0; end = value.indexOf(0, start))
+        {
+            n++;
+            start = end + 1;
+            if (start >= len)
+            {
+                break;
+            }
+        }
+
+        String[] values = new String[n];
+
+        start = 0;
+        for (int i = 0; i < n; i++)
+        {
+            end = value.indexOf(0, start);
+            values[i] = value.substring(start, end);
+            start = end + 1;
+        }
+
+        return values;
+    }
+
+    // XXX Java 1.4 compatibility hack
+    private byte[] string2bytes(String value)
+    {
+        byte[] bytes;
+
+        try
+        {
+            bytes = value.getBytes(HEX_CHARSET);
+        }
+        catch (NoSuchMethodError x)
+        {
+            try
+            {
+                bytes = value.getBytes(HEX_CHARSET.name());
+            }
+            catch (UnsupportedEncodingException ex)
+            {
+                throw new IllegalStateException(ex);
+            }
+        }
+
+        return bytes;
+    }
+}
diff --git a/src/org/ini4j/spi/ServiceFinder.java b/src/main/java/org/ini4j/spi/ServiceFinder.java
similarity index 98%
rename from src/org/ini4j/spi/ServiceFinder.java
rename to src/main/java/org/ini4j/spi/ServiceFinder.java
index 6ae47dc..8e70ff2 100644
--- a/src/org/ini4j/spi/ServiceFinder.java
+++ b/src/main/java/org/ini4j/spi/ServiceFinder.java
@@ -25,7 +25,7 @@ import java.io.InputStreamReader;
  * @author Szkiba Iv�n
  * @version $Name:  $
  */
-public final class ServiceFinder
+final class ServiceFinder
 {
     private static final String SERVICES_PATH = "META-INF/services/";
 
@@ -52,7 +52,7 @@ public final class ServiceFinder
      * @throws IllegalArgumentException keres�si vagy p�ld�nyos�t�si hiba eset�n
      * @return a keresett oszt�ly implement�l� objektum
      */
-    public static <T> T findService(Class<T> clazz)
+    static <T> T findService(Class<T> clazz)
     {
         try
         {
diff --git a/src/org/ini4j/spi/Warnings.java b/src/main/java/org/ini4j/spi/TypeValuesPair.java
similarity index 61%
copy from src/org/ini4j/spi/Warnings.java
copy to src/main/java/org/ini4j/spi/TypeValuesPair.java
index 84de3d7..2b1af21 100644
--- a/src/org/ini4j/spi/Warnings.java
+++ b/src/main/java/org/ini4j/spi/TypeValuesPair.java
@@ -15,12 +15,27 @@
  */
 package org.ini4j.spi;
 
-public final class Warnings
+import org.ini4j.Registry.Type;
+
+public class TypeValuesPair
 {
-    public static final String UNCHECKED = "unchecked";
+    private final Type _type;
+    private final String[] _values;
+
+    @SuppressWarnings("PMD.ArrayIsStoredDirectly")
+    public TypeValuesPair(Type type, String[] values)
+    {
+        _type = type;
+        _values = values;
+    }
+
+    public Type getType()
+    {
+        return _type;
+    }
 
-    private Warnings()
+    public String[] getValues()
     {
-        assert true;
+        return _values;
     }
 }
diff --git a/src/main/java/org/ini4j/spi/UnicodeInputStreamReader.java b/src/main/java/org/ini4j/spi/UnicodeInputStreamReader.java
new file mode 100644
index 0000000..eeb5bd3
--- /dev/null
+++ b/src/main/java/org/ini4j/spi/UnicodeInputStreamReader.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PushbackInputStream;
+import java.io.Reader;
+
+import java.nio.charset.Charset;
+
+class UnicodeInputStreamReader extends Reader
+{
+    private static final int BOM_SIZE = 4;
+
+    private static enum Bom
+    {
+        UTF32BE("UTF-32BE", new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0xFE, (byte) 0xFF }),
+        UTF32LE("UTF-32LE", new byte[] { (byte) 0xFF, (byte) 0xFE, (byte) 0x00, (byte) 0x00 }),
+        UTF16BE("UTF-16BE", new byte[] { (byte) 0xFE, (byte) 0xFF }),
+        UTF16LE("UTF-16LE", new byte[] { (byte) 0xFF, (byte) 0xFE }),
+        UTF8("UTF-8", new byte[] { (byte) 0xEF, (byte) 0xBB, (byte) 0xBF });
+        private final byte[] _bytes;
+        private Charset _charset;
+
+        @SuppressWarnings("PMD.ArrayIsStoredDirectly")
+        private Bom(String charsetName, byte[] bytes)
+        {
+            try
+            {
+                _charset = Charset.forName(charsetName);
+            }
+            catch (Exception x)
+            {
+                _charset = null;
+            }
+
+            _bytes = bytes;
+        }
+
+        private static Bom find(byte[] data)
+        {
+            Bom ret = null;
+
+            for (Bom bom : values())
+            {
+                if (bom.supported() && bom.match(data))
+                {
+                    ret = bom;
+
+                    break;
+                }
+            }
+
+            return ret;
+        }
+
+        private boolean match(byte[] data)
+        {
+            boolean ok = true;
+
+            for (int i = 0; i < _bytes.length; i++)
+            {
+                if (data[i] != _bytes[i])
+                {
+                    ok = false;
+
+                    break;
+                }
+            }
+
+            return ok;
+        }
+
+        private boolean supported()
+        {
+            return _charset != null;
+        }
+    }
+
+    private final Charset _defaultEncoding;
+    private InputStreamReader _reader;
+    private final PushbackInputStream _stream;
+
+    UnicodeInputStreamReader(InputStream in, Charset defaultEnc)
+    {
+        _stream = new PushbackInputStream(in, BOM_SIZE);
+        _defaultEncoding = defaultEnc;
+    }
+
+    public void close() throws IOException
+    {
+        init();
+        _reader.close();
+    }
+
+    public int read(char[] cbuf, int off, int len) throws IOException
+    {
+        init();
+
+        return _reader.read(cbuf, off, len);
+    }
+
+    /**
+     * Read-ahead four bytes and check for BOM marks. Extra bytes are
+     * unread back to the stream, only BOM bytes are skipped.
+     */
+    protected void init() throws IOException
+    {
+        if (_reader != null)
+        {
+            return;
+        }
+
+        Charset encoding;
+        byte[] data = new byte[BOM_SIZE];
+        int n;
+        int unread;
+
+        n = _stream.read(data, 0, data.length);
+        Bom bom = Bom.find(data);
+
+        if (bom == null)
+        {
+            encoding = _defaultEncoding;
+            unread = n;
+        }
+        else
+        {
+            encoding = bom._charset;
+            unread = data.length - bom._bytes.length;
+        }
+
+        if (unread > 0)
+        {
+            _stream.unread(data, (n - unread), unread);
+        }
+
+        _reader = new InputStreamReader(_stream, encoding);
+    }
+}
diff --git a/src/org/ini4j/spi/Warnings.java b/src/main/java/org/ini4j/spi/Warnings.java
similarity index 100%
copy from src/org/ini4j/spi/Warnings.java
copy to src/main/java/org/ini4j/spi/Warnings.java
diff --git a/src/main/java/org/ini4j/spi/WinEscapeTool.java b/src/main/java/org/ini4j/spi/WinEscapeTool.java
new file mode 100644
index 0000000..c2e3abf
--- /dev/null
+++ b/src/main/java/org/ini4j/spi/WinEscapeTool.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+public class WinEscapeTool extends EscapeTool
+{
+    private static final int ANSI_HEX_DIGITS = 2;
+    private static final int ANSI_OCTAL_DIGITS = 3;
+    private static final int OCTAL_RADIX = 8;
+    private static final WinEscapeTool INSTANCE = new WinEscapeTool();
+
+    public static WinEscapeTool getInstance()
+    {
+        return INSTANCE;
+    }
+
+    @Override void escapeBinary(StringBuilder buff, char c)
+    {
+        buff.append("\\x");
+        buff.append(HEX[(c >>> HEX_DIGIT_3_OFFSET) & HEX_DIGIT_MASK]);
+        buff.append(HEX[c & HEX_DIGIT_MASK]);
+    }
+
+    @Override int unescapeBinary(StringBuilder buff, char escapeType, String line, int index)
+    {
+        int ret = index;
+
+        if (escapeType == 'x')
+        {
+            try
+            {
+                buff.append((char) Integer.parseInt(line.substring(index, index + ANSI_HEX_DIGITS), HEX_RADIX));
+                ret = index + ANSI_HEX_DIGITS;
+            }
+            catch (Exception x)
+            {
+                throw new IllegalArgumentException("Malformed \\xHH encoding.", x);
+            }
+        }
+        else if (escapeType == 'o')
+        {
+            try
+            {
+                buff.append((char) Integer.parseInt(line.substring(index, index + ANSI_OCTAL_DIGITS), OCTAL_RADIX));
+                ret = index + ANSI_OCTAL_DIGITS;
+            }
+            catch (Exception x)
+            {
+                throw new IllegalArgumentException("Malformed \\oOO encoding.", x);
+            }
+        }
+
+        return ret;
+    }
+}
diff --git a/src/org/ini4j/spi/package.html b/src/main/java/org/ini4j/spi/package.html
similarity index 100%
rename from src/org/ini4j/spi/package.html
rename to src/main/java/org/ini4j/spi/package.html
diff --git a/src/org/ini4j/Config.java b/src/org/ini4j/Config.java
deleted file mode 100644
index 5ef443d..0000000
--- a/src/org/ini4j/Config.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j;
-
-public class Config implements Cloneable
-{
-    public static final String KEY_PREFIX = "org.ini4j.config.";
-    public static final String PROP_EMPTY_OPTION = "emptyOption";
-    public static final String PROP_GLOBAL_SECTION = "globalSection";
-    public static final String PROP_GLOBAL_SECTION_NAME = "globalSectionName";
-    public static final String PROP_INCLUDE = "include";
-    public static final String PROP_LOWER_CASE_OPTION = "lowerCaseOption";
-    public static final String PROP_LOWER_CASE_SECTION = "lowerCaseSection";
-    public static final String PROP_MULTI_OPTION = "multiOption";
-    public static final String PROP_MULTI_SECTION = "multiSection";
-    public static final String PROP_STRICT_OPERATOR = "strictOperator";
-    public static final String PROP_UNNAMED_SECTION = "unnamedSection";
-    public static final String PROP_ESCAPE = "escape";
-    public static final boolean DEFAULT_EMPTY_OPTION = false;
-    public static final boolean DEFAULT_GLOBAL_SECTION = false;
-    public static final String DEFAULT_GLOBAL_SECTION_NAME = "?";
-    public static final boolean DEFAULT_INCLUDE = false;
-    public static final boolean DEFAULT_LOWER_CASE_OPTION = false;
-    public static final boolean DEFAULT_LOWER_CASE_SECTION = false;
-    public static final boolean DEFAULT_MULTI_OPTION = true;
-    public static final boolean DEFAULT_MULTI_SECTION = false;
-    public static final boolean DEFAULT_STRICT_OPERATOR = false;
-    public static final boolean DEFAULT_UNNAMED_SECTION = false;
-    public static final boolean DEFAULT_ESCAPE = true;
-    private static final Config GLOBAL = new Config();
-    private boolean _emptyOption;
-    private boolean _escape;
-    private boolean _globalSection;
-    private String _globalSectionName;
-    private boolean _include;
-    private boolean _lowerCaseOption;
-    private boolean _lowerCaseSection;
-    private boolean _multiOption;
-    private boolean _multiSection;
-    private boolean _strictOperator;
-    private boolean _unnamedSection;
-
-    public Config()
-    {
-        reset();
-    }
-
-    public static Config getGlobal()
-    {
-        return GLOBAL;
-    }
-
-    public boolean isEscape()
-    {
-        return _escape;
-    }
-
-    public boolean isInclude()
-    {
-        return _include;
-    }
-
-    public void setEmptyOption(boolean value)
-    {
-        _emptyOption = value;
-    }
-
-    public void setEscape(boolean value)
-    {
-        _escape = value;
-    }
-
-    public void setGlobalSection(boolean value)
-    {
-        _globalSection = value;
-    }
-
-    public String getGlobalSectionName()
-    {
-        return _globalSectionName;
-    }
-
-    public void setGlobalSectionName(String value)
-    {
-        _globalSectionName = value;
-    }
-
-    public void setInclude(boolean value)
-    {
-        _include = value;
-    }
-
-    public void setLowerCaseOption(boolean value)
-    {
-        _lowerCaseOption = value;
-    }
-
-    public void setLowerCaseSection(boolean value)
-    {
-        _lowerCaseSection = value;
-    }
-
-    public void setMultiOption(boolean value)
-    {
-        _multiOption = value;
-    }
-
-    public void setMultiSection(boolean value)
-    {
-        _multiSection = value;
-    }
-
-    public boolean isEmptyOption()
-    {
-        return _emptyOption;
-    }
-
-    public boolean isGlobalSection()
-    {
-        return _globalSection;
-    }
-
-    public boolean isLowerCaseOption()
-    {
-        return _lowerCaseOption;
-    }
-
-    public boolean isLowerCaseSection()
-    {
-        return _lowerCaseSection;
-    }
-
-    public boolean isMultiOption()
-    {
-        return _multiOption;
-    }
-
-    public boolean isMultiSection()
-    {
-        return _multiSection;
-    }
-
-    public boolean isUnnamedSection()
-    {
-        return _unnamedSection;
-    }
-
-    public boolean isStrictOperator()
-    {
-        return _strictOperator;
-    }
-
-    public void setStrictOperator(boolean value)
-    {
-        _strictOperator = value;
-    }
-
-    public void setUnnamedSection(boolean value)
-    {
-        _unnamedSection = value;
-    }
-
-    @Override public Config clone()
-    {
-        try
-        {
-            return (Config) super.clone();
-        }
-        catch (CloneNotSupportedException x)
-        {
-            throw new AssertionError(x);
-        }
-    }
-
-    public final void reset()
-    {
-        _emptyOption = getBoolean(PROP_EMPTY_OPTION, DEFAULT_EMPTY_OPTION);
-        _globalSection = getBoolean(PROP_GLOBAL_SECTION, DEFAULT_GLOBAL_SECTION);
-        _globalSectionName = getString(PROP_GLOBAL_SECTION_NAME, DEFAULT_GLOBAL_SECTION_NAME);
-        _include = getBoolean(PROP_INCLUDE, DEFAULT_INCLUDE);
-        _lowerCaseOption = getBoolean(PROP_LOWER_CASE_OPTION, DEFAULT_LOWER_CASE_OPTION);
-        _lowerCaseSection = getBoolean(PROP_LOWER_CASE_SECTION, DEFAULT_LOWER_CASE_SECTION);
-        _multiOption = getBoolean(PROP_MULTI_OPTION, DEFAULT_MULTI_OPTION);
-        _multiSection = getBoolean(PROP_MULTI_SECTION, DEFAULT_MULTI_SECTION);
-        _strictOperator = getBoolean(PROP_STRICT_OPERATOR, DEFAULT_STRICT_OPERATOR);
-        _unnamedSection = getBoolean(PROP_UNNAMED_SECTION, DEFAULT_UNNAMED_SECTION);
-        _escape = getBoolean(PROP_ESCAPE, DEFAULT_ESCAPE);
-    }
-
-    private boolean getBoolean(String name, boolean defaultValue)
-    {
-        String key = KEY_PREFIX + name;
-
-        return System.getProperties().containsKey(key) ? Boolean.getBoolean(key) : defaultValue;
-    }
-
-    private String getString(String name, String defaultValue)
-    {
-        String key = KEY_PREFIX + name;
-
-        return System.getProperties().containsKey(key) ? System.getProperty(key) : defaultValue;
-    }
-}
diff --git a/src/org/ini4j/Ini.java b/src/org/ini4j/Ini.java
deleted file mode 100644
index 89eb5c9..0000000
--- a/src/org/ini4j/Ini.java
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j;
-
-import org.ini4j.spi.AbstractBeanInvocationHandler;
-import org.ini4j.spi.IniFormatter;
-import org.ini4j.spi.XMLFormatter;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.io.Writer;
-
-import java.lang.reflect.Array;
-import java.lang.reflect.Proxy;
-
-import java.net.URL;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class Ini extends MultiMapImpl<String, Ini.Section>
-{
-    private static final String SECTION_SYSTEM_PROPERTIES = "@prop";
-    private static final String SECTION_ENVIRONMENT = "@env";
-    private static final Pattern EXPRESSION = Pattern.compile("(?<!\\\\)\\$\\{(([^\\[]+)(\\[([0-9]+)\\])?/)?([^\\[]+)(\\[(([0-9]+))\\])?\\}");
-    private static final int G_SECTION = 2;
-    private static final int G_SECTION_IDX = 4;
-    private static final int G_OPTION = 5;
-    private static final int G_OPTION_IDX = 7;
-    private Map<Class, Object> _beans;
-    private Config _config = Config.getGlobal();
-
-    public Ini()
-    {
-        assert true;
-    }
-
-    public Ini(Reader input) throws IOException, InvalidIniFormatException
-    {
-        this();
-        load(input);
-    }
-
-    public Ini(InputStream input) throws IOException, InvalidIniFormatException
-    {
-        this();
-        load(input);
-    }
-
-    public Ini(URL input) throws IOException, InvalidIniFormatException
-    {
-        this();
-        load(input);
-    }
-
-    public void setConfig(Config value)
-    {
-        _config = value;
-    }
-
-    public Section add(String name)
-    {
-        Section s = new Section(name);
-
-        if (getConfig().isMultiSection())
-        {
-            add(name, s);
-        }
-        else
-        {
-            put(name, s);
-        }
-
-        return s;
-    }
-
-    public <T> T as(Class<T> clazz)
-    {
-        return clazz.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { clazz }, new BeanInvocationHandler()));
-    }
-
-    public void load(InputStream input) throws IOException, InvalidIniFormatException
-    {
-        IniParser.newInstance(getConfig()).parse(input, new Builder());
-    }
-
-    public void load(Reader input) throws IOException, InvalidIniFormatException
-    {
-        IniParser.newInstance(getConfig()).parse(input, new Builder());
-    }
-
-    public void load(URL input) throws IOException, InvalidIniFormatException
-    {
-        IniParser.newInstance(getConfig()).parse(input, new Builder());
-    }
-
-    public void loadFromXML(InputStream input) throws IOException, InvalidIniFormatException
-    {
-        loadFromXML(new InputStreamReader(input));
-    }
-
-    public void loadFromXML(Reader input) throws IOException, InvalidIniFormatException
-    {
-        Builder builder = new Builder();
-
-        IniParser.newInstance(getConfig()).parseXML(input, builder);
-    }
-
-    public void loadFromXML(URL input) throws IOException, InvalidIniFormatException
-    {
-        Builder builder = new Builder();
-
-        IniParser.newInstance(getConfig()).parseXML(input, builder);
-    }
-
-    public Section remove(Section section)
-    {
-        return remove((Object) section.getName());
-    }
-
-    public void store(OutputStream output) throws IOException
-    {
-        store(IniFormatter.newInstance(output, getConfig()));
-    }
-
-    public void store(Writer output) throws IOException
-    {
-        store(IniFormatter.newInstance(output, getConfig()));
-    }
-
-    public void storeToXML(OutputStream output) throws IOException
-    {
-        store(XMLFormatter.newInstance(output));
-    }
-
-    public void storeToXML(Writer output) throws IOException
-    {
-        store(XMLFormatter.newInstance(output));
-    }
-
-    @Deprecated public synchronized <T> T to(Class<T> clazz)
-    {
-        Object bean = null;
-
-        if (_beans == null)
-        {
-            _beans = new HashMap<Class, Object>();
-        }
-        else
-        {
-            bean = _beans.get(clazz);
-        }
-
-        if (bean == null)
-        {
-            bean = as(clazz);
-            _beans.put(clazz, bean);
-        }
-
-        return clazz.cast(bean);
-    }
-
-    protected Config getConfig()
-    {
-        return _config;
-    }
-
-    protected void resolve(StringBuilder buffer, Section owner)
-    {
-        Matcher m = EXPRESSION.matcher(buffer);
-
-        while (m.find())
-        {
-            String sectionName = m.group(G_SECTION);
-            String optionName = m.group(G_OPTION);
-            int optionIndex = parseOptionIndex(m);
-            Section section = parseSection(m, owner);
-            String value = null;
-
-            if (SECTION_ENVIRONMENT.equals(sectionName))
-            {
-                value = System.getenv(optionName);
-            }
-            else if (SECTION_SYSTEM_PROPERTIES.equals(sectionName))
-            {
-                value = System.getProperty(optionName);
-            }
-            else if (section != null)
-            {
-                value = (optionIndex == -1) ? section.fetch(optionName) : section.fetch(optionName, optionIndex);
-            }
-
-            if (value != null)
-            {
-                buffer.replace(m.start(), m.end(), value);
-                m.reset(buffer);
-            }
-        }
-    }
-
-    protected void store(IniHandler formatter) throws IOException
-    {
-        formatter.startIni();
-        for (Ini.Section s : values())
-        {
-            formatter.startSection(s.getName());
-            for (String name : s.keySet())
-            {
-                int n = getConfig().isMultiOption() ? s.length(name) : 1;
-
-                for (int i = 0; i < n; i++)
-                {
-                    formatter.handleOption(name, s.get(name, i));
-                }
-            }
-
-            formatter.endSection();
-        }
-
-        formatter.endIni();
-    }
-
-    private int parseOptionIndex(Matcher m)
-    {
-        return (m.group(G_OPTION_IDX) == null) ? -1 : Integer.parseInt(m.group(G_OPTION_IDX));
-    }
-
-    private Section parseSection(Matcher m, Section owner)
-    {
-        String sectionName = m.group(G_SECTION);
-        int sectionIndex = parseSectionIndex(m);
-
-        return (sectionName == null) ? owner : ((sectionIndex == -1) ? get(sectionName) : get(sectionName, sectionIndex));
-    }
-
-    private int parseSectionIndex(Matcher m)
-    {
-        return (m.group(G_SECTION_IDX) == null) ? -1 : Integer.parseInt(m.group(G_SECTION_IDX));
-    }
-
-    public class Section extends OptionMapImpl
-    {
-        private Map<Class, Object> _beans;
-        private final String _name;
-
-        public Section(String name)
-        {
-            super();
-            _name = name;
-        }
-
-        public String getName()
-        {
-            return _name;
-        }
-
-        @Deprecated public synchronized <T> T to(Class<T> clazz)
-        {
-            Object bean = null;
-
-            if (_beans == null)
-            {
-                _beans = new HashMap<Class, Object>();
-            }
-            else
-            {
-                bean = _beans.get(clazz);
-            }
-
-            if (bean == null)
-            {
-                bean = as(clazz);
-                _beans.put(clazz, bean);
-            }
-
-            return clazz.cast(bean);
-        }
-
-        @Override protected void resolve(StringBuilder buffer)
-        {
-            Ini.this.resolve(buffer, this);
-        }
-    }
-
-    private class BeanInvocationHandler extends AbstractBeanInvocationHandler
-    {
-        private final MultiMap<String, Object> _sectionBeans = new MultiMapImpl<String, Object>();
-
-        @Override protected Object getPropertySpi(String property, Class<?> clazz)
-        {
-            Object o = null;
-
-            if (clazz.isArray())
-            {
-                if (!_sectionBeans.containsKey(property) && containsKey(property))
-                {
-                    for (int i = 0; i < length(property); i++)
-                    {
-                        _sectionBeans.add(property, get(property, i).as(clazz.getComponentType()));
-                    }
-                }
-
-                if (_sectionBeans.containsKey(property))
-                {
-                    o = Array.newInstance(clazz.getComponentType(), _sectionBeans.length(property));
-                    for (int i = 0; i < _sectionBeans.length(property); i++)
-                    {
-                        Array.set(o, i, _sectionBeans.get(property, i));
-                    }
-                }
-            }
-            else
-            {
-                o = _sectionBeans.get(property);
-                if (o == null)
-                {
-                    Section section = get(property);
-
-                    if (section != null)
-                    {
-                        o = section.as(clazz);
-                        _sectionBeans.put(property, o);
-                    }
-                }
-            }
-
-            return o;
-        }
-
-        @Override protected void setPropertySpi(String property, Object value, Class<?> clazz)
-        {
-            remove(property);
-            if (value != null)
-            {
-                if (clazz.isArray())
-                {
-                    for (int i = 0; i < Array.getLength(value); i++)
-                    {
-                        Section sec = add(property);
-
-                        sec.from(Array.get(value, i));
-                    }
-                }
-                else
-                {
-                    Section sec = add(property);
-
-                    sec.from(value);
-                }
-            }
-        }
-
-        @Override protected boolean hasPropertySpi(String property)
-        {
-            return containsKey(property);
-        }
-    }
-
-    private class Builder implements IniHandler
-    {
-        private Section _currentSection;
-
-        public void endIni()
-        {
-            assert true;
-        }
-
-        @Override public void endSection()
-        {
-            _currentSection = null;
-        }
-
-        @Override public void handleOption(String name, String value)
-        {
-            if (getConfig().isMultiOption())
-            {
-                _currentSection.add(name, value);
-            }
-            else
-            {
-                _currentSection.put(name, value);
-            }
-        }
-
-        public void startIni()
-        {
-            assert true;
-        }
-
-        @Override public void startSection(String sectionName)
-        {
-            if (getConfig().isMultiSection())
-            {
-                _currentSection = add(sectionName);
-            }
-            else
-            {
-                Section s = get(sectionName);
-
-                _currentSection = (s == null) ? add(sectionName) : s;
-            }
-        }
-    }
-}
diff --git a/src/org/ini4j/IniFile.java b/src/org/ini4j/IniFile.java
deleted file mode 100644
index a835f66..0000000
--- a/src/org/ini4j/IniFile.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j;
-
-import java.io.File;
-import java.io.FileWriter;
-
-import java.util.prefs.BackingStoreException;
-
-public class IniFile extends IniPreferences
-{
-    public static enum Mode
-    {
-        RO,
-        WO,
-        RW
-    }
-
-    private final File _file;
-    private final Mode _mode;
-
-    public IniFile(File file) throws BackingStoreException
-    {
-        this(file, Mode.RO);
-    }
-
-    public IniFile(File file, Mode mode) throws BackingStoreException
-    {
-        super(new Ini());
-        _file = file;
-        _mode = mode;
-        if ((_mode == Mode.RO) || ((_mode != Mode.WO) && _file.exists()))
-        {
-            sync();
-        }
-    }
-
-    public File getFile()
-    {
-        return _file;
-    }
-
-    public Mode getMode()
-    {
-        return _mode;
-    }
-
-    @Override public void flush() throws BackingStoreException
-    {
-        if (_mode == Mode.RO)
-        {
-            throw new BackingStoreException("read only instance");
-        }
-
-        try
-        {
-            synchronized (lock)
-            {
-                FileWriter writer = new FileWriter(_file);
-
-                try
-                {
-                    getIni().store(writer);
-                }
-                finally
-                {
-                    writer.close();
-                }
-            }
-        }
-        catch (Exception x)
-        {
-            throw new BackingStoreException(x);
-        }
-    }
-
-    @Override public void sync() throws BackingStoreException
-    {
-        if (_mode == Mode.WO)
-        {
-            throw new BackingStoreException("write only instance");
-        }
-
-        try
-        {
-            synchronized (lock)
-            {
-                getIni().load(_file.toURI().toURL());
-            }
-        }
-        catch (Exception x)
-        {
-            throw new BackingStoreException(x);
-        }
-    }
-}
diff --git a/src/org/ini4j/IniParser.java b/src/org/ini4j/IniParser.java
deleted file mode 100644
index cbf42af..0000000
--- a/src/org/ini4j/IniParser.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j;
-
-import org.ini4j.spi.ServiceFinder;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-
-import java.net.URL;
-
-import java.util.Locale;
-
-import javax.xml.parsers.SAXParserFactory;
-
-public class IniParser extends AbstractParser
-{
-    private static final String COMMENTS = ";#";
-    private static final String OPERATORS = ":=";
-
-    public static final char SECTION_BEGIN = '[';
-    public static final char SECTION_END = ']';
-
-    public IniParser()
-    {
-        super(OPERATORS, COMMENTS);
-    }
-
-    public static IniParser newInstance()
-    {
-        return ServiceFinder.findService(IniParser.class);
-    }
-
-    public static IniParser newInstance(Config config)
-    {
-        IniParser instance = newInstance();
-
-        instance.setConfig(config);
-
-        return instance;
-    }
-
-    public void parse(InputStream input, IniHandler handler) throws IOException, InvalidIniFormatException
-    {
-        parse(newIniSource(input), handler);
-    }
-
-    public void parse(Reader input, IniHandler handler) throws IOException, InvalidIniFormatException
-    {
-        parse(newIniSource(input), handler);
-    }
-
-    public void parse(URL input, IniHandler handler) throws IOException, InvalidIniFormatException
-    {
-        parse(newIniSource(input), handler);
-    }
-
-    public void parseXML(InputStream input, IniHandler handler) throws IOException, InvalidIniFormatException
-    {
-        parseXML(new InputStreamReader(input), handler);
-    }
-
-    public void parseXML(Reader input, final IniHandler handler) throws IOException, InvalidIniFormatException
-    {
-        class XmlToIni extends DefaultHandler
-        {
-            private static final String TAG_SECTION = "section";
-            private static final String TAG_OPTION = "option";
-            private static final String TAG_INI = "ini";
-            private static final String ATTR_KEY = "key";
-            private static final String ATTR_VALUE = "value";
-            private static final String ATTR_VERSION = "version";
-            private static final String CURRENT_VERSION = "1.0";
-
-            @Override public void startElement(String uri, String localName, String qname, Attributes attrs) throws SAXException
-            {
-                String key = attrs.getValue(ATTR_KEY);
-
-                if (qname.equals(TAG_INI))
-                {
-                    String ver = attrs.getValue(ATTR_VERSION);
-
-                    if ((ver == null) || !ver.equals(CURRENT_VERSION))
-                    {
-                        throw new SAXException("Missing or invalid 'version' attribute");
-                    }
-
-                    handler.startIni();
-                }
-                else
-                {
-                    if (key == null)
-                    {
-                        throw new SAXException("missing '" + ATTR_KEY + "' attribute");
-                    }
-
-                    if (qname.equals(TAG_SECTION))
-                    {
-                        handler.startSection(key);
-                    }
-                    else if (qname.equals(TAG_OPTION))
-                    {
-                        handler.handleOption(key, attrs.getValue(ATTR_VALUE));
-                    }
-                    else
-                    {
-                        throw new SAXException("Invalid element: " + qname);
-                    }
-                }
-            }
-
-            @Override public void endElement(String uri, String localName, String qname) throws SAXException
-            {
-                if (qname.equals(TAG_SECTION))
-                {
-                    handler.endSection();
-                }
-                else if (qname.equals(TAG_INI))
-                {
-                    handler.endIni();
-                }
-            }
-        }
-
-        XmlToIni xmlToini = new XmlToIni();
-
-        try
-        {
-            SAXParserFactory.newInstance().newSAXParser().parse(new InputSource(input), xmlToini);
-        }
-        catch (Exception x)
-        {
-            throw new InvalidIniFormatException(x);
-        }
-    }
-
-    public void parseXML(URL input, IniHandler handler) throws IOException, InvalidIniFormatException
-    {
-        parseXML(input.openStream(), handler);
-    }
-
-    private String parseSectionLine(String line, IniSource source, IniHandler handler) throws InvalidIniFormatException
-    {
-        String sectionName;
-
-        if (line.charAt(line.length() - 1) != SECTION_END)
-        {
-            parseError(line, source.getLineNumber());
-        }
-
-        sectionName = unescape(line.substring(1, line.length() - 1).trim());
-        if ((sectionName.length() == 0) && !getConfig().isUnnamedSection())
-        {
-            parseError(line, source.getLineNumber());
-        }
-
-        if (getConfig().isLowerCaseSection())
-        {
-            sectionName = sectionName.toLowerCase(Locale.getDefault());
-        }
-
-        handler.startSection(sectionName);
-
-        return sectionName;
-    }
-
-    private void parse(IniSource source, IniHandler handler) throws IOException, InvalidIniFormatException
-    {
-        handler.startIni();
-        String sectionName = null;
-
-        for (String line = source.readLine(); line != null; line = source.readLine())
-        {
-
-            if (line.charAt(0) == SECTION_BEGIN)
-            {
-                if (sectionName != null)
-                {
-                    handler.endSection();
-                }
-
-                sectionName = parseSectionLine(line, source, handler);
-
-            }
-            else
-            {
-                if (sectionName == null)
-                {
-                    if (getConfig().isGlobalSection())
-                    {
-                        sectionName = getConfig().getGlobalSectionName();
-                        handler.startSection(sectionName);
-                    }
-                    else
-                    {
-                        parseError(line, source.getLineNumber());
-                    }
-                }
-
-                parseOptionLine(line, handler, source.getLineNumber());
-
-            }
-        }
-
-        if (sectionName != null)
-        {
-            handler.endSection();
-        }
-
-        handler.endIni();
-    }
-}
diff --git a/src/org/ini4j/IniPreferencesFactoryListener.java b/src/org/ini4j/IniPreferencesFactoryListener.java
deleted file mode 100644
index 0c5b370..0000000
--- a/src/org/ini4j/IniPreferencesFactoryListener.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import javax.servlet.ServletContext;
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-
-public class IniPreferencesFactoryListener extends IniPreferencesFactory implements ServletContextListener
-{
-    public static final String DEFAULT_USER_LOCATION = "/WEB-INF/user.ini";
-    public static final String DEFAULT_SYSTEM_LOCATION = "/WEB-INF/system.ini";
-    private ServletContext _context;
-
-    @Override public void contextDestroyed(ServletContextEvent event)
-    {
-        _context = null;
-    }
-
-    @Override public void contextInitialized(ServletContextEvent event)
-    {
-        _context = event.getServletContext();
-        System.setProperty("java.util.prefs.PreferencesFactory", getClass().getName());
-    }
-
-    @Override protected String getIniLocation(String key)
-    {
-        String location = _context.getInitParameter(key);
-
-        if (location == null)
-        {
-            location = key.equals(KEY_USER) ? DEFAULT_USER_LOCATION : DEFAULT_SYSTEM_LOCATION;
-        }
-
-        return location;
-    }
-
-    @Override protected URL getResource(String location) throws IllegalArgumentException
-    {
-        try
-        {
-            URL url = _context.getResource(location);
-
-            if (url == null)
-            {
-                url = super.getResource(location);
-            }
-
-            return url;
-        }
-        catch (MalformedURLException x)
-        {
-            throw (IllegalArgumentException) new IllegalArgumentException().initCause(x);
-        }
-    }
-}
diff --git a/src/org/ini4j/IniSource.java b/src/org/ini4j/IniSource.java
deleted file mode 100644
index f9ac242..0000000
--- a/src/org/ini4j/IniSource.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.LineNumberReader;
-import java.io.Reader;
-
-import java.net.URL;
-
-class IniSource
-{
-    public static final char INCLUDE_BEGIN = '<';
-    public static final char INCLUDE_END = '>';
-    public static final char INCLUDE_OPTIONAL = '?';
-    private static final String EMPTY = "";
-    protected final boolean allowInclude;
-    protected final String commentChars;
-    private URL _base;
-    private IniSource _chain;
-    private final LineNumberReader _reader;
-
-    protected IniSource(InputStream input, boolean includeFlag, String comments)
-    {
-        _reader = new LineNumberReader(new InputStreamReader(input));
-        allowInclude = includeFlag;
-        commentChars = comments;
-    }
-
-    protected IniSource(Reader input, boolean includeFlag, String comments)
-    {
-        _reader = new LineNumberReader(input);
-        allowInclude = includeFlag;
-        commentChars = comments;
-    }
-
-    protected IniSource(URL input, boolean includeFlag, String comments) throws IOException
-    {
-        _base = input;
-        _reader = new LineNumberReader(new InputStreamReader(input.openStream()));
-        allowInclude = includeFlag;
-        commentChars = comments;
-    }
-
-    protected int getLineNumber()
-    {
-        return _reader.getLineNumber();
-    }
-
-    protected void close() throws IOException
-    {
-        _reader.close();
-    }
-
-    protected String readLine() throws IOException
-    {
-        String line;
-
-        if (_chain == null)
-        {
-            line = readLineLocal();
-        }
-        else
-        {
-            line = _chain.readLine();
-            if (line == null)
-            {
-                _chain = null;
-                line = readLine();
-            }
-        }
-
-        return line;
-    }
-
-    private String handleInclude(String input) throws IOException
-    {
-        String line = input;
-
-        if (allowInclude && (line.length() > 2) && (line.charAt(0) == INCLUDE_BEGIN) && (line.charAt(line.length() - 1) == INCLUDE_END))
-        {
-            line = line.substring(1, line.length() - 1).trim();
-            boolean optional = line.charAt(0) == INCLUDE_OPTIONAL;
-
-            if (optional)
-            {
-                line = line.substring(1).trim();
-            }
-
-            URL loc = (_base == null) ? new URL(line) : new URL(_base, line);
-
-            if (optional)
-            {
-                try
-                {
-                    _chain = new IniSource(loc, allowInclude, commentChars);
-                }
-                catch (IOException x)
-                {
-                    assert true;
-                }
-                finally
-                {
-                    line = readLine();
-                }
-            }
-            else
-            {
-                _chain = new IniSource(loc, allowInclude, commentChars);
-                line = readLine();
-            }
-        }
-
-        return line;
-    }
-
-    private String readLineLocal() throws IOException
-    {
-        String line = readLineLocalOne();
-
-        while ((line != null) && (line.length() == 0))
-        {
-            line = readLineLocalOne();
-        }
-
-        if (line == null)
-        {
-            close();
-        }
-        else
-        {
-            line = handleInclude(line);
-        }
-
-        return line;
-    }
-
-    private String readLineLocalOne() throws IOException
-    {
-        String line = _reader.readLine();
-
-        if (line != null)
-        {
-            line = line.trim();
-            if ((line.length() != 0) && (commentChars.indexOf(line.charAt(0)) >= 0))
-            {
-                line = EMPTY;
-            }
-        }
-
-        return line;
-    }
-}
diff --git a/src/org/ini4j/InvalidIniFormatException.java b/src/org/ini4j/InvalidIniFormatException.java
deleted file mode 100644
index bb8221b..0000000
--- a/src/org/ini4j/InvalidIniFormatException.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j;
-
-import java.io.IOException;
-
-public class InvalidIniFormatException extends IOException
-{
-    private static final long serialVersionUID = 6108557026268262787L;
-
-    /**
-     * Példány képzés adott üzenettel.
-     *
-     * Ez a konstruktor rendszerint új exception generálására használatos, amikor is
-     * valamely feltétel ellenőrzése hibát jelez.
-     * @param message hiba szöveges megnevez�se
-     */
-    public InvalidIniFormatException(String message)
-    {
-        super(message);
-    }
-
-    /**
-     * Példány képzés adott kiváltó okkal.
-     *
-     * Ez a konstruktor minden további szöveges magyarázat nélkúl egy exception tovább adására
-     * szolgál.
-     * @param cause a hibát kiváltó exception
-     */
-    public InvalidIniFormatException(Throwable cause)
-    {
-        super();
-        initCause(cause);
-    }
-}
diff --git a/src/org/ini4j/OptionMapImpl.java b/src/org/ini4j/OptionMapImpl.java
deleted file mode 100644
index 836fb09..0000000
--- a/src/org/ini4j/OptionMapImpl.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j;
-
-import org.ini4j.spi.BeanAccess;
-import org.ini4j.spi.BeanTool;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class OptionMapImpl extends MultiMapImpl<String, String> implements OptionMap
-{
-    private static final char SUBST_CHAR = '$';
-    private static final String SYSTEM_PROPERTY_PREFIX = "@prop/";
-    private static final String ENVIRONMENT_PREFIX = "@env/";
-    private static final int SYSTEM_PROPERTY_PREFIX_LEN = SYSTEM_PROPERTY_PREFIX.length();
-    private static final int ENVIRONMENT_PREFIX_LEN = ENVIRONMENT_PREFIX.length();
-    private static final Pattern EXPRESSION = Pattern.compile("(?<!\\\\)\\$\\{(([^\\[]+)(\\[([0-9]+)\\])?)\\}");
-    private static final int G_OPTION = 2;
-    private static final int G_INDEX = 4;
-    private BeanAccess _defaultBeanAccess;
-
-    @Override public <T> T as(Class<T> clazz)
-    {
-        return BeanTool.getInstance().proxy(clazz, getDefaultBeanAccess());
-    }
-
-    @Override public <T> T as(Class<T> clazz, String keyPrefix)
-    {
-        return BeanTool.getInstance().proxy(clazz, newBeanAccess(keyPrefix));
-    }
-
-    @Override public String fetch(Object key)
-    {
-        int len = length(key);
-
-        return (len == 0) ? null : fetch(key, len - 1);
-    }
-
-    @Override public String fetch(Object key, int index)
-    {
-        String value = get(key, index);
-
-        if ((value != null) && (value.indexOf(SUBST_CHAR) >= 0))
-        {
-            StringBuilder buffer = new StringBuilder(value);
-
-            resolve(buffer);
-            value = buffer.toString();
-        }
-
-        return value;
-    }
-
-    @Override public void from(Object bean)
-    {
-        BeanTool.getInstance().inject(getDefaultBeanAccess(), bean);
-    }
-
-    @Override public void from(Object bean, String keyPrefix)
-    {
-        BeanTool.getInstance().inject(newBeanAccess(keyPrefix), bean);
-    }
-
-    @Override public void to(Object bean)
-    {
-        BeanTool.getInstance().inject(bean, getDefaultBeanAccess());
-    }
-
-    @Override public void to(Object bean, String keyPrefix)
-    {
-        BeanTool.getInstance().inject(bean, newBeanAccess(keyPrefix));
-    }
-
-    protected synchronized BeanAccess getDefaultBeanAccess()
-    {
-        if (_defaultBeanAccess == null)
-        {
-            _defaultBeanAccess = newBeanAccess();
-        }
-
-        return _defaultBeanAccess;
-    }
-
-    protected BeanAccess newBeanAccess()
-    {
-        return new Access();
-    }
-
-    protected BeanAccess newBeanAccess(String propertyNamePrefix)
-    {
-        return new Access(propertyNamePrefix);
-    }
-
-    protected void resolve(StringBuilder buffer)
-    {
-        Matcher m = EXPRESSION.matcher(buffer);
-
-        while (m.find())
-        {
-            String name = m.group(G_OPTION);
-            int index = (m.group(G_INDEX) == null) ? -1 : Integer.parseInt(m.group(G_INDEX));
-            String value;
-
-            if (name.startsWith(ENVIRONMENT_PREFIX))
-            {
-                value = System.getenv(name.substring(ENVIRONMENT_PREFIX_LEN));
-            }
-            else if (name.startsWith(SYSTEM_PROPERTY_PREFIX))
-            {
-                value = System.getProperty(name.substring(SYSTEM_PROPERTY_PREFIX_LEN));
-            }
-            else
-            {
-                value = (index == -1) ? fetch(name) : fetch(name, index);
-            }
-
-            if (value != null)
-            {
-                buffer.replace(m.start(), m.end(), value);
-                m.reset(buffer);
-            }
-        }
-    }
-
-    protected class Access implements BeanAccess
-    {
-        private final String _prefix;
-
-        protected Access()
-        {
-            this(null);
-        }
-
-        protected Access(String prefix)
-        {
-            _prefix = prefix;
-        }
-
-        public void propAdd(String propertyName, String value)
-        {
-            add(transform(propertyName), value);
-        }
-
-        public String propDel(String propertyName)
-        {
-            return remove(transform(propertyName));
-        }
-
-        public String propGet(String propertyName)
-        {
-            return fetch(transform(propertyName));
-        }
-
-        public String propGet(String propertyName, int index)
-        {
-            return fetch(transform(propertyName), index);
-        }
-
-        public int propLength(String propertyName)
-        {
-            return length(transform(propertyName));
-        }
-
-        public String propSet(String propertyName, String value)
-        {
-            return put(transform(propertyName), value);
-        }
-
-        public String propSet(String propertyName, String value, int index)
-        {
-            return put(transform(propertyName), value, index);
-        }
-
-        private String transform(String orig)
-        {
-            return (orig == null) ? null : ((_prefix == null) ? orig : (_prefix + orig));
-        }
-    }
-}
diff --git a/src/org/ini4j/Options.java b/src/org/ini4j/Options.java
deleted file mode 100644
index dedd8b8..0000000
--- a/src/org/ini4j/Options.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j;
-
-import org.ini4j.spi.EscapeTool;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Reader;
-import java.io.Writer;
-
-import java.net.URL;
-
-public class Options extends OptionMapImpl
-{
-    private static final char OPERATOR = '=';
-    private static final String NEWLINE = "\n";
-    private Config _config;
-
-    public Options()
-    {
-        _config = Config.getGlobal().clone();
-        _config.setEmptyOption(true);
-    }
-
-    public Options(Reader input) throws IOException, InvalidIniFormatException
-    {
-        this();
-        load(input);
-    }
-
-    public Options(InputStream input) throws IOException, InvalidIniFormatException
-    {
-        this();
-        load(input);
-    }
-
-    public Options(URL input) throws IOException, InvalidIniFormatException
-    {
-        this();
-        load(input);
-    }
-
-    public void setConfig(Config value)
-    {
-        _config = value;
-    }
-
-    public void load(InputStream input) throws IOException, InvalidIniFormatException
-    {
-        OptionParser.newInstance(getConfig()).parse(input, new Builder());
-    }
-
-    public void load(Reader input) throws IOException, InvalidIniFormatException
-    {
-        OptionParser.newInstance(getConfig()).parse(input, new Builder());
-    }
-
-    public void load(URL input) throws IOException, InvalidIniFormatException
-    {
-        OptionParser.newInstance(getConfig()).parse(input, new Builder());
-    }
-
-    public void store(OutputStream output) throws IOException
-    {
-        format(new OutputStreamWriter(output));
-    }
-
-    public void store(Writer output) throws IOException
-    {
-        format(output);
-    }
-
-    protected Config getConfig()
-    {
-        return _config;
-    }
-
-    protected String escape(String input)
-    {
-        return getConfig().isEscape() ? EscapeTool.getInstance().escape(input) : input;
-    }
-
-    protected void format(Writer output) throws IOException
-    {
-        for (String name : keySet())
-        {
-            int n = getConfig().isMultiOption() ? length(name) : 1;
-
-            for (int i = 0; i < n; i++)
-            {
-                String value = get(name, i);
-
-                if ((value != null) || getConfig().isEmptyOption())
-                {
-                    output.append(escape(name));
-                    output.append(OPERATOR);
-                    if (value != null)
-                    {
-                        output.append(escape(value));
-                    }
-
-                    output.append(NEWLINE);
-                }
-            }
-        }
-
-        output.flush();
-    }
-
-    private class Builder implements OptionHandler
-    {
-        @Override public void handleOption(String name, String value)
-        {
-            if (getConfig().isMultiOption())
-            {
-                add(name, value);
-            }
-            else
-            {
-                put(name, value);
-            }
-        }
-    }
-}
diff --git a/src/org/ini4j/PreferencesBean.java b/src/org/ini4j/PreferencesBean.java
deleted file mode 100644
index 2eb1879..0000000
--- a/src/org/ini4j/PreferencesBean.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j;
-
-import org.ini4j.spi.AbstractBeanInvocationHandler;
-
-import java.lang.reflect.Proxy;
-
-import java.util.prefs.Preferences;
-
-public final class PreferencesBean
-{
-    private PreferencesBean()
-    {
-    }
-
-    public static <T> T newInstance(Class<T> clazz, final Preferences prefs)
-    {
-        return clazz.cast(Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, new Handler(prefs)));
-    }
-
-    private static class Handler extends AbstractBeanInvocationHandler
-    {
-        private final Preferences _prefs;
-
-        public Handler(Preferences prefs)
-        {
-            _prefs = prefs;
-        }
-
-        @Override protected Object getPropertySpi(String property, Class<?> clazz)
-        {
-            return _prefs.get(property, null);
-        }
-
-        @Override protected void setPropertySpi(String property, Object value, Class<?> clazz)
-        {
-            _prefs.put(property, value.toString());
-        }
-
-        @Override protected boolean hasPropertySpi(String property)
-        {
-            return _prefs.get(property, null) != null;
-        }
-    }
-}
diff --git a/src/org/ini4j/addon/FancyIniFormatter.java b/src/org/ini4j/addon/FancyIniFormatter.java
deleted file mode 100644
index 1fa9ef8..0000000
--- a/src/org/ini4j/addon/FancyIniFormatter.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j.addon;
-
-import org.ini4j.Config;
-
-import org.ini4j.spi.IniFormatter;
-
- at Deprecated public class FancyIniFormatter extends IniFormatter
-{
-    public FancyIniFormatter()
-    {
-        Config cfg = getConfig().clone();
-
-        cfg.setEmptyOption(true);
-        cfg.setStrictOperator(true);
-        super.setConfig(cfg);
-    }
-
-    @Deprecated public synchronized void setAllowEmptyOption(boolean flag)
-    {
-        getConfig().setEmptyOption(flag);
-    }
-
-    @Deprecated public synchronized void setAllowStrictOperator(boolean flag)
-    {
-        getConfig().setStrictOperator(flag);
-    }
-
-    @Deprecated @Override public void setConfig(Config value)
-    {
-        assert true;
-    }
-
-    @Deprecated public synchronized boolean isAllowEmptyOption()
-    {
-        return getConfig().isEmptyOption();
-    }
-
-    @Deprecated public synchronized boolean isAllowStrictOperator()
-    {
-        return getConfig().isStrictOperator();
-    }
-}
diff --git a/src/org/ini4j/addon/FancyIniParser.java b/src/org/ini4j/addon/FancyIniParser.java
deleted file mode 100644
index aafecbf..0000000
--- a/src/org/ini4j/addon/FancyIniParser.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j.addon;
-
-import org.ini4j.Config;
-import org.ini4j.IniParser;
-
- at Deprecated public class FancyIniParser extends IniParser
-{
-    public FancyIniParser()
-    {
-        Config cfg = getConfig().clone();
-
-        cfg.setEmptyOption(true);
-        cfg.setGlobalSection(true);
-        cfg.setUnnamedSection(true);
-        cfg.setGlobalSectionName("?");
-        cfg.setInclude(true);
-        super.setConfig(cfg);
-    }
-
-    @Deprecated public synchronized void setAllowEmptyOption(boolean flag)
-    {
-        getConfig().setEmptyOption(flag);
-    }
-
-    @Deprecated public synchronized void setAllowInclude(boolean flag)
-    {
-        getConfig().setInclude(flag);
-    }
-
-    @Deprecated public synchronized void setAllowMissingSection(boolean flag)
-    {
-        getConfig().setGlobalSection(flag);
-    }
-
-    @Deprecated public synchronized void setAllowOptionCaseConversion(boolean flag)
-    {
-        getConfig().setLowerCaseOption(flag);
-    }
-
-    @Deprecated public synchronized void setAllowSectionCaseConversion(boolean flag)
-    {
-        getConfig().setLowerCaseSection(flag);
-    }
-
-    @Deprecated public synchronized void setAllowUnnamedSection(boolean flag)
-    {
-        getConfig().setUnnamedSection(flag);
-    }
-
-    @Deprecated @Override public void setConfig(Config value)
-    {
-        assert true;
-    }
-
-    @Deprecated public synchronized boolean isAllowInclude()
-    {
-        return getConfig().isInclude();
-    }
-
-    @Deprecated public synchronized String getMissingSectionName()
-    {
-        return getConfig().getGlobalSectionName();
-    }
-
-    @Deprecated public synchronized void setMissingSectionName(String name)
-    {
-        getConfig().setGlobalSectionName(name);
-    }
-
-    @Deprecated public synchronized boolean isAllowEmptyOption()
-    {
-        return getConfig().isEmptyOption();
-    }
-
-    @Deprecated public synchronized boolean isAllowMissingSection()
-    {
-        return getConfig().isGlobalSection();
-    }
-
-    @Deprecated public synchronized boolean isAllowOptionCaseConversion()
-    {
-        return getConfig().isLowerCaseOption();
-    }
-
-    @Deprecated public synchronized boolean isAllowSectionCaseConversion()
-    {
-        return getConfig().isLowerCaseSection();
-    }
-
-    @Deprecated public synchronized boolean isAllowUnnamedSection()
-    {
-        return getConfig().isUnnamedSection();
-    }
-}
diff --git a/src/org/ini4j/addon/PreferencesWrapper.java b/src/org/ini4j/addon/PreferencesWrapper.java
deleted file mode 100644
index b41aa69..0000000
--- a/src/org/ini4j/addon/PreferencesWrapper.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j.addon;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-import java.util.prefs.BackingStoreException;
-import java.util.prefs.NodeChangeListener;
-import java.util.prefs.PreferenceChangeListener;
-import java.util.prefs.Preferences;
-
-public class PreferencesWrapper extends Preferences
-{
-    protected Preferences peer;
-
-    public PreferencesWrapper(Preferences impl)
-    {
-        peer = impl;
-    }
-
-    @Override public boolean getBoolean(String key, boolean def)
-    {
-        return peer.getBoolean(key, def);
-    }
-
-    @Override public byte[] getByteArray(String key, byte[] def)
-    {
-        return peer.getByteArray(key, def);
-    }
-
-    @Override public double getDouble(String key, double def)
-    {
-        return peer.getDouble(key, def);
-    }
-
-    @Override public boolean isUserNode()
-    {
-        return peer.isUserNode();
-    }
-
-    @Override public float getFloat(String key, float def)
-    {
-        return peer.getFloat(key, def);
-    }
-
-    @Override public int getInt(String key, int def)
-    {
-        return peer.getInt(key, def);
-    }
-
-    @Override public long getLong(String key, long def)
-    {
-        return peer.getLong(key, def);
-    }
-
-    @Override public String absolutePath()
-    {
-        return peer.absolutePath();
-    }
-
-    @Override public void addNodeChangeListener(NodeChangeListener ncl)
-    {
-        peer.addNodeChangeListener(ncl);
-    }
-
-    @Override public void addPreferenceChangeListener(PreferenceChangeListener pcl)
-    {
-        peer.addPreferenceChangeListener(pcl);
-    }
-
-    @Override public String[] childrenNames() throws BackingStoreException
-    {
-        return peer.childrenNames();
-    }
-
-    @Override public void clear() throws BackingStoreException
-    {
-        peer.clear();
-    }
-
-    @Override public void exportNode(OutputStream os) throws IOException, BackingStoreException
-    {
-        peer.exportNode(os);
-    }
-
-    @Override public void exportSubtree(OutputStream os) throws IOException, BackingStoreException
-    {
-        peer.exportSubtree(os);
-    }
-
-    @Override public void flush() throws BackingStoreException
-    {
-        peer.flush();
-    }
-
-    @Override public String get(String key, String def)
-    {
-        return peer.get(key, def);
-    }
-
-    @Override public String[] keys() throws BackingStoreException
-    {
-        return peer.keys();
-    }
-
-    @Override public String name()
-    {
-        return peer.name();
-    }
-
-    @Override public Preferences node(String pathName)
-    {
-        return peer.node(pathName);
-    }
-
-    @Override public boolean nodeExists(String pathName) throws BackingStoreException
-    {
-        return peer.nodeExists(pathName);
-    }
-
-    @Override public Preferences parent()
-    {
-        return peer.parent();
-    }
-
-    @Override public void put(String key, String value)
-    {
-        peer.put(key, value);
-    }
-
-    @Override public void putBoolean(String key, boolean value)
-    {
-        peer.putBoolean(key, value);
-    }
-
-    @Override public void putByteArray(String key, byte[] value)
-    {
-        peer.putByteArray(key, value);
-    }
-
-    @Override public void putDouble(String key, double value)
-    {
-        peer.putDouble(key, value);
-    }
-
-    @Override public void putFloat(String key, float value)
-    {
-        peer.putFloat(key, value);
-    }
-
-    @Override public void putInt(String key, int value)
-    {
-        peer.putInt(key, value);
-    }
-
-    @Override public void putLong(String key, long value)
-    {
-        peer.putLong(key, value);
-    }
-
-    @Override public void remove(String key)
-    {
-        peer.remove(key);
-    }
-
-    @Override public void removeNode() throws BackingStoreException
-    {
-        peer.removeNode();
-    }
-
-    @Override public void removeNodeChangeListener(NodeChangeListener ncl)
-    {
-        peer.removeNodeChangeListener(ncl);
-    }
-
-    @Override public void removePreferenceChangeListener(PreferenceChangeListener pcl)
-    {
-        peer.removePreferenceChangeListener(pcl);
-    }
-
-    @Override public void sync() throws BackingStoreException
-    {
-        peer.sync();
-    }
-
-    @Override public String toString()
-    {
-        return peer.toString();
-    }
-}
diff --git a/src/org/ini4j/addon/StrictPreferences.java b/src/org/ini4j/addon/StrictPreferences.java
deleted file mode 100644
index 40e7c92..0000000
--- a/src/org/ini4j/addon/StrictPreferences.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j.addon;
-
-import java.util.NoSuchElementException;
-import java.util.prefs.Preferences;
-
-public class StrictPreferences extends PreferencesWrapper
-{
-    public StrictPreferences(Preferences peer)
-    {
-        super(peer);
-    }
-
-    public boolean getBoolean(String key) throws NoSuchElementException
-    {
-        return Boolean.valueOf(get(key));
-    }
-
-    public byte[] getByteArray(String key) throws NoSuchElementException
-    {
-        byte[] value = getByteArray(key, null);
-
-        if (value == null)
-        {
-            throw new NoSuchElementException();
-        }
-
-        return value;
-    }
-
-    public double getDouble(String key) throws NoSuchElementException
-    {
-        return Double.parseDouble(get(key));
-    }
-
-    public float getFloat(String key) throws NoSuchElementException
-    {
-        return Float.parseFloat(get(key));
-    }
-
-    public int getInt(String key) throws NoSuchElementException
-    {
-        return Integer.parseInt(get(key));
-    }
-
-    public long getLong(String key) throws NoSuchElementException
-    {
-        return Long.parseLong(get(key));
-    }
-
-    public String get(String key) throws NoSuchElementException
-    {
-        String value = get(key, null);
-
-        if (value == null)
-        {
-            throw new NoSuchElementException();
-        }
-
-        return value;
-    }
-}
diff --git a/src/org/ini4j/spi/EscapeTool.java b/src/org/ini4j/spi/EscapeTool.java
deleted file mode 100644
index 224180d..0000000
--- a/src/org/ini4j/spi/EscapeTool.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j.spi;
-
-public class EscapeTool
-{
-    private static final char[] HEX = "0123456789abcdef".toCharArray();
-    private static final EscapeTool INSTANCE = ServiceFinder.findService(EscapeTool.class);
-    private static final char ASCII_MIN = 0x20;
-    private static final char ASCII_MAX = 0x7e;
-    private static final int HEX_DIGIT_MASK = 0x0f;
-    private static final int DIGIT_3_OFFSET = 4;
-    private static final int DIGIT_2_OFFSET = 8;
-    private static final int DIGIT_1_OFFSET = 12;
-    private static final int HEX_RADIX = 16;
-    private static final int UNICODE_HEX_DIGITS = 4;
-
-    public static EscapeTool getInstance()
-    {
-        return INSTANCE;
-    }
-
-    public String escape(String line)
-    {
-        int len = line.length();
-        StringBuilder buffer = new StringBuilder(len * 2);
-
-        for (int i = 0; i < len; i++)
-        {
-            char c = line.charAt(i);
-            int idx = "\\\t\n\f".indexOf(c);
-
-            if (idx >= 0)
-            {
-                buffer.append('\\');
-                buffer.append("\\tnf".charAt(idx));
-            }
-            else
-            {
-                if ((c < ASCII_MIN) || (c > ASCII_MAX))
-                {
-                    buffer.append("\\u");
-                    buffer.append(HEX[(c >>> DIGIT_1_OFFSET) & HEX_DIGIT_MASK]);
-                    buffer.append(HEX[(c >>> DIGIT_2_OFFSET) & HEX_DIGIT_MASK]);
-                    buffer.append(HEX[(c >>> DIGIT_3_OFFSET) & HEX_DIGIT_MASK]);
-                    buffer.append(HEX[c & HEX_DIGIT_MASK]);
-                }
-                else
-                {
-                    buffer.append(c);
-                }
-            }
-        }
-
-        return buffer.toString();
-    }
-
-    public String unescape(String line)
-    {
-        int n = line.length();
-        StringBuilder buffer = new StringBuilder(n);
-        int i = 0;
-
-        while (i < n)
-        {
-            char c = line.charAt(i++);
-
-            if (c == '\\')
-            {
-                c = line.charAt(i++);
-                if (c == 'u')
-                {
-                    try
-                    {
-                        c = (char) Integer.parseInt(line.substring(i, i + UNICODE_HEX_DIGITS), HEX_RADIX);
-                        i += UNICODE_HEX_DIGITS;
-                    }
-                    catch (Exception x)
-                    {
-                        throw new IllegalArgumentException("Malformed \\uxxxx encoding.", x);
-                    }
-                }
-                else
-                {
-                    int idx = "\\tnf".indexOf(c);
-
-                    if (idx >= 0)
-                    {
-                        c = "\\\t\n\f".charAt(idx);
-                    }
-                }
-            }
-
-            buffer.append(c);
-        }
-
-        return buffer.toString();
-    }
-}
diff --git a/src/org/ini4j/spi/XMLFormatter.java b/src/org/ini4j/spi/XMLFormatter.java
deleted file mode 100644
index de748bb..0000000
--- a/src/org/ini4j/spi/XMLFormatter.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2005,2009 Ivan SZKIBA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ini4j.spi;
-
-import org.ini4j.IniHandler;
-
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.Writer;
-
-public class XMLFormatter implements IniHandler
-{
-    private PrintWriter _output;
-
-    public static XMLFormatter newInstance(Writer out)
-    {
-        XMLFormatter instance = newInstance();
-
-        instance.setOutput(new PrintWriter(out));
-
-        return instance;
-    }
-
-    public static XMLFormatter newInstance(OutputStream out)
-    {
-        return newInstance(new OutputStreamWriter(out));
-    }
-
-    public void endIni()
-    {
-        getOutput().println("</ini>");
-        getOutput().flush();
-    }
-
-    public void endSection()
-    {
-        getOutput().println(" </section>");
-    }
-
-    public void handleOption(String optionName, String optionValue)
-    {
-        getOutput().print("  <option key='");
-        getOutput().print(optionName);
-        getOutput().print("' value='");
-        getOutput().print(optionValue);
-        getOutput().println("'/>");
-    }
-
-    public void startIni()
-    {
-        getOutput().println("<ini version='1.0'>");
-    }
-
-    public void startSection(String sectionName)
-    {
-        getOutput().print(" <section key='");
-        getOutput().print(sectionName);
-        getOutput().println("'>");
-    }
-
-    protected static XMLFormatter newInstance()
-    {
-        return ServiceFinder.findService(XMLFormatter.class);
-    }
-
-    protected PrintWriter getOutput()
-    {
-        return _output;
-    }
-
-    protected void setOutput(PrintWriter value)
-    {
-        _output = value;
-    }
-}
diff --git a/src/site/apt/design.apt b/src/site/apt/design.apt
new file mode 100644
index 0000000..179c8ce
--- /dev/null
+++ b/src/site/apt/design.apt
@@ -0,0 +1,68 @@
+                ------
+                Design goals
+
+Design goals
+
+ This document summarizes the design aspects of the [ini4j] project. 
+ First let me quote something every library designer should keep in mind:
+
+
+       "Perfection (in design) is achieved not when there is nothing more to add, but rather when
+       there is nothing more to take away." - Antoine de Saint-Exupéry
+
+
+* Only .ini handling library
+
+ One of the most important aspects was that the [ini4j] is not a complex configuration management system,
+ but principally a library that allows you to manage files in .ini format.
+
+ We could say that we regard the java.util.Properties object as a pattern in designing.
+ The Properties object works as a file containing pairs of name and value. It can be read
+ from a stream or be written to a stream (lately even in XML format). This way it is understandable
+ why the Ini object doesn't need to belong to a file: you can read it from InputStream (and Reader)
+ or write it to OutputStream (and Writer).
+
+    
+* Simple API
+
+ The programming interface had to be designed as simple as possible, firstly for the sake
+ of easier understanding and usage, secondly in order to avoid needless complexity.
+ The handling of the .ini files in any application is just a tool, not the goal.
+
+ As a result of this approach the Ini object is in fact a Map composed of sections.
+ The section object is a Map composed of String values assigned to String keys.
+
+* Standard API
+
+ Another very important design aspect was that if a standard API exists, that must be used
+ to handle the .ini file. Fortunately, ever since the introduction of version 1.4, there is
+ a Preferences API, which is actually a simple tree structure of name/value pairs. This is ideal
+ for the .ini format's top level API, because the programmer using the library doesn't have to
+ learn a new API, but can handle the .ini files using the Preferences API included in the standard JDK!
+
+ So the .ini file is in fact a one-level Preferences tree. On the only level the nodes of the
+ tree are the sections of the .ini file, and the leaf nodes are the section's name/value pairs.    
+ Due to the Preferences API the conversion of values to primitive types or vice versa is solved
+ (getInt(), setInt(), etc).
+
+* Comfortable API
+
+ The designer of a library always seeks the favour of the potential users, the programmers in this case,
+ so comfort is at least as important an aspect as simplicity. An API should be as comfortable as possible.
+ The Java Beans-style programming interface was born on these terms, that is, to allow the programmer to
+ define completely freely how (as what type) he'd wish to access the values of the section as a java interface.
+
+* Sequential parser
+
+ When handling large .ini files, it is not practical to read the whole file into the memory. So there
+ might be a need for a sequential parser that calls the processing routine as a callback while parsing
+ sections or name/value pairs inside sections. The JAXP SAX parser can be regarded as a model.
+
+ Along these lines, the corresponding document object of the .ini file must be built based on this
+ sequential parser. Thus the model of the Ini object is the JAXP DOM document.
+
+ A bit of history: an early ancestor of the [ini4j] library was an exercise assigned as homework at
+ a Java programmer course. Its purpose was to prepare the exposition of JAXP SAX/DOM API. The assignment
+ was to write a sequential parser for the .ini file, and then to use it to build an object in the memory.
+
+ 
\ No newline at end of file
diff --git a/src/site/apt/overview.apt b/src/site/apt/overview.apt
new file mode 100644
index 0000000..4d7b8d4
--- /dev/null
+++ b/src/site/apt/overview.apt
@@ -0,0 +1,46 @@
+                ----
+                Java API for handling Windows ini file format
+
+Java API for handling Windows ini file format
+
+ The [ini4j] is a simple Java API for handling configuration files in Windows .ini format.
+ Additionally, the library includes
+ {{{http://java.sun.com/javase/6/docs/api/java/util/prefs/Preferences.html}Java Preferences API}}
+ implementation based on the .ini file.
+
+ Configuration management is an integral part of every application. The standard Java API
+ {{{http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html}Properties}}
+ warrants simple configuration management.
+ A bit more complicated configuration can be managed with the standard Java 
+ {{{http://java.sun.com/j2se/1.5.0/docs/api/java/util/prefs/Preferences.html}Preferences API}}
+ since Java version 1.4. There is also another (much more complicated) method of configuration management:
+ managing configuration files in XML format. 
+ In this case the standard Java XML API (JAXP) or another XML-based configuration management system, e.g.
+ {{{http://jakarta.apache.org/commons/digester/}Jakarta Commons Digester}}, is used.
+
+ The {{{http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html}Properties}}
+ file can't group settings, which is why its services are often not sufficient. 
+ The Preferences API can sort settings in a nice tree structure, but the standard implementation
+ uses hidden filesystem directory structures inside the user's home directory.
+ This means that it can't be used for managing configuration files attached to the application.
+ Managing XML configuration files through the JAXP is a tiresome work, and to learn and master
+ a complex XML-based configuration management system is a time-consuming and difficult task.
+
+ There is another way not mentioned so far: using good old Windows .ini files. 
+ This format allows you to group settings, so it's better than the Properties API, whereas it's considerably
+ easier to manage than XML configuration files. Contrary to the standard Preferences implementation, .ini
+ files can be attached to applications as configuration files without worry. At the time of writing this
+ document a "de facto" .ini file handling Java library doesn't exist. The [ini4j] tries to be one.
+
+ It was an important design aspect to use the standard API. Due to this, I chose the standard
+ {{{http://java.sun.com/j2se/1.5.0/docs/api/java/util/prefs/Preferences.html}Preferences API}}.
+ This means that the [ini4j] package contains a Preferences implementation that realizes a Preferences API on the .ini files.
+ The Preferences tree has only one level that contains the sections of the .ini file. The only [ini4j] specific
+ thing that remains is to create a Preferences tree that means creating an instance of the IniPreferences object.
+ This object corresponds to the .ini file as a Preferences object.
+
+ The {{{http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html}Properties}}
+ file is a simple String,String map, while the Ini object is a two-level map. On its first level
+ the String type keys define the Ini.Section type sections.
+ The Ini.Section object itself is a map, a String,String type map of the name/value pairs.
+
diff --git a/src/site/resources/.htaccess b/src/site/resources/.htaccess
new file mode 100644
index 0000000..29a4d93
--- /dev/null
+++ b/src/site/resources/.htaccess
@@ -0,0 +1,15 @@
+
+Redirect permanent /license.txt http://ini4j.sourceforge.net/license.html
+Redirect permanent /changelog.html http://ini4j.sourceforge.net/changes-report.html
+Redirect permanent /releasenotes.html http://ini4j.sourceforge.net/changes-report.html
+Redirect permanent /todo.html http://ini4j.sourceforge.net/taglist.html
+
+Redirect permanent /tutorial.html http://ini4j.sourceforge.net/tutorial/index.html
+Redirect permanent /tutorials.html http://ini4j.sourceforge.net/tutorial/index.html
+Redirect permanent /samples.html http://ini4j.sourceforge.net/sample/index.html
+
+RedirectMatch permanent /(testapidocs)/.* http://ini4j.sourceforge.net/
+
+RedirectMatch permanent /(pmd|links|using|cpd|checkstyle|integration|jdepend-report|surefire-report).html http://ini4j.sourceforge.net/
+
+ErrorDocument 404 /404.html
diff --git a/src/site/resources/404.html b/src/site/resources/404.html
new file mode 100644
index 0000000..d3fcbb6
--- /dev/null
+++ b/src/site/resources/404.html
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright 2005,2009 Ivan SZKIBA
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+        <title>404 Not Found</title>
+    </head>
+    <body>
+        <a href="/"><img style="float: right;border: 0;" src="/img/ini4j-banner.png"/></a>
+        <h2>404 Not Found</h2>
+        <p>
+            The requested URL was not found on this server.
+            <form action="/search.html" id="cse-search-box">
+                <div>
+                    <input type="hidden" name="cx" value="010584350824729490214:aye3mrhyyv0" />
+                    <input type="hidden" name="cof" value="FORID:9" />
+                    <input type="hidden" name="ie" value="UTF-8" />
+                    <input type="text" name="q" size="31" />
+                    <input type="submit" name="sa" value="Site Search" />
+                </div>
+            </form>
+            <script type="text/javascript" src="http://www.google.com/coop/cse/brand?form=cse-search-box&amp;lang=en"></script>
+        </p>
+    </body>
+</html>
diff --git a/src/site/resources/css/site.css b/src/site/resources/css/site.css
new file mode 100644
index 0000000..cf2a3a8
--- /dev/null
+++ b/src/site/resources/css/site.css
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+h1,h2,h3,h4,h5,h6
+{
+    border: none;
+/*    border-bottom: 1px solid #808080; */
+    background-color: white;
+    font-family: Palatino, Linotype, Georgia, "Times New Roman", Times, serif;
+    font-weight: normal;
+}
+
+h1,h2
+{
+   /* border-bottom: 3px double #808080;*/
+}
+
+#breadcrumbs
+{
+    border: none;
+    background-color: #f0f0f0;
+    border-top: 1px solid #a0a0a0;
+    color: #c0c0c0;
+    /*border-bottom: 1px solid #a0a0a0;*/
+}
+
+a:link
+{
+    color: #409040;
+}
+
+a:visited
+{
+    color: #408040;
+}
+
+a:hover, a:active
+{
+    color: #20c020;
+}
+
+#footer
+{
+    border-top: 1px solid #e0e0e0;
+    color: #a0a0a0;
+}
+
+.source
+{
+    background-color: #fffff0;
+}
+
+#leftColumn
+{
+    border: none;
+    background-color: white;
+}
+
+#navcolumn h5
+{
+    border: none;
+    font-family: Verdana, Helvetica, Arial, sans-serif;
+}
+
+#bodyColumn
+{
+    margin-left: 170px;
+}
+
+#leftcol, .docs #toc, .docs .courtesylinks, #leftColumn, #navColumn {
+/*	display: none !important;*/
+}
+#bodyColumn, body.docs div.docs {
+	/*margin: 0 !important;
+	border: none !important;*/
+}
+
+menu li
+{
+   list-style: none;
+   display: inline;
+   padding: 10px;
+}
+
+menu li img
+{
+   vertical-align: middle;
+}
+
+menu
+{
+    padding-top: 20px;
+}
+
+table
+{
+    width: 99%;
+}
diff --git a/src/site/resources/favicon.ico b/src/site/resources/favicon.ico
new file mode 100644
index 0000000..11b089f
Binary files /dev/null and b/src/site/resources/favicon.ico differ
diff --git a/src/site/resources/img/bugs.png b/src/site/resources/img/bugs.png
new file mode 100644
index 0000000..b5003f6
Binary files /dev/null and b/src/site/resources/img/bugs.png differ
diff --git a/src/site/resources/img/community.png b/src/site/resources/img/community.png
new file mode 100644
index 0000000..0def963
Binary files /dev/null and b/src/site/resources/img/community.png differ
diff --git a/src/site/resources/img/docs.png b/src/site/resources/img/docs.png
new file mode 100644
index 0000000..9de3c38
Binary files /dev/null and b/src/site/resources/img/docs.png differ
diff --git a/src/site/resources/img/download.png b/src/site/resources/img/download.png
new file mode 100644
index 0000000..520bd4e
Binary files /dev/null and b/src/site/resources/img/download.png differ
diff --git a/src/site/resources/img/feature.png b/src/site/resources/img/feature.png
new file mode 100644
index 0000000..e8b4285
Binary files /dev/null and b/src/site/resources/img/feature.png differ
diff --git a/src/site/resources/img/fedora.png b/src/site/resources/img/fedora.png
new file mode 100644
index 0000000..4fffdd8
Binary files /dev/null and b/src/site/resources/img/fedora.png differ
diff --git a/src/site/resources/img/forum.png b/src/site/resources/img/forum.png
new file mode 100644
index 0000000..4604a8d
Binary files /dev/null and b/src/site/resources/img/forum.png differ
diff --git a/src/site/resources/img/ini4j-banner.png b/src/site/resources/img/ini4j-banner.png
new file mode 100644
index 0000000..4aea4df
Binary files /dev/null and b/src/site/resources/img/ini4j-banner.png differ
diff --git a/src/site/resources/img/ini4j.png b/src/site/resources/img/ini4j.png
new file mode 100644
index 0000000..aafbbd3
Binary files /dev/null and b/src/site/resources/img/ini4j.png differ
diff --git a/src/site/resources/img/sample.png b/src/site/resources/img/sample.png
new file mode 100644
index 0000000..0d2db95
Binary files /dev/null and b/src/site/resources/img/sample.png differ
diff --git a/src/site/resources/img/tutorial.png b/src/site/resources/img/tutorial.png
new file mode 100644
index 0000000..c8ce144
Binary files /dev/null and b/src/site/resources/img/tutorial.png differ
diff --git a/src/site/resources/img/ubuntu.png b/src/site/resources/img/ubuntu.png
new file mode 100644
index 0000000..884a3d4
Binary files /dev/null and b/src/site/resources/img/ubuntu.png differ
diff --git a/src/site/resources/img/writer.png b/src/site/resources/img/writer.png
new file mode 100644
index 0000000..cf3f207
Binary files /dev/null and b/src/site/resources/img/writer.png differ
diff --git a/src/site/resources/robots.txt b/src/site/resources/robots.txt
new file mode 100644
index 0000000..e3f90e0
--- /dev/null
+++ b/src/site/resources/robots.txt
@@ -0,0 +1,2 @@
+Sitemap: http://www.ini4j.org/sitemap.xml
+
diff --git a/src/site/site.xml b/src/site/site.xml
new file mode 100644
index 0000000..690b01c
--- /dev/null
+++ b/src/site/site.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright 2005,2009 Ivan SZKIBA
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<project name="[ini4j]" xmlns="http://maven.apache.org/DECORATION/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/DECORATION/1.0.0 http://maven.apache.org/xsd/decoration-1.0.0.xsd">
+    <skin>
+        <groupId>org.apache.maven.skins</groupId>
+        <artifactId>maven-default-skin</artifactId>
+        <version>1.0</version>
+    </skin>
+    <bannerLeft>
+        <name>[ini4j]</name>
+        <src>img/ini4j-banner.png</src>
+        <href>http://ini4j.sourceforge.net/index.html</href>
+    </bannerLeft>
+    <bannerRight>
+        <name>SourceForge</name>
+        <src>http://sourceforge.net/sflogo.php?group_id=129580&amp;type=16</src>
+        <href>http://sourceforge.net</href>
+    </bannerRight>
+    <poweredBy>
+        <logo name="java.net" href="http://www.java.net"
+          img="http://java.net/images/javanet_button_90.gif"/>
+    </poweredBy>
+    <publishDate position="bottom" format="yyyy-MM-dd"/>
+    <version position="bottom"/>
+    <body>
+        <head>
+            <link rel="shortcut icon" href="/favicon.ico"/>
+            <meta name="keywords" content="windows ini,java library,ini,configuration,properties,java.util.Properties,properties replacement,properties macro substitution"></meta>
+            <meta name="author" content="szkiba"></meta>
+            <meta name="description" content="Java API for handling configuration files in Windows .ini format. The library includes its own Map based API, Java Preferences API and Java Beans API for handling .ini files. Additionally, the library includes a feature rich (variable/macro substitution, multiply property values, etc) java.util.Properties replacement."></meta>
+            <script src="http://www.google-analytics.com/ga.js" type="text/javascript"></script>
+            <script type="text/javascript">
+                    try { var pageTracker = _gat._getTracker("UA-7815137-3"); pageTracker._trackPageview(); } catch(err) {}
+            </script>
+        </head>
+
+        <links>
+            <item name="SourceForge project page" href="http://sourceforge.net/projects/ini4j"/>
+        </links>
+
+        <breadcrumbs>
+            <item name="Home" href="/index.html"/>
+            <item name="Tutorials" href="/tutorial/index.html"/>
+            <item name="Samples" href="/sample/index.html"/>
+            <item name="Demo" href="/demo.html"/>
+<!--            <item name="Documents" href="/documents.html"/> -->
+            <item name="Download" href="/download.html"/>
+            <item name="Community" href="/community.html"/>
+            <item name="Search" href="/search.html"/>
+        </breadcrumbs>
+
+        <menu name="Documents">
+            <item name="Home" href="index.html"/>
+            <item name="Overview" href="overview.html" />
+<!--            <item name="Features" href="features.html" /> -->
+            <item name="JavaDocs" href="apidocs/index.html"/>
+            <item name="Goals" href="design.html"/>
+            <item name="TODO" href="taglist.html"/>
+            <item name="Changes" href="changes-report.html"/>
+            <item name="Demo" href="demo.html"/>
+            <item name="Search" href="search.html"/>
+        </menu>
+        <menu name="Tutorials">
+            <item name="One minute" href="tutorial/OneMinuteTutorial.java.html"/>
+            <item name="Ini" href="tutorial/IniTutorial.java.html"/>
+            <item name="Reg" href="tutorial/RegTutorial.java.html"/>
+            <item name="Windows Registry" href="tutorial/WindowsRegistryTutorial.java.html"/>
+            <item name="Options" href="tutorial/OptTutorial.java.html"/>
+            <item name="Bean" href="tutorial/BeanTutorial.java.html"/>
+            <item name="Preferences" href="tutorial/PrefsTutorial.java.html"/>
+        </menu>
+        <menu name="Community">
+            <item name="Bug report" href="http://sourceforge.net/tracker2/?func=add&amp;group_id=129580&amp;atid=715133"/>
+            <item name="Feature request" href="http://sourceforge.net/tracker2/?func=add&amp;group_id=129580&amp;atid=715136"/>
+            <item name="Forums" href="http://sourceforge.net/forum/?group_id=129580"/>
+            <item name="Project page" href="http://sourceforge.net/projects/ini4j"/>
+            <item name="Donate" href="http://sourceforge.net/donate/index.php?group_id=129580"/>
+        </menu>
+        <menu ref="reports"/>
+    </body>
+</project>
diff --git a/src/site/xdoc/community.xml b/src/site/xdoc/community.xml
new file mode 100644
index 0000000..b4fe604
--- /dev/null
+++ b/src/site/xdoc/community.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright 2005,2009 Ivan SZKIBA
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+    <properties>
+        <title>Community</title>
+    </properties>
+
+    <body>
+        <section name="Community">
+            <p>
+                In my work I use a lot of opensource library/tool. I beleive the power of
+                opensource. I develop [ini4j] in my spare time, and it is my donation to
+                community.
+            </p>
+            <p>
+                The quality of this project is depend on you. You can improve quality with
+                bug reports, featrue requests, comments etc. So please do not hesitate
+                if you have some comments.
+            </p>
+            <menu>
+                <li><a href="http://sourceforge.net/tracker2/?func=add&amp;group_id=129580&amp;atid=715133"><img src="img/bugs.png"/> Bug report</a></li>
+                <li><a href="http://sourceforge.net/tracker2/?func=add&amp;group_id=129580&amp;atid=715136"><img src="img/feature.png"/> Feature request</a></li>
+                <li><a href="http://sourceforge.net/forum/?group_id=129580"><img src="img/forum.png"/> Forums</a></li>
+            </menu>
+            <subsection name="Donations">
+                <p>
+                    Running ini4j.org has some cost. You can support it by donating this
+                    project.
+                </p>
+                <p>
+                    <a href="http://sourceforge.net/donate/index.php?group_id=129580"><img src="http://images.sourceforge.net/images/project-support.jpg" width="88" height="32" border="0" alt="Support This Project" /></a>
+                </p>
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/demo.xml b/src/site/xdoc/demo.xml
new file mode 100644
index 0000000..5a18b40
--- /dev/null
+++ b/src/site/xdoc/demo.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright 2005,2009 Ivan SZKIBA
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+    <properties>
+        <title>Demo</title>
+    </properties>
+
+    <body>
+        <section name="Demo">
+            <p>
+                If have Java enabled browser (with Java version 1.5 or above) you
+                can try [ini4j] library with this demo applet (or you can download it
+                as executable jar file: <a href="./ini4j-demo.jar">ini4j-demo.jar</a>).
+                Check the <em>help</em> tab in applet for instructions.
+            </p>
+            <applet width="99%" height="500" archive="ini4j-demo.jar" code="org.ini4j.demo.DemoApplet">
+                <!-- -->
+            </applet>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/org/ini4j/addon/package.html b/src/site/xdoc/documents.xml
similarity index 60%
copy from src/org/ini4j/addon/package.html
copy to src/site/xdoc/documents.xml
index 2108c61..b0bd033 100644
--- a/src/org/ini4j/addon/package.html
+++ b/src/site/xdoc/documents.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
 
     Copyright 2005,2009 Ivan SZKIBA
@@ -16,15 +16,15 @@
     limitations under the License.
 
 -->
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-  <head>
-    <title>[ini4j] addon package</title>
-  </head>
-  <body>
-  <h1>[ini4j] addon package</h1>
-  <p>This package contains optional [ini4j] classes.
-  </p>
-  </body>
-</html>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+    <properties>
+        <title>Documents</title>
+    </properties>
+
+    <body>
+        <section name="Documents">
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/download.xml b/src/site/xdoc/download.xml
new file mode 100644
index 0000000..af88f9f
--- /dev/null
+++ b/src/site/xdoc/download.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright 2005,2009 Ivan SZKIBA
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+    <properties>
+        <title>Download</title>
+    </properties>
+
+    <body>
+        <section name="Download">
+            <p>
+                The main download location for [ini4j] is the SourceForge download site.
+            </p>
+            <p>
+                <a href="http://sourceforge.net/project/showfiles.php?group_id=129580">Download from SourceForge</a>
+            </p>
+            <p>
+ [ini4j] also deployed into Maven Central repository, so if you are using
+ Apache Maven build environment, nothing to do: maven will download
+ automatically.
+            </p>
+            <p>
+                <a href="http://repo2.maven.org/maven2/org/ini4j/ini4j/">Download from Maven Central repository</a>
+            </p>
+
+            <subsection name="Using from maven">
+                <p>
+                    If you are using Apache Maven build system, you need only specify [ini4j] as dependency:
+                </p>
+                <source><![CDATA[<dependency>
+    <groupId>org.ini4j<groupId>
+    <artifactId>ini4j</artifactID>
+    <version>X.Y.Z</version>
+</dependency>]]></source>
+            </subsection>
+
+            <subsection name="Other options">
+                <p>
+ For Linux users also easy to get [ini4j]. Most Linux distribution already
+ contains [ini4j] as installable library, called: libini4j-java. You can use
+ standard package manager (rpm/apt-get) to install it.
+                </p>
+            <menu>
+                <li><a href="http://packages.ubuntu.com/intrepid/libini4j-java"><img src="img/ubuntu.png"/></a></li>
+                <li><a href="https://admin.fedoraproject.org/pkgdb/packages/name/ini4j"><img src="img/fedora.png"/></a></li>
+            </menu>
+            <p>
+                If you have <a href="http://www.netbeans.org/about/legal/code_dependency_matrix.html">NetBeans IDE</a> installed on your computer, probably you already have [ini4j] :)
+            </p>
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/org/ini4j/addon/package.html b/src/site/xdoc/features.xml
similarity index 60%
rename from src/org/ini4j/addon/package.html
rename to src/site/xdoc/features.xml
index 2108c61..b761bcd 100644
--- a/src/org/ini4j/addon/package.html
+++ b/src/site/xdoc/features.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
 
     Copyright 2005,2009 Ivan SZKIBA
@@ -16,15 +16,15 @@
     limitations under the License.
 
 -->
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-  <head>
-    <title>[ini4j] addon package</title>
-  </head>
-  <body>
-  <h1>[ini4j] addon package</h1>
-  <p>This package contains optional [ini4j] classes.
-  </p>
-  </body>
-</html>
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+    <properties>
+        <title>Features</title>
+    </properties>
+
+    <body>
+        <section name="Features">
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml
new file mode 100644
index 0000000..74ef3b9
--- /dev/null
+++ b/src/site/xdoc/index.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright 2005,2009 Ivan SZKIBA
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+    <properties>
+        <title>Java API for handling Windows ini file format</title>
+    </properties>
+    <body>
+        <section name="Java API for handling Windows ini file format">
+            <p>
+ The [ini4j] is a simple Java API for handling configuration files in Windows .ini format.
+ Additionally, the library includes
+ <a href="http://java.sun.com/javase/6/docs/api/java/util/prefs/Preferences.html">Java Preferences API</a>
+ implementation based on the .ini file.
+            </p>
+            <menu>
+                <li><a href="tutorial/index.html"><img src="img/tutorial.png"/> Tutorials</a></li>
+<!--                <li><a href="documents.html"><img src="img/docs.png"/> Documents</a></li> -->
+                <li><a href="download.html"><img src="img/download.png"/> Download</a></li>
+                <li><a href="community.html"><img src="img/community.png"/> Community</a></li>
+            </menu>
+            <subsection name="Simple document model">
+                <p>
+                    [ini4j] has very simple, but intuitive document model for windows .ini files.
+                    Basicly it is two level of Maps. You can use full power of Collections API.
+                    The Ini (Wini) class is map of Sections. Section is map of name/value pairs (options).
+                    <a href="tutorial/IniTutorial.java.html">--&gt; Ini Tutorial</a>
+                </p>
+            </subsection>
+            <subsection name="Multiply option/section values">
+                <p>
+                    In addition of model above, [ini4j] is able to deal with multiply option/section
+                    entries. It can be handled with MultiMap interface, which simply extends Map
+                    interface, and allow multiply values per key.
+                </p>
+            </subsection>
+            <subsection name="Expression handling">
+                <p>
+                    Many .ini files contains same values more than one times. With [ini4j] you are
+                    able to refer other options value via simple macro substitution. The basic
+                    syntax is the commonly used ${REF} format: ${section/option}
+                    (or with multi option: ${section/option[index]})
+                </p>
+            </subsection>
+            <subsection name="java.util.Properties replacement">
+                <p>
+                    [ini4j] contains class <code>Options</code> which is a better
+                    Properties replacement. <code>Options</code> use .properties
+                    file format, but allow macro/variable substitution, mult value
+                    properties. Unlike original java.util.Properties, <code>Options</code> implements
+                    Map&lt;String,String&gt;, so it more confortable to use
+                    standard Collections api.
+                    <a href="tutorial/OptTutorial.java.html">--&gt; Options Tutorial</a>
+                </p>
+            </subsection>
+            <subsection name="Read/write Windows registry">
+                <p>
+                    Yes, it is possible now to read/write registry from java programs
+                    without native (JNI) code ! [ini4j] is able to read and write
+                    registry keys. And almost all .ini features (expression handling, multi value,
+                    bean interface, etc) works with windows registry.
+                    <a href="tutorial/WindowsRegistryTutorial.java.html">--&gt; Windows Registry Tutorial</a>
+                </p>
+            </subsection>
+            <subsection name="Handle .REG file format">
+                <p>
+                    On windows systems .REG file is common for registry import/export.
+                    It has similar but bit different syntax than old .ini format.
+                    [ini4j] fully support .REG file format.
+                    <a href="tutorial/RegTutorial.java.html">--&gt; Reg Tutorial</a>
+                </p>
+            </subsection>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/search.xml b/src/site/xdoc/search.xml
new file mode 100644
index 0000000..c36f68d
--- /dev/null
+++ b/src/site/xdoc/search.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright 2005,2009 Ivan SZKIBA
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+    <properties>
+        <title>Search</title>
+    </properties>
+
+    <body>
+        <section name="Search">
+            <form action="" id="cse-search-box">
+                <div>
+                    <input type="hidden" name="cx" value="010584350824729490214:aye3mrhyyv0" />
+                    <input type="hidden" name="cof" value="FORID:9" />
+                    <input type="hidden" name="ie" value="UTF-8" />
+                    <input type="text" name="q" size="31" />
+                    <input type="submit" name="sa" value="Search" />
+                </div>
+            </form>
+            <script type="text/javascript" src="http://www.google.com/coop/cse/brand?form=cse-search-box&amp;lang=en"></script>
+
+            <div id="cse-search-results"></div>
+            <script type="text/javascript"><![CDATA[  var googleSearchIframeName = 'cse-search-results';  var googleSearchFormName = 'cse-search-box';  var googleSearchFrameWidth = 500;  var googleSearchDomain = 'www.google.com';  var googleSearchPath = '/cse';]]> </script>
+            <script type="text/javascript" src="http://www.google.com/afsonline/show_afs_search.js"></script>
+        </section>
+    </body>
+</document>
\ No newline at end of file
diff --git a/src/test/java/org/ini4j/BasicMultiMapTest.java b/src/test/java/org/ini4j/BasicMultiMapTest.java
new file mode 100644
index 0000000..2bc1153
--- /dev/null
+++ b/src/test/java/org/ini4j/BasicMultiMapTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class BasicMultiMapTest extends Ini4jCase
+{
+    private static final String KEY1 = "key1";
+    private static final String KEY2 = "key2";
+    private static final String KEY3 = "key3";
+    private static final String VALUE1 = "value1";
+    private static final String VALUE2 = "value2";
+    private static final String VALUE3 = "value3";
+    private static final String[] VALUES = { VALUE1, VALUE2, VALUE3 };
+    private MultiMap<String, String> _map;
+
+    @Before @Override public void setUp() throws Exception
+    {
+        super.setUp();
+        _map = new BasicMultiMap<String, String>();
+    }
+
+    @Test public void testAdd()
+    {
+        _map.add(KEY1, VALUE1);
+        _map.add(KEY1, VALUE2);
+        _map.add(KEY1, VALUE3);
+        assertEquals(3, _map.length(KEY1));
+        _map.add(KEY1, VALUE3, 0);
+        assertEquals(4, _map.length(KEY1));
+        assertEquals(VALUE3, _map.get(KEY1, 0));
+        assertEquals(VALUE3, _map.get(KEY1, 3));
+        _map.clear();
+        assertTrue(_map.isEmpty());
+    }
+
+    @Test public void testAll()
+    {
+        _map.putAll(KEY1, Arrays.asList(VALUES));
+        assertEquals(VALUES.length, _map.length(KEY1));
+        String[] values = _map.getAll(KEY1).toArray(new String[] {});
+
+        assertArrayEquals(VALUES, values);
+    }
+
+    @Test public void testContainsValue()
+    {
+        _map.putAll(KEY1, Arrays.asList(VALUES));
+        assertTrue(_map.containsValue(VALUE1));
+        assertTrue(_map.containsValue(VALUE2));
+        assertTrue(_map.containsValue(VALUE3));
+        _map.clear();
+        _map.put(KEY2, VALUE1);
+        assertFalse(_map.containsValue(VALUE3));
+    }
+
+    @Test public void testEntrySet()
+    {
+        _map.putAll(KEY1, Arrays.asList(VALUES));
+        _map.put(KEY2, VALUE2);
+        _map.put(KEY3, VALUE3);
+        Set<Entry<String, String>> set = _map.entrySet();
+
+        assertNotNull(set);
+        assertEquals(3, set.size());
+        for (Entry<String, String> e : set)
+        {
+            if (e.getKey().equals(KEY1))
+            {
+                assertEquals(VALUES[2], e.getValue());
+                e.setValue(VALUES[1]);
+            }
+            else if (e.getKey().equals(KEY2))
+            {
+                assertEquals(VALUE2, e.getValue());
+                e.setValue(VALUE3);
+            }
+            else if (e.getKey().equals(KEY3))
+            {
+                assertEquals(VALUE3, e.getValue());
+                e.setValue(VALUE2);
+            }
+        }
+
+        assertEquals(VALUES[1], _map.get(KEY1));
+        assertEquals(VALUES.length, _map.length(KEY1));
+        assertEquals(VALUE3, _map.get(KEY2));
+        assertEquals(VALUE2, _map.get(KEY3));
+    }
+
+    @Test public void testGetEmpty()
+    {
+        assertNull(_map.get(KEY1));
+        assertNull(_map.get(KEY1, 1));
+    }
+
+    @Test public void testPut()
+    {
+        _map.put(KEY1, VALUE1);
+        _map.add(KEY1, VALUE2);
+        assertEquals(VALUE2, _map.get(KEY1, 1));
+        _map.put(KEY1, VALUE3, 1);
+        assertEquals(VALUE3, _map.get(KEY1, 1));
+        assertEquals(VALUE3, _map.get(KEY1));
+    }
+
+    @Test public void testPutAll()
+    {
+        _map.put(KEY1, VALUE1);
+        _map.put(KEY2, VALUE1);
+        _map.add(KEY2, VALUE2);
+        MultiMap<String, String> other = new BasicMultiMap<String, String>();
+
+        other.putAll(_map);
+        assertEquals(2, other.size());
+        assertEquals(2, other.length(KEY2));
+        assertEquals(1, other.length(KEY1));
+        assertEquals(VALUE1, _map.get(KEY1));
+        assertEquals(VALUE1, _map.get(KEY2, 0));
+        assertEquals(VALUE2, _map.get(KEY2, 1));
+        Map<String, String> regular = new HashMap<String, String>(_map);
+
+        _map.clear();
+        _map.putAll(regular);
+        assertEquals(regular.keySet(), _map.keySet());
+    }
+
+    @Test public void testRemove()
+    {
+        _map.add(KEY1, VALUE1);
+        _map.add(KEY2, VALUE1);
+        _map.add(KEY2, VALUE2);
+        _map.add(KEY3, VALUE1);
+        _map.add(KEY3, VALUE2);
+        _map.add(KEY3, VALUE3);
+        assertEquals(VALUE2, _map.get(KEY3, 1));
+        _map.remove(KEY3, 1);
+        assertEquals(VALUE3, _map.get(KEY3, 1));
+        _map.remove(KEY3, 1);
+        assertEquals(VALUE1, _map.get(KEY3));
+        _map.remove(KEY3, 0);
+        assertEquals(0, _map.length(KEY3));
+        assertFalse(_map.containsKey(KEY3));
+        _map.remove(KEY2);
+        assertFalse(_map.containsKey(KEY2));
+        _map.remove(KEY1);
+        assertFalse(_map.containsKey(KEY1));
+        assertEquals(0, _map.size());
+        assertTrue(_map.isEmpty());
+        assertNull(_map.remove(KEY1));
+        assertNull(_map.remove(KEY1, 1));
+    }
+
+    @Test public void testValues()
+    {
+        _map.put(KEY1, VALUE1);
+        _map.put(KEY2, VALUE2);
+        _map.add(KEY2, VALUE3);
+        String[] values = _map.values().toArray(new String[] {});
+
+        Arrays.sort(values);
+        assertArrayEquals(values, VALUES);
+    }
+}
diff --git a/src/org/ini4j/IniHandler.java b/src/test/java/org/ini4j/BasicOptionMapGate.java
similarity index 62%
copy from src/org/ini4j/IniHandler.java
copy to src/test/java/org/ini4j/BasicOptionMapGate.java
index 9354f5c..b5b0c3e 100644
--- a/src/org/ini4j/IniHandler.java
+++ b/src/test/java/org/ini4j/BasicOptionMapGate.java
@@ -15,13 +15,19 @@
  */
 package org.ini4j;
 
-public interface IniHandler extends OptionHandler
-{
-    void endIni();
+import org.ini4j.spi.BeanAccess;
 
-    void endSection();
+public class BasicOptionMapGate extends BasicOptionMap
+{
+    private static final long serialVersionUID = -479440334238558045L;
 
-    void startIni();
+    @Override public BeanAccess newBeanAccess()
+    {
+        return super.newBeanAccess();
+    }
 
-    void startSection(String sectionName);
+    @Override public BeanAccess newBeanAccess(String prefix)
+    {
+        return super.newBeanAccess(prefix);
+    }
 }
diff --git a/src/test/java/org/ini4j/BasicOptionMapTest.java b/src/test/java/org/ini4j/BasicOptionMapTest.java
new file mode 100644
index 0000000..f7a4b22
--- /dev/null
+++ b/src/test/java/org/ini4j/BasicOptionMapTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.DwarfBean;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.DwarfsData.DwarfData;
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import java.net.URI;
+
+public class BasicOptionMapTest extends Ini4jCase
+{
+    private static BasicOptionMap _map;
+
+    static
+    {
+        _map = new BasicOptionMap();
+        _map.putAll(Helper.newDwarfsOpt());
+    }
+
+    @Test public void testAddPutNullAndString()
+    {
+        OptionMap map = new BasicOptionMap();
+        Object o;
+
+        // null
+        o = null;
+        map.add(Dwarf.PROP_AGE, o);
+        assertNull(map.get(Dwarf.PROP_AGE));
+        map.put(Dwarf.PROP_AGE, new Integer(DwarfsData.doc.age));
+        assertNotNull(map.get(Dwarf.PROP_AGE));
+        map.add(Dwarf.PROP_AGE, o, 0);
+        assertNull(map.get(Dwarf.PROP_AGE, 0));
+        map.put(Dwarf.PROP_AGE, new Integer(DwarfsData.doc.age), 0);
+        assertNotNull(map.get(Dwarf.PROP_AGE, 0));
+        map.put(Dwarf.PROP_AGE, o, 0);
+        assertNull(map.get(Dwarf.PROP_AGE, 0));
+        map.remove(Dwarf.PROP_AGE);
+        map.put(Dwarf.PROP_AGE, o);
+        assertNull(map.get(Dwarf.PROP_AGE));
+
+        // str
+        map.remove(Dwarf.PROP_AGE);
+        o = String.valueOf(DwarfsData.doc.age);
+        map.add(Dwarf.PROP_AGE, o);
+        assertEquals(o, map.get(Dwarf.PROP_AGE));
+        map.remove(Dwarf.PROP_AGE);
+        map.put(Dwarf.PROP_AGE, o);
+        assertEquals(o, map.get(Dwarf.PROP_AGE));
+        o = String.valueOf(DwarfsData.happy.age);
+        map.add(Dwarf.PROP_AGE, o, 0);
+        assertEquals(new Integer(DwarfsData.happy.age), (Integer) map.get(Dwarf.PROP_AGE, 0, int.class));
+        o = String.valueOf(DwarfsData.doc.age);
+        map.put(Dwarf.PROP_AGE, o, 0);
+        assertEquals(DwarfsData.doc.age, (int) map.get(Dwarf.PROP_AGE, 0, int.class));
+    }
+
+    @Test public void testFetch()
+    {
+        OptionMap map = new BasicOptionMap();
+
+        Helper.addDwarf(map, DwarfsData.dopey, false);
+        Helper.addDwarf(map, DwarfsData.bashful);
+        Helper.addDwarf(map, DwarfsData.doc);
+
+        // dopey
+        assertEquals(DwarfsData.dopey.weight, map.fetch(Dwarf.PROP_WEIGHT, double.class), Helper.DELTA);
+        map.add(Dwarf.PROP_HEIGHT, map.get(Dwarf.PROP_HEIGHT));
+        assertEquals(DwarfsData.dopey.height, map.fetch(Dwarf.PROP_HEIGHT, 1, double.class), Helper.DELTA);
+
+        // sneezy
+        map.clear();
+        Helper.addDwarf(map, DwarfsData.happy);
+        Helper.addDwarf(map, DwarfsData.sneezy, false);
+        assertEquals(DwarfsData.sneezy.homePage, map.fetch(Dwarf.PROP_HOME_PAGE, URI.class));
+
+        // null
+        map = new BasicOptionMap();
+        map.add(Dwarf.PROP_AGE, null);
+        assertNull(map.fetch(Dwarf.PROP_AGE, 0));
+    }
+
+    @Test public void testFetchAllException()
+    {
+        OptionMap map = new BasicOptionMap();
+
+        try
+        {
+            map.fetchAll(Dwarf.PROP_FORTUNE_NUMBER, String.class);
+            missing(IllegalArgumentException.class);
+        }
+        catch (IllegalArgumentException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testFromToAs() throws Exception
+    {
+        DwarfBean bean = new DwarfBean();
+
+        _map.to(bean);
+        Helper.assertEquals(DwarfsData.dopey, bean);
+        OptionMap map = new BasicOptionMap();
+
+        map.from(bean);
+        bean = new DwarfBean();
+        map.to(bean);
+        Helper.assertEquals(DwarfsData.dopey, bean);
+        Dwarf proxy = map.as(Dwarf.class);
+
+        Helper.assertEquals(DwarfsData.dopey, proxy);
+        map.clear();
+        _map.to(proxy);
+        Helper.assertEquals(DwarfsData.dopey, proxy);
+    }
+
+    @Test public void testFromToAsPrefixed() throws Exception
+    {
+        fromToAs(DwarfsData.bashful);
+        fromToAs(DwarfsData.doc);
+        fromToAs(DwarfsData.dopey);
+        fromToAs(DwarfsData.grumpy);
+        fromToAs(DwarfsData.happy);
+        fromToAs(DwarfsData.sleepy);
+        fromToAs(DwarfsData.sneezy);
+    }
+
+    @Test public void testGet()
+    {
+        OptionMap map = new BasicOptionMap();
+
+        // bashful
+        Helper.addDwarf(map, DwarfsData.bashful, false);
+        assertEquals(DwarfsData.bashful.weight, map.get(Dwarf.PROP_WEIGHT, double.class), Helper.DELTA);
+        map.add(Dwarf.PROP_HEIGHT, map.get(Dwarf.PROP_HEIGHT));
+        assertEquals(DwarfsData.bashful.height, map.get(Dwarf.PROP_HEIGHT, 1, double.class), Helper.DELTA);
+        assertEquals(DwarfsData.bashful.homePage, map.fetch(Dwarf.PROP_HOME_PAGE, URI.class));
+    }
+
+    @Test public void testGetAllException()
+    {
+        OptionMap map = new BasicOptionMap();
+
+        try
+        {
+            map.getAll(Dwarf.PROP_FORTUNE_NUMBER, String.class);
+            missing(IllegalArgumentException.class);
+        }
+        catch (IllegalArgumentException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testPropertyFirstUpper()
+    {
+        DwarfBean bean;
+        OptionMap map = new BasicOptionMap(true);
+
+        map.from(DwarfsData.bashful);
+        assertTrue(map.containsKey("Age"));
+        assertTrue(map.containsKey("Height"));
+        assertTrue(map.containsKey("Weight"));
+        assertTrue(map.containsKey("HomePage"));
+        assertTrue(map.containsKey("HomeDir"));
+        bean = new DwarfBean();
+        map.to(bean);
+        Helper.assertEquals(DwarfsData.bashful, bean);
+        Helper.assertEquals(DwarfsData.bashful, map.as(Dwarf.class));
+    }
+
+    @Test public void testPut()
+    {
+        OptionMap map = new BasicOptionMap();
+
+        map.add(Dwarf.PROP_AGE, new Integer(DwarfsData.sneezy.age));
+        map.put(Dwarf.PROP_HEIGHT, new Double(DwarfsData.sneezy.height));
+        map.add(Dwarf.PROP_HOME_DIR, DwarfsData.sneezy.homeDir);
+        map.add(Dwarf.PROP_WEIGHT, new Double(DwarfsData.sneezy.weight), 0);
+        map.put(Dwarf.PROP_HOME_PAGE, null);
+        map.put(Dwarf.PROP_HOME_PAGE, DwarfsData.sneezy.homePage);
+        map.add(Dwarf.PROP_FORTUNE_NUMBER, new Integer(DwarfsData.sneezy.fortuneNumber[1]));
+        map.add(Dwarf.PROP_FORTUNE_NUMBER, new Integer(DwarfsData.sneezy.fortuneNumber[2]));
+        map.add(Dwarf.PROP_FORTUNE_NUMBER, new Integer(0));
+        map.put(Dwarf.PROP_FORTUNE_NUMBER, new Integer(DwarfsData.sneezy.fortuneNumber[3]), 2);
+        map.add(Dwarf.PROP_FORTUNE_NUMBER, new Integer(DwarfsData.sneezy.fortuneNumber[0]), 0);
+        Helper.assertEquals(DwarfsData.sneezy, map.as(Dwarf.class));
+    }
+
+    @Test public void testPutAllException()
+    {
+        OptionMap map = new BasicOptionMap();
+
+        try
+        {
+            map.putAll(Dwarf.PROP_FORTUNE_NUMBER, new Integer(0));
+            missing(IllegalArgumentException.class);
+        }
+        catch (IllegalArgumentException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testPutGetFetchAll()
+    {
+        OptionMap map = new BasicOptionMap();
+
+        map.putAll(Dwarf.PROP_FORTUNE_NUMBER, DwarfsData.sneezy.fortuneNumber);
+        assertEquals(DwarfsData.sneezy.fortuneNumber.length, map.length(Dwarf.PROP_FORTUNE_NUMBER));
+        assertArrayEquals(DwarfsData.sneezy.fortuneNumber, map.getAll(Dwarf.PROP_FORTUNE_NUMBER, int[].class));
+        assertArrayEquals(DwarfsData.sneezy.fortuneNumber, map.fetchAll(Dwarf.PROP_FORTUNE_NUMBER, int[].class));
+        map.putAll(Dwarf.PROP_FORTUNE_NUMBER, (int[]) null);
+        assertEquals(0, map.length(Dwarf.PROP_FORTUNE_NUMBER));
+        assertEquals(0, map.getAll(Dwarf.PROP_FORTUNE_NUMBER, int[].class).length);
+        assertEquals(0, map.fetchAll(Dwarf.PROP_FORTUNE_NUMBER, int[].class).length);
+    }
+
+    @Test public void testResolve() throws Exception
+    {
+        StringBuilder buffer;
+        String input;
+
+        // simple value
+        input = "${height}";
+        buffer = new StringBuilder(input);
+
+        _map.resolve(buffer);
+        assertEquals("" + DwarfsData.dopey.getHeight(), buffer.toString());
+
+        // system property
+        input = "${@prop/user.home}";
+        buffer = new StringBuilder(input);
+
+        _map.resolve(buffer);
+        assertEquals(System.getProperty("user.home"), buffer.toString());
+
+        // system environment
+        input = "${@env/PATH}";
+        buffer = new StringBuilder(input);
+        try
+        {
+            _map.resolve(buffer);
+            assertEquals(System.getenv("PATH"), buffer.toString());
+        }
+        catch (Error e)
+        {
+            // retroweaver + JDK 1.4 throws Error on getenv
+        }
+
+        // unknown variable
+        input = "${no such name}";
+        buffer = new StringBuilder(input);
+
+        _map.resolve(buffer);
+        assertEquals(input, buffer.toString());
+
+        // small input
+        input = "${";
+        buffer = new StringBuilder(input);
+
+        _map.resolve(buffer);
+        assertEquals(input, buffer.toString());
+
+        // incorrect references
+        input = "${weight";
+        buffer = new StringBuilder(input);
+
+        _map.resolve(buffer);
+        assertEquals(input, buffer.toString());
+
+        // empty references
+        input = "jim${}";
+        buffer = new StringBuilder(input);
+
+        _map.resolve(buffer);
+        assertEquals(input, buffer.toString());
+
+        // escaped references
+        input = "${weight}";
+        buffer = new StringBuilder(input);
+
+        _map.resolve(buffer);
+        assertEquals("" + DwarfsData.dopey.getWeight(), buffer.toString());
+        input = "\\" + input;
+        buffer = new StringBuilder(input);
+
+        assertEquals(input, buffer.toString());
+    }
+
+    private void fromToAs(DwarfData dwarf)
+    {
+        String prefix = dwarf.name + '.';
+        DwarfBean bean = new DwarfBean();
+
+        _map.to(bean, prefix);
+        Helper.assertEquals(dwarf, bean);
+        OptionMap map = new BasicOptionMap();
+
+        map.from(bean, prefix);
+        bean = new DwarfBean();
+        map.to(bean, prefix);
+        Helper.assertEquals(dwarf, bean);
+        Dwarf proxy = map.as(Dwarf.class, prefix);
+
+        Helper.assertEquals(dwarf, proxy);
+        map.clear();
+        _map.to(proxy, prefix);
+        Helper.assertEquals(dwarf, proxy);
+    }
+}
diff --git a/src/test/java/org/ini4j/BasicProfileSectionTest.java b/src/test/java/org/ini4j/BasicProfileSectionTest.java
new file mode 100644
index 0000000..c0fca44
--- /dev/null
+++ b/src/test/java/org/ini4j/BasicProfileSectionTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.Dwarfs;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.Helper;
+import org.ini4j.test.TaleData;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Test;
+
+public class BasicProfileSectionTest extends Ini4jCase
+{
+    @Test public void testAddChild() throws Exception
+    {
+        Profile prof = Helper.newTaleIni();
+        Profile.Section dwarfs = prof.get(TaleData.PROP_DWARFS);
+        Profile.Section doc = dwarfs.getChild(Dwarfs.PROP_DOC);
+        Profile.Section dopey2 = doc.addChild(Dwarfs.PROP_DOPEY);
+
+        assertSame(doc, dopey2.getParent());
+        assertSame(dopey2, dwarfs.lookup(Dwarfs.PROP_DOC, Dwarfs.PROP_DOPEY));
+        assertSame(dopey2, dwarfs.lookup(Dwarfs.PROP_DOC + '/' + Dwarfs.PROP_DOPEY));
+        assertEquals(1, doc.childrenNames().length);
+        doc.removeChild(Dwarfs.PROP_DOPEY);
+        assertEquals(0, doc.childrenNames().length);
+        assertNull(dwarfs.lookup(Dwarfs.PROP_DOC, Dwarfs.PROP_DOPEY));
+        assertNull(dwarfs.lookup(Dwarfs.PROP_DOC + '/' + Dwarfs.PROP_DOPEY));
+    }
+
+    @Test public void testGetChild() throws Exception
+    {
+        Profile prof = Helper.newTaleIni();
+        Profile.Section dwarfs = prof.get(TaleData.PROP_DWARFS);
+
+        assertArrayEquals(DwarfsData.dwarfNames, dwarfs.childrenNames());
+        assertSame(prof.get(TaleData.bashful.name), dwarfs.getChild(Dwarfs.PROP_BASHFUL));
+        assertSame(prof.get(TaleData.doc.name), dwarfs.getChild(Dwarfs.PROP_DOC));
+        assertSame(prof.get(TaleData.dopey.name), dwarfs.getChild(Dwarfs.PROP_DOPEY));
+        assertSame(prof.get(TaleData.grumpy.name), dwarfs.getChild(Dwarfs.PROP_GRUMPY));
+        assertSame(prof.get(TaleData.happy.name), dwarfs.getChild(Dwarfs.PROP_HAPPY));
+        assertSame(prof.get(TaleData.sleepy.name), dwarfs.getChild(Dwarfs.PROP_SLEEPY));
+        assertSame(prof.get(TaleData.sneezy.name), dwarfs.getChild(Dwarfs.PROP_SNEEZY));
+    }
+
+    @Test public void testGetParent() throws Exception
+    {
+        Profile prof = Helper.newTaleIni();
+        Profile.Section dwarfs = prof.get(TaleData.PROP_DWARFS);
+
+        assertNull(dwarfs.getParent());
+        assertSame(dwarfs, prof.get(TaleData.bashful.name).getParent());
+        assertSame(dwarfs, prof.get(TaleData.doc.name).getParent());
+        assertSame(dwarfs, prof.get(TaleData.dopey.name).getParent());
+        assertSame(dwarfs, prof.get(TaleData.grumpy.name).getParent());
+        assertSame(dwarfs, prof.get(TaleData.happy.name).getParent());
+        assertSame(dwarfs, prof.get(TaleData.sleepy.name).getParent());
+        assertSame(dwarfs, prof.get(TaleData.sneezy.name).getParent());
+    }
+
+    @Test public void testLoad() throws Exception
+    {
+        Profile prof = Helper.loadTaleIni();
+        Profile.Section dwarfs = prof.get(TaleData.PROP_DWARFS);
+
+        Helper.assertEquals(DwarfsData.bashful, dwarfs.getChild(Dwarfs.PROP_BASHFUL).as(Dwarf.class));
+        Helper.assertEquals(DwarfsData.doc, dwarfs.getChild(Dwarfs.PROP_DOC).as(Dwarf.class));
+        Helper.assertEquals(DwarfsData.dopey, dwarfs.getChild(Dwarfs.PROP_DOPEY).as(Dwarf.class));
+        Helper.assertEquals(DwarfsData.grumpy, dwarfs.getChild(Dwarfs.PROP_GRUMPY).as(Dwarf.class));
+        Helper.assertEquals(DwarfsData.happy, dwarfs.getChild(Dwarfs.PROP_HAPPY).as(Dwarf.class));
+        Helper.assertEquals(DwarfsData.sleepy, dwarfs.getChild(Dwarfs.PROP_SLEEPY).as(Dwarf.class));
+        Helper.assertEquals(DwarfsData.sneezy, dwarfs.getChild(Dwarfs.PROP_SNEEZY).as(Dwarf.class));
+    }
+}
diff --git a/src/test/java/org/ini4j/BasicProfileTest.java b/src/test/java/org/ini4j/BasicProfileTest.java
new file mode 100644
index 0000000..46a190c
--- /dev/null
+++ b/src/test/java/org/ini4j/BasicProfileTest.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.DwarfBean;
+import org.ini4j.sample.Dwarfs;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.DwarfsData.DwarfData;
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import java.net.URI;
+
+public class BasicProfileTest extends Ini4jCase
+{
+    private static final String SECTION = "section";
+    private static final String NUMBER = "number";
+    private static final String SINGLE = "single";
+    private static final String SOLO = "solo";
+    private static final String LOCATION = "location";
+    private static final String LOCATION_1 = "http://www.ini4j.org";
+    private static final String LOCATION_2 = "http://ini4j.org";
+
+    /*
+     * thanx to Gary Pampara for bug report
+     */
+    @Test public void bug_2817403() throws Exception
+    {
+        BasicProfile prof = new BasicProfile();
+        Profile.Section sec = prof.add("section");
+
+        sec.add("player.name", "Joe");
+        sec.add("player.greeting", "Hi ${player.name}!");
+        sec.add("player.domain", "foo.bar");
+        sec.add("player.email", "${player.name}@${player.domain}");
+
+        //
+        assertEquals("Joe", sec.fetch("player.name"));
+        assertEquals("Hi Joe!", sec.fetch("player.greeting"));
+        assertEquals("foo.bar", sec.fetch("player.domain"));
+        assertEquals("Joe at foo.bar", sec.fetch("player.email"));
+
+        //
+        sec = prof.add("other");
+        sec.add("option", "${section/player.name}");
+        assertEquals("Joe", sec.fetch("option"));
+        sec.put("option", "${section/player.email}");
+        assertEquals("Joe at foo.bar", sec.fetch("option"));
+        sec.put("option2", "${option} ${section/player.name} ${section/player.domain}");
+        assertEquals("Joe at foo.bar Joe foo.bar", sec.fetch("option2"));
+    }
+
+    @Test public void testAddPut()
+    {
+        Profile prof = new BasicProfile();
+
+        prof.add(SECTION, Dwarf.PROP_AGE, DwarfsData.sneezy.age);
+        prof.put(SECTION, Dwarf.PROP_HEIGHT, DwarfsData.sneezy.height);
+        prof.add(SECTION, Dwarf.PROP_HOME_DIR, DwarfsData.sneezy.homeDir);
+        prof.add(SECTION, Dwarf.PROP_WEIGHT, DwarfsData.sneezy.weight);
+        prof.put(SECTION, Dwarf.PROP_HOME_PAGE, null);
+        prof.put(SECTION, Dwarf.PROP_HOME_PAGE, DwarfsData.sneezy.homePage);
+        prof.add(SECTION, Dwarf.PROP_FORTUNE_NUMBER, DwarfsData.sneezy.fortuneNumber[0]);
+        prof.add(SECTION, Dwarf.PROP_FORTUNE_NUMBER, DwarfsData.sneezy.fortuneNumber[1]);
+        prof.add(SECTION, Dwarf.PROP_FORTUNE_NUMBER, DwarfsData.sneezy.fortuneNumber[2]);
+        prof.add(SECTION, Dwarf.PROP_FORTUNE_NUMBER, DwarfsData.sneezy.fortuneNumber[3]);
+        Helper.assertEquals(DwarfsData.sneezy, prof.get(SECTION).as(Dwarf.class));
+        assertNotNull(prof.remove(SECTION, Dwarf.PROP_FORTUNE_NUMBER));
+        assertEquals(0, prof.get(SECTION).length(Dwarf.PROP_FORTUNE_NUMBER));
+        assertNotNull(prof.remove(SECTION));
+        assertNull(prof.remove(SECTION, Dwarf.PROP_FORTUNE_NUMBER));
+    }
+
+    @Test public void testFirstUpper()
+    {
+        BasicProfile prof = new BasicProfile(true, true);
+        DwarfsRW dwarfs = prof.as(DwarfsRW.class);
+
+        dwarfs.setBashful(DwarfsData.bashful);
+        assertTrue(prof.containsKey("Bashful"));
+        assertNotNull(dwarfs.getBashful());
+    }
+
+    @Test public void testFromToAs() throws Exception
+    {
+        BasicProfile prof = new BasicProfile();
+
+        Helper.addDwarfs(prof);
+        fromToAs(prof, DwarfsData.bashful);
+        fromToAs(prof, DwarfsData.doc);
+        fromToAs(prof, DwarfsData.dopey);
+        fromToAs(prof, DwarfsData.grumpy);
+        fromToAs(prof, DwarfsData.happy);
+        fromToAs(prof, DwarfsData.sleepy);
+        fromToAs(prof, DwarfsData.sneezy);
+
+        //
+        DwarfsRW dwarfs = prof.as(DwarfsRW.class);
+
+        Helper.assertEquals(DwarfsData.bashful, dwarfs.getBashful());
+        Helper.assertEquals(DwarfsData.doc, dwarfs.getDoc());
+        Helper.assertEquals(DwarfsData.dopey, dwarfs.getDopey());
+        Helper.assertEquals(DwarfsData.grumpy, dwarfs.getGrumpy());
+        Helper.assertEquals(DwarfsData.happy, dwarfs.getHappy());
+        Helper.assertEquals(DwarfsData.sleepy, dwarfs.getSleepy());
+        Helper.assertEquals(DwarfsData.sneezy, dwarfs.getSneezy());
+
+        //
+        prof.remove(Dwarfs.PROP_BASHFUL);
+        assertNull(prof.get(Dwarfs.PROP_BASHFUL));
+        assertEquals(0, prof.length(Dwarfs.PROP_BASHFUL));
+        assertNull(dwarfs.getBashful());
+        dwarfs.setBashful(DwarfsData.dopey);
+        Helper.assertEquals(DwarfsData.dopey, dwarfs.getBashful());
+    }
+
+    @Test public void testIniGetFetch()
+    {
+        Profile prof = new BasicProfile();
+        Profile.Section sec = Helper.addDwarf(prof, DwarfsData.dopey);
+
+        Helper.addDwarf(prof, DwarfsData.bashful);
+        Helper.addDwarf(prof, DwarfsData.doc);
+        assertEquals(sec.get(Dwarf.PROP_AGE), prof.get(Dwarfs.PROP_DOPEY, Dwarf.PROP_AGE));
+        assertEquals(DwarfsData.dopey.age, (int) prof.get(Dwarfs.PROP_DOPEY, Dwarf.PROP_AGE, int.class));
+        assertEquals(sec.get(Dwarf.PROP_WEIGHT), prof.get(Dwarfs.PROP_DOPEY, Dwarf.PROP_WEIGHT));
+        assertEquals(DwarfsData.dopey.weight, prof.fetch(Dwarfs.PROP_DOPEY, Dwarf.PROP_WEIGHT, double.class), Helper.DELTA);
+        assertEquals(sec.fetch(Dwarf.PROP_HEIGHT), prof.fetch(Dwarfs.PROP_DOPEY, Dwarf.PROP_HEIGHT));
+        assertEquals(DwarfsData.dopey.weight, prof.fetch(Dwarfs.PROP_DOPEY, Dwarf.PROP_WEIGHT, double.class), Helper.DELTA);
+        assertEquals(sec.fetch(Dwarf.PROP_HOME_PAGE), prof.fetch(Dwarfs.PROP_DOPEY, Dwarf.PROP_HOME_PAGE));
+        assertEquals(DwarfsData.dopey.homePage, prof.fetch(Dwarfs.PROP_DOPEY, Dwarf.PROP_HOME_PAGE, URI.class));
+
+        // nulls
+        assertNull(prof.get(SECTION, Dwarf.PROP_AGE));
+        assertEquals(0, (int) prof.get(SECTION, Dwarf.PROP_AGE, int.class));
+        assertNull(prof.get(SECTION, Dwarf.PROP_WEIGHT));
+        assertEquals(0.0, prof.fetch(SECTION, Dwarf.PROP_WEIGHT, double.class), Helper.DELTA);
+        assertNull(prof.fetch(SECTION, Dwarf.PROP_HEIGHT));
+        assertEquals(0.0, prof.fetch(SECTION, Dwarf.PROP_WEIGHT, double.class), Helper.DELTA);
+        assertNull(prof.fetch(SECTION, Dwarf.PROP_HOME_PAGE));
+        assertNull(prof.fetch(SECTION, Dwarf.PROP_HOME_PAGE, URI.class));
+    }
+
+    @Test public void testOptionArray() throws Exception
+    {
+        BasicProfile prof = new BasicProfile();
+        Profile.Section sec = prof.add(SECTION);
+
+        sec.add(NUMBER, 1);
+        sec.add(LOCATION, LOCATION_1);
+        sec.add(NUMBER, 2);
+        sec.add(LOCATION, LOCATION_2);
+        Section s = prof.get(SECTION).as(Section.class);
+
+        assertNotNull(s);
+        assertEquals(2, s.getNumber().length);
+        assertEquals(1, s.getNumber()[0]);
+        assertEquals(2, s.getNumber()[1]);
+        assertEquals(2, s.getLocation().length);
+        assertEquals(new URI(LOCATION_1), s.getLocation()[0]);
+        assertNull(s.getMissing());
+        int[] numbers = new int[] { 1, 2, 3, 4, 5 };
+
+        s.setNumber(numbers);
+        assertEquals(5, sec.length(NUMBER));
+    }
+
+    @Test public void testResolve() throws Exception
+    {
+        BasicProfile prof = new BasicProfile();
+
+        Helper.addDwarf(prof, DwarfsData.happy);
+        Profile.Section doc = Helper.addDwarf(prof, DwarfsData.doc);
+        StringBuilder buffer;
+        String input;
+
+        // other sections's value
+        input = "${happy/weight}";
+        buffer = new StringBuilder(input);
+
+        prof.resolve(buffer, doc);
+        assertEquals(String.valueOf(DwarfsData.happy.weight), buffer.toString());
+
+        // same sections's value
+        input = "${height}";
+        buffer = new StringBuilder(input);
+
+        prof.resolve(buffer, doc);
+        assertEquals(String.valueOf(DwarfsData.doc.height), buffer.toString());
+
+        // system property
+        input = "${@prop/user.home}";
+        buffer = new StringBuilder(input);
+
+        prof.resolve(buffer, doc);
+        assertEquals(System.getProperty("user.home"), buffer.toString());
+
+        // system environment
+        input = "${@env/PATH}";
+        buffer = new StringBuilder(input);
+        try
+        {
+            prof.resolve(buffer, doc);
+            assertEquals(System.getenv("PATH"), buffer.toString());
+        }
+        catch (Error e)
+        {
+            // retroweaver + JDK 1.4 throws Error on getenv
+        }
+
+        // unknown variable
+        input = "${no such name}";
+        buffer = new StringBuilder(input);
+
+        prof.resolve(buffer, doc);
+        assertEquals(input, buffer.toString());
+
+        // unknown section's unknown variable
+        input = "${no such section/no such name}";
+        buffer = new StringBuilder(input);
+
+        prof.resolve(buffer, doc);
+        assertEquals(input, buffer.toString());
+
+        // other section's unknown variable
+        input = "${happy/no such name}";
+        buffer = new StringBuilder(input);
+
+        prof.resolve(buffer, doc);
+        assertEquals(input, buffer.toString());
+
+        // small input
+        input = "${";
+        buffer = new StringBuilder(input);
+
+        prof.resolve(buffer, doc);
+        assertEquals(input, buffer.toString());
+
+        // incorrect references
+        input = "${doc/weight";
+        buffer = new StringBuilder(input);
+
+        prof.resolve(buffer, doc);
+        assertEquals(input, buffer.toString());
+
+        // empty references
+        input = "jim${}";
+        buffer = new StringBuilder(input);
+
+        prof.resolve(buffer, doc);
+        assertEquals(input, buffer.toString());
+
+        // escaped references
+        input = "${happy/weight}";
+        buffer = new StringBuilder(input);
+
+        prof.resolve(buffer, doc);
+        assertEquals("" + DwarfsData.happy.weight, buffer.toString());
+        input = "\\" + input;
+        buffer = new StringBuilder(input);
+
+        prof.resolve(buffer, doc);
+        assertEquals(input, buffer.toString());
+    }
+
+    @Test public void testResolveArray() throws Exception
+    {
+        StringBuilder buffer;
+        BasicProfile prof = new BasicProfile();
+
+        prof.add(SECTION).add(NUMBER, 1);
+        prof.add(SECTION).add(NUMBER, 2);
+        Profile.Section sec = prof.get(SECTION);
+
+        //
+        buffer = new StringBuilder("${section[0]/number}");
+        prof.resolve(buffer, sec);
+        assertEquals("1", buffer.toString());
+        buffer = new StringBuilder("${section[1]/number}");
+        prof.resolve(buffer, sec);
+        assertEquals("2", buffer.toString());
+        buffer = new StringBuilder("${section[0]/number}-${section[1]/number}");
+        prof.resolve(buffer, sec);
+        assertEquals("1-2", buffer.toString());
+
+        //
+        prof.clear();
+        sec = prof.add(SECTION);
+        sec.add(NUMBER, 1);
+        sec.add(NUMBER, 2);
+        sec = prof.get(SECTION);
+        assertEquals(2, sec.length(NUMBER));
+        buffer = new StringBuilder("${number}");
+        prof.resolve(buffer, sec);
+        assertEquals("2", buffer.toString());
+        buffer = new StringBuilder("${number[0]}-${section/number[1]}-${section[0]/number}");
+        prof.resolve(buffer, sec);
+        assertEquals("1-2-2", buffer.toString());
+    }
+
+    @Test public void testSectionArray() throws Exception
+    {
+        BasicProfile prof = new BasicProfile();
+
+        prof.add(SECTION).add(NUMBER, 1);
+        prof.add(SECTION).add(NUMBER, 2);
+        prof.add(SINGLE).add(NUMBER, 3);
+        Global g = prof.as(Global.class);
+
+        assertNotNull(g);
+        assertEquals(2, g.getSection().length);
+        assertEquals(1, g.getSingle().length);
+        assertNull(g.getMissing());
+        assertTrue(g.hasSection());
+    }
+
+    @Test public void testSetter()
+    {
+        BasicProfile prof = new BasicProfile();
+        Global g = prof.as(Global.class);
+        Section s1 = new SectionBean();
+        Section s2 = new SectionBean();
+        Section[] all = new Section[] { s1, s2 };
+
+        g.setSection(all);
+        assertEquals(2, prof.length("section"));
+        assertNull(g.getSolo());
+        g.setSolo(s1);
+        assertNotNull(g.getSolo());
+        g.setSolo(null);
+        assertEquals(0, prof.length("solo"));
+    }
+
+    private void fromToAs(BasicProfile prof, DwarfData dwarf)
+    {
+        Profile.Section sec = prof.get(dwarf.name);
+        Profile.Section dup = new BasicProfileSection(prof, SECTION);
+        DwarfBean bean = new DwarfBean();
+
+        sec.to(bean);
+        Helper.assertEquals(dwarf, bean);
+        dup.from(bean);
+        bean = new DwarfBean();
+        dup.to(bean);
+        Helper.assertEquals(dwarf, bean);
+        Dwarf proxy = dup.as(Dwarf.class);
+
+        Helper.assertEquals(dwarf, proxy);
+        dup.clear();
+        sec.to(proxy);
+        Helper.assertEquals(dwarf, proxy);
+        prof.remove(dup);
+    }
+
+    public static interface DwarfsRW extends Dwarfs
+    {
+        void setBashful(Dwarf value);
+    }
+
+    public static interface Global
+    {
+        Section[] getMissing();
+
+        Section[] getSection();
+
+        void setSection(Section[] value);
+
+        Section[] getSingle();
+
+        Section getSolo();
+
+        void setSolo(Section value);
+
+        boolean hasSection();
+    }
+
+    public static interface Section
+    {
+        URI[] getLocation();
+
+        void setLocation(URI[] value);
+
+        String[] getMissing();
+
+        void setMissing(String[] value);
+
+        int[] getNumber();
+
+        void setNumber(int[] value);
+    }
+
+    public static class SectionBean implements Section
+    {
+        private URI[] _location;
+        private String[] _missing;
+        private int[] _number;
+
+        @Override public URI[] getLocation()
+        {
+            return _location;
+        }
+
+        @Override public void setLocation(URI[] value)
+        {
+            _location = value;
+        }
+
+        @Override public String[] getMissing()
+        {
+            return _missing;
+        }
+
+        @Override public void setMissing(String[] value)
+        {
+            _missing = value;
+        }
+
+        @Override public int[] getNumber()
+        {
+            return _number;
+        }
+
+        @Override public void setNumber(int[] value)
+        {
+            _number = value;
+        }
+    }
+}
diff --git a/src/test/java/org/ini4j/BasicRegistryKeyTest.java b/src/test/java/org/ini4j/BasicRegistryKeyTest.java
new file mode 100644
index 0000000..d2c09ac
--- /dev/null
+++ b/src/test/java/org/ini4j/BasicRegistryKeyTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.Registry.Type;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Test;
+
+public class BasicRegistryKeyTest extends Ini4jCase
+{
+    private static final String KEY = "key";
+    private static final String DUMMY = "dummy";
+    private static final String OPTION = "option";
+
+    @Test public void testWrapped() throws Exception
+    {
+        BasicRegistry reg = new BasicRegistry();
+        Registry.Key parent = reg.add(KEY);
+        Registry.Key child = parent.addChild(DUMMY);
+
+        assertSame(parent, child.getParent());
+        assertSame(child, parent.getChild(DUMMY));
+        Registry.Key kid = child.addChild(KEY);
+
+        assertSame(kid, parent.lookup(DUMMY, KEY));
+        parent.put(OPTION, DUMMY);
+        parent.putType(OPTION, Type.REG_BINARY);
+        assertEquals(Type.REG_BINARY, parent.getType(OPTION));
+        parent.removeType(OPTION);
+        assertNull(parent.getType(OPTION));
+        parent.putType(OPTION, Type.REG_BINARY);
+        parent.remove(OPTION);
+        assertNull(parent.getType(OPTION));
+    }
+}
diff --git a/src/test/java/org/ini4j/BasicRegistryTest.java b/src/test/java/org/ini4j/BasicRegistryTest.java
new file mode 100644
index 0000000..cd0f7a6
--- /dev/null
+++ b/src/test/java/org/ini4j/BasicRegistryTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class BasicRegistryTest extends Ini4jCase
+{
+    private static final String KEY = "key";
+    private static final String DUMMY = "dummy";
+    private static final String VERSION = "Windows Registry Editor Version 5.00";
+
+    @Test public void testVersion()
+    {
+        BasicRegistry reg = new BasicRegistry();
+
+        assertEquals(VERSION, reg.getVersion());
+        reg.setVersion(DUMMY);
+        assertEquals(DUMMY, reg.getVersion());
+    }
+
+    @Test public void testWrapped() throws Exception
+    {
+        BasicRegistry reg = new BasicRegistry();
+        Registry.Key key1 = reg.add(KEY);
+        Registry.Key key2 = reg.add(KEY);
+
+        assertNotNull(key1);
+        assertNotNull(key2);
+        assertTrue(reg.get(KEY) instanceof Registry.Key);
+        assertTrue(reg.get(KEY, 1) instanceof Registry.Key);
+        Registry.Key key3 = new BasicRegistryKey(reg, KEY);
+
+        assertSame(key1, reg.put(KEY, key3, 0));
+        assertSame(key2, reg.put(KEY, key3));
+        assertSame(key3, reg.remove(KEY, 1));
+        assertSame(key3, reg.remove(KEY));
+        key1 = reg.add(KEY);
+        assertSame(key1, reg.remove(key1));
+    }
+}
diff --git a/src/test/java/org/ini4j/CommonMultiMapTest.java b/src/test/java/org/ini4j/CommonMultiMapTest.java
new file mode 100644
index 0000000..bf7a6da
--- /dev/null
+++ b/src/test/java/org/ini4j/CommonMultiMapTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CommonMultiMapTest extends Ini4jCase
+{
+    private static final String KEY = "key";
+    private static final String VALUE = "value";
+    private static final String COMMENT = "comment";
+
+    @Test public void testClearAndRemove() throws Exception
+    {
+        CommonMultiMap<String, String> map = new CommonMultiMap<String, String>();
+
+        assertNull(map.removeComment(KEY));
+
+        //
+        map.put(KEY, VALUE);
+        map.clear();
+        assertTrue(map.isEmpty());
+
+        //
+        map.put(KEY, VALUE);
+        map.remove(KEY);
+        assertNull(map.getComment(KEY));
+
+        //
+        map.put(KEY, VALUE);
+        map.remove(KEY, 0);
+        assertNull(map.getComment(KEY));
+
+        //
+        map.add(KEY, VALUE);
+        map.add(KEY, VALUE);
+        map.putComment(KEY, COMMENT);
+        map.remove(KEY, 0);
+        assertEquals(COMMENT, map.getComment(KEY));
+
+        //
+        map.put(KEY, VALUE);
+        map.putComment(KEY, COMMENT);
+        assertEquals(COMMENT, map.getComment(KEY));
+        map.clear();
+        assertNull(map.getComment(KEY));
+
+        //
+        map.put(KEY, VALUE);
+        map.putComment(KEY, COMMENT);
+        map.remove(KEY);
+        assertNull(map.getComment(KEY));
+
+        //
+        map.put(KEY, VALUE);
+        map.putComment(KEY, COMMENT);
+        assertEquals(COMMENT, map.removeComment(KEY));
+        assertNull(map.getComment(KEY));
+
+        //
+        map.put(KEY, VALUE);
+        map.putComment(KEY, COMMENT);
+        map.remove(KEY, 0);
+        assertNull(map.getComment(KEY));
+    }
+
+    @Test public void testPutAll() throws Exception
+    {
+        CommonMultiMap<String, String> map = new CommonMultiMap<String, String>();
+        CommonMultiMap<String, String> copy = new CommonMultiMap<String, String>();
+
+        map.put(KEY, VALUE);
+        map.putComment(KEY, COMMENT);
+        copy.putAll(map);
+        assertEquals(COMMENT, copy.getComment(KEY));
+        Map<String, String> simple = new HashMap<String, String>();
+
+        simple.put(KEY, VALUE);
+        copy.clear();
+        assertTrue(copy.isEmpty());
+        copy.putAll(simple);
+        assertNull(copy.getComment(KEY));
+        assertEquals(VALUE, copy.get(KEY));
+
+        //
+        map = new CommonMultiMap<String, String>();
+        map.put(KEY, VALUE);
+        copy.clear();
+        copy.putAll(map);
+        assertEquals(VALUE, copy.get(KEY));
+        assertNull(copy.getComment(KEY));
+    }
+}
diff --git a/src/test/java/org/ini4j/ConfigParserTest.java b/src/test/java/org/ini4j/ConfigParserTest.java
new file mode 100644
index 0000000..a0e080c
--- /dev/null
+++ b/src/test/java/org/ini4j/ConfigParserTest.java
@@ -0,0 +1,490 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.Dwarfs;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.Helper;
+
+import org.junit.After;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.StringReader;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ConfigParserTest extends Ini4jCase
+{
+    private static final String SECTION = "section";
+    private static final String OPTION = "option";
+    private static final String DWARFS_PATH = "org/ini4j/addon/dwarfs-py.ini";
+    private static final String BAD = "[bashful\nage=3";
+    private static final String TEST_DATA_PATH = "src/test/resources";
+    private static final String TEST_WORK_PATH = "target";
+    private static final String MISSING = "missing";
+    private static final String MISSING_REF = "%(missing)";
+    private static final String DUMMY = "dummy";
+    protected ConfigParser instance;
+
+    @Before @Override public void setUp() throws Exception
+    {
+        super.setUp();
+        instance = new ConfigParser();
+    }
+
+    @After @Override public void tearDown()
+    {
+    }
+
+    @Test public void testAddHasRemove() throws Exception
+    {
+        assertFalse(instance.hasSection(SECTION));
+        assertFalse(instance.hasOption(SECTION, OPTION));
+        assertFalse(instance.getIni().containsKey(SECTION));
+        instance.addSection(SECTION);
+        assertTrue(instance.hasSection(SECTION));
+        instance.set(SECTION, OPTION, "dummy");
+        assertTrue(instance.hasOption(SECTION, OPTION));
+        assertTrue(instance.getIni().get(SECTION).containsKey(OPTION));
+        instance.removeOption(SECTION, OPTION);
+        assertFalse(instance.hasOption(SECTION, OPTION));
+        instance.removeSection(SECTION);
+        assertFalse(instance.hasSection(SECTION));
+    }
+
+    @Test public void testAddSectionDuplicate() throws Exception
+    {
+        instance.addSection(SECTION);
+        try
+        {
+            instance.addSection(SECTION);
+            missing(ConfigParser.DuplicateSectionException.class);
+        }
+        catch (ConfigParser.DuplicateSectionException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testAddSectionIllegal() throws Exception
+    {
+        try
+        {
+            instance.addSection(ConfigParser.PyIni.DEFAULT_SECTION_NAME);
+            missing(IllegalArgumentException.class);
+        }
+        catch (IllegalArgumentException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testDefaults() throws Exception
+    {
+        Map<String, String> defs = new HashMap<String, String>();
+
+        instance = new ConfigParser(defs);
+
+        assertSame(defs, instance.defaults());
+    }
+
+    @Test public void testDwarfs() throws Exception
+    {
+        readDwarfs();
+        checkEquals(DwarfsData.bashful, Dwarfs.PROP_BASHFUL);
+        checkEquals(DwarfsData.doc, Dwarfs.PROP_DOC);
+        checkEquals(DwarfsData.dopey, Dwarfs.PROP_DOPEY);
+        checkEquals(DwarfsData.happy, Dwarfs.PROP_HAPPY);
+        checkEquals(DwarfsData.grumpy, Dwarfs.PROP_GRUMPY);
+        checkEquals(DwarfsData.sleepy, Dwarfs.PROP_SLEEPY);
+        checkEquals(DwarfsData.sneezy, Dwarfs.PROP_SNEEZY);
+    }
+
+    @Test public void testGet() throws Exception
+    {
+        Ini.Section section = instance.getIni().add(SECTION);
+
+        section.put(OPTION, "on");
+        assertTrue(instance.getBoolean(SECTION, OPTION));
+        section.put(OPTION, "1");
+        assertTrue(instance.getBoolean(SECTION, OPTION));
+        section.put(OPTION, "true");
+        assertTrue(instance.getBoolean(SECTION, OPTION));
+        section.put(OPTION, "yes");
+        assertTrue(instance.getBoolean(SECTION, OPTION));
+        section.put(OPTION, "TruE");
+        assertTrue(instance.getBoolean(SECTION, OPTION));
+
+        //
+        section.put(OPTION, "off");
+        assertFalse(instance.getBoolean(SECTION, OPTION));
+        section.put(OPTION, "0");
+        assertFalse(instance.getBoolean(SECTION, OPTION));
+        section.put(OPTION, "no");
+        assertFalse(instance.getBoolean(SECTION, OPTION));
+        section.put(OPTION, "false");
+        assertFalse(instance.getBoolean(SECTION, OPTION));
+        section.put(OPTION, "FalsE");
+        assertFalse(instance.getBoolean(SECTION, OPTION));
+
+        // ints
+        section.put(OPTION, "12");
+        assertEquals(12, instance.getInt(SECTION, OPTION));
+        assertEquals(12L, instance.getLong(SECTION, OPTION));
+        section.put(OPTION, "1.2");
+        assertEquals(1.2f, instance.getFloat(SECTION, OPTION), Helper.DELTA);
+        assertEquals(1.2, instance.getDouble(SECTION, OPTION), Helper.DELTA);
+    }
+
+    @Test public void testGetBooleanException() throws Exception
+    {
+        Ini.Section section = instance.getIni().add(SECTION);
+
+        section.put(OPTION, "joe");
+        try
+        {
+            instance.getBoolean(SECTION, OPTION);
+            missing(IllegalArgumentException.class);
+        }
+        catch (IllegalArgumentException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testGetMissinOptionException() throws Exception
+    {
+        instance.addSection(SECTION);
+        instance.set(SECTION, OPTION, MISSING_REF);
+        try
+        {
+            instance.get(SECTION, OPTION);
+            missing(ConfigParser.InterpolationMissingOptionException.class);
+        }
+        catch (ConfigParser.InterpolationMissingOptionException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testGetNoOption() throws Exception
+    {
+        instance.getIni().add(SECTION);
+        try
+        {
+            instance.get(SECTION, OPTION);
+            missing(ConfigParser.NoOptionException.class);
+        }
+        catch (ConfigParser.NoOptionException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testGetNoSection() throws Exception
+    {
+        try
+        {
+            instance.get(SECTION, OPTION);
+            missing(ConfigParser.NoSectionException.class);
+        }
+        catch (ConfigParser.NoSectionException x)
+        {
+            //
+        }
+    }
+
+    @Test
+    @SuppressWarnings("empty-statement")
+    public void testGetVars() throws Exception
+    {
+        Map<String, String> vars = new HashMap<String, String>();
+
+        instance = new ConfigParser(vars);
+
+        instance.addSection(SECTION);
+        instance.set(SECTION, OPTION, MISSING_REF);
+        assertEquals(MISSING_REF, instance.get(SECTION, OPTION, true));
+        requireMissingOptionException(SECTION, OPTION);
+        vars.put(MISSING, DUMMY);
+        assertEquals(DUMMY, instance.get(SECTION, OPTION));
+        vars.remove(MISSING);
+        requireMissingOptionException(SECTION, OPTION);
+        instance.getIni().add(ConfigParser.PyIni.DEFAULT_SECTION_NAME);
+        ((ConfigParser.PyIni) instance.getIni()).getDefaultSection().put(MISSING, DUMMY);
+        assertEquals(DUMMY, instance.get(SECTION, OPTION));
+        ((ConfigParser.PyIni) instance.getIni()).getDefaultSection().remove(MISSING);
+        requireMissingOptionException(SECTION, OPTION);
+        instance = new ConfigParser();
+        instance.addSection(SECTION);
+        instance.set(SECTION, OPTION, MISSING_REF);
+        vars.put(MISSING, DUMMY);
+        assertEquals(DUMMY, instance.get(SECTION, OPTION, false, vars));
+    }
+
+    @Test public void testItems() throws Exception
+    {
+        Ini ini = new Ini();
+
+        ini.add(SECTION).from(DwarfsData.dopey);
+        Ini.Section section = ini.get(SECTION);
+        Ini.Section dopey = ini.add(Dwarfs.PROP_DOPEY);
+
+        for (String key : section.keySet())
+        {
+            dopey.put(key.toLowerCase(), section.get(key));
+        }
+
+        readDwarfs();
+        List<Map.Entry<String, String>> items = instance.items(Dwarfs.PROP_DOPEY);
+
+        assertEquals(5, items.size());
+        assertEquals(6, dopey.size());
+        for (Map.Entry<String, String> entry : items)
+        {
+            assertEquals(dopey.get(entry.getKey()), entry.getValue());
+        }
+
+        // raw
+        dopey = instance.getIni().get(Dwarfs.PROP_DOPEY);
+        items = instance.items(Dwarfs.PROP_DOPEY, true);
+
+        assertEquals(5, items.size());
+        assertEquals("%(_weight)", dopey.get(Dwarf.PROP_WEIGHT));
+        assertEquals("%(_height)", dopey.get(Dwarf.PROP_HEIGHT));
+    }
+
+    @Test public void testOptions() throws Exception
+    {
+        instance.addSection(SECTION);
+        assertEquals(0, instance.options(SECTION).size());
+        for (int i = 0; i < 10; i++)
+        {
+            instance.set(SECTION, OPTION + i, DUMMY);
+        }
+
+        assertEquals(10, instance.options(SECTION).size());
+    }
+
+    @Test public void testRead() throws Exception
+    {
+        File file = newTestFile(DWARFS_PATH);
+
+        assertTrue(file.exists());
+        instance.read(file.getCanonicalPath());
+        instance.read(file);
+        instance.read(new FileReader(file));
+        instance.read(new FileInputStream(file));
+        instance.read(file.toURI().toURL());
+    }
+
+    @Test public void testReadFileException() throws Exception
+    {
+        try
+        {
+            instance.read(badFile());
+            missing(ConfigParser.ParsingException.class);
+        }
+        catch (ConfigParser.ParsingException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testReadReaderException() throws Exception
+    {
+        try
+        {
+            instance.read(new StringReader(BAD));
+            missing(ConfigParser.ParsingException.class);
+        }
+        catch (ConfigParser.ParsingException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testReadStreamException() throws Exception
+    {
+        try
+        {
+            instance.read(new ByteArrayInputStream(BAD.getBytes()));
+            missing(ConfigParser.ParsingException.class);
+        }
+        catch (ConfigParser.ParsingException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testReadURLException() throws Exception
+    {
+        try
+        {
+            instance.read(badFile().toURI().toURL());
+            missing(ConfigParser.ParsingException.class);
+        }
+        catch (ConfigParser.ParsingException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testSections() throws Exception
+    {
+        instance.addSection(SECTION);
+        assertEquals(1, instance.sections().size());
+        for (int i = 0; i < 10; i++)
+        {
+            instance.addSection(SECTION + i);
+        }
+
+        assertEquals(11, instance.sections().size());
+    }
+
+    @Test public void testSet() throws Exception
+    {
+        instance.addSection(SECTION);
+        instance.set(SECTION, OPTION, "dummy");
+        assertEquals("dummy", instance.getIni().get(SECTION).get(OPTION));
+        assertTrue(instance.hasOption(SECTION, OPTION));
+        instance.set(SECTION, OPTION, null);
+        assertFalse(instance.hasOption(SECTION, OPTION));
+    }
+
+    @Test public void testSetNoSection() throws Exception
+    {
+        try
+        {
+            instance.set(SECTION, OPTION, "dummy");
+            missing(ConfigParser.NoSectionException.class);
+        }
+        catch (ConfigParser.NoSectionException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testWrite() throws Exception
+    {
+        File input = newTestFile(DWARFS_PATH);
+        File output = new File(TEST_WORK_PATH, input.getName());
+
+        instance.read(input);
+        instance.write(output);
+        checkWrite(output);
+        instance.write(new FileWriter(output));
+        checkWrite(output);
+        instance.write(new FileOutputStream(output));
+        checkWrite(output);
+    }
+
+    protected void checkEquals(Dwarf expected, String sectionName) throws Exception
+    {
+        assertEquals("" + expected.getAge(), instance.get(sectionName, Dwarf.PROP_AGE));
+        assertEquals("" + expected.getHeight(), instance.get(sectionName, Dwarf.PROP_HEIGHT));
+        assertEquals("" + expected.getWeight(), instance.get(sectionName, Dwarf.PROP_WEIGHT));
+        assertEquals("" + expected.getHomePage(), instance.get(sectionName, Dwarf.PROP_HOME_PAGE.toLowerCase()));
+        assertEquals("" + expected.getHomeDir(), instance.get(sectionName, Dwarf.PROP_HOME_DIR.toLowerCase()));
+    }
+
+    protected File newTestFile(String path)
+    {
+        return new File(TEST_DATA_PATH, path);
+    }
+
+    protected void readDwarfs() throws Exception
+    {
+        instance.read(newTestFile(DWARFS_PATH));
+    }
+
+    private File badFile() throws IOException
+    {
+        File f = File.createTempFile("test", "ini");
+
+        f.deleteOnExit();
+        FileWriter w = new FileWriter(f);
+
+        w.write(BAD, 0, BAD.length());
+        w.close();
+
+        return f;
+    }
+
+    private void checkEquals(Map<String, String> a, Map<String, String> b) throws Exception
+    {
+        if (a == null)
+        {
+            assertNull(b);
+        }
+        else
+        {
+            assertEquals(a.size(), b.size());
+            for (String key : a.keySet())
+            {
+                assertEquals(a.get(key), b.get(key));
+            }
+        }
+    }
+
+    private void checkWrite(File file) throws Exception
+    {
+        ConfigParser saved = new ConfigParser(instance.defaults());
+
+        saved.read(file);
+        checkEquals(((ConfigParser.PyIni) instance.getIni()).getDefaultSection(),
+            ((ConfigParser.PyIni) saved.getIni()).getDefaultSection());
+        assertEquals(instance.sections().size(), saved.sections().size());
+        for (String sectionName : instance.sections())
+        {
+            checkEquals(instance.getIni().get(sectionName), saved.getIni().get(sectionName));
+        }
+    }
+
+    @SuppressWarnings("empty-statement")
+    private void requireMissingOptionException(String sectionName, String optionName) throws Exception
+    {
+        try
+        {
+            instance.get(sectionName, optionName);
+            fail();
+        }
+        catch (ConfigParser.InterpolationMissingOptionException x)
+        {
+            ;
+        }
+    }
+}
diff --git a/src/test/java/org/ini4j/ConfigTest.java b/src/test/java/org/ini4j/ConfigTest.java
new file mode 100644
index 0000000..cfdf967
--- /dev/null
+++ b/src/test/java/org/ini4j/ConfigTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.nio.charset.Charset;
+
+public class ConfigTest extends Ini4jCase
+{
+    @Test public void testDefaults()
+    {
+        Config def = newDefaultConfig();
+
+        assertEquals(def, new Config());
+        assertEquals(def, Config.getGlobal());
+        assertEquals(def, Config.getGlobal().clone());
+    }
+
+    @Test public void testSystemProperties()
+    {
+        Config exp = newInverseConfig();
+
+        setBoolean(Config.PROP_EMPTY_OPTION, exp.isEmptyOption());
+        setBoolean(Config.PROP_EMPTY_SECTION, exp.isEmptySection());
+        setBoolean(Config.PROP_GLOBAL_SECTION, exp.isGlobalSection());
+        setString(Config.PROP_GLOBAL_SECTION_NAME, exp.getGlobalSectionName());
+        setBoolean(Config.PROP_INCLUDE, exp.isInclude());
+        setBoolean(Config.PROP_LOWER_CASE_OPTION, exp.isLowerCaseOption());
+        setBoolean(Config.PROP_LOWER_CASE_SECTION, exp.isLowerCaseSection());
+        setBoolean(Config.PROP_MULTI_OPTION, exp.isMultiOption());
+        setBoolean(Config.PROP_MULTI_SECTION, exp.isMultiSection());
+        setBoolean(Config.PROP_STRICT_OPERATOR, exp.isStrictOperator());
+        setBoolean(Config.PROP_UNNAMED_SECTION, exp.isUnnamedSection());
+        setBoolean(Config.PROP_ESCAPE, exp.isEscape());
+        setChar(Config.PROP_PATH_SEPARATOR, exp.getPathSeparator());
+        setBoolean(Config.PROP_TREE, exp.isTree());
+        setBoolean(Config.PROP_PROPERTY_FIRST_UPPER, exp.isPropertyFirstUpper());
+        setString(Config.PROP_LINE_SEPARATOR, exp.getLineSeparator());
+        setCharset(Config.PROP_FILE_ENCODING, exp.getFileEncoding());
+        setBoolean(Config.PROP_COMMENT, exp.isComment());
+        setBoolean(Config.PROP_HEADER_COMMENT, exp.isHeaderComment());
+        Config cfg = new Config();
+
+        assertEquals(exp, cfg);
+    }
+
+    private void setBoolean(String prop, boolean value)
+    {
+        System.setProperty(Config.KEY_PREFIX + prop, String.valueOf(value));
+    }
+
+    private void setChar(String prop, char value)
+    {
+        System.setProperty(Config.KEY_PREFIX + prop, String.valueOf(value));
+    }
+
+    private void setCharset(String prop, Charset value)
+    {
+        System.setProperty(Config.KEY_PREFIX + prop, String.valueOf(value));
+    }
+
+    private void setString(String prop, String value)
+    {
+        System.setProperty(Config.KEY_PREFIX + prop, value);
+    }
+
+    private void assertEquals(Config exp, Config act)
+    {
+        Assert.assertEquals(exp.isEmptyOption(), act.isEmptyOption());
+        Assert.assertEquals(exp.isEmptySection(), act.isEmptySection());
+        Assert.assertEquals(exp.isEscape(), act.isEscape());
+        Assert.assertEquals(exp.isGlobalSection(), act.isGlobalSection());
+        Assert.assertEquals(exp.isInclude(), act.isInclude());
+        Assert.assertEquals(exp.isLowerCaseOption(), act.isLowerCaseOption());
+        Assert.assertEquals(exp.isLowerCaseSection(), act.isLowerCaseSection());
+        Assert.assertEquals(exp.isMultiOption(), act.isMultiOption());
+        Assert.assertEquals(exp.isMultiSection(), act.isMultiSection());
+        Assert.assertEquals(exp.isStrictOperator(), act.isStrictOperator());
+        Assert.assertEquals(exp.isUnnamedSection(), act.isUnnamedSection());
+        Assert.assertEquals(exp.getGlobalSectionName(), act.getGlobalSectionName());
+        Assert.assertEquals(exp.getPathSeparator(), act.getPathSeparator());
+        Assert.assertEquals(exp.isTree(), act.isTree());
+        Assert.assertEquals(exp.isPropertyFirstUpper(), act.isPropertyFirstUpper());
+        Assert.assertEquals(exp.getLineSeparator(), act.getLineSeparator());
+        Assert.assertEquals(exp.getFileEncoding(), act.getFileEncoding());
+        Assert.assertEquals(exp.isComment(), act.isComment());
+        Assert.assertEquals(exp.isHeaderComment(), act.isHeaderComment());
+    }
+
+    private Config newDefaultConfig()
+    {
+        Config cfg = new Config();
+
+        cfg.setEmptyOption(false);
+        cfg.setEmptySection(false);
+        cfg.setEscape(true);
+        cfg.setGlobalSection(false);
+        cfg.setGlobalSectionName("?");
+        cfg.setInclude(false);
+        cfg.setLowerCaseOption(false);
+        cfg.setLowerCaseSection(false);
+        cfg.setMultiSection(false);
+        cfg.setMultiOption(true);
+        cfg.setStrictOperator(false);
+        cfg.setUnnamedSection(false);
+        cfg.setPathSeparator('/');
+        cfg.setTree(true);
+        cfg.setPropertyFirstUpper(false);
+        cfg.setLineSeparator(System.getProperty("line.separator"));
+        cfg.setFileEncoding(Charset.forName("UTF-8"));
+        cfg.setComment(true);
+        cfg.setHeaderComment(true);
+
+        return cfg;
+    }
+
+    private Config newInverseConfig()
+    {
+        Config cfg = newDefaultConfig();
+
+        cfg.setEmptyOption(!cfg.isEmptyOption());
+        cfg.setEmptySection(!cfg.isEmptySection());
+        cfg.setEscape(!cfg.isEscape());
+        cfg.setGlobalSection(!cfg.isGlobalSection());
+        cfg.setGlobalSectionName("+");
+        cfg.setInclude(!cfg.isInclude());
+        cfg.setLowerCaseOption(!cfg.isLowerCaseOption());
+        cfg.setLowerCaseSection(!cfg.isLowerCaseSection());
+        cfg.setMultiSection(!cfg.isMultiSection());
+        cfg.setMultiOption(!cfg.isMultiOption());
+        cfg.setStrictOperator(!cfg.isStrictOperator());
+        cfg.setUnnamedSection(!cfg.isUnnamedSection());
+        cfg.setPathSeparator('?');
+        cfg.setTree(!cfg.isTree());
+        cfg.setPropertyFirstUpper(!cfg.isPropertyFirstUpper());
+        cfg.setComment(!cfg.isComment());
+        cfg.setHeaderComment(!cfg.isHeaderComment());
+
+        //cfg.setLineSeparator("\t");
+        //cfg.setFileEncoding(Charset.forName("ASCII"));
+        return cfg;
+    }
+}
diff --git a/src/org/ini4j/OptionMap.java b/src/test/java/org/ini4j/Ini4jCase.java
similarity index 63%
copy from src/org/ini4j/OptionMap.java
copy to src/test/java/org/ini4j/Ini4jCase.java
index 5cb908d..7cffeac 100644
--- a/src/org/ini4j/OptionMap.java
+++ b/src/test/java/org/ini4j/Ini4jCase.java
@@ -15,21 +15,20 @@
  */
 package org.ini4j;
 
-public interface OptionMap extends MultiMap<String, String>
-{
-    <T> T as(Class<T> clazz);
-
-    <T> T as(Class<T> clazz, String keyPrefix);
-
-    String fetch(Object key);
-
-    String fetch(Object key, int index);
+import junit.framework.TestCase;
 
-    void from(Object bean);
+import org.ini4j.test.Helper;
 
-    void from(Object bean, String keyPrefix);
-
-    void to(Object bean);
-
-    void to(Object bean, String keyPrefix);
+public class Ini4jCase extends TestCase
+{
+    @Override protected void setUp() throws Exception
+    {
+        super.setUp();
+        Helper.resetConfig();
+    }
+
+    protected void missing(Class<? extends Exception> clazz)
+    {
+        fail("Missing exception: " + clazz.getName());
+    }
 }
diff --git a/src/test/java/org/ini4j/IniPreferencesFactoryTest.java b/src/test/java/org/ini4j/IniPreferencesFactoryTest.java
new file mode 100644
index 0000000..e6a3384
--- /dev/null
+++ b/src/test/java/org/ini4j/IniPreferencesFactoryTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+import java.util.prefs.Preferences;
+
+public class IniPreferencesFactoryTest extends Ini4jCase
+{
+    private static final String DUMMY = "dummy";
+
+    @Test public void testGetIniLocation() throws Exception
+    {
+        IniPreferencesFactory factory = new IniPreferencesFactory();
+
+        System.setProperty(DUMMY, DUMMY);
+        assertEquals(DUMMY, factory.getIniLocation(DUMMY));
+        System.getProperties().remove(DUMMY);
+        assertNull(factory.getIniLocation(DUMMY));
+    }
+
+    @SuppressWarnings("empty-statement")
+    @Test public void testGetResourceAsStream() throws Exception
+    {
+        IniPreferencesFactory factory = new IniPreferencesFactory();
+
+        // class path
+        assertNotNull(factory.getResourceAsStream(Helper.DWARFS_INI));
+
+        // url
+        String location = Helper.getResourceURL(Helper.DWARFS_INI).toString();
+
+        assertNotNull(factory.getResourceAsStream(location));
+
+        // invalid url should throw IllegalArgumentException
+        try
+        {
+            factory.getResourceAsStream("http://");
+            fail();
+        }
+        catch (IllegalArgumentException x)
+        {
+            ;
+        }
+    }
+
+    @Test public void testNewIniPreferences()
+    {
+        System.setProperty(DUMMY, DUMMY);
+        try
+        {
+            new IniPreferencesFactory().newIniPreferences(DUMMY);
+            missing(IllegalArgumentException.class);
+        }
+        catch (IllegalArgumentException x)
+        {
+            //
+        }
+        finally
+        {
+            System.getProperties().remove(DUMMY);
+        }
+    }
+
+    @Test public void testSystemRoot() throws Exception
+    {
+        Preferences prefs = Preferences.systemRoot();
+
+        assertNotNull(prefs);
+        assertEquals(IniPreferences.class, prefs.getClass());
+        assertSame(prefs, Preferences.systemRoot());
+    }
+
+    @Test public void testUserRoot() throws Exception
+    {
+        Preferences prefs = Preferences.userRoot();
+
+        assertNotNull(prefs);
+        assertEquals(IniPreferences.class, prefs.getClass());
+        assertSame(prefs, Preferences.userRoot());
+    }
+}
diff --git a/src/test/java/org/ini4j/IniPreferencesTest.java b/src/test/java/org/ini4j/IniPreferencesTest.java
new file mode 100644
index 0000000..4d2f3fe
--- /dev/null
+++ b/src/test/java/org/ini4j/IniPreferencesTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.Dwarfs;
+
+import org.ini4j.spi.BeanAccess;
+import org.ini4j.spi.BeanTool;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.Helper;
+import org.ini4j.test.TaleData;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+import java.util.prefs.Preferences;
+
+public class IniPreferencesTest extends Ini4jCase
+{
+    private static final String DUMMY = "dummy";
+
+    @Test public void testConstructor() throws Exception
+    {
+        Ini ini = Helper.newDwarfsIni();
+        IniPreferences prefs = new IniPreferences(ini);
+
+        assertSame(ini, prefs.getIni());
+        Helper.assertEquals(DwarfsData.dwarfs, ini.as(Dwarfs.class));
+        prefs = new IniPreferences(Helper.getResourceStream(Helper.DWARFS_INI));
+        Helper.assertEquals(DwarfsData.doc, newDwarf(prefs.node(Dwarfs.PROP_DOC)));
+        prefs = new IniPreferences(Helper.getResourceReader(Helper.DWARFS_INI));
+        Helper.assertEquals(DwarfsData.happy, newDwarf(prefs.node(Dwarfs.PROP_HAPPY)));
+        prefs = new IniPreferences(Helper.getResourceURL(Helper.DWARFS_INI));
+        Helper.assertEquals(DwarfsData.sleepy, newDwarf(prefs.node(Dwarfs.PROP_SLEEPY)));
+    }
+
+    @Test public void testMisc() throws Exception
+    {
+        Ini ini = new Ini();
+        IniPreferences prefs = new IniPreferences(ini);
+
+        // do nothing, but doesn't throw exception
+        prefs.sync();
+        prefs.flush();
+
+        // node & key count
+        assertEquals(0, prefs.keysSpi().length);
+        assertEquals(0, prefs.childrenNamesSpi().length);
+
+        // childNode for new and for existing section
+        assertNotNull(prefs.node(Dwarfs.PROP_DOC));
+        assertEquals(1, prefs.childrenNamesSpi().length);
+        ini.add(Dwarfs.PROP_HAPPY);
+        assertNotNull(prefs.node(Dwarfs.PROP_HAPPY));
+        assertEquals(2, prefs.childrenNamesSpi().length);
+
+        // SectionPreferences
+        IniPreferences.SectionPreferences sec = (IniPreferences.SectionPreferences) prefs.node(Dwarfs.PROP_DOC);
+
+        assertEquals(0, sec.childrenNamesSpi().length);
+
+        // do nothing, but doesn't throw exception
+        sec.sync();
+        sec.syncSpi();
+        sec.flush();
+        sec.flushSpi();
+
+        // empty
+        assertEquals(0, sec.keysSpi().length);
+
+        // add one key
+        sec.put(Dwarf.PROP_AGE, "87");
+        sec.flush();
+        assertEquals("87", sec.getSpi(Dwarf.PROP_AGE));
+
+        // has one key
+        assertEquals(1, sec.keysSpi().length);
+
+        // remove key
+        sec.remove(Dwarf.PROP_AGE);
+        sec.flush();
+
+        // has 0 key
+        assertEquals(0, sec.keysSpi().length);
+        sec.removeNode();
+        prefs.flush();
+        assertNull(ini.get(Dwarfs.PROP_DOC));
+    }
+
+    @Test public void testTaleTree() throws Exception
+    {
+        Ini ini = Helper.newTaleIni();
+        IniPreferences prefs = new IniPreferences(ini);
+        Preferences dwarfs = prefs.node(TaleData.PROP_DWARFS);
+
+        Helper.assertEquals(DwarfsData.doc, newDwarf(dwarfs.node(Dwarfs.PROP_DOC)));
+        assertArrayEquals(DwarfsData.dwarfNames, dwarfs.childrenNames());
+        assertEquals(1, prefs.childrenNames().length);
+    }
+
+    @Test public void testTree() throws Exception
+    {
+        Ini ini = new Ini();
+        IniPreferences prefs = new IniPreferences(ini);
+        IniPreferences.SectionPreferences sec = (IniPreferences.SectionPreferences) prefs.node(Dwarfs.PROP_DOC);
+        Preferences child = sec.node(DUMMY);
+
+        assertNotNull(child);
+        assertNotNull(sec.node(DUMMY));
+        assertNotNull(ini.get(Dwarfs.PROP_DOC).getChild(DUMMY));
+        assertEquals(1, prefs.childrenNames().length);
+    }
+
+    @SuppressWarnings("empty-statement")
+    @Test public void testUnsupported() throws Exception
+    {
+        Ini ini = new Ini();
+        IniPreferences prefs = new IniPreferences(ini);
+
+        try
+        {
+            prefs.getSpi(DUMMY);
+            fail();
+        }
+        catch (UnsupportedOperationException x)
+        {
+            ;
+        }
+
+        try
+        {
+            prefs.putSpi(DUMMY, DUMMY);
+            fail();
+        }
+        catch (UnsupportedOperationException x)
+        {
+            ;
+        }
+
+        try
+        {
+            prefs.removeNodeSpi();
+            fail();
+        }
+        catch (UnsupportedOperationException x)
+        {
+            ;
+        }
+
+        try
+        {
+            prefs.removeSpi(DUMMY);
+            fail();
+        }
+        catch (UnsupportedOperationException x)
+        {
+            ;
+        }
+    }
+
+    private Dwarf newDwarf(Preferences node)
+    {
+        return BeanTool.getInstance().proxy(Dwarf.class, new Access(node));
+    }
+
+    public static class Access implements BeanAccess
+    {
+        private final Preferences _node;
+
+        public Access(Preferences node)
+        {
+            _node = node;
+        }
+
+        public void propAdd(String propertyName, String value)
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public String propDel(String propertyName)
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public String propGet(String propertyName)
+        {
+            return _node.get(propertyName, null);
+        }
+
+        public String propGet(String propertyName, int index)
+        {
+            return (index == 0) ? propGet(propertyName) : null;
+        }
+
+        public int propLength(String propertyName)
+        {
+            return (propGet(propertyName) == null) ? 0 : 1;
+        }
+
+        public String propSet(String propertyName, String value)
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public String propSet(String propertyName, String value, int index)
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+    }
+}
diff --git a/src/test/java/org/ini4j/IniTest.java b/src/test/java/org/ini4j/IniTest.java
new file mode 100644
index 0000000..49554ce
--- /dev/null
+++ b/src/test/java/org/ini4j/IniTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.sample.Dwarfs;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+
+public class IniTest extends Ini4jCase
+{
+    private static final String COMMENT_ONLY = "# first line\n# second line\n";
+    private static final String COMMENT_ONLY_VALUE = " first line\n second line";
+    private static final String INI_ONE_HEADER = COMMENT_ONLY + "\n\n[section]\nkey=value\n";
+    private static final String COMMENTED_OPTION = COMMENT_ONLY + "\n\n[section]\n;comment\nkey=value\n";
+    private static final String MULTI = "[section]\noption=value\noption=value2\n[section]\noption=value3\noption=value4\noption=value5\n";
+
+    @Test public void testCommentedOption() throws Exception
+    {
+        Ini ini = new Ini(new StringReader(COMMENTED_OPTION));
+
+        assertEquals("comment", ini.get("section").getComment("key"));
+    }
+
+    @Test public void testCommentOnly() throws Exception
+    {
+        Ini ini = new Ini(new StringReader(COMMENT_ONLY));
+
+        assertEquals(COMMENT_ONLY_VALUE, ini.getComment());
+    }
+
+    @Test public void testLoad() throws Exception
+    {
+        Ini ini;
+
+        ini = new Ini(Helper.getResourceURL(Helper.DWARFS_INI));
+        Helper.assertEquals(DwarfsData.dwarfs, ini.as(Dwarfs.class));
+        ini = new Ini(Helper.getResourceStream(Helper.DWARFS_INI));
+        Helper.assertEquals(DwarfsData.dwarfs, ini.as(Dwarfs.class));
+        ini = new Ini(Helper.getResourceReader(Helper.DWARFS_INI));
+        Helper.assertEquals(DwarfsData.dwarfs, ini.as(Dwarfs.class));
+        ini = new Ini(Helper.getSourceFile(Helper.DWARFS_INI));
+        Helper.assertEquals(DwarfsData.dwarfs, ini.as(Dwarfs.class));
+        ini = new Ini();
+        ini.setFile(Helper.getSourceFile(Helper.DWARFS_INI));
+        ini.load();
+        Helper.assertEquals(DwarfsData.dwarfs, ini.as(Dwarfs.class));
+    }
+
+    @Test public void testLoadException() throws Exception
+    {
+        Ini ini = new Ini();
+
+        try
+        {
+            ini.load();
+            missing(FileNotFoundException.class);
+        }
+        catch (FileNotFoundException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testMulti() throws Exception
+    {
+        Ini ini = new Ini(new StringReader(MULTI));
+        Ini.Section sec;
+
+        assertEquals(1, ini.length("section"));
+        assertEquals(5, ini.get("section", 0).length("option"));
+        ini.clear();
+        Config cfg = new Config();
+
+        cfg.setMultiSection(true);
+        ini.setConfig(cfg);
+        ini.load(new StringReader(MULTI));
+        assertEquals(2, ini.get("section", 0).length("option"));
+        assertEquals(3, ini.get("section", 1).length("option"));
+
+        //
+        StringWriter writer = new StringWriter();
+
+        cfg.setMultiOption(false);
+        ini.store(writer);
+        ini.clear();
+        cfg.setMultiOption(true);
+        ini.load(new StringReader(writer.toString()));
+        assertEquals(1, ini.get("section", 0).length("option"));
+        assertEquals(1, ini.get("section", 1).length("option"));
+        assertEquals("value2", ini.get("section", 0).get("option"));
+        assertEquals("value5", ini.get("section", 1).get("option"));
+
+        //
+        ini.clear();
+        cfg.setMultiOption(false);
+        ini.load(new StringReader(MULTI));
+        assertEquals(1, ini.get("section", 0).length("option"));
+        assertEquals(1, ini.get("section", 1).length("option"));
+    }
+
+    @Test public void testOneHeaderOnly() throws Exception
+    {
+        Ini ini = new Ini(new StringReader(INI_ONE_HEADER));
+
+        assertEquals(COMMENT_ONLY_VALUE, ini.getComment());
+    }
+
+    @Test public void testStore() throws Exception
+    {
+        Ini ini = Helper.newDwarfsIni();
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+        ini.store(buffer);
+        Ini dup = new Ini();
+
+        dup.load(new ByteArrayInputStream(buffer.toByteArray()));
+        Helper.assertEquals(DwarfsData.dwarfs, dup.as(Dwarfs.class));
+        buffer = new ByteArrayOutputStream();
+        ini.store(new OutputStreamWriter(buffer));
+        dup = new Ini();
+        dup.load(new InputStreamReader(new ByteArrayInputStream(buffer.toByteArray())));
+        Helper.assertEquals(DwarfsData.dwarfs, dup.as(Dwarfs.class));
+
+        //
+        File file = File.createTempFile("test", ".ini");
+
+        file.deleteOnExit();
+        ini.setFile(file);
+        assertEquals(file, ini.getFile());
+        ini.store();
+        dup = new Ini();
+        dup.setFile(file);
+        dup.load();
+        Helper.assertEquals(DwarfsData.dwarfs, dup.as(Dwarfs.class));
+        file.delete();
+    }
+
+    @Test public void testStoreException() throws Exception
+    {
+        Ini ini = new Ini();
+
+        try
+        {
+            ini.store();
+            missing(FileNotFoundException.class);
+        }
+        catch (FileNotFoundException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testWithComment() throws Exception
+    {
+        Ini ini = new Ini();
+
+        ini.load(Helper.getResourceStream(Helper.DWARFS_INI));
+        assertNotNull(ini.getComment());
+        for (Ini.Section sec : ini.values())
+        {
+            assertNotNull(ini.getComment(sec.getName()));
+        }
+    }
+
+    @Test public void testWithoutComment() throws Exception
+    {
+        Ini ini = new Ini();
+        Config cfg = new Config();
+
+        cfg.setComment(false);
+        ini.setConfig(cfg);
+        ini.load(Helper.getResourceStream(Helper.DWARFS_INI));
+        assertNull(ini.getComment());
+        for (Ini.Section sec : ini.values())
+        {
+            assertNull(ini.getComment(sec.getName()));
+        }
+
+        ini = new Ini();
+        ini.setConfig(cfg);
+        ini.setComment("comment");
+        Ini.Section sec = ini.add("section");
+
+        sec.add("option", "value");
+        ini.putComment("section", "section-comment");
+        StringWriter writer = new StringWriter();
+
+        ini.store(writer);
+        assertEquals("[section]\noption = value\n\n", writer.toString());
+    }
+
+    @Test public void testWithoutHeaderComment() throws Exception
+    {
+        Ini ini = new Ini();
+        Config cfg = new Config();
+
+        cfg.setHeaderComment(false);
+        cfg.setComment(true);
+        ini.setConfig(cfg);
+        ini.load(Helper.getResourceStream(Helper.DWARFS_INI));
+        assertNull(ini.getComment());
+        for (Ini.Section sec : ini.values())
+        {
+            assertNotNull(ini.getComment(sec.getName()));
+        }
+
+        ini = new Ini();
+        ini.setConfig(cfg);
+        ini.setComment("comment");
+        Ini.Section sec = ini.add("section");
+
+        sec.add("option", "value");
+        ini.putComment("section", "section-comment");
+        StringWriter writer = new StringWriter();
+
+        ini.store(writer);
+        assertEquals("#section-comment\n[section]\noption = value\n\n", writer.toString());
+    }
+}
diff --git a/src/test/java/org/ini4j/OptionsTest.java b/src/test/java/org/ini4j/OptionsTest.java
new file mode 100644
index 0000000..85e429b
--- /dev/null
+++ b/src/test/java/org/ini4j/OptionsTest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.sample.Dwarf;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.StringReader;
+
+public class OptionsTest extends Ini4jCase
+{
+    private static final String[] _badOptions = { "=value\n", "\\u000d\\u000d=value\n" };
+    private static final String COMMENT_ONLY = "# first line\n# second line\n";
+    private static final String COMMENT_ONLY_VALUE = " first line\n second line";
+    private static final String OPTIONS_ONE_HEADER = COMMENT_ONLY + "\n\nkey=value\n";
+    private static final String MULTI = "option=value\noption=value2\noption=value3\noption=value4\noption=value5\n";
+
+    @Test public void testCommentOnly() throws Exception
+    {
+        Options opt = new Options(new StringReader(COMMENT_ONLY));
+
+        assertEquals(COMMENT_ONLY_VALUE, opt.getComment());
+    }
+
+    @Test public void testConfig()
+    {
+        Options opts = new Options();
+        Config conf = opts.getConfig();
+
+        assertTrue(conf.isEmptyOption());
+        assertTrue(conf.isEscape());
+        assertFalse(conf.isInclude());
+        assertTrue(conf.isMultiOption());
+        conf = new Config();
+        opts.setConfig(conf);
+        assertSame(conf, opts.getConfig());
+    }
+
+    @Test public void testDwarfs() throws Exception
+    {
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        Options happy = new Options();
+
+        happy.from(DwarfsData.happy);
+        happy.store(buffer);
+        Options dup = new Options(new ByteArrayInputStream(buffer.toByteArray()));
+
+        Helper.assertEquals(DwarfsData.happy, dup.as(Dwarf.class));
+        buffer = new ByteArrayOutputStream();
+        happy.store(new OutputStreamWriter(buffer));
+        dup = new Options(new ByteArrayInputStream(buffer.toByteArray()));
+        Helper.assertEquals(DwarfsData.happy, dup.as(Dwarf.class));
+        File file = File.createTempFile("test", ".opt");
+
+        file.deleteOnExit();
+        happy.setFile(file);
+        happy.store();
+        dup = new Options();
+        dup.setFile(file);
+        assertEquals(file, dup.getFile());
+        dup.load();
+        Helper.assertEquals(DwarfsData.happy, dup.as(Dwarf.class));
+        file.delete();
+    }
+
+    @Test public void testLoad() throws Exception
+    {
+        Options o1 = new Options(Helper.getResourceURL(Helper.DWARFS_OPT));
+        Options o2 = new Options(Helper.getResourceURL(Helper.DWARFS_OPT).openStream());
+        Options o3 = new Options(new InputStreamReader(Helper.getResourceURL(Helper.DWARFS_OPT).openStream()));
+        Options o4 = new Options(Helper.getResourceURL(Helper.DWARFS_OPT));
+        Options o5 = new Options(Helper.getSourceFile(Helper.DWARFS_OPT));
+        Options o6 = new Options();
+
+        o6.setFile(Helper.getSourceFile(Helper.DWARFS_OPT));
+        o6.load();
+        Helper.assertEquals(DwarfsData.dopey, o1.as(Dwarf.class));
+        Helper.assertEquals(DwarfsData.dopey, o2.as(Dwarf.class));
+        Helper.assertEquals(DwarfsData.dopey, o3.as(Dwarf.class));
+        Helper.assertEquals(DwarfsData.dopey, o4.as(Dwarf.class));
+        Helper.assertEquals(DwarfsData.dopey, o5.as(Dwarf.class));
+        Helper.assertEquals(DwarfsData.dopey, o6.as(Dwarf.class));
+    }
+
+    @Test public void testLoadException() throws Exception
+    {
+        Options opt = new Options();
+
+        try
+        {
+            opt.load();
+            missing(FileNotFoundException.class);
+        }
+        catch (FileNotFoundException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testLowerCase() throws Exception
+    {
+        Config cfg = new Config();
+        Options opts = new Options();
+
+        cfg.setLowerCaseOption(true);
+        opts.setConfig(cfg);
+        opts.load(new StringReader("OptIon=value\n"));
+        assertTrue(opts.containsKey("option"));
+    }
+
+    @Test public void testMultiOption() throws Exception
+    {
+        Options opts = new Options(new StringReader(MULTI));
+
+        assertEquals(5, opts.length("option"));
+        opts.clear();
+        Config cfg = new Config();
+
+        cfg.setMultiOption(false);
+        opts.setConfig(cfg);
+        opts.load(new StringReader(MULTI));
+        assertEquals(1, opts.length("option"));
+    }
+
+    @Test public void testNoEmptyOption() throws Exception
+    {
+        Config cfg = new Config();
+        Options opts = new Options();
+
+        opts.setConfig(cfg);
+        try
+        {
+            opts.load(new StringReader("foo\n"));
+            missing(InvalidFileFormatException.class);
+        }
+        catch (InvalidFileFormatException x)
+        {
+            //
+        }
+
+        cfg.setEmptyOption(true);
+        opts.load(new StringReader("dummy\n"));
+        assertTrue(opts.containsKey("dummy"));
+        assertNull(opts.get("dummy"));
+    }
+
+    @Test public void testOneHeaderOnly() throws Exception
+    {
+        Options opt = new Options(new StringReader(OPTIONS_ONE_HEADER));
+
+        assertEquals(COMMENT_ONLY_VALUE, opt.getComment());
+    }
+
+    @Test
+    @SuppressWarnings("empty-statement")
+    public void testParseError() throws Exception
+    {
+        for (String s : _badOptions)
+        {
+            try
+            {
+                new Options(new ByteArrayInputStream(s.getBytes()));
+                fail("expected InvalidIniFormatException: " + s);
+            }
+            catch (InvalidFileFormatException x)
+            {
+                ;
+            }
+        }
+    }
+
+    @Test public void testStoreException() throws Exception
+    {
+        Options opt = new Options();
+
+        try
+        {
+            opt.store();
+            missing(FileNotFoundException.class);
+        }
+        catch (FileNotFoundException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testWithComment() throws Exception
+    {
+        Options opts = new Options();
+
+        opts.load(Helper.getResourceStream(Helper.DWARFS_OPT));
+        assertNotNull(opts.getComment());
+    }
+
+    @Test public void testWithoutComment() throws Exception
+    {
+        Options opts = new Options();
+        Config cfg = new Config();
+
+        cfg.setComment(false);
+        opts.setConfig(cfg);
+        opts.load(Helper.getResourceStream(Helper.DWARFS_OPT));
+        assertNull(opts.getComment());
+    }
+
+    @Test public void testWithoutHeaderComment() throws Exception
+    {
+        Options opts = new Options();
+        Config cfg = new Config();
+
+        cfg.setComment(true);
+        cfg.setHeaderComment(false);
+        opts.setConfig(cfg);
+        opts.load(Helper.getResourceStream(Helper.DWARFS_OPT));
+        assertNull(opts.getComment());
+    }
+}
diff --git a/src/test/java/org/ini4j/RegTest.java b/src/test/java/org/ini4j/RegTest.java
new file mode 100644
index 0000000..3b7d19b
--- /dev/null
+++ b/src/test/java/org/ini4j/RegTest.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.sample.Dwarfs;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+
+public class RegTest extends Ini4jCase
+{
+    private static final String DWARFS_PATH = Helper.DWARFS_REG_PATH + "\\dwarfs\\";
+
+    @Test public void proba() throws Exception
+    {
+    }
+
+    @Test public void testDwarfs() throws Exception
+    {
+        Reg reg = Helper.loadDwarfsReg();
+        Dwarfs dwarfs = reg.as(Dwarfs.class, DWARFS_PATH);
+
+        assertNotNull(dwarfs);
+        Helper.assertEquals(DwarfsData.dwarfs, dwarfs);
+    }
+
+    @Test public void testInvalidFileFormatException() throws Exception
+    {
+        try
+        {
+            new Reg(Helper.getResourceReader(Helper.DWARFS_INI));
+            missing(InvalidFileFormatException.class);
+        }
+        catch (InvalidFileFormatException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testIsWindwos()
+    {
+        assertEquals(isWindows(), Reg.isWindows());
+    }
+
+    @Test public void testLoad() throws Exception
+    {
+        Reg r1 = new Reg(new InputStreamReader(Helper.getResourceStream(Helper.DWARFS_REG), "UnicodeLittle"));
+        Reg r2 = new Reg(Helper.getResourceStream(Helper.DWARFS_REG));
+        Reg r3 = new Reg(Helper.getResourceURL(Helper.DWARFS_REG));
+        File f = Helper.getSourceFile(Helper.DWARFS_REG);
+        Reg r4 = new Reg(f);
+        Reg r5 = new Reg();
+
+        r5.setFile(f);
+        r5.load();
+        Helper.assertEquals(DwarfsData.dwarfs, r1.as(Dwarfs.class, DWARFS_PATH));
+        Helper.assertEquals(DwarfsData.dwarfs, r2.as(Dwarfs.class, DWARFS_PATH));
+        Helper.assertEquals(DwarfsData.dwarfs, r3.as(Dwarfs.class, DWARFS_PATH));
+        Helper.assertEquals(DwarfsData.dwarfs, r4.as(Dwarfs.class, DWARFS_PATH));
+        Helper.assertEquals(DwarfsData.dwarfs, r5.as(Dwarfs.class, DWARFS_PATH));
+        assertSame(f, r4.getFile());
+    }
+
+    @Test public void testLoadFileNotFoundException() throws Exception
+    {
+        Reg reg = new Reg();
+
+        try
+        {
+            reg.load();
+            missing(FileNotFoundException.class);
+        }
+        catch (FileNotFoundException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testLoadSave() throws Exception
+    {
+        Reg reg = new Reg(Helper.getResourceURL(Helper.TEST_REG));
+
+        checkLoadSave(Helper.TEST_REG, reg);
+    }
+
+    @Test public void testMissingVersion() throws Exception
+    {
+        try
+        {
+            new Reg(new StringReader("\r\n\r\n[section]\r\n\"option\"=\"value\""));
+            missing(InvalidFileFormatException.class);
+        }
+        catch (InvalidFileFormatException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testNonWindwosExec() throws Exception
+    {
+        if (isSkip(isWindows(), "testNonWindwosExec"))
+        {
+            return;
+        }
+
+        Reg reg = new Reg();
+
+        reg.exec(new String[] { "/bin/true" });
+        try
+        {
+            reg.exec(new String[] { "/bin/ls", "no such file" });
+            fail("IOException expected");
+        }
+        catch (IOException x)
+        {
+            assert true;
+        }
+    }
+
+    @Test public void testReadException() throws Exception
+    {
+        if (!isWindows())
+        {
+            try
+            {
+                new Reg(Reg.Hive.HKEY_CURRENT_USER.toString());
+                fail("missing UnsupportedOperationException");
+            }
+            catch (UnsupportedOperationException x)
+            {
+                assert true;
+            }
+        }
+        else
+        {
+            try
+            {
+                new Reg("no such key");
+                fail("missing IOException");
+            }
+            catch (IOException x)
+            {
+                assert true;
+            }
+        }
+    }
+
+    @Test public void testReadWrite() throws Exception
+    {
+        if (isSkip(!isWindows(), "testReadWrite"))
+        {
+            return;
+        }
+
+        Reg reg = Helper.loadDwarfsReg();
+
+        reg.write();
+        Reg dup = new Reg(Helper.DWARFS_REG_PATH);
+
+        Helper.assertEquals(reg.get(Helper.DWARFS_REG_PATH), dup.get(Helper.DWARFS_REG_PATH));
+        Dwarfs dwarfs = dup.as(Dwarfs.class, DWARFS_PATH);
+
+        assertNotNull(dwarfs);
+        Helper.assertEquals(DwarfsData.dwarfs, dwarfs);
+    }
+
+    @Test public void testStore() throws Exception
+    {
+        Reg reg = Helper.loadDwarfsReg();
+        File tmp = File.createTempFile(Reg.TMP_PREFIX, Reg.DEFAULT_SUFFIX);
+
+        tmp.deleteOnExit();
+        reg.setFile(tmp);
+        reg.store();
+        reg = new Reg(tmp);
+        Helper.assertEquals(DwarfsData.dwarfs, reg.as(Dwarfs.class, DWARFS_PATH));
+        tmp.delete();
+    }
+
+    @Test public void testStoreFileNotFoundException() throws Exception
+    {
+        try
+        {
+            new Reg().store();
+            missing(FileNotFoundException.class);
+        }
+        catch (FileNotFoundException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testUnsupportedOperatingSystem() throws Exception
+    {
+        if (isSkip(isWindows(), "testUnsupportedOperatingSystem"))
+        {
+            return;
+        }
+
+        Reg reg = new Reg();
+
+        try
+        {
+            reg.read(Helper.DWARFS_REG_PATH);
+            fail("UnsupportedOperationException expected");
+        }
+        catch (UnsupportedOperationException x)
+        {
+            assert true;
+        }
+
+        try
+        {
+            reg.write();
+            fail("UnsupportedOperationException expected");
+        }
+        catch (UnsupportedOperationException x)
+        {
+            assert true;
+        }
+    }
+
+    private boolean isSkip(boolean flag, String testName)
+    {
+        if (!flag)
+        {
+            System.out.println("Skipping " + getClass().getName() + '#' + testName);
+        }
+
+        return flag;
+    }
+
+    private boolean isWindows()
+    {
+        String family = System.getProperty("os.family");
+
+        return (family != null) && family.equals("windows");
+    }
+
+    private void checkLoadSave(String path, Reg reg) throws Exception
+    {
+        File tmp = File.createTempFile(Reg.TMP_PREFIX, Reg.DEFAULT_SUFFIX);
+
+        tmp.deleteOnExit();
+        reg.store(new FileOutputStream(tmp));
+        assertArrayEquals(read(Helper.getResourceStream(path)), read(new FileInputStream(tmp)));
+    }
+
+    private byte[] read(InputStream input) throws Exception
+    {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        byte[] buff = new byte[81912];
+        int n;
+
+        while ((n = input.read(buff)) >= 0)
+        {
+            out.write(buff, 0, n);
+        }
+
+        return out.toByteArray();
+    }
+}
diff --git a/src/test/java/org/ini4j/WiniTest.java b/src/test/java/org/ini4j/WiniTest.java
new file mode 100644
index 0000000..daa0342
--- /dev/null
+++ b/src/test/java/org/ini4j/WiniTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j;
+
+import org.ini4j.spi.WinEscapeToolTest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+import java.net.URL;
+
+public class WiniTest extends Ini4jCase
+{
+    @Test public void testConstructors() throws Exception
+    {
+        File f = File.createTempFile("wini", "test");
+
+        f.deleteOnExit();
+        assertTrue(new WiniHelper(new FileInputStream(f)).isOK());
+        assertTrue(new WiniHelper(new FileReader(f)).isOK());
+        assertTrue(new WiniHelper(f).isOK());
+        assertTrue(new WiniHelper(f.toURI().toURL()).isOK());
+    }
+
+    @Test public void testDefaults()
+    {
+        Wini wini = new Wini();
+        Config cfg = wini.getConfig();
+
+        assertTrue(cfg.isGlobalSection());
+        assertTrue(cfg.isEmptyOption());
+        assertFalse(cfg.isMultiOption());
+        assertFalse(cfg.isEscape());
+    }
+
+    @Test public void testEscape()
+    {
+        Wini instance = new Wini();
+
+        assertEquals(WinEscapeToolTest.ESCAPE1, instance.escape(WinEscapeToolTest.VALUE1));
+        assertEquals(WinEscapeToolTest.ESCAPE2, instance.escape(WinEscapeToolTest.VALUE2));
+        assertEquals(WinEscapeToolTest.ESCAPE3, instance.escape(WinEscapeToolTest.VALUE3));
+        assertEquals(WinEscapeToolTest.ESCAPE4, instance.escape(WinEscapeToolTest.VALUE4));
+        assertEquals(WinEscapeToolTest.ESCAPE5, instance.escape(WinEscapeToolTest.VALUE5));
+    }
+
+    @Test public void testUnescape() throws Exception
+    {
+        Wini instance = new Wini();
+
+        assertEquals(WinEscapeToolTest.VALUE1, instance.unescape(WinEscapeToolTest.ESCAPE1));
+        assertEquals(WinEscapeToolTest.VALUE2, instance.unescape(WinEscapeToolTest.ESCAPE2));
+        assertEquals(WinEscapeToolTest.VALUE3, instance.unescape(WinEscapeToolTest.ESCAPE3));
+        assertEquals(WinEscapeToolTest.VALUE4, instance.unescape(WinEscapeToolTest.ESCAPE4));
+        assertEquals(WinEscapeToolTest.VALUE5, instance.unescape(WinEscapeToolTest.ESCAPE5));
+        assertEquals("=", instance.unescape("\\="));
+        assertEquals("xAx", instance.unescape("x\\o101x"));
+    }
+
+    private static class WiniHelper extends Wini
+    {
+        private boolean _ok;
+
+        public WiniHelper(Reader input) throws IOException, InvalidFileFormatException
+        {
+            super(input);
+        }
+
+        public WiniHelper(InputStream input) throws IOException, InvalidFileFormatException
+        {
+            super(input);
+        }
+
+        public WiniHelper(URL input) throws IOException, InvalidFileFormatException
+        {
+            super(input);
+        }
+
+        public WiniHelper(File input) throws IOException, InvalidFileFormatException
+        {
+            super(input);
+        }
+
+        public boolean isOK()
+        {
+            return _ok;
+        }
+
+        @Override public void load(InputStream input) throws IOException, InvalidFileFormatException
+        {
+            _ok = true;
+        }
+
+        @Override public void load(Reader input) throws IOException, InvalidFileFormatException
+        {
+            _ok = true;
+        }
+
+        @Override public void load(File input) throws IOException, InvalidFileFormatException
+        {
+            _ok = true;
+        }
+
+        @Override public void load(URL input) throws IOException, InvalidFileFormatException
+        {
+            _ok = true;
+        }
+    }
+}
diff --git a/src/test/java/org/ini4j/demo/Demo.java b/src/test/java/org/ini4j/demo/Demo.java
new file mode 100644
index 0000000..016d022
--- /dev/null
+++ b/src/test/java/org/ini4j/demo/Demo.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.demo;
+
+import bsh.util.JConsole;
+
+import org.ini4j.demo.DemoModel.Mode;
+
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import java.io.IOException;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextArea;
+
+public class Demo
+{
+    private enum Command
+    {
+        MODE_INI,
+        MODE_REG,
+        MODE_OPTIONS,
+        LOAD_TEST_DATA,
+        PARSE_DATA,
+        CLEAR_DATA
+    }
+
+    private JConsole _console;
+    private final Container _container;
+    private JTextArea _dataTextArea;
+    private JTextArea _helpTextArea;
+    private DemoModel _model;
+    private JTextArea _tipTextArea;
+    private ActionListener _actionListener = new ActionListener()
+    {
+        public void actionPerformed(ActionEvent event)
+        {
+            Command cmd = Command.valueOf(event.getActionCommand());
+
+            switch (cmd)
+            {
+
+                case MODE_INI:
+                    doMode(Mode.INI);
+                    break;
+
+                case MODE_REG:
+                    doMode(Mode.REG);
+                    break;
+
+                case MODE_OPTIONS:
+                    doMode(Mode.OPTIONS);
+                    break;
+
+                case LOAD_TEST_DATA:
+                    doLoad();
+                    break;
+
+                case PARSE_DATA:
+                    doParse();
+                    break;
+
+                case CLEAR_DATA:
+                    doClear();
+                    break;
+            }
+        }
+    };
+
+    public Demo(Container container)
+    {
+        _container = container;
+    }
+
+    public void init()
+    {
+        _container.setBackground(Color.WHITE);
+        _container.setLayout(new BoxLayout(_container, BoxLayout.PAGE_AXIS));
+        initInputPane();
+        initButtonsPane();
+        initOutputPane();
+
+        //
+        new Thread(_model).start();
+        doMode(Mode.INI);
+    }
+
+    private void addButton(JPanel panel, String label, Command command)
+    {
+        JButton button = new JButton();
+
+        button.setText(label);
+        button.setActionCommand(command.name());
+        button.addActionListener(_actionListener);
+        panel.add(button);
+    }
+
+    private void addModeButton(ButtonGroup group, JPanel panel, Mode mode)
+    {
+        String label = mode.name().charAt(0) + mode.name().toLowerCase().substring(1);
+        JRadioButton button = new JRadioButton(label);
+
+        button.setActionCommand("MODE_" + mode.name());
+        button.setSelected(mode == Mode.INI);
+        panel.add(button);
+        button.addActionListener(_actionListener);
+        group.add(button);
+    }
+
+    private void doClear()
+    {
+        try
+        {
+            _dataTextArea.setText("");
+            _model.clear();
+        }
+        catch (Exception x)
+        {
+            exceptionThrown(x);
+        }
+    }
+
+    private void doLoad()
+    {
+        try
+        {
+            _dataTextArea.setText(_model.load());
+            _console.println("Test data loaded");
+        }
+        catch (Exception x)
+        {
+            exceptionThrown(x);
+        }
+    }
+
+    private void doMode(Mode mode)
+    {
+        _model.setMode(mode);
+        try
+        {
+            _tipTextArea.setText(_model.tip());
+        }
+        catch (Exception x)
+        {
+            exceptionThrown(x);
+        }
+    }
+
+    private void doParse()
+    {
+        try
+        {
+            _model.parse(_dataTextArea.getText());
+            _console.println("Parse ready");
+        }
+        catch (Exception x)
+        {
+            exceptionThrown(x);
+        }
+    }
+
+    private void exceptionThrown(Exception exception)
+    {
+        _console.error(exception);
+        _console.error("\n");
+        exception.printStackTrace();
+    }
+
+    private void initButtonsPane()
+    {
+        JPanel buttons = new JPanel();
+
+        buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS));
+        buttons.setBackground(Color.WHITE);
+        buttons.add(new JLabel("Mode: "));
+        ButtonGroup group = new ButtonGroup();
+
+        addModeButton(group, buttons, Mode.INI);
+        addModeButton(group, buttons, Mode.REG);
+        addModeButton(group, buttons, Mode.OPTIONS);
+        buttons.add(Box.createHorizontalGlue());
+        addButton(buttons, " C L E A R ", Command.CLEAR_DATA);
+        addButton(buttons, " L O A D ", Command.LOAD_TEST_DATA);
+        addButton(buttons, " P A R S E ", Command.PARSE_DATA);
+        _container.add(buttons);
+    }
+
+    private void initInputPane()
+    {
+        JTabbedPane inputPane = new JTabbedPane(JTabbedPane.TOP);
+
+        inputPane.setPreferredSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE));
+        inputPane.setBackground(Color.WHITE);
+        _dataTextArea = new JTextArea();
+        JScrollPane sp = new JScrollPane(_dataTextArea);
+
+        inputPane.addTab("data", sp);
+        _tipTextArea = new JTextArea();
+        _tipTextArea.setEditable(false);
+        sp = new JScrollPane(_tipTextArea);
+        inputPane.addTab("tip", sp);
+        _helpTextArea = new JTextArea();
+        _helpTextArea.setEditable(false);
+        sp = new JScrollPane(_helpTextArea);
+        inputPane.addTab("help", sp);
+//
+        _container.add(inputPane);
+    }
+
+    private void initOutputPane()
+    {
+        JTabbedPane output = new JTabbedPane(JTabbedPane.BOTTOM);
+        JConsole console = new JConsole();
+
+        console.setBackground(Color.WHITE);
+        _model = new DemoModel(console);
+        _console = new JConsole();
+
+        output.addTab("Console", _console);
+        output.setBackground(Color.WHITE);
+        output.setPreferredSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE));
+        output.addTab("Interpreter", console);
+        try
+        {
+
+            //
+            _helpTextArea.setText(_model.help());
+        }
+        catch (IOException x)
+        {
+            exceptionThrown(x);
+        }
+
+        //
+        _container.add(output);
+    }
+}
diff --git a/src/org/ini4j/spi/Warnings.java b/src/test/java/org/ini4j/demo/DemoApplet.java
similarity index 53%
copy from src/org/ini4j/spi/Warnings.java
copy to src/test/java/org/ini4j/demo/DemoApplet.java
index 84de3d7..9ef4e9c 100644
--- a/src/org/ini4j/spi/Warnings.java
+++ b/src/test/java/org/ini4j/demo/DemoApplet.java
@@ -13,14 +13,31 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.ini4j.spi;
+package org.ini4j.demo;
 
-public final class Warnings
+import javax.swing.JApplet;
+import javax.swing.UIManager;
+
+public class DemoApplet extends JApplet
 {
-    public static final String UNCHECKED = "unchecked";
+    private static final long serialVersionUID = -7269388726751989763L;
 
-    private Warnings()
+    public DemoApplet()
     {
-        assert true;
+        try
+        {
+            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+        }
+        catch (Exception x)
+        {
+            x.printStackTrace();
+        }
+    }
+
+    @Override public void init()
+    {
+        Demo demo = new Demo(getContentPane());
+
+        demo.init();
     }
 }
diff --git a/src/test/java/org/ini4j/demo/DemoMain.java b/src/test/java/org/ini4j/demo/DemoMain.java
new file mode 100644
index 0000000..aa811ea
--- /dev/null
+++ b/src/test/java/org/ini4j/demo/DemoMain.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.demo;
+
+import java.awt.Dimension;
+
+import javax.swing.JFrame;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+
+public class DemoMain implements Runnable
+{
+    private Demo _demo;
+
+    public static void main(String[] args)
+    {
+        try
+        {
+            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+        }
+        catch (Exception x)
+        {
+            x.printStackTrace();
+        }
+
+        SwingUtilities.invokeLater(new DemoMain());
+    }
+
+    @Override public void run()
+    {
+        JFrame frame = new JFrame("TopLevelDemo");
+
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        frame.setPreferredSize(new Dimension(800, 600));
+        _demo = new Demo(frame.getContentPane());
+        _demo.init();
+        frame.pack();
+        frame.setVisible(true);
+    }
+}
diff --git a/src/test/java/org/ini4j/demo/DemoModel.java b/src/test/java/org/ini4j/demo/DemoModel.java
new file mode 100644
index 0000000..e462eda
--- /dev/null
+++ b/src/test/java/org/ini4j/demo/DemoModel.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.demo;
+
+import bsh.ConsoleInterface;
+import bsh.EvalError;
+import bsh.Interpreter;
+import bsh.NameSpace;
+
+import org.ini4j.Config;
+import org.ini4j.Ini;
+import org.ini4j.Options;
+import org.ini4j.Persistable;
+import org.ini4j.Reg;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+
+public class DemoModel implements Runnable
+{
+    public static enum Mode
+    {
+        INI,
+        REG,
+        OPTIONS;
+    }
+
+    private Persistable _data;
+    private Interpreter _interpreter;
+    private Mode _mode = Mode.INI;
+
+    public DemoModel(ConsoleInterface console)
+    {
+        _interpreter = new Interpreter(console);
+        NameSpace namespace = _interpreter.getNameSpace();
+
+        namespace.importPackage("org.ini4j.spi");
+        namespace.importPackage("org.ini4j");
+        namespace.importPackage("org.ini4j.sample");
+    }
+
+    public Object getData()
+    {
+        return _data;
+    }
+
+    public Mode getMode()
+    {
+        return _mode;
+    }
+
+    public void setMode(Mode mode)
+    {
+        _mode = mode;
+    }
+
+    public void clear() throws EvalError
+    {
+        _interpreter.unset("data");
+    }
+
+    public String help() throws IOException
+    {
+        return readResource("help.txt");
+    }
+
+    public String load() throws IOException
+    {
+        return readResource(_mode.name().toLowerCase() + "-data.txt");
+    }
+
+    public void parse(String text) throws IOException, EvalError
+    {
+        Persistable data = newData();
+
+        data.load(new StringReader(text));
+        _interpreter.set("data", data);
+        _data = data;
+    }
+
+    @Override public void run()
+    {
+        _interpreter.setExitOnEOF(false);
+        _interpreter.run();
+    }
+
+    public String tip() throws IOException
+    {
+        return readResource(_mode.name().toLowerCase() + "-tip.txt");
+    }
+
+    private Persistable newData()
+    {
+        Persistable ret = null;
+
+        switch (_mode)
+        {
+
+            case INI:
+                ret = new Ini();
+                break;
+
+            case REG:
+                ret = new Reg();
+                break;
+
+            case OPTIONS:
+                ret = new Options();
+                break;
+        }
+
+        return ret;
+    }
+
+    private String readResource(String path) throws IOException
+    {
+        InputStream in = getClass().getResourceAsStream(path);
+        Reader reader = new InputStreamReader(in, Config.DEFAULT_FILE_ENCODING);
+        StringBuilder str = new StringBuilder();
+        char[] buff = new char[8192];
+        int n;
+
+        while ((n = reader.read(buff)) >= 0)
+        {
+            str.append(buff, 0, n);
+        }
+
+        return str.toString();
+    }
+}
diff --git a/src/test/java/org/ini4j/demo/help.txt b/src/test/java/org/ini4j/demo/help.txt
new file mode 100644
index 0000000..8fed2c8
--- /dev/null
+++ b/src/test/java/org/ini4j/demo/help.txt
@@ -0,0 +1,11 @@
+You can try Ini, Reg and Options class features usgin this demo. In "data" tab
+you may type data file or load sample test data by pressing "L O A D" button.
+
+After typing or loading data, you can parse it by pressing "P A R S E" button.
+This will parse the data for the format you select. When parse successfully
+completed, a variable with name "data" will be defined in "Interpreter". You
+can copy/paste sample expressions from "tip" panel.
+
+In "Interpreter" panel you can find a BeanShell interpreter. BeanShell language
+is very close to Java, you can use it as Java interpreter. So in this panel
+you can test your parsed data object interactive mode.
diff --git a/src/test/java/org/ini4j/demo/ini-data.txt b/src/test/java/org/ini4j/demo/ini-data.txt
new file mode 100644
index 0000000..dfbc410
--- /dev/null
+++ b/src/test/java/org/ini4j/demo/ini-data.txt
@@ -0,0 +1,26 @@
+; bashful
+[bashful]
+weight = 45.7
+height = 98.8
+age = 67
+homePage = http://snowwhite.tale/~bashful
+homeDir = /home/bashful
+
+; doc
+[doc]
+weight = 49.5
+height = 87.7
+age = 63
+homePage = http://doc.dwarfs
+homeDir = c:\Documents and Settings\doc
+
+; dopey
+[dopey]
+weight = ${bashful/weight}
+height = ${doc/height}
+age = 23
+homePage = http://dopey.snowwhite.tale/
+homeDir = c:\\Documents and Settings\\dopey
+fortuneNumber = 11
+fortuneNumber = 33
+fortuneNumber = 55
diff --git a/src/test/java/org/ini4j/demo/ini-tip.txt b/src/test/java/org/ini4j/demo/ini-tip.txt
new file mode 100644
index 0000000..4eb35bb
--- /dev/null
+++ b/src/test/java/org/ini4j/demo/ini-tip.txt
@@ -0,0 +1,15 @@
+Here is some cut/paste code you can use in "Interpreter" tab to test test data file:
+
+print(data.size());
+
+print(data.keySet());
+
+print(data.get("doc").keySet());
+
+print(data.get("doc","age",int.class));
+
+for(String s : data.get("doc").keySet())
+{
+    print(s + " = " + data.get("doc",s));
+}
+
diff --git a/src/test/java/org/ini4j/demo/options-data.txt b/src/test/java/org/ini4j/demo/options-data.txt
new file mode 100644
index 0000000..b4c1ec1
--- /dev/null
+++ b/src/test/java/org/ini4j/demo/options-data.txt
@@ -0,0 +1,33 @@
+! dopey properties without prefix
+weight = ${bashful.weight}
+height = ${doc.height}
+age = 23
+homePage = http://dopey.snowwhite.tale/
+homeDir = c:\\Documents and Settings\\dopey
+fortuneNumber = 11
+fortuneNumber = 33
+fortuneNumber = 55
+
+! bashful
+bashful.weight = 45.7
+bashful.height = 98.8
+bashful.age = 67
+bashful.homePage = http://snowwhite.tale/~bashful
+bashful.homeDir = /home/bashful
+
+! doc
+doc.weight = 49.5
+doc.height = 87.7
+doc.age = 63
+doc.homePage = http://doc.dwarfs
+doc.homeDir = c:\Documents and Settings\doc
+
+! dopey
+dopey.weight = ${bashful.weight}
+dopey.height = ${doc.height}
+dopey.age = 23
+dopey.homePage = http://dopey.snowwhite.tale/
+dopey.homeDir = c:\\Documents and Settings\\dopey
+dopey.fortuneNumber = 11
+dopey.fortuneNumber = 33
+dopey.fortuneNumber = 55
diff --git a/src/test/java/org/ini4j/demo/options-tip.txt b/src/test/java/org/ini4j/demo/options-tip.txt
new file mode 100644
index 0000000..56f4fe1
--- /dev/null
+++ b/src/test/java/org/ini4j/demo/options-tip.txt
@@ -0,0 +1,13 @@
+Here is some cut/paste code you can use in "Interpreter" tab to test test data file:
+
+print(data.size());
+
+print(data.keySet());
+
+print(data.get("doc.age",int.class));
+
+for(String s : data.keySet())
+{
+    print(s + " = " + data.get(s));
+}
+
diff --git a/src/test/java/org/ini4j/demo/reg-data.txt b/src/test/java/org/ini4j/demo/reg-data.txt
new file mode 100644
index 0000000..71ee5ae
--- /dev/null
+++ b/src/test/java/org/ini4j/demo/reg-data.txt
@@ -0,0 +1,53 @@
+Windows Registry Editor Version 5.00
+
+[HKEY_CURRENT_USER]
+
+[HKEY_CURRENT_USER\Software]
+
+[HKEY_CURRENT_USER\Software\ini4j-test]
+
+[HKEY_CURRENT_USER\Software\ini4j-test\dwarfs]
+
+; bashful
+;   weight=45.7
+;   age=67
+[HKEY_CURRENT_USER\Software\ini4j-test\dwarfs\bashful]
+@="bashful"
+"weight"=hex(2):34,00,35,00,2e,00,37,00,00,00
+"height"="98.8"
+"age"=dword:00000043
+"homePage"="http://snowwhite.tale/~bashful"
+"homeDir"="/home/bashful"
+
+; doc
+;   weight=49.5
+;   age=63
+[HKEY_CURRENT_USER\Software\ini4j-test\dwarfs\doc]
+@="doc"
+"weight"=hex(2):34,00,39,00,2e,00,35,00,00,00
+"height"="87.7"
+"age"=dword:0000003f
+"homePage"="http://doc.dwarfs"
+"homeDir"="c:Documents and Settingsdoc"
+
+; dopey
+;   weight=${HKEY_CURRENT_USER\Software\ini4j-test\dwarfs\bashful/weight}
+;   age=23
+;   fortuneNumber=11
+;   fortuneNumber=33
+;   fortuneNumber=55
+[HKEY_CURRENT_USER\Software\ini4j-test\dwarfs\dopey]
+@="dopey"
+"weight"=hex(2):24,00,7b,00,48,00,4b,00,45,00,59,00,5f,00,43,00,55,00,52,00,52,\
+                00,45,00,4e,00,54,00,5f,00,55,00,53,00,45,00,52,00,5c,00,53,00,\
+                6f,00,66,00,74,00,77,00,61,00,72,00,65,00,5c,00,69,00,6e,00,69,\
+                00,34,00,6a,00,2d,00,74,00,65,00,73,00,74,00,5c,00,64,00,77,00,\
+                61,00,72,00,66,00,73,00,5c,00,62,00,61,00,73,00,68,00,66,00,75,\
+                00,6c,00,2f,00,77,00,65,00,69,00,67,00,68,00,74,00,7d,00,00,00
+"height"="${HKEY_CURRENT_USER\\Software\\ini4j-test\\dwarfs\\doc/height}"
+"age"=dword:00000017
+"homePage"="http://dopey.snowwhite.tale/"
+"homeDir"="c:\\Documents and Settings\\dopey"
+"fortuneNumber"=hex(7):31,00,31,00,00,00,32,00,32,00,00,00,35,00,35,00,00,00,\
+                       00,00
+
diff --git a/src/test/java/org/ini4j/demo/reg-tip.txt b/src/test/java/org/ini4j/demo/reg-tip.txt
new file mode 100644
index 0000000..4eb35bb
--- /dev/null
+++ b/src/test/java/org/ini4j/demo/reg-tip.txt
@@ -0,0 +1,15 @@
+Here is some cut/paste code you can use in "Interpreter" tab to test test data file:
+
+print(data.size());
+
+print(data.keySet());
+
+print(data.get("doc").keySet());
+
+print(data.get("doc","age",int.class));
+
+for(String s : data.get("doc").keySet())
+{
+    print(s + " = " + data.get("doc",s));
+}
+
diff --git a/src/test/java/org/ini4j/sample/BeanEventSample.java b/src/test/java/org/ini4j/sample/BeanEventSample.java
new file mode 100644
index 0000000..78cd5c2
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/BeanEventSample.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                       ---------------
+//|                       BeanEventSample
+//|
+//|BeanEventSample
+//|
+//| A sample that demonstrates the handling of the bound bean properties.
+//|
+//| This sample program expect the .ini file as a command line argument.
+//| If there is no such argument, it use the {{{dwarfs.ini.html}dwarfs.ini}} file.
+//|
+//| Source code for bean: {{{Dwarf.java.html}Dwarf}},
+//|
+//</editor-fold>
+//{
+import org.ini4j.Ini;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import java.io.FileInputStream;
+
+public class BeanEventSample
+{
+    public static final String FILENAME = "dwarfs.ini";
+
+    public static void main(String[] args) throws Exception
+    {
+        String filename = (args.length > 0) ? args[0] : FILENAME;
+        Ini ini = new Ini(new FileInputStream(filename));
+        Dwarf sneezy = ini.get("sneezy").as(Dwarf.class);
+
+        sneezy.addPropertyChangeListener("age", new PropertyChangeListener()
+            {
+                public void propertyChange(PropertyChangeEvent event)
+                {
+                    System.out.println("property " + event.getPropertyName() + " change");
+                    System.out.println(event.getOldValue() + " -> " + event.getNewValue());
+                }
+            });
+        System.out.println("Sneezy's age: " + sneezy.getAge());
+        sneezy.setAge(44);
+        System.out.println("Sneezy's age: " + sneezy.getAge());
+    }
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/BeanSample.java b/src/test/java/org/ini4j/sample/BeanSample.java
new file mode 100644
index 0000000..33f069b
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/BeanSample.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                          ----------
+//|                          BeanSample
+//|
+//|BeanSample
+//|
+//| Accessing the whole .ini file as Java Beans-style beans.
+//|
+//| This sample program expect the .ini file as a command line argument.
+//| If there is no such argument, it use the {{{dwarfs.ini.html}dwarfs.ini}} file.
+//|
+//| Source code for beans: {{{Dwarf.java.html}Dwarf}},
+//| {{{Dwarfs.java.html}Dwarfs}}
+//|
+//</editor-fold>
+//{
+import org.ini4j.Ini;
+
+import java.io.FileInputStream;
+
+public class BeanSample
+{
+    public static final String FILENAME = "dwarfs.ini";
+
+    public static void main(String[] args) throws Exception
+    {
+        String filename = (args.length > 0) ? args[0] : FILENAME;
+        Dwarfs dwarfs = new Ini(new FileInputStream(filename)).as(Dwarfs.class);
+        Dwarf happy = dwarfs.getHappy();
+        Dwarf doc = dwarfs.getDoc();
+
+        System.out.println("Happy's age: " + happy.getAge());
+        doc.setAge(44);
+        System.out.println("Doc's age: " + doc.getAge());
+    }
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/DumpSample.java b/src/test/java/org/ini4j/sample/DumpSample.java
new file mode 100644
index 0000000..3ef4caf
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/DumpSample.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                                 ----------
+//|                                 DumpSample
+//|
+//|DumpSample
+//|
+//| Writing the .ini file received as input to the stand output.
+//|
+//| This sample program expect the .ini file as a command line argument.
+//| If there is no such argument, it use the {{{dwarfs.ini.html}dwarfs.ini}} file.
+//|
+//</editor-fold>
+//{
+import org.ini4j.Ini;
+
+import java.io.FileReader;
+
+public class DumpSample
+{
+    public static final String FILENAME = "dwarfs.ini";
+
+    public static void main(String[] args) throws Exception
+    {
+        String filename = (args.length > 0) ? args[0] : FILENAME;
+        Ini ini = new Ini(new FileReader(filename));
+
+        for (Ini.Section section : ini.values())
+        {
+            System.out.println("[" + section.getName() + "]");
+            for (String option : section.keySet())
+            {
+                System.out.println(option + " = " + section.fetch(option));
+            }
+
+            System.out.println();
+        }
+    }
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/Dwarf.java b/src/test/java/org/ini4j/sample/Dwarf.java
new file mode 100644
index 0000000..5ea1e2f
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/Dwarf.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyVetoException;
+import java.beans.VetoableChangeListener;
+
+import java.net.URI;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                ---------------
+//|                Dwarf interface
+//|
+//|Dwarf interface
+//|
+//| This is a very simple bean interface with a few getter and setter. Some of
+//| the properties are java primitive types. The <<<homePage>>> property has a
+//| complex type (java.net.URI). It is not a problem for \[ini4j\] to do the
+//| required type conversion automatically between java.lang.String and the tpye
+//| of the given property. The <<<fortuneNumber>>> property is indexed, just to
+//| show you may use indexed properties as well.
+//|
+//</editor-fold>
+//{
+public interface Dwarf
+{
+    String PROP_AGE = "age";
+    String PROP_FORTUNE_NUMBER = "fortuneNumber";
+    String PROP_HEIGHT = "height";
+    String PROP_HOME_DIR = "homeDir";
+    String PROP_HOME_PAGE = "homePage";
+    String PROP_WEIGHT = "weight";
+
+    int getAge();
+
+    void setAge(int age);
+
+    int[] getFortuneNumber();
+
+    void setFortuneNumber(int[] value);
+
+    double getHeight();
+
+    void setHeight(double height) throws PropertyVetoException;
+
+    String getHomeDir();
+
+    void setHomeDir(String dir);
+
+    URI getHomePage();
+
+    void setHomePage(URI location);
+
+    double getWeight();
+
+    void setWeight(double weight);
+
+    void addPropertyChangeListener(String property, PropertyChangeListener listener);
+
+    void addVetoableChangeListener(String property, VetoableChangeListener listener);
+
+    boolean hasAge();
+
+    boolean hasHeight();
+
+    boolean hasHomePage();
+
+    boolean hasWeight();
+
+    void removePropertyChangeListener(String property, PropertyChangeListener listener);
+
+    void removeVetoableChangeListener(String property, VetoableChangeListener listener);
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/DwarfBean.java b/src/test/java/org/ini4j/sample/DwarfBean.java
new file mode 100644
index 0000000..c63191e
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/DwarfBean.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.beans.PropertyVetoException;
+import java.beans.VetoableChangeListener;
+import java.beans.VetoableChangeSupport;
+
+import java.net.URI;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                ---------------
+//|                DwarfBean class
+//|
+//|DwarfBean class
+//|
+//</editor-fold>
+//{
+public class DwarfBean implements Dwarf
+{
+    private int _age;
+    private int[] _fortuneNumber;
+    private double _height;
+    private String _homeDir;
+    private URI _homePage;
+    private final PropertyChangeSupport _pcSupport;
+    private final VetoableChangeSupport _vcSupport;
+    private double _weight;
+
+    public DwarfBean()
+    {
+        _pcSupport = new PropertyChangeSupport(this);
+        _vcSupport = new VetoableChangeSupport(this);
+    }
+
+    @Override public int getAge()
+    {
+        return _age;
+    }
+
+    @Override public void setAge(int value)
+    {
+        int old = _age;
+
+        _age = value;
+
+        _pcSupport.firePropertyChange(PROP_AGE, old, value);
+    }
+
+    @Override public int[] getFortuneNumber()
+    {
+        return _fortuneNumber;
+    }
+
+    @Override public void setFortuneNumber(int[] value)
+    {
+        _fortuneNumber = value;
+    }
+
+    @Override public double getHeight()
+    {
+        return _height;
+    }
+
+    @Override public void setHeight(double value) throws PropertyVetoException
+    {
+        _vcSupport.fireVetoableChange(PROP_HEIGHT, _height, value);
+        double old = _height;
+
+        _height = value;
+
+        _pcSupport.firePropertyChange(PROP_HEIGHT, old, value);
+    }
+
+    @Override public String getHomeDir()
+    {
+        return _homeDir;
+    }
+
+    @Override public void setHomeDir(String value)
+    {
+        String old = _homeDir;
+
+        _homeDir = value;
+
+        _pcSupport.firePropertyChange(PROP_HOME_DIR, old, value);
+    }
+
+    @Override public URI getHomePage()
+    {
+        return _homePage;
+    }
+
+    @Override public void setHomePage(URI value)
+    {
+        URI old = _homePage;
+
+        _homePage = value;
+
+        _pcSupport.firePropertyChange(PROP_HOME_PAGE, old, value);
+    }
+
+    @Override public double getWeight()
+    {
+        return _weight;
+    }
+
+    @Override public void setWeight(double value)
+    {
+        double old = _weight;
+
+        _weight = value;
+
+        _pcSupport.firePropertyChange(PROP_WEIGHT, old, value);
+    }
+
+    @Override public void addPropertyChangeListener(String property, PropertyChangeListener listener)
+    {
+        _pcSupport.addPropertyChangeListener(property, listener);
+    }
+
+    @Override public void addVetoableChangeListener(String property, VetoableChangeListener listener)
+    {
+        _vcSupport.addVetoableChangeListener(property, listener);
+    }
+
+    @Override public boolean hasAge()
+    {
+        return _age != 0;
+    }
+
+    @Override public boolean hasHeight()
+    {
+        return _height != 0.0;
+    }
+
+    @Override public boolean hasHomePage()
+    {
+        return _homePage != null;
+    }
+
+    @Override public boolean hasWeight()
+    {
+        return _weight != 0.0;
+    }
+
+    @Override public void removePropertyChangeListener(String property, PropertyChangeListener listener)
+    {
+        _pcSupport.removePropertyChangeListener(property, listener);
+    }
+
+    @Override public void removeVetoableChangeListener(String property, VetoableChangeListener listener)
+    {
+        _vcSupport.removeVetoableChangeListener(property, listener);
+    }
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/Dwarfs.java b/src/test/java/org/ini4j/sample/Dwarfs.java
new file mode 100644
index 0000000..b9f2cc7
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/Dwarfs.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                ---------------
+//|                Dwarfs interface
+//|
+//|Dwarfs interface
+//|
+//</editor-fold>
+//{
+public interface Dwarfs
+{
+    String PROP_BASHFUL = "bashful";
+    String PROP_DOC = "doc";
+    String PROP_DOPEY = "dopey";
+    String PROP_GRUMPY = "grumpy";
+    String PROP_HAPPY = "happy";
+    String PROP_SLEEPY = "sleepy";
+    String PROP_SNEEZY = "sneezy";
+
+    Dwarf getBashful();
+
+    Dwarf getDoc();
+
+    Dwarf getDopey();
+
+    Dwarf getGrumpy();
+
+    Dwarf getHappy();
+
+    Dwarf getSleepy();
+
+    Dwarf getSneezy();
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/DwarfsBean.java b/src/test/java/org/ini4j/sample/DwarfsBean.java
new file mode 100644
index 0000000..2543ab5
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/DwarfsBean.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                ----------------
+//|                DwarfsBean class
+//|
+//|DwarfsBean class
+//|
+//</editor-fold>
+//{
+public class DwarfsBean implements Dwarfs
+{
+    private Dwarf _bashful;
+    private Dwarf _doc;
+    private Dwarf _dopey;
+    private Dwarf _grumpy;
+    private Dwarf _happy;
+    private Dwarf _sleepy;
+    private Dwarf _sneezy;
+
+    @Override public Dwarf getBashful()
+    {
+        return _bashful;
+    }
+
+    public void setBashful(Dwarf value)
+    {
+        _bashful = value;
+    }
+
+    @Override public Dwarf getDoc()
+    {
+        return _doc;
+    }
+
+    public void setDoc(Dwarf value)
+    {
+        _doc = value;
+    }
+
+    @Override public Dwarf getDopey()
+    {
+        return _dopey;
+    }
+
+    public void setDopey(Dwarf value)
+    {
+        _dopey = value;
+    }
+
+    @Override public Dwarf getGrumpy()
+    {
+        return _grumpy;
+    }
+
+    public void setGrumpy(Dwarf value)
+    {
+        _grumpy = value;
+    }
+
+    @Override public Dwarf getHappy()
+    {
+        return _happy;
+    }
+
+    public void setHappy(Dwarf value)
+    {
+        _happy = value;
+    }
+
+    @Override public Dwarf getSleepy()
+    {
+        return _sleepy;
+    }
+
+    public void setSleepy(Dwarf value)
+    {
+        _sleepy = value;
+    }
+
+    @Override public Dwarf getSneezy()
+    {
+        return _sneezy;
+    }
+
+    public void setSneezy(Dwarf value)
+    {
+        _sneezy = value;
+    }
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/FromSample.java b/src/test/java/org/ini4j/sample/FromSample.java
new file mode 100644
index 0000000..5a80f35
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/FromSample.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                               ----------
+//|                               FromSample
+//|
+//|FromSample
+//|
+//| Marshall Java Beans to Ini.Section.
+//|
+//| This sample program expect the .ini file as a command line argument.
+//| If there is no such argument, it use the {{{dwarfs.ini.html}dwarfs.ini}} file.
+//|
+//| Source code for beans: {{{Dwarf.java.html}Dwarf}},
+//| {{{DwarfBean.java.html}DwarfBean}}
+//|
+//</editor-fold>
+//{
+import org.ini4j.Ini;
+
+import java.net.URI;
+
+public class FromSample
+{
+    public static void main(String[] args) throws Exception
+    {
+        Ini ini = new Ini();
+        DwarfBean doc = new DwarfBean();
+
+        doc.setAge(123);
+        doc.setHeight(111);
+        doc.setWeight(22.1);
+        doc.setHomePage(new URI("http://foo.bar"));
+        ini.add("doc").from(doc);
+        ini.store(System.out);
+    }
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/IniSample.java b/src/test/java/org/ini4j/sample/IniSample.java
new file mode 100644
index 0000000..378ec00
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/IniSample.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                               ---------
+//|                               IniSample
+//|
+//|IniSample
+//|
+//| The [ini4j] library has a simple API: the .ini file is a map of sections, while the section is a map of options.
+//| Due to the Java 1.5 generics these are type safe maps.
+//|
+//| This sample program expect the .ini file as a command line argument.
+//| If there is no such argument, it use the {{{dwarfs.ini.html}dwarfs.ini}} file.
+//|
+//</editor-fold>
+//{
+import org.ini4j.Ini;
+
+import java.io.FileReader;
+
+public class IniSample
+{
+    public static final String FILENAME = "dwarfs.ini";
+
+    public static void main(String[] args) throws Exception
+    {
+        String filename = (args.length > 0) ? args[0] : FILENAME;
+        Ini ini = new Ini(new FileReader(filename));
+
+        for (String key : ini.get("sleepy").keySet())
+        {
+            System.out.println("sleepy/" + key + " = " + ini.get("sleepy").fetch(key));
+        }
+    }
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/ListenerSample.java b/src/test/java/org/ini4j/sample/ListenerSample.java
new file mode 100644
index 0000000..7c7b864
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/ListenerSample.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                             --------------
+//|                             ListenerSample
+//|
+//|ListenerSample
+//|
+//| This advanced sample presents the event-controlled usage. The \[ini4j\]
+//| library fully supports the events defined in Preferences API.
+//|
+//| This sample program expect the .ini file as a command line argument.
+//| If there is no such argument, it use the {{{dwarfs.ini.html}dwarfs.ini}} file.
+//|
+//</editor-fold>
+//{
+import org.ini4j.IniPreferences;
+
+import java.io.FileInputStream;
+
+import java.util.prefs.*;
+
+public class ListenerSample
+{
+    public static final String FILENAME = "dwarfs.ini";
+
+    public static void main(String[] args) throws Exception
+    {
+        String filename = (args.length > 0) ? args[0] : FILENAME;
+        Preferences prefs = new IniPreferences(new FileInputStream(filename));
+        Listener listener = new Listener();
+
+        prefs.addNodeChangeListener(listener);
+        Preferences jerry = prefs.node("jerry");
+
+        jerry.addPreferenceChangeListener(listener);
+        jerry.put("color", "blue");
+        jerry.put("color", "gray");
+        jerry.putInt("age", 2);
+    }
+
+    static class Listener implements NodeChangeListener, PreferenceChangeListener
+    {
+        public void childAdded(NodeChangeEvent event)
+        {
+            System.out.println("node added: " + event.getChild().name());
+        }
+
+        public void childRemoved(NodeChangeEvent event)
+        {
+            System.out.println("node removed: " + event.getChild().name());
+        }
+
+        public void preferenceChange(PreferenceChangeEvent event)
+        {
+            System.out.println("preference changed: " + event.getKey() + " = " + event.getNewValue());
+        }
+    }
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/NoImportSample.java b/src/test/java/org/ini4j/sample/NoImportSample.java
new file mode 100644
index 0000000..400edea
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/NoImportSample.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                               --------------
+//|                               NoImportSample
+//|
+//|NoImportSample
+//|
+//| Using [ini4j] without class level dependency (no Java imports). You may use
+//| \[ini4j\] library as full Preferences implementation for user and system
+//| root.
+//|
+//| This sample program expect the .ini file as a command line argument.
+//| If there is no such argument, it use the {{{dwarfs.ini.html}dwarfs.ini}} file.
+//|
+//</editor-fold>
+//{
+import java.util.prefs.Preferences;
+
+public class NoImportSample
+{
+
+    static
+    {
+        System.setProperty("java.util.prefs.PreferencesFactory", "org.ini4j.IniPreferencesFactory");
+
+        // you should set file:///... like URL as property value to work
+        System.setProperty("org.ini4j.prefs.user", "org/ini4j/sample/dwarfs.ini");
+    }
+
+    public static void main(String[] args) throws Exception
+    {
+        Preferences prefs = Preferences.userRoot();
+
+        System.out.println("grumpy/homePage: " + prefs.node("grumpy").get("homePage", null));
+        System.out.println(prefs.getClass());
+    }
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/PyReadSample.java b/src/test/java/org/ini4j/sample/PyReadSample.java
new file mode 100644
index 0000000..e98f878
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/PyReadSample.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                                 ----------------------------------
+//|                                 Using Python like ConfigParser API
+//|
+//|Using Python like ConfigParser API
+//|
+//| For Python programmers the simlest API to use is ConfigParser.
+//| This sample presents accessing .ini files as Python ConfigParser objects.
+//|
+//| This sample program expect the .ini file as a command line argument.
+//| If there is no such argument, it use the {{{dwarfs-py.ini.html}dwarfs-py.ini}}
+//| file.
+//|
+//</editor-fold>
+//{
+import org.ini4j.ConfigParser;
+
+public class PyReadSample
+{
+    public static final String FILENAME = "dwarfs-py.ini";
+
+    public static void main(String[] args) throws Exception
+    {
+        String filename = (args.length > 0) ? args[0] : FILENAME;
+        ConfigParser config = new ConfigParser();
+
+        config.read(filename);
+        for (String key : config.options("sleepy"))
+        {
+            System.out.println("sleepy/" + key + " = " + config.get("sleepy", key));
+        }
+    }
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/ReadPrimitiveSample.java b/src/test/java/org/ini4j/sample/ReadPrimitiveSample.java
new file mode 100644
index 0000000..5910b4a
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/ReadPrimitiveSample.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                          -------------------
+//|                          ReadPrimitiveSample
+//|
+//|ReadPrimitiveSample
+//|
+//| Reading some Java primitive type values.
+//|
+//| This sample program expect the .ini file as a command line argument.
+//| If there is no such argument, it use the {{{dwarfs.ini.html}dwarfs.ini}} file.
+//|
+//{
+//</editor-fold>
+import org.ini4j.Ini;
+import org.ini4j.IniPreferences;
+
+import java.io.File;
+
+import java.util.prefs.Preferences;
+
+public class ReadPrimitiveSample
+{
+    public static final String FILENAME = "dwarfs.ini";
+
+    public static void main(String[] args) throws Exception
+    {
+        String filename = (args.length > 0) ? args[0] : FILENAME;
+        Preferences prefs = new IniPreferences(new Ini(new File(filename)));
+        Preferences dopey = prefs.node("dopey");
+        int age = dopey.getInt("age", 0);
+        float weight = dopey.getFloat("weight", 0);
+
+        System.out.println("dopey/age: " + age);
+        System.out.println("dopey/weight: " + weight);
+    }
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/ReadStringSample.java b/src/test/java/org/ini4j/sample/ReadStringSample.java
new file mode 100644
index 0000000..cbd945b
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/ReadStringSample.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                           ----------------
+//|                           ReadStringSample
+//|
+//|ReadStringSample
+//|
+//| This sample presents accessing String type values.
+//|
+//| This sample program expect the .ini file as a command line argument.
+//| If there is no such argument, it use the {{{dwarfs.ini.html}dwarfs.ini}} file.
+//|
+//</editor-fold>
+//{
+import org.ini4j.Ini;
+import org.ini4j.IniPreferences;
+
+import java.io.File;
+
+import java.util.prefs.Preferences;
+
+public class ReadStringSample
+{
+    public static final String FILENAME = "dwarfs.ini";
+
+    public static void main(String[] args) throws Exception
+    {
+        String filename = (args.length > 0) ? args[0] : FILENAME;
+        Preferences prefs = new IniPreferences(new Ini(new File(filename)));
+
+        System.out.println("grumpy/homePage: " + prefs.node("grumpy").get("homePage", null));
+    }
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/StreamSample.java b/src/test/java/org/ini4j/sample/StreamSample.java
new file mode 100644
index 0000000..2f774eb
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/StreamSample.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                               ------------
+//|                               StreamSample
+//|
+//|StreamSample
+//|
+//| This sample demonstrates that the Preferences API may be used without a
+//| filesystem access. In this case, naturally, there's no way of saving the
+//| altered settings, they may only be accessed in the memory.
+//|
+//| This sample program expect the .ini file as a command line argument.
+//| If there is no such argument, it use the {{{dwarfs.ini.html}dwarfs.ini}} file.
+//|
+//</editor-fold>
+//{
+import org.ini4j.IniPreferences;
+
+import java.io.FileInputStream;
+
+import java.util.prefs.Preferences;
+
+public class StreamSample
+{
+    public static final String FILENAME = "dwarfs.ini";
+
+    public static void main(String[] args) throws Exception
+    {
+        String filename = (args.length > 0) ? args[0] : FILENAME;
+        Preferences prefs = new IniPreferences(new FileInputStream(filename));
+
+        for (String key : prefs.node("sleepy").keys())
+        {
+            System.out.println("sleepy/" + key + " = " + prefs.node("sleepy").get(key, null));
+        }
+    }
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/ToSample.java b/src/test/java/org/ini4j/sample/ToSample.java
new file mode 100644
index 0000000..726496a
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/ToSample.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.sample;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                                 --------
+//|                                 ToSample
+//|
+//|ToSample
+//|
+//| Unmarshall Java Beans from Ini.Section.
+//|
+//| This sample program expect the .ini file as a command line argument.
+//| If there is no such argument, it use the {{{dwarfs.ini.html}dwarfs.ini}} file.
+//|
+//| Source code for beans: {{{Dwarf.java.html}Dwarf}},
+//| {{{DwarfBean.java.html}DwarfBean}}
+//|
+//</editor-fold>
+//{
+import org.ini4j.Ini;
+
+import java.io.FileInputStream;
+
+public class ToSample
+{
+    public static final String FILENAME = "dwarfs.ini";
+
+    public static void main(String[] args) throws Exception
+    {
+        String filename = (args.length > 0) ? args[0] : FILENAME;
+        Ini ini = new Ini(new FileInputStream(filename));
+        DwarfBean happy = new DwarfBean();
+
+        ini.get("happy").to(happy);
+        System.out.println("Happy's age: " + happy.getAge());
+        System.out.println("Happy's homePage: " + happy.getHomePage());
+    }
+}
+//}
diff --git a/src/test/java/org/ini4j/sample/dwarfs-py.ini b/src/test/java/org/ini4j/sample/dwarfs-py.ini
new file mode 100644
index 0000000..0f04bca
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/dwarfs-py.ini
@@ -0,0 +1,90 @@
+; Copyright 2005,2009 Ivan SZKIBA
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+;      http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+
+;|                        ----------
+;|                        dwarfs.ini
+;|
+;|dwarfs-py.ini
+;|
+;| Common .ini file for sample programs using Python like  ConfigParser class
+;|
+;{
+
+; bashful
+[bashful]
+weight = 45.7
+height = 98.8
+age = 67
+homePage = http://snowwhite.tale/~bashful
+homeDir = /home/bashful
+
+; doc
+[doc]
+weight = 49.5
+height = 87.7
+age = 63
+homePage = http://doc.dwarfs
+homeDir = c:Documents and Settingsdoc
+
+; dopey
+[dopey]
+weight = %(_weight)
+height = %(_height)
+age = 23
+homePage = http://dopey.snowwhite.tale/
+homeDir = c:\Documents and Settings\dopey
+
+; grumpy
+[grumpy]
+weight = 65.3
+height = %(_height)
+age = 76
+homePage = http://snowwhite.tale/~grumpy/
+homeDir = /home/grumpy
+
+; happy
+[happy]
+weight = 56.4
+height = 77.66
+age = 99
+homePage = dummy
+homeDir = /home/happy
+
+; sleepy
+[sleepy]
+name=sleepy
+weight = 76.11
+height = %(_height)8
+age = 121
+homePage = http://snowwhite.tale/~%(name)
+homeDir = /home/%(name)
+
+; sneezy
+[sneezy]
+weight = 69.7
+height = 76.88
+age = 64
+homePage = %(_site)/~sneezy
+homeDir = /home/sneezy
+
+; happy again
+[happy]
+homePage = http://happy.smurf
+
+[default]
+_site = http://happy.smurf
+_age = 99
+_weight = 45.7
+_height = 87.7
+;}
diff --git a/src/test/java/org/ini4j/sample/dwarfs.ini b/src/test/java/org/ini4j/sample/dwarfs.ini
new file mode 100644
index 0000000..1416f17
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/dwarfs.ini
@@ -0,0 +1,90 @@
+; Copyright 2005,2009 Ivan SZKIBA
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+;      http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+
+;|                        ----------
+;|                        dwarfs.ini
+;|
+;|dwarfs.ini
+;|
+;| Common .ini file for samples
+;{
+
+; bashful
+[bashful]
+weight = 45.7
+height = 98.8
+age = 67
+homePage = http://snowwhite.tale/~bashful
+homeDir = /home/bashful
+
+; doc
+[doc]
+weight = 49.5
+height = 87.7
+age = 63
+homePage = http://doc.dwarfs
+homeDir = c:\Documents and Settings\doc
+
+; dopey
+[dopey]
+weight = ${bashful/weight}
+height = ${doc/height}
+age = 23
+homePage = http://dopey.snowwhite.tale/
+homeDir = c:\\Documents and Settings\\dopey
+fortuneNumber = 11
+fortuneNumber = 33
+fortuneNumber = 55
+
+; grumpy
+[grumpy]
+weight = 65.3
+height = ${dopey/height}
+age = 76
+homePage = http://snowwhite.tale/~grumpy/
+homeDir = /home/grumpy
+
+; happy
+[happy]
+weight = 56.4
+height = 77.66
+age = 99
+homePage = dummy
+homeDir = /home/happy
+
+; sleepy
+[sleepy]
+weight = 76.11
+height = ${doc/height}8
+age = 121
+homePage = http://snowwhite.tale/~sleepy
+homeDir = /home/sleepy
+fortuneNumber = 99
+
+; sneezy
+[sneezy]
+weight = 69.7
+height = 76.88
+age = 64
+homePage = ${happy/homePage}/~sneezy
+homeDir = /home/sneezy
+fortuneNumber = 11
+fortuneNumber = 22
+fortuneNumber = 33
+fortuneNumber = 44
+
+; happy again
+[happy]
+homePage = http://happy.smurf
+;}
diff --git a/src/test/java/org/ini4j/sample/dwarfs.opt b/src/test/java/org/ini4j/sample/dwarfs.opt
new file mode 100644
index 0000000..e4d4156
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/dwarfs.opt
@@ -0,0 +1,100 @@
+# Copyright 2005,2009 Ivan SZKIBA
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#|                        ----------
+#|                        dwarfs.opt
+#|
+#|dwarfs.opt
+#|
+#| Common .opt file for tutorials
+#|
+#{
+
+!
+! Dopey properties without prefix
+!
+weight = ${bashful.weight}
+height = ${doc.height}
+age = 23
+homePage = http://dopey.snowwhite.tale/
+homeDir = c:\\Documents and Settings\\dopey
+fortuneNumber = 11
+fortuneNumber = 33
+fortuneNumber = 55
+
+!
+! dwarf properties prefixed with dwarf names
+!
+
+! bashful
+bashful.weight = 45.7
+bashful.height = 98.8
+bashful.age = 67
+bashful.homePage = http://snowwhite.tale/~bashful
+bashful.homeDir = /home/bashful
+
+! doc
+doc.weight = 49.5
+doc.height = 87.7
+doc.age = 63
+doc.homePage = http://doc.dwarfs
+doc.homeDir = c:\Documents and Settings\doc
+
+! dopey
+dopey.weight = ${bashful.weight}
+dopey.height = ${doc.height}
+dopey.age = 23
+dopey.homePage = http://dopey.snowwhite.tale/
+dopey.homeDir = c:\\Documents and Settings\\dopey
+dopey.fortuneNumber = 11
+dopey.fortuneNumber = 33
+dopey.fortuneNumber = 55
+
+! grumpy
+grumpy.weight = 65.3
+grumpy.height = ${dopey.height}
+grumpy.age = 76
+grumpy.homePage = http://snowwhite.tale/~grumpy/
+grumpy.homeDir = /home/grumpy
+
+! happy
+happy.weight = 56.4
+happy.height = 77.66
+happy.age = 99
+happy.homePage = dummy
+happy.homeDir = /home/happy
+
+! sleepy
+sleepy.weight = 76.11
+sleepy.height = ${doc.height}8
+sleepy.age = 121
+sleepy.homePage = http://snowwhite.tale/~sleepy
+sleepy.homeDir = /home/sleepy
+sleepy.fortuneNumber = 99
+
+! sneezy
+sneezy.weight = 69.7
+sneezy.height = 76.88
+sneezy.age = 64
+sneezy.homePage = ${happy.homePage}/~sneezy
+sneezy.homeDir = /home/sneezy
+sneezy.fortuneNumber = 11
+sneezy.fortuneNumber = 22
+sneezy.fortuneNumber = 33
+sneezy.fortuneNumber = 44
+
+! happy again
+happy.homePage = http://happy.smurf
+
+#}
diff --git a/src/test/java/org/ini4j/sample/dwarfs.reg b/src/test/java/org/ini4j/sample/dwarfs.reg
new file mode 100644
index 0000000..1db0310
Binary files /dev/null and b/src/test/java/org/ini4j/sample/dwarfs.reg differ
diff --git a/src/org/ini4j/IniHandler.java b/src/test/java/org/ini4j/sample/package-info.java
similarity index 58%
copy from src/org/ini4j/IniHandler.java
copy to src/test/java/org/ini4j/sample/package-info.java
index 9354f5c..f171d78 100644
--- a/src/org/ini4j/IniHandler.java
+++ b/src/test/java/org/ini4j/sample/package-info.java
@@ -13,15 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.ini4j;
-
-public interface IniHandler extends OptionHandler
-{
-    void endIni();
-
-    void endSection();
-
-    void startIni();
-
-    void startSection(String sectionName);
-}
+package org.ini4j.sample;
+//|
+//|                -------
+//|                Samples
+//|
+//|Samples
+//|
+//| The simple (but useful) samples you can find here illustrate the usage of [ini4j].
+//| These are small, independent programs that may be translates and run.
+//| The build process of [ini4j] guarantees compilability and runability, since no
+//| distribution may be created without compiling and running these programs.
+//|
diff --git a/src/test/java/org/ini4j/sample/tale.ini b/src/test/java/org/ini4j/sample/tale.ini
new file mode 100644
index 0000000..2d36fd5
--- /dev/null
+++ b/src/test/java/org/ini4j/sample/tale.ini
@@ -0,0 +1,90 @@
+; Copyright 2005,2009 Ivan SZKIBA
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+;      http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+
+;|                        ----------
+;|                        tale.ini
+;|
+;|tale.ini
+;|
+;| Common .ini file for samples
+;{
+
+; bashful
+[dwarfs/bashful]
+weight = 45.7
+height = 98.8
+age = 67
+homePage = http://snowwhite.tale/~bashful
+homeDir = /home/bashful
+
+; doc
+[dwarfs/doc]
+weight = 49.5
+height = 87.7
+age = 63
+homePage = http://doc.dwarfs
+homeDir = c:\Documents and Settings\doc
+
+; dopey
+[dwarfs/dopey]
+weight = ${dwarfs/bashful/weight}
+height = ${dwarfs/doc/height}
+age = 23
+homePage = http://dopey.snowwhite.tale/
+homeDir = c:\\Documents and Settings\\dopey
+fortuneNumber = 11
+fortuneNumber = 33
+fortuneNumber = 55
+
+; grumpy
+[dwarfs/grumpy]
+weight = 65.3
+height = ${dwarfs/dopey/height}
+age = 76
+homePage = http://snowwhite.tale/~grumpy/
+homeDir = /home/grumpy
+
+; happy
+[dwarfs/happy]
+weight = 56.4
+height = 77.66
+age = 99
+homePage = dummy
+homeDir = /home/happy
+
+; sleepy
+[dwarfs/sleepy]
+weight = 76.11
+height = ${dwarfs/doc/height}8
+age = 121
+homePage = http://snowwhite.tale/~sleepy
+homeDir = /home/sleepy
+fortuneNumber = 99
+
+; sneezy
+[dwarfs/sneezy]
+weight = 69.7
+height = 76.88
+age = 64
+homePage = ${dwarfs/happy/homePage}/~sneezy
+homeDir = /home/sneezy
+fortuneNumber = 11
+fortuneNumber = 22
+fortuneNumber = 33
+fortuneNumber = 44
+
+; happy again
+[dwarfs/happy]
+homePage = http://happy.smurf
+;}
diff --git a/src/test/java/org/ini4j/spi/AbstractBeanInvocationHandlerTest.java b/src/test/java/org/ini4j/spi/AbstractBeanInvocationHandlerTest.java
new file mode 100644
index 0000000..d627ea4
--- /dev/null
+++ b/src/test/java/org/ini4j/spi/AbstractBeanInvocationHandlerTest.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.ini4j.Ini4jCase;
+
+import org.ini4j.sample.Dwarf;
+
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyVetoException;
+import java.beans.VetoableChangeListener;
+
+import java.lang.reflect.Proxy;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class AbstractBeanInvocationHandlerTest extends Ini4jCase
+{
+    private static final String PROP_AGE = Dwarf.PROP_AGE;
+    private static final String PROP_HEIGHT = Dwarf.PROP_HEIGHT;
+
+    @Test public void testGetProperty() throws Exception
+    {
+        Map<String, String> map = new HashMap<String, String>();
+        MapBeanHandler handler = new MapBeanHandler(map);
+        Integer i = new Integer(23);
+
+        map.put(PROP_AGE, "23");
+        assertEquals(i, (Integer) handler.getProperty(PROP_AGE, Integer.class));
+        assertTrue(handler.hasProperty(PROP_AGE));
+        assertFalse(handler.hasProperty(null));
+        map.put(PROP_AGE, "?.");
+        assertEquals(null, handler.getProperty(PROP_AGE, Integer.class));
+        assertEquals("?.", (String) handler.getProperty(PROP_AGE, String.class));
+        handler = new MapBeanHandler(map)
+        {
+            @Override protected boolean hasPropertySpi(String property)
+            {
+                throw new UnsupportedOperationException();
+            }
+        };
+        assertFalse(handler.hasProperty(PROP_AGE));
+    }
+
+    @Test public void testGetSetHas() throws Exception
+    {
+        Dwarf dwarf = MapBeanHandler.newBean(Dwarf.class);
+
+        assertFalse(dwarf.hasAge());
+        dwarf.setAge(23);
+        assertEquals(23, dwarf.getAge());
+        assertNull(dwarf.getHomeDir());
+        dwarf.setHomeDir("dummy");
+    }
+
+    @Test public void testMisc() throws Exception
+    {
+        Map<String, String> map = new HashMap<String, String>();
+        MapBeanHandler handler = new MapBeanHandler(map);
+        Dummy dummy = (Dummy) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { Dummy.class }, handler);
+
+        assertNull(handler.getProxy());
+
+        // non existend method calls
+        dummy.dummy();
+        dummy.addDummy();
+        dummy.removeDummy();
+
+        // boolean invoke
+        map.put("dummy", "true");
+        assertTrue(dummy.isDummy());
+        assertSame(dummy, handler.getProxy());
+
+        // subclass should call fire methods any time
+        // so null support reference should be not a problem
+        handler.firePropertyChange(PROP_AGE, Integer.valueOf(1), Integer.valueOf(2));
+        handler.fireVetoableChange(PROP_AGE, Integer.valueOf(1), Integer.valueOf(2));
+    }
+
+    @Test public void testPropertyChangeListener() throws Exception
+    {
+        class Listener implements PropertyChangeListener
+        {
+            int _counter;
+            String _property;
+            PropertyChangeEvent _event;
+
+            Listener(String property)
+            {
+                _property = property;
+            }
+
+            @Override public void propertyChange(PropertyChangeEvent event)
+            {
+                if (_property.equals(event.getPropertyName()))
+                {
+                    _counter++;
+                    _event = event;
+                }
+            }
+        }
+
+        Dwarf d = MapBeanHandler.newBean(Dwarf.class);
+        Listener l = new Listener(PROP_AGE);
+
+        // test add and remove: invalid state should be OK
+        d.removePropertyChangeListener(PROP_AGE, l);
+        d.addPropertyChangeListener(PROP_AGE, l);
+        d.addPropertyChangeListener(PROP_AGE, l);
+        d.removePropertyChangeListener(PROP_AGE, l);
+        d.removePropertyChangeListener(PROP_AGE, l);
+
+        // check listener call
+        d.setAge(23);
+        d.addPropertyChangeListener(PROP_AGE, l);
+        d.setAge(45);
+        assertNotNull(l._event);
+        assertEquals(23, ((Integer) l._event.getOldValue()).intValue());
+        assertEquals(45, ((Integer) l._event.getNewValue()).intValue());
+
+        // check listener call again
+        d.setAge(2);
+        d.setWeight(23.4);
+        assertEquals(2, l._counter);
+        d.removePropertyChangeListener(PROP_AGE, l);
+
+        // should not run listener
+        d.setAge(44);
+        assertEquals(2, l._counter);
+
+        // test remove listener> invalid state should be OK
+        d.removePropertyChangeListener(PROP_AGE, l);
+    }
+
+    @Test public void testSetProperty() throws Exception
+    {
+        Map<String, String> map = new HashMap<String, String>();
+        MapBeanHandler handler = new MapBeanHandler(map);
+
+        // very special case: set string property to non stirng value implies string conversion
+        handler.setProperty(PROP_AGE, new Integer(23), String.class);
+        assertEquals("23", handler.getProperty(PROP_AGE, String.class));
+    }
+
+    @Test public void testVetoableChangeListener() throws Exception
+    {
+        class HeightCheck implements VetoableChangeListener
+        {
+            @Override public void vetoableChange(PropertyChangeEvent event) throws PropertyVetoException
+            {
+                if (PROP_HEIGHT.equals(event.getPropertyName()))
+                {
+                    if (((Double) event.getNewValue()) < 0.0)
+                    {
+                        throw new PropertyVetoException("invalid value", event);
+                    }
+                }
+            }
+        }
+
+        Dwarf d = MapBeanHandler.newBean(Dwarf.class);
+        HeightCheck l = new HeightCheck();
+
+        // test add and remove: invalid state should be OK
+        d.removeVetoableChangeListener(PROP_HEIGHT, l);
+        d.addVetoableChangeListener(PROP_HEIGHT, l);
+        d.addVetoableChangeListener(PROP_HEIGHT, l);
+        d.removeVetoableChangeListener(PROP_HEIGHT, l);
+        d.removeVetoableChangeListener(PROP_HEIGHT, l);
+
+        // set invalid value without lsitener
+        d.setHeight(-2.0);
+        d.setHeight(33.0);
+        d.addVetoableChangeListener(PROP_HEIGHT, l);
+
+        // set invalid value with listener
+        try
+        {
+            d.setHeight(-3.4);
+            fail();
+        }
+        catch (PropertyVetoException x)
+        {
+            assertEquals(33.0, d.getHeight(), Helper.DELTA);
+        }
+
+        // set valid value
+        d.setHeight(44.0);
+        assertEquals(44.0, d.getHeight(), Helper.DELTA);
+        d.removeVetoableChangeListener(PROP_HEIGHT, l);
+
+        // set invalid value without lsitener
+        d.setHeight(-4.0);
+        assertEquals(-4.0, d.getHeight(), Helper.DELTA);
+
+        // test remove: invalid state should be OK
+        d.removeVetoableChangeListener(PROP_HEIGHT, l);
+    }
+
+    static interface Dummy
+    {
+        boolean isDummy();
+
+        void addDummy();
+
+        void dummy();
+
+        void removeDummy();
+    }
+
+    static class MapBeanHandler extends AbstractBeanInvocationHandler
+    {
+        private Map<String, String> _map;
+
+        MapBeanHandler(Map<String, String> map)
+        {
+            super();
+            _map = map;
+        }
+
+        protected static <T> T newBean(Class<T> clazz)
+        {
+            return newBean(clazz, new HashMap<String, String>());
+        }
+
+        protected static <T> T newBean(Class<T> clazz, Map<String, String> map)
+        {
+            return clazz.cast(Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, new MapBeanHandler(map)));
+        }
+
+        @Override protected Object getPropertySpi(String property, Class clazz)
+        {
+            return _map.get(property);
+        }
+
+        @Override protected void setPropertySpi(String property, Object value, Class clazz)
+        {
+            _map.put(property, value.toString());
+        }
+
+        @Override protected boolean hasPropertySpi(String property)
+        {
+            return _map.containsKey(property);
+        }
+    }
+}
diff --git a/src/test/java/org/ini4j/spi/BeanToolTest.java b/src/test/java/org/ini4j/spi/BeanToolTest.java
new file mode 100644
index 0000000..aeb24ae
--- /dev/null
+++ b/src/test/java/org/ini4j/spi/BeanToolTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.ini4j.BasicOptionMapGate;
+import org.ini4j.Ini4jCase;
+
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.DwarfBean;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.net.URI;
+import java.net.URL;
+
+import java.util.TimeZone;
+
+public class BeanToolTest extends Ini4jCase
+{
+    protected BeanTool instance;
+
+    @Before @Override public void setUp() throws Exception
+    {
+        super.setUp();
+        instance = BeanTool.getInstance();
+    }
+
+    @Test public void testInject() throws Exception
+    {
+        testInject(null);
+        testInject("dummy");
+    }
+
+    @Test public void testInjectIllegalArgument1() throws Exception
+    {
+        TestMap map = new TestMap();
+
+        try
+        {
+            instance.inject(map.newBeanAccess(), new BadBean());
+            missing(IllegalArgumentException.class);
+        }
+        catch (IllegalArgumentException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testInjectIllegalArgument2() throws Exception
+    {
+        TestMap map = new TestMap();
+
+        map.put("name", "bad");
+        try
+        {
+            instance.inject(new BadBean(), map.newBeanAccess());
+            missing(IllegalArgumentException.class);
+        }
+        catch (IllegalArgumentException x)
+        {
+            //
+        }
+    }
+
+    @SuppressWarnings("empty-statement")
+    @Test public void testParse() throws Exception
+    {
+        String input = "6";
+        int value = 6;
+
+        assertEquals(value, instance.parse(input, byte.class).byteValue());
+        assertEquals(value, instance.parse(input, short.class).shortValue());
+        assertEquals(value, instance.parse(input, int.class).intValue());
+        assertEquals(value, instance.parse(input, long.class).longValue());
+        assertEquals((float) value, instance.parse(input, float.class).floatValue(), Helper.DELTA);
+        assertEquals((double) value, instance.parse(input, double.class).doubleValue(), Helper.DELTA);
+        assertFalse(instance.parse(input, boolean.class));
+        assertEquals('6', instance.parse(input, char.class).charValue());
+
+        // parse null mean zero
+        assertEquals(0, instance.parse(null, byte.class).byteValue());
+
+        // parse to null class mean exception
+        try
+        {
+            instance.parse(input, null);
+            fail();
+        }
+        catch (IllegalArgumentException x)
+        {
+            ;
+        }
+
+        // invalid primitive value mean exception
+        try
+        {
+            instance.parse("?", int.class);
+            fail();
+        }
+        catch (IllegalArgumentException x)
+        {
+            ;
+        }
+
+        // standard, but not primitive types
+        assertSame(input, instance.parse(input, String.class));
+        assertEquals(new Character('6'), instance.parse(input, Character.class));
+        assertEquals(new Byte(input), instance.parse(input, Byte.class));
+
+        // special values
+        input = "http://www.ini4j.org";
+        assertEquals(new URL(input), instance.parse(input, URL.class));
+        assertEquals(new URI(input), instance.parse(input, URI.class));
+        assertEquals(new File(input), instance.parse(input, File.class));
+        input = "Europe/Budapest";
+        assertEquals(input, instance.parse(input, TimeZone.class).getID());
+        input = "java.lang.String";
+        assertEquals(String.class, instance.parse(input, Class.class));
+
+        // invalid value should throw IllegalArgumentException
+        try
+        {
+            instance.parse("", URL.class);
+        }
+        catch (IllegalArgumentException x)
+        {
+            ;
+        }
+    }
+
+    @Test public void testSetGet() throws Exception
+    {
+        TestMap map = new TestMap();
+        Dwarf proxy = instance.proxy(Dwarf.class, map.newBeanAccess());
+
+        assertNull(proxy.getHomeDir());
+        assertFalse(proxy.hasHomePage());
+        assertNull(proxy.getFortuneNumber());
+        proxy.setAge(DwarfsData.sneezy.age);
+        proxy.setHeight(DwarfsData.sneezy.height);
+        proxy.setWeight(DwarfsData.sneezy.weight);
+        proxy.setHomePage(DwarfsData.sneezy.homePage);
+        proxy.setHomeDir(DwarfsData.sneezy.homeDir);
+        proxy.setFortuneNumber(DwarfsData.sneezy.fortuneNumber);
+        Helper.assertEquals(DwarfsData.sneezy, proxy);
+        assertArrayEquals(DwarfsData.sneezy.fortuneNumber, proxy.getFortuneNumber());
+    }
+
+    @Test public void testSingleton() throws Exception
+    {
+        assertEquals(BeanTool.class, BeanTool.getInstance().getClass());
+    }
+
+    @Test public void testZero() throws Exception
+    {
+        assertEquals(null, instance.zero(Object.class));
+        assertEquals(0, instance.zero(byte.class).byteValue());
+        assertEquals(0, instance.zero(short.class).shortValue());
+        assertEquals(0, instance.zero(int.class).intValue());
+        assertEquals(0, instance.zero(long.class).longValue());
+        assertEquals(0.0f, instance.zero(float.class).floatValue(), Helper.DELTA);
+        assertEquals(0.0, instance.zero(double.class).doubleValue(), Helper.DELTA);
+        assertNotNull((instance.zero(boolean.class)));
+        assertFalse(instance.zero(boolean.class));
+        assertEquals('\0', instance.zero(char.class).charValue());
+    }
+
+    protected void testInject(String prefix) throws Exception
+    {
+        String p = (prefix == null) ? "" : prefix;
+        Dwarf bean = new DwarfBean();
+
+        bean.setAge(23);
+        bean.setHeight(5.3);
+        URI uri = new URI("http://www.ini4j.org");
+
+        bean.setHomePage(uri);
+        String dir = "/home/happy";
+
+        bean.setHomeDir(dir);
+        bean.setFortuneNumber(new int[] { 1, 2, 3 });
+        TestMap map = new TestMap();
+
+        instance.inject(map.newBeanAccess(prefix), bean);
+        assertEquals(6, map.size());
+        assertEquals("23", map.get(p + Dwarf.PROP_AGE));
+        assertEquals("5.3", map.get(p + Dwarf.PROP_HEIGHT));
+        assertEquals(uri.toString(), map.get(p + Dwarf.PROP_HOME_PAGE));
+        assertEquals(dir, map.get(p + Dwarf.PROP_HOME_DIR));
+        assertEquals(3, map.length(p + Dwarf.PROP_FORTUNE_NUMBER));
+        assertEquals("1", map.get(p + Dwarf.PROP_FORTUNE_NUMBER, 0));
+        assertEquals("2", map.get(p + Dwarf.PROP_FORTUNE_NUMBER, 1));
+        assertEquals("3", map.get(p + Dwarf.PROP_FORTUNE_NUMBER, 2));
+        bean.setAge(0);
+        bean.setHeight(0);
+        bean.setHomePage(null);
+        instance.inject(bean, map.newBeanAccess(prefix));
+        assertEquals(23, bean.getAge());
+        assertEquals(5.3, bean.getHeight(), Helper.DELTA);
+        assertEquals(uri, bean.getHomePage());
+        assertEquals(dir, bean.getHomeDir());
+        assertArrayEquals(new int[] { 1, 2, 3 }, bean.getFortuneNumber());
+
+        //
+        // bean interface
+        //
+        Dwarf proxy = instance.proxy(Dwarf.class, map.newBeanAccess(prefix));
+
+        assertEquals(23, proxy.getAge());
+        assertEquals(5.3, proxy.getHeight(), Helper.DELTA);
+        assertEquals(uri, proxy.getHomePage());
+        assertEquals(dir, proxy.getHomeDir());
+        assertArrayEquals(new int[] { 1, 2, 3 }, proxy.getFortuneNumber());
+    }
+
+    static class TestMap extends BasicOptionMapGate
+    {
+        private static final long serialVersionUID = 4818386732025655044L;
+    }
+
+    private static class BadBean
+    {
+        public String getName() throws IOException
+        {
+            throw new IOException();
+        }
+
+        public void setName(String value) throws IOException
+        {
+            throw new IOException();
+        }
+    }
+}
diff --git a/src/test/java/org/ini4j/spi/EscapeToolTest.java b/src/test/java/org/ini4j/spi/EscapeToolTest.java
new file mode 100644
index 0000000..bd219e1
--- /dev/null
+++ b/src/test/java/org/ini4j/spi/EscapeToolTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.ini4j.Ini4jCase;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class EscapeToolTest extends Ini4jCase
+{
+    private static final String VALUE1 = "simple";
+    private static final String ESCAPE1 = "simple";
+    private static final String VALUE2 = "Iv\ufffdn";
+    private static final String ESCAPE2 = "Iv\\ufffdn";
+    private static final String VALUE3 = "1\t2\n3\f4\b5\r6";
+    private static final String ESCAPE3 = "1\\t2\\n3\\f4\\b5\\r6";
+    private static final String VALUE4 = "Iv\u0017n";
+    private static final String ESCAPE4 = "Iv\\u0017n";
+    private static final String INVALID_UNICODE = "\\u98x";
+    private static final String UNQUOTED1 = "simple";
+    private static final String QUOTED1 = "\"simple\"";
+    private static final String UNQUOTED2 = "no\\csak\"";
+    private static final String QUOTED2 = "\"no\\\\csak\\\"\"";
+    private static final String UNQUOTED3 = "";
+    private static final String QUOTED3 = "";
+    protected EscapeTool instance;
+
+    @Before @Override public void setUp() throws Exception
+    {
+        super.setUp();
+        instance = EscapeTool.getInstance();
+    }
+
+    @Test public void testEscape() throws Exception
+    {
+        assertEquals(ESCAPE1, instance.escape(VALUE1));
+        assertEquals(ESCAPE2, instance.escape(VALUE2));
+        assertEquals(ESCAPE3, instance.escape(VALUE3));
+        assertEquals(ESCAPE4, instance.escape(VALUE4));
+    }
+
+    @Test public void testInvalidUnicode()
+    {
+        try
+        {
+            instance.unescape(INVALID_UNICODE);
+            missing(IllegalArgumentException.class);
+        }
+        catch (IllegalArgumentException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testQuote() throws Exception
+    {
+        assertEquals(QUOTED1, instance.quote(UNQUOTED1));
+        assertEquals(QUOTED2, instance.quote(UNQUOTED2));
+        assertEquals(QUOTED3, instance.quote(UNQUOTED3));
+        assertNull(instance.quote(null));
+    }
+
+    @Test public void testSingleton() throws Exception
+    {
+        assertEquals(EscapeTool.class, EscapeTool.getInstance().getClass());
+    }
+
+    @SuppressWarnings("empty-statement")
+    @Test public void testUnescape() throws Exception
+    {
+        assertEquals(VALUE1, instance.unescape(ESCAPE1));
+        assertEquals(VALUE2, instance.unescape(ESCAPE2));
+        assertEquals(VALUE3, instance.unescape(ESCAPE3));
+        assertEquals(VALUE4, instance.unescape(ESCAPE4));
+        assertEquals("=", instance.unescape("\\="));
+    }
+}
diff --git a/src/test/java/org/ini4j/spi/IniFormatterTest.java b/src/test/java/org/ini4j/spi/IniFormatterTest.java
new file mode 100644
index 0000000..bfdaa06
--- /dev/null
+++ b/src/test/java/org/ini4j/spi/IniFormatterTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.easymock.EasyMock;
+
+import org.ini4j.Config;
+import org.ini4j.Ini;
+import org.ini4j.Ini4jCase;
+
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.Dwarfs;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+
+public class IniFormatterTest extends Ini4jCase
+{
+    private static final String NL = System.getProperty("line.separator");
+    private static final String DUMMY = "dummy";
+
+    @Test public void testFormat() throws Exception
+    {
+        Ini ini = Helper.newDwarfsIni();
+        IniHandler handler = EasyMock.createMock(IniHandler.class);
+        Dwarf dwarf;
+
+        handler.startIni();
+        handler.handleComment(Helper.HEADER_COMMENT);
+        handler.handleComment(" " + Dwarfs.PROP_BASHFUL);
+        dwarf = DwarfsData.bashful;
+        handler.startSection(Dwarfs.PROP_BASHFUL);
+        handler.handleOption(Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.endSection();
+        handler.handleComment(" " + Dwarfs.PROP_DOC);
+        dwarf = DwarfsData.doc;
+        handler.startSection(Dwarfs.PROP_DOC);
+        handler.handleOption(Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.endSection();
+        handler.handleComment(" " + Dwarfs.PROP_DOPEY);
+        dwarf = DwarfsData.dopey;
+        handler.startSection(Dwarfs.PROP_DOPEY);
+        handler.handleOption(Dwarf.PROP_WEIGHT, DwarfsData.INI_DOPEY_WEIGHT);
+        handler.handleOption(Dwarf.PROP_HEIGHT, DwarfsData.INI_DOPEY_HEIGHT);
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[0]));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[1]));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[2]));
+        handler.endSection();
+        handler.handleComment(" " + Dwarfs.PROP_GRUMPY);
+        dwarf = DwarfsData.grumpy;
+        handler.startSection(Dwarfs.PROP_GRUMPY);
+        handler.handleOption(Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(Dwarf.PROP_HEIGHT, DwarfsData.INI_GRUMPY_HEIGHT);
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.endSection();
+        handler.handleComment(" " + Dwarfs.PROP_HAPPY);
+        dwarf = DwarfsData.happy;
+        handler.startSection(Dwarfs.PROP_HAPPY);
+        handler.handleOption(Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.endSection();
+        handler.handleComment(" " + Dwarfs.PROP_SLEEPY);
+        dwarf = DwarfsData.sleepy;
+        handler.startSection(Dwarfs.PROP_SLEEPY);
+        handler.handleOption(Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(Dwarf.PROP_HEIGHT, DwarfsData.INI_SLEEPY_HEIGHT);
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[0]));
+        handler.endSection();
+        handler.handleComment(" " + Dwarfs.PROP_SNEEZY);
+        dwarf = DwarfsData.sneezy;
+        handler.startSection(Dwarfs.PROP_SNEEZY);
+        handler.handleOption(Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, DwarfsData.INI_SNEEZY_HOME_PAGE);
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[0]));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[1]));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[2]));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[3]));
+        handler.endSection();
+        handler.endIni();
+        EasyMock.replay(handler);
+        verify(ini, handler);
+    }
+
+    @Test public void testNewInstance() throws Exception
+    {
+        StringWriter stringWriter;
+        PrintWriter printWriter;
+        Config cfg = new Config();
+        IniFormatter instance;
+
+        stringWriter = new StringWriter();
+        instance = IniFormatter.newInstance(stringWriter, cfg);
+
+        instance.getOutput().print(DUMMY);
+        instance.getOutput().flush();
+        assertEquals(DUMMY, stringWriter.toString());
+        assertSame(cfg, instance.getConfig());
+
+        //
+        stringWriter = new StringWriter();
+        instance = IniFormatter.newInstance(stringWriter, cfg);
+
+        instance.getOutput().print(DUMMY);
+        instance.getOutput().flush();
+        assertEquals(DUMMY, stringWriter.toString());
+
+        //
+        printWriter = new PrintWriter(stringWriter);
+        instance = IniFormatter.newInstance(printWriter, cfg);
+
+        assertSame(printWriter, instance.getOutput());
+    }
+
+    @Test public void testWithEmptyOption() throws Exception
+    {
+        Config cfg = new Config();
+
+        cfg.setEmptyOption(true);
+        Ini ini = new Ini();
+        Ini.Section sec = ini.add(Dwarfs.PROP_BASHFUL);
+
+        sec.put(Dwarf.PROP_FORTUNE_NUMBER, null);
+        ini.setConfig(cfg);
+        IniHandler handler = EasyMock.createMock(IniHandler.class);
+
+        handler.startIni();
+        handler.startSection(Dwarfs.PROP_BASHFUL);
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, "");
+        handler.endSection();
+        handler.endIni();
+        EasyMock.replay(handler);
+        verify(ini, handler);
+    }
+
+    @Test public void testWithoutConfig() throws Exception
+    {
+        Ini ini = new Ini();
+        Ini.Section sec = ini.add(Dwarfs.PROP_BASHFUL);
+
+        sec.put(Dwarf.PROP_FORTUNE_NUMBER, null);
+        IniHandler handler = EasyMock.createMock(IniHandler.class);
+
+        handler.startIni();
+        handler.startSection(Dwarfs.PROP_BASHFUL);
+        handler.endSection();
+        handler.endIni();
+        EasyMock.replay(handler);
+        verify(ini, handler);
+    }
+
+    @Test public void testWithStrictOperator() throws Exception
+    {
+        Config cfg = new Config();
+
+        cfg.setStrictOperator(true);
+        Ini ini = new Ini();
+        Ini.Section sec = ini.add(Dwarfs.PROP_BASHFUL);
+
+        sec.put(Dwarf.PROP_AGE, DwarfsData.bashful.age);
+        ini.setConfig(cfg);
+        StringWriter writer = new StringWriter();
+
+        ini.store(writer);
+        StringBuilder exp = new StringBuilder();
+
+        exp.append(IniParser.SECTION_BEGIN);
+        exp.append(Dwarfs.PROP_BASHFUL);
+        exp.append(IniParser.SECTION_END);
+        exp.append(NL);
+        exp.append(Dwarf.PROP_AGE);
+        exp.append('=');
+        exp.append(DwarfsData.bashful.age);
+        exp.append(NL);
+        exp.append(NL);
+        assertEquals(exp.toString(), writer.toString());
+    }
+
+    private void verify(Ini ini, IniHandler mock) throws Exception
+    {
+        StringWriter writer = new StringWriter();
+
+        ini.store(writer);
+        IniParser parser = new IniParser();
+
+        parser.parse(new StringReader(writer.toString()), mock);
+        EasyMock.verify(mock);
+    }
+}
diff --git a/src/test/java/org/ini4j/spi/IniParserTest.java b/src/test/java/org/ini4j/spi/IniParserTest.java
new file mode 100644
index 0000000..4281a13
--- /dev/null
+++ b/src/test/java/org/ini4j/spi/IniParserTest.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.easymock.EasyMock;
+
+import org.ini4j.Config;
+import org.ini4j.Ini4jCase;
+import org.ini4j.InvalidFileFormatException;
+
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.Dwarfs;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.StringReader;
+
+public class IniParserTest extends Ini4jCase
+{
+    private static final String[] BAD = { "[section\noption=value\n", "[]\noption=value", "section\noption=value", "[section]\noption\n", "[section]\n=value\n", "[section]\n\\u000d\\u000d=value\n" };
+    private static final String CFG_LOWER = "[SectioN]\n\nOptioN=ValuE\n";
+    private static final String CFG_UNNAMED = "[]\noption=value\n";
+    private static final String CFG_EMPTY_OPTION = "[section]\noption\n";
+    private static final String CFG_GLOBAL = "option=value\n";
+    private static final String[] CFG_EXTRA = { CFG_EMPTY_OPTION, CFG_UNNAMED, CFG_GLOBAL };
+    private static final String ANONYMOUS = "?";
+    private static final String EMPTY = "";
+    private static final String SECTION = "section";
+    private static final String OPTION = "option";
+    private static final String VALUE = "value";
+
+    @Test public void testEmpty() throws Exception
+    {
+        IniParser parser = new IniParser();
+        IniHandler handler = EasyMock.createMock(IniHandler.class);
+
+        handler.startIni();
+        handler.endIni();
+        EasyMock.replay(handler);
+        parser.parse(new StringReader(EMPTY), handler);
+        EasyMock.verify(handler);
+    }
+
+    @Test public void testEmptyOption() throws Exception
+    {
+        IniParser parser = new IniParser();
+        IniHandler handler = EasyMock.createMock(IniHandler.class);
+
+        handler.startIni();
+        handler.startSection(SECTION);
+        handler.handleOption(OPTION, null);
+        handler.endSection();
+        handler.endIni();
+        EasyMock.replay(handler);
+        Config cfg = new Config();
+
+        cfg.setEmptyOption(true);
+        parser.setConfig(cfg);
+        parser.parse(new StringReader(CFG_EMPTY_OPTION), handler);
+        EasyMock.verify(handler);
+    }
+
+    @Test public void testGlobalSection() throws Exception
+    {
+        IniParser parser = new IniParser();
+        IniHandler handler = EasyMock.createMock(IniHandler.class);
+
+        handler.startIni();
+        handler.startSection(ANONYMOUS);
+        handler.handleOption(OPTION, VALUE);
+        handler.endSection();
+        handler.endIni();
+        EasyMock.replay(handler);
+        Config cfg = new Config();
+
+        cfg.setGlobalSection(true);
+        parser.setConfig(cfg);
+        parser.parse(new StringReader(CFG_GLOBAL), handler);
+        EasyMock.verify(handler);
+    }
+
+    @Test public void testLower() throws Exception
+    {
+        IniParser parser = new IniParser();
+        IniHandler handler = EasyMock.createMock(IniHandler.class);
+
+        handler.startIni();
+        handler.startSection(SECTION);
+        handler.handleOption(OPTION, "ValuE");
+        handler.endSection();
+        handler.endIni();
+        EasyMock.replay(handler);
+        Config cfg = new Config();
+
+        cfg.setLowerCaseOption(true);
+        cfg.setLowerCaseSection(true);
+        parser.setConfig(cfg);
+        parser.parse(new StringReader(CFG_LOWER), handler);
+        EasyMock.verify(handler);
+    }
+
+    @Test public void testNewInstance() throws Exception
+    {
+        Config cfg = new Config();
+        IniParser parser = IniParser.newInstance();
+
+        assertEquals(IniParser.class, parser.getClass());
+        parser = IniParser.newInstance(cfg);
+        assertEquals(IniParser.class, parser.getClass());
+        assertSame(cfg, parser.getConfig());
+    }
+
+    @Test public void testParse() throws Exception
+    {
+        IniParser parser = new IniParser();
+        IniHandler handler = EasyMock.createMock(IniHandler.class);
+        Dwarf dwarf;
+
+        handler.startIni();
+        handler.handleComment(Helper.HEADER_COMMENT);
+        handler.handleComment((String) EasyMock.anyObject());
+        handler.handleComment(" " + Dwarfs.PROP_BASHFUL);
+        dwarf = DwarfsData.bashful;
+        handler.startSection(Dwarfs.PROP_BASHFUL);
+        handler.handleOption(Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.endSection();
+        handler.handleComment(" " + Dwarfs.PROP_DOC);
+        dwarf = DwarfsData.doc;
+        handler.startSection(Dwarfs.PROP_DOC);
+        handler.handleOption(Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.endSection();
+        handler.handleComment(" " + Dwarfs.PROP_DOPEY);
+        dwarf = DwarfsData.dopey;
+        handler.startSection(Dwarfs.PROP_DOPEY);
+        handler.handleOption(Dwarf.PROP_WEIGHT, DwarfsData.INI_DOPEY_WEIGHT);
+        handler.handleOption(Dwarf.PROP_HEIGHT, DwarfsData.INI_DOPEY_HEIGHT);
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[0]));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[1]));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[2]));
+        handler.endSection();
+        handler.handleComment(" " + Dwarfs.PROP_GRUMPY);
+        dwarf = DwarfsData.grumpy;
+        handler.startSection(Dwarfs.PROP_GRUMPY);
+        handler.handleOption(Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(Dwarf.PROP_HEIGHT, DwarfsData.INI_GRUMPY_HEIGHT);
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.endSection();
+        handler.handleComment(" " + Dwarfs.PROP_HAPPY);
+        dwarf = DwarfsData.happy;
+        handler.startSection(Dwarfs.PROP_HAPPY);
+        handler.handleOption(Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(EasyMock.eq(Dwarf.PROP_HOME_PAGE), (String) EasyMock.anyObject());
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.endSection();
+        handler.handleComment(" " + Dwarfs.PROP_SLEEPY);
+        dwarf = DwarfsData.sleepy;
+        handler.startSection(Dwarfs.PROP_SLEEPY);
+        handler.handleOption(Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(Dwarf.PROP_HEIGHT, DwarfsData.INI_SLEEPY_HEIGHT);
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[0]));
+        handler.endSection();
+        handler.handleComment(" " + Dwarfs.PROP_SNEEZY);
+        dwarf = DwarfsData.sneezy;
+        handler.startSection(Dwarfs.PROP_SNEEZY);
+        handler.handleOption(Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, DwarfsData.INI_SNEEZY_HOME_PAGE);
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[0]));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[1]));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[2]));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[3]));
+        handler.endSection();
+        handler.handleComment(" " + Dwarfs.PROP_HAPPY + " again");
+        dwarf = DwarfsData.happy;
+        handler.startSection(Dwarfs.PROP_HAPPY);
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleComment("}");
+        handler.endSection();
+        handler.endIni();
+
+        //
+        EasyMock.replay(handler);
+        parser.parse(Helper.getResourceURL(Helper.DWARFS_INI), handler);
+        EasyMock.verify(handler);
+    }
+
+    @Test public void testParseExceptions() throws Exception
+    {
+        assertBad(BAD);
+        assertBad(CFG_EXTRA);
+    }
+
+    @Test public void testUnnamedSection() throws Exception
+    {
+        IniParser parser = new IniParser();
+        IniHandler handler = EasyMock.createMock(IniHandler.class);
+
+        handler.startIni();
+        handler.startSection(EMPTY);
+        handler.handleOption(OPTION, VALUE);
+        handler.endSection();
+        handler.endIni();
+        EasyMock.replay(handler);
+        Config cfg = new Config();
+
+        cfg.setUnnamedSection(true);
+        parser.setConfig(cfg);
+        parser.parse(new StringReader(CFG_UNNAMED), handler);
+        EasyMock.verify(handler);
+    }
+
+    @SuppressWarnings("empty-statement")
+    private void assertBad(String[] values) throws Exception
+    {
+        IniParser parser = new IniParser();
+        IniHandler handler = EasyMock.createNiceMock(IniHandler.class);
+
+        for (String s : values)
+        {
+            try
+            {
+                parser.parse(new ByteArrayInputStream(s.getBytes()), handler);
+                fail("expected InvalidIniFormatException: " + s);
+            }
+            catch (InvalidFileFormatException x)
+            {
+                ;
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/ini4j/spi/IniSourceTest.java b/src/test/java/org/ini4j/spi/IniSourceTest.java
new file mode 100644
index 0000000..c7b3980
--- /dev/null
+++ b/src/test/java/org/ini4j/spi/IniSourceTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.easymock.EasyMock;
+
+import org.ini4j.Config;
+import org.ini4j.Ini4jCase;
+
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+public class IniSourceTest extends Ini4jCase
+{
+    private static final String COMMENTS = ";#";
+    private static final String NESTED_TXT = "nested.txt";
+    private static final String NESTED = ":" + NESTED_TXT;
+    private static final String NESTED_PATH = "org/ini4j/spi/" + NESTED_TXT;
+    private static final String INCLUDE = ":include.txt";
+    private static final String PART1 = ":part1.txt";
+    private static final String PART2 = ":part2.txt";
+    private static final String OUTER = ":outer";
+
+    @Test public void testWithInclude() throws Exception
+    {
+        HandlerBase handler = EasyMock.createMock(HandlerBase.class);
+
+        handler.handleComment("-1" + OUTER);
+        handler.handleComment("-1" + NESTED);
+        handler.handleComment("-2" + NESTED);
+        handler.handleComment("-1" + INCLUDE);
+        handler.handleComment("-2" + INCLUDE);
+        handler.handleComment("-1" + PART1);
+        handler.handleComment("-2" + PART1);
+        handler.handleComment("-3" + INCLUDE);
+        handler.handleComment("-4" + INCLUDE);
+        handler.handleComment("-5" + INCLUDE);
+        handler.handleComment("-6" + INCLUDE);
+        handler.handleComment("-1" + PART2);
+        handler.handleComment("-2" + PART2);
+        handler.handleComment("-7" + INCLUDE);
+        handler.handleComment("-8" + INCLUDE);
+        handler.handleComment("-3" + NESTED);
+        handler.handleComment("-4" + NESTED);
+        handler.handleComment("-2" + OUTER);
+        EasyMock.replay(handler);
+        StringBuilder outer = new StringBuilder();
+
+        outer.append(";-1" + OUTER + '\n');
+        outer.append("1" + OUTER + '\n');
+        outer.append('<');
+        outer.append(Helper.getResourceURL(NESTED_PATH).toExternalForm());
+        outer.append(">\n");
+        outer.append("2" + OUTER + '\n');
+        outer.append(";-2" + OUTER + '\n');
+        InputStream in = new ByteArrayInputStream(outer.toString().getBytes());
+        Config cfg = new Config();
+
+        cfg.setInclude(true);
+        IniSource src = new IniSource(in, handler, COMMENTS, cfg);
+
+        assertEquals("1" + OUTER, src.readLine());
+        assertEquals(2, src.getLineNumber());
+        assertEquals("1" + NESTED, src.readLine());
+        assertEquals(2, src.getLineNumber());
+        assertEquals("1" + INCLUDE, src.readLine());
+        assertEquals(2, src.getLineNumber());
+        assertEquals("1" + PART1, src.readLine());
+        assertEquals(2, src.getLineNumber());
+        assertEquals("2" + PART1, src.readLine());
+        assertEquals(4, src.getLineNumber());
+        assertEquals("3" + PART1 + "\\\\", src.readLine());
+        assertEquals(5, src.getLineNumber());
+        assertEquals("4:\\\\part1.txt", src.readLine());
+        assertEquals(7, src.getLineNumber());
+        assertEquals("5" + PART1 + "\\\\\\\\", src.readLine());
+        assertEquals(8, src.getLineNumber());
+        assertEquals("6" + PART1 + ";", src.readLine());
+        assertEquals(10, src.getLineNumber());
+        assertEquals("2" + INCLUDE, src.readLine());
+        assertEquals(6, src.getLineNumber());
+        assertEquals("3" + INCLUDE, src.readLine());
+        assertEquals(10, src.getLineNumber());
+        assertEquals("1" + PART2, src.readLine());
+        assertEquals(3, src.getLineNumber());
+        assertEquals("4" + INCLUDE, src.readLine());
+        assertEquals(14, src.getLineNumber());
+        assertEquals("2" + NESTED, src.readLine());
+        assertEquals(6, src.getLineNumber());
+        assertEquals("2" + OUTER, src.readLine());
+        assertEquals(4, src.getLineNumber());
+        assertNull(src.readLine());
+        EasyMock.verify(handler);
+    }
+
+    @Test public void testWithoutInclude() throws Exception
+    {
+        HandlerBase handler = EasyMock.createMock(HandlerBase.class);
+
+        handler.handleComment("-1" + NESTED);
+        handler.handleComment("-2" + NESTED);
+        handler.handleComment("-3" + NESTED);
+        handler.handleComment("-4" + NESTED);
+        EasyMock.replay(handler);
+        Config cfg = new Config();
+
+        cfg.setInclude(false);
+        IniSource src = new IniSource(Helper.getResourceURL(NESTED_PATH), handler, COMMENTS, cfg);
+
+        assertEquals("1" + NESTED, src.readLine());
+        assertEquals("<include.txt>", src.readLine());
+        assertEquals("2" + NESTED, src.readLine());
+        assertNull(src.readLine());
+        EasyMock.verify(handler);
+    }
+}
diff --git a/src/test/java/org/ini4j/spi/OptionsFormatterTest.java b/src/test/java/org/ini4j/spi/OptionsFormatterTest.java
new file mode 100644
index 0000000..313645c
--- /dev/null
+++ b/src/test/java/org/ini4j/spi/OptionsFormatterTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.easymock.EasyMock;
+
+import org.ini4j.Config;
+import org.ini4j.Ini4jCase;
+import org.ini4j.Options;
+
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.Dwarfs;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+
+public class OptionsFormatterTest extends Ini4jCase
+{
+    private static final String NL = System.getProperty("line.separator");
+    private static final String DUMMY = "dummy";
+
+    @Test public void testFormat() throws Exception
+    {
+        Options opts = Helper.newDwarfsOpt();
+        OptionsHandler handler = EasyMock.createMock(OptionsHandler.class);
+        Dwarf dwarf;
+        String prefix;
+
+        handler.startOptions();
+        handler.handleComment(Helper.HEADER_COMMENT);
+        handler.handleComment((String) EasyMock.anyObject());
+        dwarf = DwarfsData.dopey;
+        handler.handleOption(Dwarf.PROP_WEIGHT, DwarfsData.OPT_DOPEY_WEIGHT);
+        handler.handleOption(Dwarf.PROP_HEIGHT, DwarfsData.OPT_DOPEY_HEIGHT);
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, "11");
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, "33");
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, "55");
+//
+
+        //
+        handler.handleComment(" " + Dwarfs.PROP_BASHFUL);
+        dwarf = DwarfsData.bashful;
+        prefix = Dwarfs.PROP_BASHFUL + ".";
+
+        handler.handleOption(prefix + Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(prefix + Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(prefix + Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleComment(" " + Dwarfs.PROP_DOC);
+        dwarf = DwarfsData.doc;
+        prefix = Dwarfs.PROP_DOC + ".";
+
+        handler.handleOption(prefix + Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(prefix + Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(prefix + Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleComment(" " + Dwarfs.PROP_DOPEY);
+        dwarf = DwarfsData.dopey;
+        prefix = Dwarfs.PROP_DOPEY + ".";
+
+        handler.handleOption(prefix + Dwarf.PROP_WEIGHT, DwarfsData.OPT_DOPEY_WEIGHT);
+        handler.handleOption(prefix + Dwarf.PROP_HEIGHT, DwarfsData.OPT_DOPEY_HEIGHT);
+        handler.handleOption(prefix + Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[0]));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[1]));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[2]));
+        handler.handleComment(" " + Dwarfs.PROP_GRUMPY);
+        dwarf = DwarfsData.grumpy;
+        prefix = Dwarfs.PROP_GRUMPY + ".";
+
+        handler.handleOption(prefix + Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(prefix + Dwarf.PROP_HEIGHT, DwarfsData.OPT_GRUMPY_HEIGHT);
+        handler.handleOption(prefix + Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleComment(" " + Dwarfs.PROP_HAPPY);
+        dwarf = DwarfsData.happy;
+        prefix = Dwarfs.PROP_HAPPY + ".";
+
+        handler.handleOption(prefix + Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(prefix + Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(prefix + Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleComment(" " + Dwarfs.PROP_SLEEPY);
+        dwarf = DwarfsData.sleepy;
+        prefix = Dwarfs.PROP_SLEEPY + ".";
+
+        handler.handleOption(prefix + Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(prefix + Dwarf.PROP_HEIGHT, DwarfsData.OPT_SLEEPY_HEIGHT);
+        handler.handleOption(prefix + Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[0]));
+        handler.handleComment(" " + Dwarfs.PROP_SNEEZY);
+        dwarf = DwarfsData.sneezy;
+        prefix = Dwarfs.PROP_SNEEZY + ".";
+
+        handler.handleOption(prefix + Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(prefix + Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(prefix + Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_PAGE, DwarfsData.OPT_SNEEZY_HOME_PAGE);
+        handler.handleOption(prefix + Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[0]));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[1]));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[2]));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[3]));
+        handler.endOptions();
+
+        //
+        EasyMock.replay(handler);
+        verify(opts, handler);
+    }
+
+    @Test public void testNewInstance() throws Exception
+    {
+        StringWriter stringWriter;
+        PrintWriter printWriter;
+
+        ;
+        Config cfg = new Config();
+        OptionsFormatter instance;
+
+        stringWriter = new StringWriter();
+        instance = OptionsFormatter.newInstance(stringWriter, cfg);
+
+        instance.getOutput().print(DUMMY);
+        instance.getOutput().flush();
+        assertEquals(DUMMY, stringWriter.toString());
+        assertSame(cfg, instance.getConfig());
+
+        //
+        stringWriter = new StringWriter();
+        instance = OptionsFormatter.newInstance(stringWriter, cfg);
+
+        instance.getOutput().print(DUMMY);
+        instance.getOutput().flush();
+        assertEquals(DUMMY, stringWriter.toString());
+
+        //
+        printWriter = new PrintWriter(stringWriter);
+        instance = OptionsFormatter.newInstance(printWriter, cfg);
+
+        assertSame(printWriter, instance.getOutput());
+    }
+
+    @Test public void testWithStrictOperatorEmptyOptions() throws Exception
+    {
+        Config cfg = new Config();
+
+        cfg.setStrictOperator(true);
+        cfg.setEmptyOption(true);
+        Options opts = new Options();
+
+        opts.setConfig(cfg);
+        opts.put(Dwarf.PROP_AGE, DwarfsData.bashful.age);
+        opts.put(Dwarf.PROP_WEIGHT, null);
+        StringWriter writer = new StringWriter();
+
+        opts.store(writer);
+        StringBuilder exp = new StringBuilder();
+
+        exp.append(Dwarf.PROP_AGE);
+        exp.append('=');
+        exp.append(DwarfsData.bashful.age);
+        exp.append(NL);
+        exp.append(Dwarf.PROP_WEIGHT);
+        exp.append('=');
+        exp.append(NL);
+        assertEquals(exp.toString(), writer.toString());
+    }
+
+    @Test public void testWithStrictOperatorNoEmptyOptions() throws Exception
+    {
+        Config cfg = new Config();
+
+        cfg.setStrictOperator(true);
+        cfg.setEmptyOption(false);
+        Options opts = new Options();
+
+        opts.setConfig(cfg);
+        opts.put(Dwarf.PROP_AGE, DwarfsData.bashful.age);
+        opts.put(Dwarf.PROP_WEIGHT, null);
+        StringWriter writer = new StringWriter();
+
+        opts.store(writer);
+        StringBuilder exp = new StringBuilder();
+
+        exp.append(Dwarf.PROP_AGE);
+        exp.append('=');
+        exp.append(DwarfsData.bashful.age);
+        exp.append(NL);
+        assertEquals(exp.toString(), writer.toString());
+    }
+
+    private void verify(Options opts, OptionsHandler mock) throws Exception
+    {
+        StringWriter writer = new StringWriter();
+
+        opts.store(writer);
+        OptionsParser parser = new OptionsParser();
+
+        parser.parse(new StringReader(writer.toString()), mock);
+        EasyMock.verify(mock);
+    }
+}
diff --git a/src/test/java/org/ini4j/spi/OptionsParserTest.java b/src/test/java/org/ini4j/spi/OptionsParserTest.java
new file mode 100644
index 0000000..4a30b98
--- /dev/null
+++ b/src/test/java/org/ini4j/spi/OptionsParserTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.easymock.EasyMock;
+
+import org.ini4j.Config;
+import org.ini4j.Ini4jCase;
+import org.ini4j.InvalidFileFormatException;
+
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.Dwarfs;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.StringReader;
+
+public class OptionsParserTest extends Ini4jCase
+{
+    private static final String CFG_EMPTY_OPTION = "option\n";
+    private static final String NONAME = "=value\n";
+    private static final String OPTION = "option";
+    private static final String UNICODE_STRING = "áÁéÉíÍóÓöÖőŐúÚüÜűŰ-ÄÖÜäöü";
+
+    @Test public void testBad() throws Exception
+    {
+        OptionsParser parser = new OptionsParser();
+        OptionsHandler handler = EasyMock.createNiceMock(OptionsHandler.class);
+
+        try
+        {
+            parser.parse(new ByteArrayInputStream(NONAME.getBytes()), handler);
+            missing(InvalidFileFormatException.class);
+        }
+        catch (InvalidFileFormatException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testEmptyOption() throws Exception
+    {
+        OptionsParser parser = new OptionsParser();
+        OptionsHandler handler = EasyMock.createMock(OptionsHandler.class);
+
+        handler.startOptions();
+        handler.handleOption(OPTION, null);
+        handler.endOptions();
+        EasyMock.replay(handler);
+        Config cfg = new Config();
+
+        cfg.setEmptyOption(true);
+        parser.setConfig(cfg);
+        parser.parse(new StringReader(CFG_EMPTY_OPTION), handler);
+        EasyMock.verify(handler);
+    }
+
+    @Test public void testNewInstance() throws Exception
+    {
+        Config cfg = new Config();
+        OptionsParser parser = OptionsParser.newInstance();
+
+        assertEquals(OptionsParser.class, parser.getClass());
+        parser = OptionsParser.newInstance(cfg);
+        assertEquals(OptionsParser.class, parser.getClass());
+        assertSame(cfg, parser.getConfig());
+    }
+
+    @Test public void testParse() throws Exception
+    {
+        OptionsParser parser = new OptionsParser();
+        OptionsHandler handler = EasyMock.createMock(OptionsHandler.class);
+        Dwarf dwarf;
+        String prefix;
+
+        handler.startOptions();
+        handler.handleComment(Helper.HEADER_COMMENT);
+        handler.handleComment((String) EasyMock.anyObject());
+        handler.handleComment((String) EasyMock.anyObject());
+        dwarf = DwarfsData.dopey;
+        handler.handleOption(Dwarf.PROP_WEIGHT, DwarfsData.OPT_DOPEY_WEIGHT);
+        handler.handleOption(Dwarf.PROP_HEIGHT, DwarfsData.OPT_DOPEY_HEIGHT);
+        handler.handleOption(Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, "11");
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, "33");
+        handler.handleOption(Dwarf.PROP_FORTUNE_NUMBER, "55");
+//
+        handler.handleComment((String) EasyMock.anyObject());
+
+        //
+        handler.handleComment(" " + Dwarfs.PROP_BASHFUL);
+        dwarf = DwarfsData.bashful;
+        prefix = Dwarfs.PROP_BASHFUL + ".";
+
+        handler.handleOption(prefix + Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(prefix + Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(prefix + Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleComment(" " + Dwarfs.PROP_DOC);
+        dwarf = DwarfsData.doc;
+        prefix = Dwarfs.PROP_DOC + ".";
+
+        handler.handleOption(prefix + Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(prefix + Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(prefix + Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleComment(" " + Dwarfs.PROP_DOPEY);
+        dwarf = DwarfsData.dopey;
+        prefix = Dwarfs.PROP_DOPEY + ".";
+
+        handler.handleOption(prefix + Dwarf.PROP_WEIGHT, DwarfsData.OPT_DOPEY_WEIGHT);
+        handler.handleOption(prefix + Dwarf.PROP_HEIGHT, DwarfsData.OPT_DOPEY_HEIGHT);
+        handler.handleOption(prefix + Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[0]));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[1]));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[2]));
+        handler.handleComment(" " + Dwarfs.PROP_GRUMPY);
+        dwarf = DwarfsData.grumpy;
+        prefix = Dwarfs.PROP_GRUMPY + ".";
+
+        handler.handleOption(prefix + Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(prefix + Dwarf.PROP_HEIGHT, DwarfsData.OPT_GRUMPY_HEIGHT);
+        handler.handleOption(prefix + Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleComment(" " + Dwarfs.PROP_HAPPY);
+        dwarf = DwarfsData.happy;
+        prefix = Dwarfs.PROP_HAPPY + ".";
+
+        handler.handleOption(prefix + Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(prefix + Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(prefix + Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(EasyMock.eq(prefix + Dwarf.PROP_HOME_PAGE), (String) EasyMock.anyObject());
+        handler.handleOption(prefix + Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleComment(" " + Dwarfs.PROP_SLEEPY);
+        dwarf = DwarfsData.sleepy;
+        prefix = Dwarfs.PROP_SLEEPY + ".";
+
+        handler.handleOption(prefix + Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(prefix + Dwarf.PROP_HEIGHT, DwarfsData.OPT_SLEEPY_HEIGHT);
+        handler.handleOption(prefix + Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[0]));
+        handler.handleComment(" " + Dwarfs.PROP_SNEEZY);
+        dwarf = DwarfsData.sneezy;
+        prefix = Dwarfs.PROP_SNEEZY + ".";
+
+        handler.handleOption(prefix + Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        handler.handleOption(prefix + Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        handler.handleOption(prefix + Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        handler.handleOption(prefix + Dwarf.PROP_HOME_PAGE, DwarfsData.OPT_SNEEZY_HOME_PAGE);
+        handler.handleOption(prefix + Dwarf.PROP_HOME_DIR, String.valueOf(dwarf.getHomeDir()));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[0]));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[1]));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[2]));
+        handler.handleOption(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(dwarf.getFortuneNumber()[3]));
+        handler.handleComment(" " + Dwarfs.PROP_HAPPY + " again");
+        dwarf = DwarfsData.happy;
+        prefix = Dwarfs.PROP_HAPPY + ".";
+
+        handler.handleOption(prefix + Dwarf.PROP_HOME_PAGE, String.valueOf(dwarf.getHomePage()));
+        handler.handleComment("}");
+        handler.endOptions();
+
+        //
+        EasyMock.replay(handler);
+        parser.parse(Helper.getResourceURL(Helper.DWARFS_OPT), handler);
+        EasyMock.verify(handler);
+    }
+
+    @Test public void testUnicode() throws Exception
+    {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        OptionsFormatter formatter = new OptionsFormatter();
+
+        formatter.setOutput(new PrintWriter(new OutputStreamWriter(out)));
+        formatter.handleOption(OPTION, UNICODE_STRING);
+        formatter.getOutput().flush();
+        Reader in = new InputStreamReader(new ByteArrayInputStream(out.toByteArray()));
+        String line = new BufferedReader(in).readLine();
+
+        assertEquals(OPTION + " = " + UNICODE_STRING, EscapeTool.getInstance().unescape(line));
+    }
+}
diff --git a/src/org/ini4j/OptionMap.java b/src/test/java/org/ini4j/spi/RegEscapeToolTest.java
similarity index 50%
rename from src/org/ini4j/OptionMap.java
rename to src/test/java/org/ini4j/spi/RegEscapeToolTest.java
index 5cb908d..2ee7163 100644
--- a/src/org/ini4j/OptionMap.java
+++ b/src/test/java/org/ini4j/spi/RegEscapeToolTest.java
@@ -13,23 +13,33 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.ini4j;
+package org.ini4j.spi;
 
-public interface OptionMap extends MultiMap<String, String>
-{
-    <T> T as(Class<T> clazz);
-
-    <T> T as(Class<T> clazz, String keyPrefix);
-
-    String fetch(Object key);
+import org.ini4j.Ini4jCase;
 
-    String fetch(Object key, int index);
+import static org.junit.Assert.assertEquals;
 
-    void from(Object bean);
+import org.junit.Before;
+import org.junit.Test;
 
-    void from(Object bean, String keyPrefix);
-
-    void to(Object bean);
-
-    void to(Object bean, String keyPrefix);
+public class RegEscapeToolTest extends Ini4jCase
+{
+    protected RegEscapeTool instance;
+
+    @Before @Override public void setUp() throws Exception
+    {
+        super.setUp();
+        instance = RegEscapeTool.getInstance();
+    }
+
+    @Test public void testHexadecimal()
+    {
+        assertEquals(0, instance.hexadecimal(null).length());
+        assertEquals(0, instance.hexadecimal("").length());
+    }
+
+    @Test public void testSingleton() throws Exception
+    {
+        assertEquals(RegEscapeTool.class, RegEscapeTool.getInstance().getClass());
+    }
 }
diff --git a/src/test/java/org/ini4j/spi/ServiceFinderTest.java b/src/test/java/org/ini4j/spi/ServiceFinderTest.java
new file mode 100644
index 0000000..6f58c3b
--- /dev/null
+++ b/src/test/java/org/ini4j/spi/ServiceFinderTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.ini4j.Ini4jCase;
+
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class ServiceFinderTest extends Ini4jCase
+{
+    static final String DUMMY = "dummy";
+    static final String DUMMY_SERVICE = "org.ini4j.Dummy";
+    static final String BAD_CONFIG_SERVICE = "org.ini4j.BadConfig";
+    static final String EMPTY_CONFIG_SERVICE = "org.ini4j.EmptyConfig";
+    static final String DUMMY_IMPL = "DummyImpl";
+
+    @Test public void testFindService() throws Exception
+    {
+        boolean flag = false;
+
+        System.setProperty(IniParser.class.getName(), Helper.class.getName());
+        try
+        {
+            ServiceFinder.findService(IniParser.class);
+        }
+        catch (IllegalArgumentException x)
+        {
+            flag = true;
+        }
+
+        // System.clearProperty(IniParser.SERVICE_ID); missing in 1.4
+        System.getProperties().remove(IniParser.class.getName());
+        assertTrue(flag);
+    }
+
+    @Test public void testFindServiceClass() throws Exception
+    {
+        boolean flag = false;
+
+        System.setProperty(IniParser.class.getName(), DUMMY);
+        try
+        {
+            ServiceFinder.findServiceClass(IniParser.class);
+        }
+        catch (IllegalArgumentException x)
+        {
+            flag = true;
+        }
+
+        // System.clearProperty(IniParser.SERVICE_ID); missing in 1.4
+        System.getProperties().remove(IniParser.class.getName());
+        assertTrue(flag);
+    }
+
+    @Test public void testFindServiceClassName() throws Exception
+    {
+        System.setProperty(IniParser.class.getName(), DUMMY);
+        assertEquals(DUMMY, ServiceFinder.findServiceClassName(IniParser.class.getName()));
+
+        // System.clearProperty(IniParser.SERVICE_ID); missing in 1.4
+        System.getProperties().remove(IniParser.class.getName());
+        assertNull(ServiceFinder.findServiceClassName(IniParser.class.getName()));
+        assertEquals(DUMMY_IMPL, ServiceFinder.findServiceClassName(DUMMY_SERVICE));
+        assertNull(DUMMY, ServiceFinder.findServiceClassName(BAD_CONFIG_SERVICE));
+        assertNull(DUMMY, ServiceFinder.findServiceClassName(EMPTY_CONFIG_SERVICE));
+    }
+}
diff --git a/src/test/java/org/ini4j/spi/UnicodeInputStreamReaderTest.java b/src/test/java/org/ini4j/spi/UnicodeInputStreamReaderTest.java
new file mode 100644
index 0000000..ffdc174
--- /dev/null
+++ b/src/test/java/org/ini4j/spi/UnicodeInputStreamReaderTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.ini4j.Config;
+import org.ini4j.Ini;
+import org.ini4j.Ini4jCase;
+import org.ini4j.InvalidFileFormatException;
+
+import org.junit.Test;
+
+import java.nio.charset.Charset;
+
+public class UnicodeInputStreamReaderTest extends Ini4jCase
+{
+    @Test public void _testUTF32BE() throws Exception
+    {
+        test("UTF-32BE.ini", "UTF-32BE");
+    }
+
+    @Test public void _testUTF32BE_BOM() throws Exception
+    {
+        test("UTF-32BE-BOM.ini", null);
+        test("UTF-32BE-BOM.ini", "UTF-8");
+        test("UTF-32BE-BOM.ini", "UTF-16");
+    }
+
+    @Test public void _testUTF32BE_fail() throws Exception
+    {
+        try
+        {
+            test("UTF-32BE.ini", "ISO-8859-1");
+            missing(IllegalStateException.class);
+        }
+        catch (IllegalStateException x)
+        {
+            //
+        }
+    }
+
+    @Test public void _testUTF32LE() throws Exception
+    {
+        test("UTF-32LE.ini", "UTF-32LE");
+    }
+
+    @Test public void _testUTF32LE_BOM() throws Exception
+    {
+        test("UTF-32LE-BOM.ini", null);
+        test("UTF-32LE-BOM.ini", "UTF-8");
+        test("UTF-32LE-BOM.ini", "UTF-16");
+    }
+
+    @Test public void _testUTF32LE_fail() throws Exception
+    {
+        try
+        {
+            test("UTF-32LE.ini", "ISO-8859-1");
+            missing(IllegalStateException.class);
+        }
+        catch (IllegalStateException x)
+        {
+            //
+        }
+    }
+
+    @Test public void t_e_s_tUTF16BE_fail() throws Exception
+    {
+        try
+        {
+            test("UTF-16BE.ini", "ISO-8859-1");
+            missing(IllegalStateException.class);
+        }
+        catch (IllegalStateException x)
+        {
+            //
+        }
+    }
+
+    @Test public void t_e_s_tUTF16LE_fail() throws Exception
+    {
+        try
+        {
+            test("UTF-16LE.ini", "ISO-8859-1");
+            missing(IllegalStateException.class);
+        }
+        catch (IllegalStateException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testUTF16BE() throws Exception
+    {
+        test("UTF-16BE.ini", "UTF-16BE");
+    }
+
+    @Test public void testUTF16BE_BOM() throws Exception
+    {
+        test("UTF-16BE-BOM.ini", null);
+        test("UTF-16BE-BOM.ini", "UTF-8");
+        test("UTF-16BE-BOM.ini", "UTF-16");
+    }
+
+    @Test public void testUTF16LE() throws Exception
+    {
+        test("UTF-16LE.ini", "UTF-16LE");
+    }
+
+    @Test public void testUTF16LE_BOM() throws Exception
+    {
+        test("UTF-16LE-BOM.ini", null);
+        test("UTF-16LE-BOM.ini", "UTF-8");
+        test("UTF-16LE-BOM.ini", "UTF-16");
+    }
+
+    @Test public void testUTF8() throws Exception
+    {
+        test("UTF-8.ini", null);
+        test("UTF-8.ini", "UTF-8");
+    }
+
+    @Test public void testUTF8_BOM() throws Exception
+    {
+        test("UTF-8-BOM.ini", null);
+        test("UTF-8-BOM.ini", "UTF-8");
+        test("UTF-8-BOM.ini", "UTF-16");
+    }
+
+    @Test public void testUTF8_fail() throws Exception
+    {
+        try
+        {
+            test("UTF-8.ini", "UTF-16");
+            missing(InvalidFileFormatException.class);
+        }
+        catch (InvalidFileFormatException x)
+        {
+            //
+        }
+    }
+
+    private UnicodeInputStreamReader instantiate(String filename, String defaultEncoding)
+    {
+        Charset charset = (defaultEncoding == null) ? Charset.defaultCharset() : Charset.forName(defaultEncoding);
+
+        return new UnicodeInputStreamReader(getClass().getResourceAsStream(filename), charset);
+    }
+
+    private void test(String filename, String defaultEncoding) throws Exception
+    {
+        Charset charset = (defaultEncoding == null) ? Config.DEFAULT_FILE_ENCODING : Charset.forName(defaultEncoding);
+        UnicodeInputStreamReader reader = new UnicodeInputStreamReader(getClass().getResourceAsStream(filename), charset);
+        Ini ini = new Ini();
+
+        ini.setConfig(Config.getGlobal().clone());
+        ini.getConfig().setFileEncoding(charset);
+        ini.load(reader);
+        Ini.Section sec = ini.get("section");
+
+        if (sec == null)
+        {
+            throw new IllegalStateException("Missing section: section");
+        }
+
+        if (!"value".equals(sec.get("option")))
+        {
+            throw new IllegalStateException("Missing option: option");
+        }
+    }
+}
diff --git a/src/test/java/org/ini4j/spi/WinEscapeToolTest.java b/src/test/java/org/ini4j/spi/WinEscapeToolTest.java
new file mode 100644
index 0000000..a00694d
--- /dev/null
+++ b/src/test/java/org/ini4j/spi/WinEscapeToolTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.spi;
+
+import org.ini4j.Ini4jCase;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class WinEscapeToolTest extends Ini4jCase
+{
+    public static final String VALUE1 = "simple";
+    public static final String ESCAPE1 = "simple";
+    public static final String VALUE2 = "Iván";
+    public static final String ESCAPE2 = "Iv\\xe1n";
+    public static final String VALUE3 = "1\t2\n3\f4\b5\r6";
+    public static final String ESCAPE3 = "1\\t2\\n3\\f4\\b5\\r6";
+    public static final String VALUE4 = "Iv\u0017n";
+    public static final String ESCAPE4 = "Iv\\x17n";
+    public static final String VALUE5 = "Árvíztrtükörfúrógép";
+    public static final String ESCAPE5 = "\\xc1rv\\xedztrt\\xfck\\xf6rf\\xfar\\xf3g\\xe9p";
+    private static final String INVALID_HEX = "\\x1_";
+    private static final String INVALID_OCT = "\\o19_";
+    protected WinEscapeTool instance;
+
+    @Before @Override public void setUp() throws Exception
+    {
+        super.setUp();
+        instance = WinEscapeTool.getInstance();
+    }
+
+    @Test public void testEscape() throws Exception
+    {
+        assertEquals(ESCAPE1, instance.escape(VALUE1));
+        assertEquals(ESCAPE2, instance.escape(VALUE2));
+        assertEquals(ESCAPE3, instance.escape(VALUE3));
+        assertEquals(ESCAPE4, instance.escape(VALUE4));
+        assertEquals(ESCAPE5, instance.escape(VALUE5));
+    }
+
+    @Test public void testInvalidHex()
+    {
+        try
+        {
+            instance.unescape(INVALID_HEX);
+            missing(IllegalArgumentException.class);
+        }
+        catch (IllegalArgumentException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testInvalidOctal()
+    {
+        try
+        {
+            instance.unescape(INVALID_OCT);
+            missing(IllegalArgumentException.class);
+        }
+        catch (IllegalArgumentException x)
+        {
+            //
+        }
+    }
+
+    @Test public void testSingleton() throws Exception
+    {
+        assertEquals(WinEscapeTool.class, WinEscapeTool.getInstance().getClass());
+    }
+
+    @Test public void testUnescape() throws Exception
+    {
+        assertEquals(VALUE1, instance.unescape(ESCAPE1));
+        assertEquals(VALUE2, instance.unescape(ESCAPE2));
+        assertEquals(VALUE3, instance.unescape(ESCAPE3));
+        assertEquals(VALUE4, instance.unescape(ESCAPE4));
+        assertEquals(VALUE5, instance.unescape(ESCAPE5));
+        assertEquals("=", instance.unescape("\\="));
+        assertEquals("xAx", instance.unescape("x\\o101x"));
+    }
+}
diff --git a/src/test/java/org/ini4j/test/DwarfsData.java b/src/test/java/org/ini4j/test/DwarfsData.java
new file mode 100644
index 0000000..18644fd
--- /dev/null
+++ b/src/test/java/org/ini4j/test/DwarfsData.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.test;
+
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.Dwarfs;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyVetoException;
+import java.beans.VetoableChangeListener;
+
+import java.net.URI;
+
+public final class DwarfsData implements Dwarfs
+{
+    public static final DwarfData bashful;
+    public static final DwarfData doc;
+    public static final DwarfData dopey;
+    public static final DwarfData grumpy;
+    public static final DwarfData happy;
+    public static final DwarfData sleepy;
+    public static final DwarfData sneezy;
+    public static final Dwarfs dwarfs;
+    public static final String[] dwarfNames;
+    public static final String INI_DOPEY_WEIGHT = "${bashful/weight}";
+    public static final String INI_DOPEY_HEIGHT = "${doc/height}";
+    public static final String INI_GRUMPY_HEIGHT = "${dopey/height}";
+    public static final String INI_SLEEPY_HEIGHT = "${doc/height}8";
+    public static final String INI_SNEEZY_HOME_PAGE = "${happy/homePage}/~sneezy";
+    public static final String OPT_DOPEY_WEIGHT = "${bashful.weight}";
+    public static final String OPT_DOPEY_HEIGHT = "${doc.height}";
+    public static final String OPT_GRUMPY_HEIGHT = "${dopey.height}";
+    public static final String OPT_SLEEPY_HEIGHT = "${doc.height}8";
+    public static final String OPT_SNEEZY_HOME_PAGE = "${happy.homePage}/~sneezy";
+
+    static
+    {
+
+        // age, fortuneNumber, height, homeDir, homePage, weight
+        bashful = new DwarfData(PROP_BASHFUL, 67, null, 98.8, "/home/bashful", "http://snowwhite.tale/~bashful", 45.7);
+        doc = new DwarfData(PROP_DOC, 63, null, 87.7, "c:Documents and Settingsdoc", "http://doc.dwarfs", 49.5);
+        dopey = new DwarfData(PROP_DOPEY, 23, new int[] { 11, 33, 55 }, doc.height, "c:\\Documents and Settings\\dopey", "http://dopey.snowwhite.tale/", bashful.weight);
+        grumpy = new DwarfData(PROP_GRUMPY, 76, null, dopey.height, "/home/grumpy", "http://snowwhite.tale/~grumpy/", 65.3);
+        happy = new DwarfData(PROP_HAPPY, 99, null, 77.66, "/home/happy", "http://happy.smurf", 56.4);
+        sleepy = new DwarfData(PROP_SLEEPY, 121, new int[] { 99 }, doc.height + 0.08, "/home/sleepy", "http://snowwhite.tale/~sleepy", 76.11);
+        sneezy = new DwarfData(PROP_SNEEZY, 64, new int[] { 11, 22, 33, 44 }, 76.88, "/home/sneezy", happy.homePage.toString() + "/~sneezy", 69.7);
+        dwarfs = new DwarfsData();
+        dwarfNames = new String[] { bashful.name, doc.name, dopey.name, grumpy.name, happy.name, sleepy.name, sneezy.name };
+    }
+
+    @SuppressWarnings("empty-statement")
+    private DwarfsData()
+    {
+        ;
+    }
+
+    public Dwarf getBashful()
+    {
+        return bashful;
+    }
+
+    public Dwarf getDoc()
+    {
+        return doc;
+    }
+
+    public Dwarf getDopey()
+    {
+        return dopey;
+    }
+
+    public Dwarf getGrumpy()
+    {
+        return grumpy;
+    }
+
+    public Dwarf getHappy()
+    {
+        return happy;
+    }
+
+    public Dwarf getSleepy()
+    {
+        return sleepy;
+    }
+
+    public Dwarf getSneezy()
+    {
+        return sneezy;
+    }
+
+    public static class DwarfData implements Dwarf
+    {
+        private static final String READ_ONLY_INSTANCE = "Read only instance";
+        public final int age;
+        public final int[] fortuneNumber;
+        public final double height;
+        public final String homeDir;
+        public final URI homePage;
+        public final String name;
+        public final double weight;
+
+        public DwarfData(String name, int age, int[] fortuneNumber, double height, String homeDir, String homePage, double weight)
+        {
+            this.name = name;
+            this.age = age;
+            this.fortuneNumber = fortuneNumber;
+            this.height = height;
+            this.homeDir = homeDir;
+            this.homePage = URI.create(homePage);
+            this.weight = weight;
+        }
+
+        public int getAge()
+        {
+            return age;
+        }
+
+        public void setAge(int age)
+        {
+            throw new UnsupportedOperationException(READ_ONLY_INSTANCE);
+        }
+
+        public int[] getFortuneNumber()
+        {
+            return fortuneNumber;
+        }
+
+        public void setFortuneNumber(int[] value)
+        {
+            throw new UnsupportedOperationException(READ_ONLY_INSTANCE);
+        }
+
+        public double getHeight()
+        {
+            return height;
+        }
+
+        public void setHeight(double height) throws PropertyVetoException
+        {
+            throw new UnsupportedOperationException(READ_ONLY_INSTANCE);
+        }
+
+        public String getHomeDir()
+        {
+            return homeDir;
+        }
+
+        public void setHomeDir(String dir)
+        {
+            throw new UnsupportedOperationException(READ_ONLY_INSTANCE);
+        }
+
+        public URI getHomePage()
+        {
+            return homePage;
+        }
+
+        public void setHomePage(URI location)
+        {
+            throw new UnsupportedOperationException(READ_ONLY_INSTANCE);
+        }
+
+        public double getWeight()
+        {
+            return weight;
+        }
+
+        public void setWeight(double weight)
+        {
+            throw new UnsupportedOperationException(READ_ONLY_INSTANCE);
+        }
+
+        public void addPropertyChangeListener(String property, PropertyChangeListener listener)
+        {
+            throw new UnsupportedOperationException(READ_ONLY_INSTANCE);
+        }
+
+        public void addVetoableChangeListener(String property, VetoableChangeListener listener)
+        {
+            throw new UnsupportedOperationException(READ_ONLY_INSTANCE);
+        }
+
+        public boolean hasAge()
+        {
+            return age != 0;
+        }
+
+        public boolean hasHeight()
+        {
+            return height != 0.0;
+        }
+
+        public boolean hasHomePage()
+        {
+            return homePage != null;
+        }
+
+        public boolean hasWeight()
+        {
+            return weight != 0.0;
+        }
+
+        public void removePropertyChangeListener(String property, PropertyChangeListener listener)
+        {
+            throw new UnsupportedOperationException(READ_ONLY_INSTANCE);
+        }
+
+        public void removeVetoableChangeListener(String property, VetoableChangeListener listener)
+        {
+            throw new UnsupportedOperationException(READ_ONLY_INSTANCE);
+        }
+    }
+}
diff --git a/src/test/java/org/ini4j/test/Helper.java b/src/test/java/org/ini4j/test/Helper.java
new file mode 100644
index 0000000..b41c2a3
--- /dev/null
+++ b/src/test/java/org/ini4j/test/Helper.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.test;
+
+import org.ini4j.Config;
+import org.ini4j.Ini;
+import org.ini4j.OptionMap;
+import org.ini4j.Options;
+import org.ini4j.Profile;
+import org.ini4j.Reg;
+import org.ini4j.Registry;
+
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.Dwarfs;
+
+import org.ini4j.spi.IniFormatter;
+import org.ini4j.spi.IniParser;
+
+import org.ini4j.test.DwarfsData.DwarfData;
+
+import org.junit.Assert;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import java.net.URL;
+
+import java.util.Properties;
+
+public class Helper
+{
+    private static final String RESOURCE_PREFIX = "org/ini4j/sample/";
+    private static final File _sourceDir = new File(System.getProperty("basedir") + "/src/test/java/");
+    private static final File _targetDir = new File(System.getProperty("basedir") + "/target");
+    public static final String DWARFS_INI = RESOURCE_PREFIX + "dwarfs.ini";
+    public static final String TALE_INI = RESOURCE_PREFIX + "tale.ini";
+    public static final String DWARFS_OPT = RESOURCE_PREFIX + "dwarfs.opt";
+    public static final String DWARFS_REG = RESOURCE_PREFIX + "dwarfs.reg";
+    public static final String TEST_REG = "org/ini4j/mozilla.reg";
+    public static final String DWARFS_REG_PATH = Reg.Hive.HKEY_CURRENT_USER + "\\Software\\ini4j-test";
+    public static final float DELTA = 0.00000001f;
+    private static final String[] CONFIG_PROPERTIES =
+        {
+            Config.PROP_EMPTY_OPTION, Config.PROP_GLOBAL_SECTION, Config.PROP_GLOBAL_SECTION_NAME, Config.PROP_INCLUDE,
+            Config.PROP_LOWER_CASE_OPTION, Config.PROP_LOWER_CASE_SECTION, Config.PROP_MULTI_OPTION, Config.PROP_MULTI_SECTION,
+            Config.PROP_STRICT_OPERATOR, Config.PROP_UNNAMED_SECTION, Config.PROP_ESCAPE
+        };
+    private static final String[] FACTORY_PROPERTIES = { IniFormatter.class.getName(), IniParser.class.getName() };
+    public static final String HEADER_COMMENT = " Copyright 2005,2009 Ivan SZKIBA\n" + "\n"
+        + " Licensed under the Apache License, Version 2.0 (the \"License\");\n"
+        + " you may not use this file except in compliance with the License.\n" + " You may obtain a copy of the License at\n" + "\n"
+        + "      http://www.apache.org/licenses/LICENSE-2.0\n" + "\n"
+        + " Unless required by applicable law or agreed to in writing, software\n"
+        + " distributed under the License is distributed on an \"AS IS\" BASIS,\n"
+        + " WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
+        + " See the License for the specific language governing permissions and\n" + " limitations under the License.";
+
+    private Helper()
+    {
+    }
+
+    public static File getBuildDirectory()
+    {
+        return _targetDir;
+    }
+
+    public static Reader getResourceReader(String path) throws Exception
+    {
+        return new InputStreamReader(getResourceURL(path).openStream());
+    }
+
+    public static InputStream getResourceStream(String path) throws Exception
+    {
+        return getResourceURL(path).openStream();
+    }
+
+    public static URL getResourceURL(String path) throws Exception
+    {
+        return Helper.class.getClassLoader().getResource(path);
+    }
+
+    public static File getSourceFile(String path) throws Exception
+    {
+        return new File(_sourceDir, path).getCanonicalFile();
+    }
+
+    public static void addDwarf(OptionMap opts, DwarfData dwarf)
+    {
+        addDwarf(opts, dwarf, true);
+    }
+
+    public static Profile.Section addDwarf(Profile prof, DwarfData dwarf)
+    {
+        Profile.Section s = prof.add(dwarf.name);
+
+        inject(s, dwarf, "");
+        if (dwarf.name.equals(Dwarfs.PROP_DOPEY))
+        {
+            s.put(Dwarf.PROP_WEIGHT, DwarfsData.INI_DOPEY_WEIGHT, 0);
+            s.put(Dwarf.PROP_HEIGHT, DwarfsData.INI_DOPEY_HEIGHT, 0);
+        }
+        else if (dwarf.name.equals(Dwarfs.PROP_GRUMPY))
+        {
+            s.put(Dwarf.PROP_HEIGHT, DwarfsData.INI_GRUMPY_HEIGHT, 0);
+        }
+        else if (dwarf.name.equals(Dwarfs.PROP_SLEEPY))
+        {
+            s.put(Dwarf.PROP_HEIGHT, DwarfsData.INI_SLEEPY_HEIGHT, 0);
+        }
+        else if (dwarf.name.equals(Dwarfs.PROP_SNEEZY))
+        {
+            s.put(Dwarf.PROP_HOME_PAGE, DwarfsData.INI_SNEEZY_HOME_PAGE, 0);
+        }
+
+        return s;
+    }
+
+    public static Ini.Section addDwarf(Ini ini, DwarfData dwarf)
+    {
+        Ini.Section s = addDwarf((Profile) ini, dwarf);
+
+        ini.putComment(dwarf.name, " " + dwarf.name);
+
+        return s;
+    }
+
+    public static void addDwarf(OptionMap opts, DwarfData dwarf, boolean addNamePrefix)
+    {
+        String prefix = addNamePrefix ? (dwarf.name + '.') : "";
+
+        opts.putComment(prefix + Dwarf.PROP_WEIGHT, " " + dwarf.name);
+        inject(opts, dwarf, prefix);
+        if (dwarf.name.equals(Dwarfs.PROP_DOPEY))
+        {
+            opts.put(prefix + Dwarf.PROP_WEIGHT, DwarfsData.OPT_DOPEY_WEIGHT, 0);
+            opts.put(prefix + Dwarf.PROP_HEIGHT, DwarfsData.OPT_DOPEY_HEIGHT, 0);
+        }
+        else if (dwarf.name.equals(Dwarfs.PROP_GRUMPY))
+        {
+            opts.put(prefix + Dwarf.PROP_HEIGHT, DwarfsData.OPT_GRUMPY_HEIGHT, 0);
+        }
+        else if (dwarf.name.equals(Dwarfs.PROP_SLEEPY))
+        {
+            opts.put(prefix + Dwarf.PROP_HEIGHT, DwarfsData.OPT_SLEEPY_HEIGHT, 0);
+        }
+        else if (dwarf.name.equals(Dwarfs.PROP_SNEEZY))
+        {
+            opts.put(prefix + Dwarf.PROP_HOME_PAGE, DwarfsData.OPT_SNEEZY_HOME_PAGE, 0);
+        }
+    }
+
+    public static void addDwarfs(Profile prof)
+    {
+        addDwarf(prof, DwarfsData.bashful);
+        addDwarf(prof, DwarfsData.doc);
+        addDwarf(prof, DwarfsData.dopey);
+        addDwarf(prof, DwarfsData.grumpy);
+        addDwarf(prof, DwarfsData.happy);
+        addDwarf(prof, DwarfsData.sleepy);
+        addDwarf(prof, DwarfsData.sneezy);
+    }
+
+    public static void assertEquals(Registry.Key exp, Registry.Key act)
+    {
+        Assert.assertNotNull(exp);
+        Assert.assertEquals(exp.size(), act.size());
+        for (String child : exp.childrenNames())
+        {
+            assertEquals(exp.getChild(child), act.getChild(child));
+        }
+
+        for (String name : exp.keySet())
+        {
+            Assert.assertEquals(exp.get(name), act.get(name));
+        }
+    }
+
+    public static void assertEquals(Dwarfs expected, Dwarfs actual)
+    {
+        assertEquals(expected.getBashful(), actual.getBashful());
+        assertEquals(expected.getDoc(), actual.getDoc());
+        assertEquals(expected.getDopey(), actual.getDopey());
+        assertEquals(expected.getGrumpy(), actual.getGrumpy());
+        assertEquals(expected.getHappy(), actual.getHappy());
+        assertEquals(expected.getSleepy(), actual.getSleepy());
+        assertEquals(expected.getSneezy(), actual.getSneezy());
+    }
+
+    public static void assertEquals(Dwarf expected, Dwarf actual)
+    {
+        Assert.assertEquals(expected.getAge(), actual.getAge());
+        Assert.assertEquals(expected.getHeight(), actual.getHeight(), DELTA);
+        Assert.assertEquals(expected.getWeight(), actual.getWeight(), DELTA);
+        Assert.assertEquals(expected.getHomePage().toString(), actual.getHomePage().toString());
+        Assert.assertEquals(expected.getHomeDir().toString(), actual.getHomeDir().toString());
+        Assert.assertEquals(expected.hasAge(), actual.hasAge());
+        Assert.assertEquals(expected.hasHeight(), actual.hasHeight());
+        Assert.assertEquals(expected.hasWeight(), actual.hasWeight());
+        Assert.assertEquals(expected.hasHomePage(), actual.hasHomePage());
+    }
+
+    public static Ini loadDwarfsIni() throws Exception
+    {
+        return new Ini(Helper.class.getClassLoader().getResourceAsStream(DWARFS_INI));
+    }
+
+    public static Ini loadDwarfsIni(Config config) throws Exception
+    {
+        Ini ini = new Ini();
+
+        ini.setConfig(config);
+        ini.load(Helper.class.getClassLoader().getResourceAsStream(DWARFS_INI));
+
+        return ini;
+    }
+
+    public static Options loadDwarfsOpt() throws Exception
+    {
+        return new Options(Helper.class.getClassLoader().getResourceAsStream(DWARFS_OPT));
+    }
+
+    public static Options loadDwarfsOpt(Config config) throws Exception
+    {
+        Options opt = new Options();
+
+        opt.setConfig(config);
+        opt.load(Helper.class.getClassLoader().getResourceAsStream(DWARFS_OPT));
+
+        return opt;
+    }
+
+    public static Reg loadDwarfsReg() throws Exception
+    {
+        return new Reg(Helper.class.getClassLoader().getResourceAsStream(DWARFS_REG));
+    }
+
+    public static Ini loadTaleIni() throws Exception
+    {
+        return new Ini(Helper.class.getClassLoader().getResourceAsStream(TALE_INI));
+    }
+
+    public static Ini loadTaleIni(Config config) throws Exception
+    {
+        Ini ini = new Ini();
+
+        ini.setConfig(config);
+        ini.load(Helper.class.getClassLoader().getResourceAsStream(TALE_INI));
+
+        return ini;
+    }
+
+    public static Ini newDwarfsIni()
+    {
+        Ini ini = new Ini();
+
+        ini.setComment(HEADER_COMMENT);
+        addDwarf(ini, DwarfsData.bashful);
+        addDwarf(ini, DwarfsData.doc);
+        addDwarf(ini, DwarfsData.dopey);
+        addDwarf(ini, DwarfsData.grumpy);
+        addDwarf(ini, DwarfsData.happy);
+        addDwarf(ini, DwarfsData.sleepy);
+        addDwarf(ini, DwarfsData.sneezy);
+
+        return ini;
+    }
+
+    public static Options newDwarfsOpt()
+    {
+        Options opts = new Options();
+
+        opts.setComment(HEADER_COMMENT);
+        addDwarf(opts, DwarfsData.dopey, false);
+        addDwarf(opts, DwarfsData.bashful);
+        addDwarf(opts, DwarfsData.doc);
+        addDwarf(opts, DwarfsData.dopey);
+        addDwarf(opts, DwarfsData.grumpy);
+        addDwarf(opts, DwarfsData.happy);
+        addDwarf(opts, DwarfsData.sleepy);
+        addDwarf(opts, DwarfsData.sneezy);
+
+        return opts;
+    }
+
+    public static Ini newTaleIni()
+    {
+        Ini ini = new Ini();
+
+        ini.setComment(HEADER_COMMENT);
+        ini.add(TaleData.PROP_DWARFS);
+        addDwarf(ini, TaleData.bashful);
+        addDwarf(ini, TaleData.doc);
+        addDwarf(ini, TaleData.dopey);
+        addDwarf(ini, TaleData.grumpy);
+        addDwarf(ini, TaleData.happy);
+        addDwarf(ini, TaleData.sleepy);
+        addDwarf(ini, TaleData.sneezy);
+
+        return ini;
+    }
+
+    public static void resetConfig() throws Exception
+    {
+        Properties props = System.getProperties();
+
+        for (String name : CONFIG_PROPERTIES)
+        {
+            props.remove(Config.KEY_PREFIX + name);
+        }
+
+        for (String name : FACTORY_PROPERTIES)
+        {
+            props.remove(name);
+        }
+    }
+
+    private static void inject(OptionMap map, Dwarf dwarf, String prefix)
+    {
+        map.put(prefix + Dwarf.PROP_WEIGHT, String.valueOf(dwarf.getWeight()));
+        map.put(prefix + Dwarf.PROP_HEIGHT, String.valueOf(dwarf.getHeight()));
+        map.put(prefix + Dwarf.PROP_AGE, String.valueOf(dwarf.getAge()));
+        map.put(prefix + Dwarf.PROP_HOME_PAGE, dwarf.getHomePage().toString());
+        map.put(prefix + Dwarf.PROP_HOME_DIR, dwarf.getHomeDir());
+        int[] numbers = dwarf.getFortuneNumber();
+
+        if ((numbers != null) && (numbers.length > 0))
+        {
+            for (int i = 0; i < numbers.length; i++)
+            {
+                map.add(prefix + Dwarf.PROP_FORTUNE_NUMBER, String.valueOf(numbers[i]));
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/ini4j/test/SampleRunnerTest.java b/src/test/java/org/ini4j/test/SampleRunnerTest.java
new file mode 100644
index 0000000..70210e6
--- /dev/null
+++ b/src/test/java/org/ini4j/test/SampleRunnerTest.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.test;
+
+import org.ini4j.sample.BeanEventSample;
+import org.ini4j.sample.BeanSample;
+import org.ini4j.sample.DumpSample;
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.DwarfBean;
+import org.ini4j.sample.Dwarfs;
+import org.ini4j.sample.DwarfsBean;
+import org.ini4j.sample.FromSample;
+import org.ini4j.sample.IniSample;
+import org.ini4j.sample.ListenerSample;
+import org.ini4j.sample.NoImportSample;
+import org.ini4j.sample.PyReadSample;
+import org.ini4j.sample.ReadPrimitiveSample;
+import org.ini4j.sample.ReadStringSample;
+import org.ini4j.sample.StreamSample;
+import org.ini4j.sample.ToSample;
+
+import org.ini4j.tutorial.BeanTutorial;
+import org.ini4j.tutorial.IniTutorial;
+import org.ini4j.tutorial.OneMinuteTutorial;
+import org.ini4j.tutorial.OptTutorial;
+import org.ini4j.tutorial.PrefsTutorial;
+import org.ini4j.tutorial.RegTutorial;
+import org.ini4j.tutorial.WindowsRegistryTutorial;
+
+import org.junit.Test;
+
+import org.junit.runner.RunWith;
+
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+
+import java.lang.reflect.Method;
+
+import java.nio.charset.Charset;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+ at RunWith(Parameterized.class)
+public class SampleRunnerTest
+{
+    private static final String DOC_PATH = "generated-site/apt";
+    private static final String JAVA_SUFFIX = ".java";
+    private static final String PACKAGE_INFO = "package-info" + JAVA_SUFFIX;
+    private static final String APT_SUFFIX = ".apt";
+    private static final String APT_INDEX = "index" + APT_SUFFIX;
+    private static final String CODE_BEGIN = "\n+----+\n";
+    private static final String CODE_END = "+----+\n\n";
+    private static File _documentDir;
+
+    static
+    {
+        System.setProperty("java.util.prefs.PreferencesFactory", "org.ini4j.IniPreferencesFactory");
+        _documentDir = new File(Helper.getBuildDirectory(), DOC_PATH);
+        _documentDir.mkdirs();
+        try
+        {
+            document(sourceFile(Dwarf.class), "//");
+            document(sourceFile(DwarfBean.class), "//");
+            document(sourceFile(Dwarfs.class), "//");
+            document(sourceFile(DwarfsBean.class), "//");
+            document(sourceFile(IniTutorial.class.getPackage()), "//");
+            document(sourceFile(IniSample.class.getPackage()), "//");
+        }
+        catch (Exception x)
+        {
+            throw new IllegalStateException(x);
+        }
+    }
+
+    private final Class _clazz;
+    private final File _sourceFile;
+
+    public SampleRunnerTest(Class sampleClass) throws Exception
+    {
+        _clazz = sampleClass;
+        _sourceFile = sourceFile(_clazz);
+    }
+
+    @Parameters public static Collection data()
+    {
+        return Arrays.asList(
+                new Object[][]
+                {
+
+                    // samples
+                    { ReadStringSample.class },
+                    { ReadPrimitiveSample.class },
+                    { IniSample.class },
+                    { StreamSample.class },
+                    { DumpSample.class },
+                    { NoImportSample.class },
+                    { ListenerSample.class },
+                    { BeanSample.class },
+                    { BeanEventSample.class },
+                    { FromSample.class },
+                    { ToSample.class },
+                    { PyReadSample.class },
+
+                    // tutorials
+                    { OneMinuteTutorial.class },
+                    { IniTutorial.class },
+                    { RegTutorial.class },
+                    { WindowsRegistryTutorial.class },
+                    { OptTutorial.class },
+                    { BeanTutorial.class },
+                    { PrefsTutorial.class },
+                });
+    }
+
+    @Test public void test() throws Exception
+    {
+        System.out.println("Executing " + _clazz.getName());
+        PrintStream saved = System.out;
+        File tmp = File.createTempFile(getClass().getSimpleName(), ".out");
+        PrintStream out = new PrintStream(new FileOutputStream(tmp));
+
+        System.setOut(out);
+        try
+        {
+            execute();
+        }
+        finally
+        {
+            System.setOut(saved);
+            out.flush();
+        }
+
+        document(_sourceFile, "//");
+        index(source2document(_sourceFile), source2index(_clazz));
+        if (tmp.length() > 0)
+        {
+            append(tmp);
+        }
+
+        tmp.delete();
+    }
+
+    private static void document(File src, String comment) throws Exception
+    {
+        Pattern docPattern = Pattern.compile(String.format("^\\s*%s\\|(.*)$", comment));
+        Pattern beginPattern = Pattern.compile(String.format("^\\s*%s\\{.*$", comment));
+        Pattern endPattern = Pattern.compile(String.format("^\\s*%s\\}.*$", comment));
+        LineNumberReader reader = new LineNumberReader(openReader(src));
+        PrintWriter writer = new PrintWriter(new FileWriter(source2document(src)));
+        boolean in = false;
+
+        for (String line = reader.readLine(); line != null; line = reader.readLine())
+        {
+            if (in)
+            {
+                if (endPattern.matcher(line).matches())
+                {
+                    in = false;
+                    writer.println(CODE_END);
+                }
+                else
+                {
+                    writer.println(line);
+                }
+            }
+            else
+            {
+                if (beginPattern.matcher(line).matches())
+                {
+                    in = true;
+                    writer.println(CODE_BEGIN);
+                }
+                else
+                {
+                    Matcher m = docPattern.matcher(line);
+
+                    if (m.matches())
+                    {
+                        writer.println(m.group(1));
+                    }
+                }
+            }
+        }
+
+        reader.close();
+        writer.close();
+    }
+
+    private static void index(File src, File dst) throws Exception
+    {
+        LineNumberReader reader = new LineNumberReader(new FileReader(src));
+        PrintWriter writer = new PrintWriter(new FileWriter(dst, true));
+        String name = src.getName().replace(".apt", ".html");
+        boolean h1 = false;
+        boolean p = false;
+
+        for (String line = reader.readLine(); line != null; line = reader.readLine())
+        {
+            if (line.length() == 0)
+            {
+                if (p)
+                {
+                    writer.println();
+
+                    break;
+                }
+                else if (h1)
+                {
+                    p = true;
+                }
+            }
+            else
+            {
+                if (Character.isSpaceChar(line.charAt(0)))
+                {
+                    if (p)
+                    {
+                        writer.println(line);
+                    }
+                }
+                else
+                {
+                    if (!h1)
+                    {
+                        h1 = true;
+                        writer.print(String.format(" *{{{%s}%s}}", name, line));
+                        writer.println();
+                        writer.println();
+                    }
+                }
+            }
+        }
+
+        writer.close();
+        reader.close();
+    }
+
+    private static Reader openReader(File src) throws Exception
+    {
+        InputStream stream = new FileInputStream(src);
+        byte[] head = new byte[2];
+        int n = stream.read(head);
+
+        stream.close();
+        Charset charset;
+
+        if ((n == 2) && (head[0] == -1) && (head[1] == -2))
+        {
+            charset = Charset.forName("UnicodeLittle");
+        }
+        else
+        {
+            charset = Charset.forName("UTF-8");
+        }
+
+        return new InputStreamReader(new FileInputStream(src), charset);
+    }
+
+    private static File source2document(File sourceFile) throws Exception
+    {
+        String name = sourceFile.getName();
+        File dir = new File(_documentDir, sourceFile.getParentFile().getName());
+
+        dir.mkdir();
+
+        return new File(dir, name.equals(PACKAGE_INFO) ? APT_INDEX : (name + APT_SUFFIX));
+    }
+
+    private static File source2index(Class clazz) throws Exception
+    {
+        return source2document(sourceFile(clazz.getPackage()));
+    }
+
+    private static File sourceFile(Class clazz) throws Exception
+    {
+        return Helper.getSourceFile(clazz.getName().replaceAll("\\.", "/") + JAVA_SUFFIX);
+    }
+
+    private static File sourceFile(Package pkg) throws Exception
+    {
+        return Helper.getSourceFile(pkg.getName().replaceAll("\\.", "/") + '/' + PACKAGE_INFO);
+    }
+
+    private void append(File stdout) throws Exception
+    {
+        PrintWriter writer = new PrintWriter(new FileWriter(source2document(_sourceFile), true));
+
+        writer.println("\n Standard output:\n");
+        writer.println(CODE_BEGIN);
+        LineNumberReader reader = new LineNumberReader(new FileReader(stdout));
+
+        for (String line = reader.readLine(); line != null; line = reader.readLine())
+        {
+            writer.println(line);
+        }
+
+        writer.println(CODE_END);
+        reader.close();
+        writer.close();
+    }
+
+    @SuppressWarnings("unchecked")
+    private void execute() throws Exception
+    {
+        Method main = _clazz.getMethod("main", String[].class);
+        String[] args;
+
+        try
+        {
+            File argument = new File(_sourceFile.getParentFile(), (String) _clazz.getField("FILENAME").get(null));
+
+            document(argument, "[#;!]");
+            args = new String[] { argument.getCanonicalPath() };
+        }
+        catch (NoSuchFieldException x)
+        {
+            args = new String[] {};
+        }
+
+        main.invoke(null, (Object) args);
+    }
+}
diff --git a/src/test/java/org/ini4j/test/TaleData.java b/src/test/java/org/ini4j/test/TaleData.java
new file mode 100644
index 0000000..75ad3b3
--- /dev/null
+++ b/src/test/java/org/ini4j/test/TaleData.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.test;
+
+import org.ini4j.test.DwarfsData.DwarfData;
+
+public final class TaleData
+{
+    public static final String PROP_DWARFS = "dwarfs";
+    public static final char PATH_SEPARATOR = '/';
+    public static final DwarfData bashful;
+    public static final DwarfData doc;
+    public static final DwarfData dopey;
+    public static final DwarfData grumpy;
+    public static final DwarfData happy;
+    public static final DwarfData sleepy;
+    public static final DwarfData sneezy;
+
+    static
+    {
+        bashful = newDwarfData(DwarfsData.bashful);
+        doc = newDwarfData(DwarfsData.doc);
+        dopey = newDwarfData(DwarfsData.dopey);
+        grumpy = newDwarfData(DwarfsData.grumpy);
+        happy = newDwarfData(DwarfsData.happy);
+        sleepy = newDwarfData(DwarfsData.sleepy);
+        sneezy = newDwarfData(DwarfsData.sneezy);
+    }
+
+    private TaleData()
+    {
+    }
+
+    private static DwarfData newDwarfData(DwarfData orig)
+    {
+        return new DwarfData(PROP_DWARFS + PATH_SEPARATOR + orig.name, orig.age, orig.fortuneNumber, orig.height, orig.homeDir, orig.homePage.toString(), orig.weight);
+    }
+}
diff --git a/src/org/ini4j/spi/Warnings.java b/src/test/java/org/ini4j/tutorial/AbstractTutorial.java
similarity index 65%
rename from src/org/ini4j/spi/Warnings.java
rename to src/test/java/org/ini4j/tutorial/AbstractTutorial.java
index 84de3d7..a138153 100644
--- a/src/org/ini4j/spi/Warnings.java
+++ b/src/test/java/org/ini4j/tutorial/AbstractTutorial.java
@@ -13,14 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.ini4j.spi;
+package org.ini4j.tutorial;
 
-public final class Warnings
+import java.io.File;
+
+public abstract class AbstractTutorial
 {
-    public static final String UNCHECKED = "unchecked";
+    public static final String FILENAME = "../sample/dwarfs.ini";
+
+    protected abstract void run(File arg) throws Exception;
 
-    private Warnings()
+    protected static File filearg(String[] args)
     {
-        assert true;
+        return new File((args.length > 0) ? args[0] : FILENAME);
     }
 }
diff --git a/src/test/java/org/ini4j/tutorial/BeanTutorial.java b/src/test/java/org/ini4j/tutorial/BeanTutorial.java
new file mode 100644
index 0000000..046a342
--- /dev/null
+++ b/src/test/java/org/ini4j/tutorial/BeanTutorial.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.tutorial;
+
+import org.ini4j.Config;
+import org.ini4j.Ini;
+import org.ini4j.Options;
+
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.DwarfBean;
+import org.ini4j.sample.Dwarfs;
+
+import org.ini4j.test.DwarfsData;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.net.URI;
+import java.net.URL;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                -------------
+//|                Bean Tutorial
+//|
+//|Bean Tutorial - Using your own API !
+//|
+//| Yes, it can be done! To access the contents of sections you can use any of
+//| your self-defined Java Beans compatible API.
+//| In order to do this you only have to create a Java Beans-style interface or class.
+//|
+//| Source code for beans: {{{../sample/Dwarf.java.html}Dwarf}},
+//| {{{../sample/DwarfBean.java.html}DwarfBean}}
+//|
+//| Code sniplets in this tutorial tested with the following .ini file:
+//| {{{../sample/dwarfs.ini.html}dwarfs.ini}}
+//|
+//</editor-fold>
+public class BeanTutorial extends AbstractTutorial
+{
+    public static void main(String[] args) throws Exception
+    {
+        new BeanTutorial().run(filearg(args));
+    }
+
+    @Override protected void run(File arg) throws Exception
+    {
+        Ini ini = new Ini(arg.toURI().toURL());
+
+        sample01(ini);
+        sample02(ini);
+        sample03(ini);
+        sample04(arg.toURI().toURL());
+        Options opts = new Options();
+
+        opts.putAll(ini.get(Dwarfs.PROP_BASHFUL));
+        sample05(opts);
+
+        //
+        File optFile = new File(arg.getParentFile(), OptTutorial.FILENAME);
+
+        sample06(optFile.toURI().toURL());
+    }
+
+//|
+//|* Accessing sections as beans
+//|
+//| While writing a program we usually know the type of the section's values,
+//| so we can define one or more java interfaces to access them. An advantage of
+//| this solution is that the programmer doesn't have to convert the values
+//| because they are converted automatically to the type defined in the
+//| interface.
+//|
+//| Ofcourse you may use setters as well, not just getters. In this way you can
+//| change values type safe way.
+//{
+    void sample01(Ini ini)
+    {
+        Ini.Section sec = ini.get("happy");
+        Dwarf happy = sec.as(Dwarf.class);
+        int age = happy.getAge();
+        URI homePage = happy.getHomePage();
+
+        happy.setWeight(45.55);
+
+//}
+//|
+//| The <<<happy instanceof Dwarf>>> relation is of course fulfilled in the
+//| example above.
+//|
+        assertEquals(DwarfsData.happy.homePage.toString(), homePage.toString());
+        assertEquals(DwarfsData.happy.age, age);
+        assertEquals(45.55, happy.getWeight(), 0.01);
+    }
+
+//|
+//|* Marshalling beans
+//|
+//| Sometimes we want to store existing java beans in text file. This operation
+//| usually called marshalling.
+//|
+//| With [ini4j] it is easy to store bean properties in a section. You simply
+//| create a section, and call the sections's <<<from()>>> method. Thats it.
+//{
+    void sample02(Ini ini)
+    {
+        DwarfBean sleepy = new DwarfBean();
+
+        sleepy.setAge(87);
+        sleepy.setWeight(44.3);
+        Ini.Section sec = ini.add("sleepy");
+
+        sec.from(sleepy);
+
+//}
+//|
+        assertTrue(sec.containsKey(Dwarf.PROP_AGE));
+        assertTrue(sec.containsKey(Dwarf.PROP_WEIGHT));
+    }
+
+//|
+//|* Unmarshalling beans
+//|
+//| If you have a marshalled bean in text file then you may want to read it
+//| into bean. This operation usually called unmarshalling.
+//|
+//| With [ini4j] it is easy to load bean properties from a section. You simply
+//| instantiate a bean, and call the sections's <<<to()>>> method. Thats it.
+//{
+    void sample03(Ini ini)
+    {
+        DwarfBean grumpy = new DwarfBean();
+
+        ini.get("grumpy").to(grumpy);
+
+//}
+//|
+        assertEquals(DwarfsData.grumpy.age, grumpy.getAge());
+        assertEquals(DwarfsData.grumpy.homeDir, grumpy.getHomeDir());
+    }
+
+//|
+//|* Indexed properties
+//|
+//| For handling indexed properties, you should allow mulpti option value
+//| handling in configuration. After enabling this feature, option may contains
+//| multiply values (multi line in file). These values can mapped to indexed
+//| bean property.
+//{
+    void sample04(URL location) throws IOException
+    {
+        Config cfg = new Config();
+
+        cfg.setMultiOption(true);
+        Ini ini = new Ini();
+
+        ini.setConfig(cfg);
+        ini.load(location);
+        Ini.Section sec = ini.get("sneezy");
+        Dwarf sneezy = sec.as(Dwarf.class);
+        int[] numbers = sneezy.getFortuneNumber();
+
+        //
+        // same as above but with unmarshalling...
+        //
+        DwarfBean sneezyBean = new DwarfBean();
+
+        sec.to(sneezyBean);
+        numbers = sneezyBean.getFortuneNumber();
+
+//}
+        assertArrayEquals(DwarfsData.sneezy.fortuneNumber, numbers);
+        assertEquals(DwarfsData.sneezy.fortuneNumber.length, sec.length("fortuneNumber"));
+        assertArrayEquals(DwarfsData.sneezy.fortuneNumber, sneezy.getFortuneNumber());
+        assertArrayEquals(DwarfsData.sneezy.fortuneNumber, sneezyBean.getFortuneNumber());
+    }
+
+//|
+//|* Options
+//|
+//| Not only Ini and Ini.Section has bean interface. There is a bean interface
+//| for OptionMap class and each derived class for example for Options.
+//| Options is an improved java.util.Properties replacement.
+//{
+    void sample05(Options opts)
+    {
+        Dwarf dwarf = opts.as(Dwarf.class);
+        int age = dwarf.getAge();
+
+        //
+        // same as above but with unmarshalling
+        //
+        DwarfBean dwarfBean = new DwarfBean();
+
+        opts.to(dwarfBean);
+        age = dwarfBean.getAge();
+
+//}
+//|
+//| In sample above the top level properties (like "age") mapped to bean
+//| properties.
+//|
+        assertEquals(DwarfsData.bashful.age, dwarf.getAge());
+        assertEquals(DwarfsData.bashful.age, dwarfBean.getAge());
+    }
+
+//|
+//|* Prefixed mapping
+//|
+//| Both Ini.Section and Options has possibility to add a prefix to property
+//| names while mapping from bean property name to Ini.Section or Options
+//| key.
+//{
+    void sample06(URL optPath) throws IOException
+    {
+        Options opt = new Options(optPath);
+        Dwarf dwarf = opt.as(Dwarf.class, "happy.");
+        DwarfBean bean = new DwarfBean();
+
+        opt.to(bean, "dopey.");
+
+//}
+//|
+//| In the above example, <<<dwarf>>> bean will contain properties starts with
+//| <<<happy.>>> while <<<bean>>> will contain properties starts with
+//| <<<dopey.>>>
+        assertEquals(DwarfsData.happy.age, dwarf.getAge());
+        assertEquals(DwarfsData.dopey.age, bean.getAge());
+    }
+//}
+}
diff --git a/src/test/java/org/ini4j/tutorial/IniTutorial.java b/src/test/java/org/ini4j/tutorial/IniTutorial.java
new file mode 100644
index 0000000..08e300f
--- /dev/null
+++ b/src/test/java/org/ini4j/tutorial/IniTutorial.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.tutorial;
+
+import org.ini4j.Ini;
+
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.Dwarfs;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+import java.util.Map;
+import java.util.Set;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                -------------
+//|                Ini Tutorial
+//|
+//|Ini Tutorial - How to use \[ini4j\] api
+//|
+//| This tutorial familiarize the reader with the usage of
+//| the [ini4j] library's natural interface.
+//|
+//| Code sniplets in this tutorial tested with the following .ini file:
+//| {{{../sample/dwarfs.ini.html}dwarfs.ini}}
+//|
+//</editor-fold>
+public class IniTutorial extends AbstractTutorial
+{
+    public static void main(String[] args) throws Exception
+    {
+        new IniTutorial().run(filearg(args));
+    }
+
+    @Override protected void run(File arg) throws Exception
+    {
+        Ini ini = new Ini(arg.toURI().toURL());
+
+        sample01(ini);
+        sample02(arg);
+        sample03(ini);
+        sample04(ini);
+    }
+
+//|* Data model
+//|
+//| Data model for .ini files is represented by org.ini4j.Ini class. This class
+//| implements Map\<String,Section\>. It mean you can access sections using
+//| java.util.Map collection API interface. The Section is also a map, which is
+//| implements Map\<String,String\>.
+//{
+    void sample01(Ini ini)
+    {
+        Ini.Section section = ini.get("happy");
+
+        //
+        // read some values
+        //
+        String age = section.get("age");
+        String weight = section.get("weight");
+        String homeDir = section.get("homeDir");
+
+        //
+        // .. or just use java.util.Map interface...
+        //
+        Map<String, String> map = ini.get("happy");
+
+        age = map.get("age");
+        weight = map.get("weight");
+        homeDir = map.get("homeDir");
+
+        // get all section names
+        Set<String> sectionNames = ini.keySet();
+
+//}
+        Helper.assertEquals(DwarfsData.happy, section.as(Dwarf.class));
+    }
+
+//|
+//|* Loading and storing data
+//|
+//| There is several way to load data into Ini object. It can be done by using
+//| <<<load>>> methods or overloaded constructors. Data can be load from
+//| InputStream, Reader, URL or File.
+//|
+//| You can store data using <<<store>>> methods. Data can store to OutputStream,
+//| Writer, or File.
+//{
+    void sample02(File file) throws IOException
+    {
+        Ini ini = new Ini();
+
+        ini.load(new FileReader(file));
+
+        //
+        // or instantiate and load data:
+        //
+        ini = new Ini(new FileReader(file));
+        File copy = File.createTempFile("sample", ".ini");
+
+        ini.store(copy);
+//}
+        ini = new Ini(copy);
+        Helper.assertEquals(DwarfsData.dwarfs, ini.as(Dwarfs.class));
+        copy.delete();
+    }
+
+//|
+//|* Macro/variable substitution
+//|
+//| To get a value, besides <<<get()>>> you can also
+//| use <<<fetch()>>> which resolves any occurrent $\{section/option\} format
+//| variable references in the needed value.
+//|
+//{
+    void sample03(Ini ini)
+    {
+        Ini.Section dopey = ini.get("dopey");
+
+        // get method doesn't resolve variable references
+        String weightRaw = dopey.get("weight");  // = ${bashful/weight}
+        String heightRaw = dopey.get("height");  // = ${doc/height}
+
+        // to resolve references, you should use fetch method
+        String weight = dopey.fetch("weight");  // = 45.7
+        String height = dopey.fetch("height");  // = 87.7
+
+//}
+//| Assuming we have an .ini file with the following sections:
+//|
+//|+--------------+
+//| [dopey]
+//| weight = ${bashful/weight}
+//| height = ${doc/height}
+//|
+//|[bashful]
+//| weight = 45.7
+//| height = 98.8
+//|
+//| [doc]
+//| weight = 49.5
+//| height = 87.7
+//|+--------------+
+//|
+        assertEquals(DwarfsData.INI_DOPEY_WEIGHT, weightRaw);
+        assertEquals(DwarfsData.INI_DOPEY_HEIGHT, heightRaw);
+        assertEquals(String.valueOf(DwarfsData.dopey.weight), weight);
+        assertEquals(String.valueOf(DwarfsData.dopey.height), height);
+    }
+
+//|
+//|* Multi values
+//|
+//| \[ini4j\] library introduces MultiMap interface, which is extends normal
+//| Map, but allows multiply values per keys. You can simply index values for
+//| a given key, similar to indexed properties in JavaBeans api.
+//|
+//{
+    void sample04(Ini ini)
+    {
+        Ini.Section sneezy = ini.get("sneezy");
+        String n1 = sneezy.get("fortuneNumber", 0);  // = 11
+        String n2 = sneezy.get("fortuneNumber", 1);  // = 22
+        String n3 = sneezy.get("fortuneNumber", 2);  // = 33
+        String n4 = sneezy.get("fortuneNumber", 3);  // = 44
+
+        // ok, lets do in it easier...
+        int[] n = sneezy.getAll("fortuneNumber", int[].class);
+//}
+        // #2817399
+
+        assertEquals("11", n1);
+        assertEquals("22", n2);
+        assertEquals("33", n3);
+        assertEquals("44", n4);
+        assertEquals(4, n.length);
+        assertEquals(11, n[0]);
+        assertEquals(22, n[1]);
+        assertEquals(33, n[2]);
+        assertEquals(44, n[3]);
+    }
+
+//|
+//|* Tree model
+//|
+//| Beyond two level map model, Ini class provides tree model. You can access
+//| Sections as tree. It means that section names becomes path names, with a
+//| path separator character ('/' and '\' on Wini and Reg).
+//|
+//{
+    void sample05()
+    {
+        Ini ini = new Ini();
+
+        // lets add a section, it will create needed intermediate sections as well
+        ini.add("root/child/sub");
+
+        //
+        Ini.Section root;
+        Ini.Section sec;
+
+        root = ini.get("root");
+        sec = root.getChild("child").getChild("sub");
+
+        // or...
+        sec = root.lookup("child", "sub");
+
+        // or...
+        sec = root.lookup("child/sub");
+
+        // or even...
+        sec = ini.get("root/child/sub");
+
+//}
+//| If you are using Wini instead of Ini class, the path separator become '\'.
+//|
+        assertNotNull(root.lookup("child", "sub"));
+        assertNotNull(ini.get("root/child"));
+    }
+}
diff --git a/src/test/java/org/ini4j/tutorial/OneMinuteTutorial.java b/src/test/java/org/ini4j/tutorial/OneMinuteTutorial.java
new file mode 100644
index 0000000..da6cb80
--- /dev/null
+++ b/src/test/java/org/ini4j/tutorial/OneMinuteTutorial.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.tutorial;
+
+import org.ini4j.Wini;
+
+import org.ini4j.sample.Dwarf;
+import org.ini4j.sample.Dwarfs;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                -------------------
+//|                One minute Tutorial
+//|
+//|One minute Tutorial - First step
+//|
+//| First step with \[ini4j\] library. No data model, no interfaces, no design
+//| patterns, simply read and write windows .ini files.
+//|
+//</editor-fold>
+public class OneMinuteTutorial extends AbstractTutorial
+{
+    public static void main(String[] args) throws Exception
+    {
+        new OneMinuteTutorial().run(filearg(args));
+    }
+
+    protected void copy(File inputFile, File outputFile) throws IOException
+    {
+        FileInputStream is = new FileInputStream(inputFile);
+        FileOutputStream os = new FileOutputStream(outputFile);
+        byte[] buff = new byte[8192];
+        int n;
+
+        while ((n = is.read(buff)) > 0)
+        {
+            os.write(buff, 0, n);
+        }
+
+        is.close();
+        os.close();
+    }
+
+    @Override protected void run(File arg) throws Exception
+    {
+        File file = File.createTempFile("tutorial", ".ini");
+
+        file.deleteOnExit();
+        copy(arg, file);
+        sample01(file.getCanonicalPath());
+        sample02(file.getCanonicalPath());
+    }
+
+//|
+//| Lets read some value from .ini file...
+//|
+//{
+    void sample01(String filename) throws IOException
+    {
+        Wini ini = new Wini(new File(filename));
+        int age = ini.get("happy", "age", int.class);
+        double height = ini.get("happy", "height", double.class);
+        String dir = ini.get("happy", "homeDir");
+
+//}
+//| ... assuming there is a section with name <<<happy>>>, which contains at least
+//| the following options: <<<age>>>, <<<height>>> and <<<homeDir>>>, something like
+//| this:
+//|
+//|+---------+
+//| [happy]
+//| age = 99
+//| height = 77.66
+//| homeDir = /home/happy
+//|+---------+
+//|
+//|
+        assertEquals(DwarfsData.happy.age, age);
+        assertEquals(DwarfsData.happy.height, height, Helper.DELTA);
+        assertEquals(DwarfsData.happy.homeDir, dir);
+    }
+
+//| Now let see how to write values....
+//|
+//{
+    void sample02(String filename) throws IOException
+    {
+        Wini ini = new Wini(new File(filename));
+
+        ini.put("sleepy", "age", 55);
+        ini.put("sleepy", "weight", 45.6);
+        ini.store();
+
+//}
+//| ... and then file will have a section <<<sleepy>>> and this section
+//| will contains at least two options: <<<age>>> with value <<<55>>> and <<<weight>>>
+//| with value <<<45.6>>>, something like this:
+//|
+//|+---------+
+//| [sleepy]
+//| age = 55
+//| weight = 45.6
+//|+---------+
+//|
+        assertEquals(55, (int) ini.get(Dwarfs.PROP_SLEEPY, Dwarf.PROP_AGE, int.class));
+        assertEquals(45.6, (double) ini.get(Dwarfs.PROP_SLEEPY, Dwarf.PROP_WEIGHT, double.class), Helper.DELTA);
+    }
+
+//|
+//| If you want to know more about this library, read
+//| {{{../tutorial/index.html}tutorials}}
+}
diff --git a/src/test/java/org/ini4j/tutorial/OptTutorial.java b/src/test/java/org/ini4j/tutorial/OptTutorial.java
new file mode 100644
index 0000000..b0e9d05
--- /dev/null
+++ b/src/test/java/org/ini4j/tutorial/OptTutorial.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.tutorial;
+
+import org.ini4j.Options;
+
+import org.ini4j.sample.Dwarf;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+import java.util.Set;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                ----------------
+//|                Options Tutorial
+//|
+//|Options Tutorial - java.util.Properties replacement
+//|
+//| Options (org.ini4j.Options) is a java.util.Properties replacement with
+//| several useful features, like:
+//|
+//|  * variable/macro substitution. You may refer to other property's value with
+//|  $\{NAME\} expression, where NAME is the name of the referred property.
+//|  ofcourse you can use more than one property reference per property, and
+//|  you can mix constant text and property references:
+//|
+//|+-------------------+
+//|player.name = Joe
+//|player.greetings = Hi ${player.name}!
+//|player.domain = foo.bar
+//|player.email = ${player.name}@${player.domain}
+//|+-------------------+
+//|
+//|  * multiply property values. You can refer to multi value properties with
+//| integer indexes. Ofcource it is also works in macro/variable substitutions:
+//| $\{user.fortuneNumber\[2\]\}
+//|
+//|+-------------------+
+//|player.fortuneNumber = 33
+//|player.fortuneNumber = 44
+//|player.fortuneNumber = 55
+//|player.fortuneNumber = 66
+//|
+//|magicNumber = ${player.foruneNumber[1]}
+//|+--------------------+
+//|
+//|  The magicNumber property will have value: <<<44>>>
+//|
+//|  * as Java class, Options is basicly map of Strings indexed with Strings. It
+//|  is standard Collection API (ok, it is a bit enhanced to deal with multi
+//|  values, but in general it is a Map\<String,String\>).
+//|
+//|  * Java Beans api. You can read/write properties in type safe way. To do it
+//|  you just define an interface, call Options#as() method. This method will
+//|  provide an implementation of given interface on top of Options. Property
+//|  types are mapped automatically between Java type and String.
+//|
+//|* Why need Options
+//|
+//| With standard Properties class there is several small problem. Most of them
+//| came from backward compatibility.
+//|
+//|  * not implements Map\<String,String\>, but Map\<Object,Object\>. If you
+//|    want to use Collections api, it is a bit unconfortable.
+//|
+//|  * only single property values allowed. Probably you already see ugly
+//|    workarounds: index number in property names, like: file.1, file.2 ...
+//|
+//|  * no macro/variable substitution. In some environment, like
+//|    Apache Ant, you can use ${name} like references, but with standard
+//|    java.util.Properties you can't.
+//|
+//| As side effect of \[ini4j\] development, there is a solution for aboves.
+//| This is the org.ini4j.Options class, which is basicly a feature rich
+//| replacement for java.util.Properties.
+//|
+//| Code sniplets in this tutorial tested with the following .opt file:
+//| {{{../sample/dwarfs.opt.html}dwarfs.opt}}
+//|
+//</editor-fold>
+public class OptTutorial extends AbstractTutorial
+{
+    public static final String FILENAME = "../sample/dwarfs.opt";
+
+    public static void main(String[] args) throws Exception
+    {
+        new OptTutorial().run(filearg(args));
+    }
+
+    protected void run(File arg) throws Exception
+    {
+        Options opt = new Options(arg.toURI().toURL());
+
+        sample01(arg);
+        sample02(opt);
+    }
+
+//|
+//|* Instantiating
+//|
+//| There is nothing special with instantiating Options object, but there is a
+//| few constructor, to simplify loading data. These constructors simply call
+//| the <<<load()>>> method on newly created instance. Ofcource these
+//| constructors are throws IOException.
+//{
+    void sample01(File file) throws IOException
+    {
+        Options opt = new Options();
+
+        //
+        // or instantiate and load data:
+        //
+        opt = new Options(new FileReader(file));
+
+//}
+        assertFalse(opt.keySet().isEmpty());
+    }
+
+//|
+//|* Map of String
+//{
+    void sample02(Options opt)
+    {
+        Set<String> optionNames = opt.keySet();
+
+        //
+        String age = opt.get("age");
+        String weight = opt.fetch("weight");
+        String height = opt.fetch("height");
+
+//}
+//|
+//| The Options is a MultiMap\<String,String\>, that is, a map that assigns
+//| String values to String keys. So the <<<get>>> method is used to get values
+//| inside the options. To get a value, besides <<<get()>>> you can also
+//| use <<<fetch()>>> which resolves any occurrent $\{option\} format
+//| variable references in the needed value.
+        Helper.assertEquals(DwarfsData.dopey, opt.as(Dwarf.class));
+    }
+}
diff --git a/src/test/java/org/ini4j/tutorial/OptionMapTutorial.java b/src/test/java/org/ini4j/tutorial/OptionMapTutorial.java
new file mode 100644
index 0000000..21fce1b
--- /dev/null
+++ b/src/test/java/org/ini4j/tutorial/OptionMapTutorial.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.tutorial;
+
+import org.ini4j.Ini;
+
+import org.ini4j.sample.Dwarfs;
+
+import org.ini4j.test.DwarfsData;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+
+import java.util.Set;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                -------------
+//|                OptionMap Tutorial
+//|
+//|OptionMap Tutorial - more than just String,String map
+//|
+//| Option is a name/value pair stored in OptionMap. But OptionMap adds a lot of
+//| usefull data access methods than simple get. OptionMap is base interface for
+//| both Ini.Section, Registry.Key and Options classes, so this tutorial will
+//| usefull for all of these.
+//|
+//| So in samples bellow you can use either Ini.Section, Registry.Key or Options
+//| classes instead of OptionMap interface, because these classes implements
+//| OptionMap.
+//|
+//| Code sniplets in this tutorial tested with the following files:
+//| {{{../sample/dwarfs.ini.html}dwarfs.ini}}
+//| {{{../sample/dwarfs.opt.html}dwarfs.opt}}
+//|
+//</editor-fold>
+public class OptionMapTutorial extends AbstractTutorial
+{
+    public static void main(String[] args) throws Exception
+    {
+        new OptionMapTutorial().run(filearg(args));
+    }
+
+    @Override protected void run(File arg) throws Exception
+    {
+        Ini ini = new Ini(arg.toURI().toURL());
+
+        sample01(ini.get(Dwarfs.PROP_HAPPY));
+        sample03(ini);
+        sample04(ini);
+    }
+
+//|* Data model
+//|
+//| OptionMap implements Map\<String,String\>, so you can access options using
+//| standard collection api.
+//{
+    void sample01(Ini.Section section)
+    {
+
+        //
+        // read some values
+        //
+        String age = section.get("age");
+        String weight = section.get("weight");
+        String homeDir = section.get("homeDir");
+
+        // get all option names
+        Set<String> optionNames = section.keySet();
+
+//}
+        assertEquals(String.valueOf(DwarfsData.happy.age), age);
+        assertEquals(String.valueOf(DwarfsData.happy.weight), weight);
+        assertEquals(String.valueOf(DwarfsData.happy.homeDir), homeDir);
+    }
+
+//|
+//|* Macro/variable substitution
+//|
+//| To get a value, besides <<<get()>>> you can also
+//| use <<<fetch()>>> which resolves any occurrent $\{section/option\} format
+//| variable references in the needed value.
+//|
+//{
+    void sample03(Ini ini)
+    {
+        Ini.Section dopey = ini.get("dopey");
+
+        // get method doesn't resolve variable references
+        String weightRaw = dopey.get("weight");  // = ${bashful/weight}
+        String heightRaw = dopey.get("height");  // = ${doc/height}
+
+        // to resolve references, you should use fetch method
+        String weight = dopey.fetch("weight");  // = 45.7
+        String height = dopey.fetch("height");  // = 87.7
+
+//}
+//| Assuming we have an .ini file with the following sections:
+//|
+//|+--------------+
+//| [dopey]
+//| weight = ${bashful/weight}
+//| height = ${doc/height}
+//|
+//|[bashful]
+//| weight = 45.7
+//| height = 98.8
+//|
+//| [doc]
+//| weight = 49.5
+//| height = 87.7
+//|+--------------+
+//|
+        assertEquals(DwarfsData.INI_DOPEY_WEIGHT, weightRaw);
+        assertEquals(DwarfsData.INI_DOPEY_HEIGHT, heightRaw);
+        assertEquals(String.valueOf(DwarfsData.dopey.weight), weight);
+        assertEquals(String.valueOf(DwarfsData.dopey.height), height);
+    }
+
+//|
+//|* Multi values
+//|
+//| \[ini4j\] library introduces MultiMap interface, which is extends normal
+//| Map, but allows multiply values per keys. You can simply index values for
+//| a given key, similar to indexed properties in JavaBeans api.
+//|
+//{
+    void sample04(Ini ini)
+    {
+        Ini.Section sneezy = ini.get("sneezy");
+        String n1 = sneezy.get("fortuneNumber", 0);  // = 11
+        String n2 = sneezy.get("fortuneNumber", 1);  // = 22
+        String n3 = sneezy.get("fortuneNumber", 2);  // = 33
+        String n4 = sneezy.get("fortuneNumber", 3);  // = 44
+
+        // ok, lets do in it easier...
+        int[] n = sneezy.get("fortuneNumber", int[].class);
+//}
+    }
+}
diff --git a/src/test/java/org/ini4j/tutorial/PrefsTutorial.java b/src/test/java/org/ini4j/tutorial/PrefsTutorial.java
new file mode 100644
index 0000000..5931b8e
--- /dev/null
+++ b/src/test/java/org/ini4j/tutorial/PrefsTutorial.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.tutorial;
+
+import org.ini4j.Ini;
+import org.ini4j.IniPreferences;
+
+import org.ini4j.test.DwarfsData;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.util.prefs.Preferences;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                -------------
+//|                Preferences Tutorial
+//|
+//|Preferences Tutorial
+//|
+//| The purpose of this document is to familiarize the reader with the usage of
+//| the [ini4j] library's Preferences interface. Each chapter contains all the
+//| necessary code portions and explanation for a given function.
+//|
+//| Code sniplets in this tutorial tested with the following .ini file:
+//| {{{../sample/dwarfs.ini.html}dwarfs.ini}}
+//|
+//| As soon as the Preferences object is created it functions as a standard Preferences node, and should be
+//| used as such. Implicitly only new nodes can be created in the root node (these will be the sections).
+//| In the first level nodes (sections) only values can be created (these will be the options).
+//|
+//| In the case of an invalid operation an <<<UnsupportedOperationException>>> type runtime exception is generated.
+//| This happens if we try to set a value at the root node or to create a node on the second level,
+//| since these operations cannot be interpreted on the whole .ini file under Preferences.
+//|
+//</editor-fold>
+public class PrefsTutorial extends AbstractTutorial
+{
+    public static void main(String[] args) throws Exception
+    {
+        new PrefsTutorial().run(filearg(args));
+    }
+
+    protected void run(File arg) throws Exception
+    {
+        Ini ini = new Ini(arg.toURI().toURL());
+
+        sample01(ini);
+    }
+
+//|
+//|* Reading and writing values
+//|
+//| Values can read and write like any other Preferences node, there is no
+//| differences.
+//{
+    void sample01(Ini ini) throws IOException
+    {
+        Preferences prefs = new IniPreferences(ini);
+        Preferences bashful = prefs.node("bashful");
+        String home = bashful.get("homeDir", "/home");
+        int age = bashful.getInt("age", -1);
+
+        bashful.putDouble("weight", 55.6);
+
+//}
+        assertEquals(DwarfsData.bashful.homeDir, bashful.get("homeDir", null));
+        assertEquals(DwarfsData.bashful.age, bashful.getInt("age", -1));
+        assertEquals(55.6, bashful.getDouble("weight", -1), 0.001);
+    }
+}
diff --git a/src/test/java/org/ini4j/tutorial/RegTutorial.java b/src/test/java/org/ini4j/tutorial/RegTutorial.java
new file mode 100644
index 0000000..4703b17
--- /dev/null
+++ b/src/test/java/org/ini4j/tutorial/RegTutorial.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.tutorial;
+
+import org.ini4j.Reg;
+
+import org.ini4j.sample.Dwarf;
+
+import org.ini4j.test.DwarfsData;
+import org.ini4j.test.Helper;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.net.URI;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                -------------
+//|                Reg Tutorial
+//|
+//|Reg Tutorial - Windows .REG file handling
+//|
+//| Windows regedit commands .REG file format is very close to .ini format.
+//| \[ini4j\] provides org.ini4j.Reg class to model .REG format. This tutorial
+//| show the differences between Ini and Reg classes.
+//|
+//| Code sniplets in this tutorial tested with the following .reg file:
+//| {{{../sample/dwarfs.reg.html}dwarfs.reg}}
+//|
+//</editor-fold>
+public class RegTutorial extends AbstractTutorial
+{
+    public static final String FILENAME = "../sample/dwarfs.reg";
+
+    public static void main(String[] args) throws Exception
+    {
+        new RegTutorial().run(filearg(args));
+    }
+
+    @Override protected void run(File arg) throws Exception
+    {
+        Reg reg = new Reg(arg.toURI().toURL());
+
+        sample01(arg);
+        sample02();
+    }
+
+//|
+//|* Loading and storing
+//|
+//| There is nothing special with loading and storing data, it works exactly same
+//| as in Ini class. But while loading data, Reg class will strip .REG special
+//| values (double qoute around strings, type data from option, etc). So after
+//| loading a .REG file, you can use it exactly same way as Ini class. Ofcource
+//| if you store Reg class, it will put all above meta information int file, so
+//| the result will be a valid .REG file. You don't need to worry about file
+//| encoding, version in first line, etc,etc.
+//|
+//| Assume you have a .REG file, with the following section/key:
+//|
+//|+---+
+//|[HKEY_CURRENT_USER\Software\ini4j-test\dwarfs\bashful]
+//|@="bashful"
+//|"weight"=hex(2):34,00,35,00,2e,00,37,00,00,00
+//|"height"="98.8"
+//|"age"=dword:00000043
+//|"homePage"="http://snowwhite.tale/~bashful"
+//|"homeDir"="/home/bashful"
+//|+---+
+//|
+//| As you see, "weight" and "age" is not simlpe strings. The "height" is a REG_DWORD
+//| type while "weight" is REG_EXPAND_SZ. Don't worry, Reg class take care about
+//| type conversion, you will access these as with regular .ini files:
+//{
+    void sample01(File file) throws IOException
+    {
+        Reg reg = new Reg(file);
+        Reg.Key hive = reg.get(Reg.Hive.HKEY_CURRENT_USER.toString());
+        Reg.Key bashful;
+
+        bashful = hive.lookup("Software", "ini4j-test", "dwarfs", "bashful");
+
+        // or ...
+        bashful = hive.lookup("Software\\ini4j-test\\dwarfs\\bashful");
+
+        // or even...
+        bashful = reg.get("HKEY_CURRENT_USER\\Software\\ini4j-test\\dwarfs\\bashful");
+
+        // read some data
+        double weight = bashful.get("weight", double.class);  // = 45.7
+        double height = bashful.get("height", double.class);  // = 98.8
+        int age = bashful.get("age", int.class);  // = 67
+        URI homePage = bashful.get("homePage", URI.class);  // = new URI("http://snowwhite.tale/~bashful");
+        String homeDir = bashful.get("homeDir");  // = "/home/bashful"
+
+//}
+        assertNotNull(reg.get(Helper.DWARFS_REG_PATH + "\\dwarfs"));
+        Helper.assertEquals(DwarfsData.bashful, bashful.as(Dwarf.class));
+    }
+
+//|
+//|* Types
+//|
+//| When you load data into Reg class, it will preserve meta informations, such as
+//| type informations. If you create new values, by default these will have
+//| tpye REG_SZ. Ofcource you may specify type information for values.
+//{
+    void sample02()
+    {
+        Reg reg = new Reg();
+        Reg.Key key = reg.add("HKEY_CURRENT_USER\\Software\\ini4j-test\\dwarfs\\sleepy");
+
+        key.put("fortuneNumber", 99);
+        key.putType("fortuneNumber", Reg.Type.REG_MULTI_SZ);
+
+//}
+//|
+//| If you store reg object above, it will contains a section similar to this:
+//|
+//|+---+
+//|[HKEY_CURRENT_USER\Software\ini4j-test\dwarfs\sleepy]
+//|"fortuneNumber"=hex(7):39,00,39,00,00,00,00,00
+//|+---+
+//|
+    }
+}
diff --git a/src/test/java/org/ini4j/tutorial/WindowsRegistryTutorial.java b/src/test/java/org/ini4j/tutorial/WindowsRegistryTutorial.java
new file mode 100644
index 0000000..95b9eda
--- /dev/null
+++ b/src/test/java/org/ini4j/tutorial/WindowsRegistryTutorial.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2005,2009 Ivan SZKIBA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.ini4j.tutorial;
+
+import org.ini4j.Reg;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.IOException;
+
+//<editor-fold defaultstate="collapsed" desc="apt documentation">
+//|
+//|                -------------------------
+//|                Windows Registry Tutorial
+//|
+//|Windows Registry Tutorial - Read/Write windows registry
+//|
+//| Yes, it is possible now to read/write registry from java programs
+//| without native (JNI) code !
+//|
+//</editor-fold>
+public class WindowsRegistryTutorial extends AbstractTutorial
+{
+    public static final String FILENAME = "../sample/dwarfs.reg";
+
+    public static void main(String[] args) throws Exception
+    {
+        if (Reg.isWindows())
+        {
+            new WindowsRegistryTutorial().run(filearg(args));
+        }
+    }
+
+    @Override protected void run(File arg) throws Exception
+    {
+        sample01();
+        sample02();
+        sample03();
+    }
+
+//|
+//|* Write
+//|
+//| Lets write something to registry
+//{
+    void sample01() throws IOException
+    {
+        Reg reg = new Reg();
+        Reg.Key key = reg.add("HKEY_CURRENT_USER\\hello");
+
+        key.put("world", "Hello World !");
+        reg.write();
+//}
+//| This code will create a "hello" key in HKEY_CURRENT_USER hive, and
+//| put "Hello World !" with name "world".
+    }
+
+//|
+//|* Read
+//|
+//| Lets read something from Control Panel settings...
+//{
+    void sample02() throws IOException
+    {
+        Reg reg = new Reg("HKEY_CURRENT_USER\\Control Panel");
+        Reg.Key cp = reg.get("HKEY_CURRENT_USER\\Control Panel");
+        Reg.Key sound = cp.getChild("Sound");
+        String beep = sound.get("Beep");
+
+//}
+//|
+    }
+
+//|
+//|* Create environment variable
+//|
+//| Lets create a new environment variable under current users environment....
+//{
+    void sample03() throws IOException
+    {
+        Reg reg = new Reg();
+        Reg.Key env = reg.add("HKEY_CURRENT_USER\\Environment");
+
+        env.put("SAMPLE_HOME", "c:\\sample");
+        reg.write();
+//}
+//| Thats it ! Now your environment contains variable SAMPLE_HOME ! Unfortunetly
+//| you have to restart Windows to see this variable.... but hey, we crated new
+//| environment variable from java without any native code !
+    }
+}
diff --git a/src/org/ini4j/IniHandler.java b/src/test/java/org/ini4j/tutorial/package-info.java
similarity index 62%
rename from src/org/ini4j/IniHandler.java
rename to src/test/java/org/ini4j/tutorial/package-info.java
index 9354f5c..c56d20a 100644
--- a/src/org/ini4j/IniHandler.java
+++ b/src/test/java/org/ini4j/tutorial/package-info.java
@@ -13,15 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.ini4j;
-
-public interface IniHandler extends OptionHandler
-{
-    void endIni();
-
-    void endSection();
-
-    void startIni();
-
-    void startSection(String sectionName);
-}
+package org.ini4j.tutorial;
+//|
+//|                ---------
+//|                Tutorials
+//|
+//|Tutorials
+//|
+//| The following tutorials organized on different tpye of API that ini4j provide.
+//| The build process of [ini4j] guarantees compilability and runability, since no
+//| distribution may be created without compiling and running the tutorial codes.
+//|
diff --git a/src/test/resources/META-INF/services/org.ini4j.BadConfig b/src/test/resources/META-INF/services/org.ini4j.BadConfig
new file mode 100644
index 0000000..3a39c60
--- /dev/null
+++ b/src/test/resources/META-INF/services/org.ini4j.BadConfig
@@ -0,0 +1,2 @@
+
+org.ini4j.IniParser
diff --git a/src/test/resources/META-INF/services/org.ini4j.Dummy b/src/test/resources/META-INF/services/org.ini4j.Dummy
new file mode 100644
index 0000000..afcde62
--- /dev/null
+++ b/src/test/resources/META-INF/services/org.ini4j.Dummy
@@ -0,0 +1 @@
+DummyImpl
\ No newline at end of file
diff --git a/src/test/resources/META-INF/services/org.ini4j.EmptyConfig b/src/test/resources/META-INF/services/org.ini4j.EmptyConfig
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/resources/org/ini4j/addon/dwarfs-py-bad.ini b/src/test/resources/org/ini4j/addon/dwarfs-py-bad.ini
new file mode 100644
index 0000000..aeb41cf
--- /dev/null
+++ b/src/test/resources/org/ini4j/addon/dwarfs-py-bad.ini
@@ -0,0 +1,18 @@
+;
+; Copyright 2005,2009 Ivan SZKIBA
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+;      http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+;
+; Bashful
+[bashful
+age=3
diff --git a/src/test/resources/org/ini4j/addon/dwarfs-py.ini b/src/test/resources/org/ini4j/addon/dwarfs-py.ini
new file mode 100644
index 0000000..8e4731a
--- /dev/null
+++ b/src/test/resources/org/ini4j/addon/dwarfs-py.ini
@@ -0,0 +1,82 @@
+;
+; Copyright 2005,2009 Ivan SZKIBA
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+;      http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+;
+; Bashful
+[bashful]
+weight = 45.7
+height = 98.8
+age = 67
+homePage = http://snowwhite.tale/~bashful
+homeDir = /home/bashful
+
+; Doc
+[doc]
+weight = 49.5
+height = 87.7
+age = 63
+homePage = http://doc.dwarfs
+homeDir = c:Documents and Settingsdoc
+
+; Dopey
+[dopey]
+weight = %(_weight)
+height = %(_height)
+age = 23
+homePage = http://dopey.snowwhite.tale/
+homeDir = c:\Documents and Settings\dopey
+
+; Grumpy
+[grumpy]
+weight = 65.3
+height = %(_height)
+age = 76
+homePage = http://snowwhite.tale/~grumpy/
+homeDir = /home/grumpy
+
+; Happy
+[happy]
+weight = 56.4
+height = 77.66
+age = 99
+homePage = dummy
+homeDir = /home/happy
+
+; Sleepy
+[sleepy]
+name=sleepy
+weight = 76.11
+height = %(_height)8
+age = 121
+homePage = http://snowwhite.tale/~%(name)
+homeDir = /home/%(name)
+
+; Sneezy
+[sneezy]
+weight = 69.7
+height = 76.88
+age = 64
+homePage = %(_site)/~sneezy
+homeDir = /home/sneezy
+
+; Happy again
+[happy]
+homePage = http://happy.smurf
+
+
+[default]
+_site = http://happy.smurf
+_age = 99
+_weight = 45.7
+_height = 87.7
diff --git a/src/test/resources/org/ini4j/mozilla.reg b/src/test/resources/org/ini4j/mozilla.reg
new file mode 100644
index 0000000..2faf19a
Binary files /dev/null and b/src/test/resources/org/ini4j/mozilla.reg differ
diff --git a/src/test/resources/org/ini4j/spi/UTF-16BE-BOM.ini b/src/test/resources/org/ini4j/spi/UTF-16BE-BOM.ini
new file mode 100644
index 0000000..c8439fb
Binary files /dev/null and b/src/test/resources/org/ini4j/spi/UTF-16BE-BOM.ini differ
diff --git a/src/test/resources/org/ini4j/spi/UTF-16BE.ini b/src/test/resources/org/ini4j/spi/UTF-16BE.ini
new file mode 100644
index 0000000..f4d27ff
Binary files /dev/null and b/src/test/resources/org/ini4j/spi/UTF-16BE.ini differ
diff --git a/src/test/resources/org/ini4j/spi/UTF-16LE-BOM.ini b/src/test/resources/org/ini4j/spi/UTF-16LE-BOM.ini
new file mode 100644
index 0000000..ae5d2fd
Binary files /dev/null and b/src/test/resources/org/ini4j/spi/UTF-16LE-BOM.ini differ
diff --git a/src/test/resources/org/ini4j/spi/UTF-16LE.ini b/src/test/resources/org/ini4j/spi/UTF-16LE.ini
new file mode 100644
index 0000000..c1e0dad
Binary files /dev/null and b/src/test/resources/org/ini4j/spi/UTF-16LE.ini differ
diff --git a/src/test/resources/org/ini4j/spi/UTF-32BE-BOM.ini b/src/test/resources/org/ini4j/spi/UTF-32BE-BOM.ini
new file mode 100644
index 0000000..2643352
Binary files /dev/null and b/src/test/resources/org/ini4j/spi/UTF-32BE-BOM.ini differ
diff --git a/src/test/resources/org/ini4j/spi/UTF-32BE.ini b/src/test/resources/org/ini4j/spi/UTF-32BE.ini
new file mode 100644
index 0000000..c47d269
Binary files /dev/null and b/src/test/resources/org/ini4j/spi/UTF-32BE.ini differ
diff --git a/src/test/resources/org/ini4j/spi/UTF-32LE-BOM.ini b/src/test/resources/org/ini4j/spi/UTF-32LE-BOM.ini
new file mode 100644
index 0000000..87ccfa5
Binary files /dev/null and b/src/test/resources/org/ini4j/spi/UTF-32LE-BOM.ini differ
diff --git a/src/test/resources/org/ini4j/spi/UTF-32LE.ini b/src/test/resources/org/ini4j/spi/UTF-32LE.ini
new file mode 100644
index 0000000..d95a55f
Binary files /dev/null and b/src/test/resources/org/ini4j/spi/UTF-32LE.ini differ
diff --git a/src/test/resources/org/ini4j/spi/UTF-8-BOM.ini b/src/test/resources/org/ini4j/spi/UTF-8-BOM.ini
new file mode 100644
index 0000000..cafea01
--- /dev/null
+++ b/src/test/resources/org/ini4j/spi/UTF-8-BOM.ini
@@ -0,0 +1,2 @@
+[section]
+option = value
diff --git a/src/test/resources/org/ini4j/spi/UTF-8.ini b/src/test/resources/org/ini4j/spi/UTF-8.ini
new file mode 100644
index 0000000..b548137
--- /dev/null
+++ b/src/test/resources/org/ini4j/spi/UTF-8.ini
@@ -0,0 +1,2 @@
+[section]
+option = value
diff --git a/src/test/resources/org/ini4j/spi/include.txt b/src/test/resources/org/ini4j/spi/include.txt
new file mode 100644
index 0000000..1ab3197
--- /dev/null
+++ b/src/test/resources/org/ini4j/spi/include.txt
@@ -0,0 +1,16 @@
+#-1:include.txt
+1:include.txt
+#-2:include.txt
+<part1.txt>
+#-3:include.txt
+2:include.txt
+#-4:include.txt
+<? no such file or directory>
+#-5:include.txt
+3:include.txt
+#-6:include.txt
+  < ? part2.txt >
+#-7:include.txt
+4:include.txt
+#-8:include.txt
+
diff --git a/src/test/resources/org/ini4j/spi/nested.txt b/src/test/resources/org/ini4j/spi/nested.txt
new file mode 100644
index 0000000..4a01310
--- /dev/null
+++ b/src/test/resources/org/ini4j/spi/nested.txt
@@ -0,0 +1,7 @@
+#-1:nested.txt
+1:nested.txt
+#-2:nested.txt
+<include.txt>
+#-3:nested.txt
+2:nested.txt
+#-4:nested.txt
diff --git a/src/test/resources/org/ini4j/spi/part1.txt b/src/test/resources/org/ini4j/spi/part1.txt
new file mode 100644
index 0000000..7042ddf
--- /dev/null
+++ b/src/test/resources/org/ini4j/spi/part1.txt
@@ -0,0 +1,11 @@
+;-1:part1.txt
+1:part1.txt
+2:\
+part1.txt
+3:part1.txt\\
+4:\\\
+part1.txt
+5:part1.txt\\\\
+6:part1.txt\
+;
+;-2:part1.txt
diff --git a/src/test/resources/org/ini4j/spi/part2.txt b/src/test/resources/org/ini4j/spi/part2.txt
new file mode 100644
index 0000000..c8156ad
--- /dev/null
+++ b/src/test/resources/org/ini4j/spi/part2.txt
@@ -0,0 +1,5 @@
+;-1:part2.txt
+
+1:part2.txt
+
+;-2:part2.txt


hooks/post-receive
-- 
libini4j-java packaging



More information about the pkg-java-commits mailing list