OSDN Git Service

初回インポート
authorolyutorskii <olyutorskii@users.sourceforge.jp>
Thu, 12 May 2011 15:47:24 +0000 (00:47 +0900)
committerolyutorskii <olyutorskii@users.sourceforge.jp>
Thu, 12 May 2011 15:47:24 +0000 (00:47 +0900)
46 files changed:
.hgeol [new file with mode: 0644]
.hgignore [new file with mode: 0644]
CHANGELOG.txt [new file with mode: 0644]
LICENSE.txt [new file with mode: 0644]
README.txt [new file with mode: 0644]
build.xml [new file with mode: 0644]
pom.xml [new file with mode: 0644]
src/main/assembly/descriptor.xml [new file with mode: 0644]
src/main/config/checks.xml [new file with mode: 0644]
src/main/config/pmdrules.xml [new file with mode: 0644]
src/main/config/suppressions.xml [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/JsArray.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/JsBoolean.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/JsComposition.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/JsNull.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/JsNumber.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/JsObject.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/JsPair.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/JsParseException.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/JsString.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/JsTypes.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/JsValue.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/JsVisitException.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/Json.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/JsonAppender.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/JsonSource.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/UnmodIterator.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/ValueVisitor.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jovsonz/package-info.java [new file with mode: 0644]
src/main/resources/jp/sourceforge/jovsonz/resources/version.properties [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/JsArrayTest.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/JsBooleanTest.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/JsNullTest.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/JsNumberTest.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/JsObjectTest.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/JsPairTest.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/JsParseExceptionTest.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/JsStringTest.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/JsTypesTest.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/JsVisitExceptionTest.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/JsonAppenderTest.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/JsonSourceTest.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/JsonTest.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/TroubleAppender.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/TroubleReader.java [new file with mode: 0644]
src/test/java/jp/sourceforge/jovsonz/UnmodIteratorTest.java [new file with mode: 0644]

diff --git a/.hgeol b/.hgeol
new file mode 100644 (file)
index 0000000..ca5f1c1
--- /dev/null
+++ b/.hgeol
@@ -0,0 +1,13 @@
+[patterns]
+
+**.txt = native
+
+**.java = native
+**.properties = LF
+
+**.xml = LF
+**.xsd = LF
+
+**.css = LF
+**.html = LF
+**.png = BIN
diff --git a/.hgignore b/.hgignore
new file mode 100644 (file)
index 0000000..c94f6de
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,12 @@
+syntax: regexp
+
+\.orig$
+\.orig\..*$
+\.chg\..*$
+\.rej$
+\.conflict\~$
+^maven-build\.properties$
+^maven-build\.xml$
+^nb-configuration\.xml$
+^nbactions\.xml$
+^target$
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
new file mode 100644 (file)
index 0000000..db72931
--- /dev/null
@@ -0,0 +1,11 @@
+[UTF-8 Japanese]
+
+
+Jovsonz 変更履歴
+
+
+1.101.1-SNAPSHOT (2011-05-11)
+    ・初回リリース準備。
+
+
+--- EOF ---
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644 (file)
index 0000000..55bc3e6
--- /dev/null
@@ -0,0 +1,34 @@
+[UTF-8 English & Japanese]
+
+The MIT License
+
+
+Copyright(c) 2009 olyutorskii
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+
+Jovsonzオリジナル制作者自身からのコメント:
+
+  ※ 少なくともこのソフトウェアの実行、複製、配布、改造は自由です。
+  ※ 少なくともこのソフトウェアは無保証です。
+
+
+--- EOF ---
diff --git a/README.txt b/README.txt
new file mode 100644 (file)
index 0000000..5291797
--- /dev/null
@@ -0,0 +1,56 @@
+[UTF-8 Japanese]
+
+                              J O V S O N Z
+                                  Readme
+
+                                              Copyright(c) 2009 olyutorskii
+
+
+=== Jovsonzとは ===
+
+ Jovsonzライブラリは、JSONデータの入出力を行うためのJavaライブラリです。
+JovsonzはJindolfプロジェクトから派生したオープンソースプロジェクトです。
+
+※ このアーカイブは、開発者向けにJovsonzのソースコードのみをまとめたものです。
+
+
+=== 実行環境 ===
+
+ - JovsonzはJava言語(JLS3)で記述されたプログラムです。
+ - JovsonzはJRE1.5に準拠したJava実行環境で利用できるように作られています。
+   原則として、JRE1.5に準拠した実行系であれば、プラットフォームを選びません。
+
+
+=== ディレクトリ内訳構成 ===
+
+基本的にはMaven2のmaven-archetype-quickstart構成に準じます。
+
+./README.txt
+    あなたが今見てるこれ。
+
+./CHANGELOG.txt
+    変更履歴。
+
+./LICENSE.txt
+    ライセンスに関して。
+
+./pom.xml
+    Maven2用プロジェクト構成定義ファイル。
+
+./src/main/java/
+    Javaのソースコード。
+
+./src/test/java/
+    JUnit 4.* 用のユニットテストコード。
+
+./src/main/config/
+    各種ビルド・構成管理に必要なファイル群。
+
+./src/main/config/checks.xml
+    Checkstyle用configファイル。
+
+./src/main/config/pmdrules.xml
+    PMD用ルール定義ファイル。
+
+
+--- EOF ---
diff --git a/build.xml b/build.xml
new file mode 100644 (file)
index 0000000..2216cdc
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- ====================================================================== -->
+<!-- Ant build file (http://ant.apache.org/) for Ant 1.6.2 or above.        -->
+<!-- ====================================================================== -->
+
+<project name="jovsonz" default="package" basedir=".">
+
+<!-- ====================================================================== -->
+<!-- Import maven-build.xml into the current project                        -->
+<!-- Maven2 command "mvn ant:ant" will put maven-build.xml                  -->
+<!-- ====================================================================== -->
+
+    <import optional="true" file="maven-build.xml"/>
+
+<!-- ====================================================================== -->
+<!-- Help target                                                            -->
+<!-- ====================================================================== -->
+
+    <target name="help">
+        <echo message="Please run: $ant -projecthelp"/>
+    </target>
+
+<!-- ====================================================================== -->
+<!-- sanitize files for native environment                                  -->
+<!-- ====================================================================== -->
+
+    <target description="sanitize files" name="sanitize" >
+        <echo message="sanitize files..." />
+
+        <fixcrlf
+            srcDir="./src/" includes="**/*.java"
+            encoding="UTF-8" outputencoding="UTF-8"
+            tablength="4" tab="remove" javafiles="true"
+            eof="remove"
+        />
+
+        <fixcrlf
+            srcDir="./src/" includes="**/*.xml,**/*.xsd"
+            encoding="UTF-8" outputencoding="UTF-8"
+            tablength="8" tab="remove"
+            eol="lf"
+            eof="remove"
+        />
+
+        <fixcrlf
+            srcDir="./src/" includes="**/*.properties"
+            encoding="ISO-8859-1" outputencoding="ISO-8859-1"
+            eol="lf"
+            eof="remove"
+        />
+
+        <fixcrlf
+            srcDir="." includes="*.txt"
+            encoding="UTF-8" outputencoding="UTF-8"
+            tablength="8" tab="remove"
+            eof="remove"
+        />
+
+        <chmod type="file" perm="a-x">
+            <fileset dir="." includes="**/*" excludes="**/*.sh" />
+        </chmod>
+
+    </target>
+
+</project>
+
+<!-- EOF -->
diff --git a/pom.xml b/pom.xml
new file mode 100644 (file)
index 0000000..b577b57
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,346 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    Maven2 POM definition file
+-->
+
+<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>
+    <!--parent/-->
+
+    <groupId>jp.sourceforge.jovsonz</groupId>
+    <artifactId>jovsonz</artifactId>
+
+    <version>1.101.1-SNAPSHOT</version>
+
+    <packaging>jar</packaging>
+    <name>Jovsonz</name>
+
+    <description><!--
+-->Jovsonz is a JSON library for Java<!--
+ --></description>
+
+    <url>http://jovsonz.sourceforge.jp/</url>
+    <inceptionYear>2009</inceptionYear>
+
+    <organization>
+        <name>Jovsonz Partners</name>
+        <url>http://sourceforge.jp/projects/jovsonz/</url>
+    </organization>
+
+    <licenses>
+        <license>
+            <name>The MIT License</name>
+            <url>http://www.opensource.org/licenses/mit-license.php</url>
+            <distribution>manual</distribution>
+        </license>
+    </licenses>
+
+    <developers>
+        <developer>
+            <id>olyutorskii</id>
+            <url>http://sites.google.com/site/olyutorskiipit/</url>
+            <organization>Jovsonz Partners</organization>
+            <organizationUrl>http://sourceforge.jp/projects/jovsonz/</organizationUrl>
+            <roles>
+                <role>Project Founder</role>
+                <role>Java Developer</role>
+            </roles>
+        </developer>
+    </developers>
+
+    <contributors/>
+    <mailingLists/>
+
+    <prerequisites>
+        <maven>2.2</maven>
+    </prerequisites>
+
+    <modules/>
+
+    <scm>
+        <connection>scm:hg:http://hg.sourceforge.jp/view/jovsonz/Jovsonz</connection>
+        <developerConnection>scm:hg:ssh://hg.sourceforge.jp//hgroot/jovsonz/Jovsonz</developerConnection>
+        <url>http://hg.sourceforge.jp/view/jovsonz/Jovsonz/</url>
+    </scm>
+
+    <issueManagement>
+        <system>SourceForge.JP</system>
+        <url>http://sourceforge.jp/projects/jovsonz/ticket/</url>
+    </issueManagement>
+
+    <ciManagement/>
+    <distributionManagement/>
+
+    <properties>
+        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
+
+        <maven.compiler.source>1.5</maven.compiler.source>
+        <maven.compiler.target>1.5</maven.compiler.target>
+
+        <maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
+        <maven.compiler.showWarnings>true</maven.compiler.showWarnings>
+
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+
+        <project.myrepoconf>${project.basedir}/src/main/config</project.myrepoconf>
+
+        <!-- Walkaround for Maven3&PMD-plugin bug -->
+        <targetJdk>${maven.compiler.source}</targetJdk>
+
+        <!-- Walkaround for Maven3&checkstyle bug -->
+        <checkstyle.config.location>${project.myrepoconf}/checks.xml</checkstyle.config.location>
+
+    </properties>
+
+    <dependencyManagement/>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>[4.8.2,)</version>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+    <repositories/>
+    <pluginRepositories/>
+
+    <build>
+        <pluginManagement/>
+
+        <plugins>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-clean-plugin</artifactId>
+                <version>2.4.1</version>
+                <configuration>
+                    <filesets>
+                        <fileset>
+                            <directory>${project.basedir}</directory>
+                            <includes>
+                                <include>**/.DS_Store</include>
+                                <include>**/Thumbs.db</include>
+                                <include>**/core</include>
+                            </includes>
+                        </fileset>
+                    </filesets>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>2.3.2</version>
+                <configuration>
+                    <source>1.5</source>  <!-- for NetBeans IDE -->
+                    <target>1.5</target>
+                    <showDeprecation>true</showDeprecation>
+                    <showWarnings>true</showWarnings>
+                    <compilerArguments>
+                        <Xlint/>
+                    </compilerArguments>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>2.3.1</version>
+                <configuration>
+                    <archive>
+                        <manifestEntries>
+                            <Built-By>${project.organization.name}</Built-By>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>2.2.1</version>
+                <configuration>
+                    <descriptors>
+                        <descriptor>src/main/assembly/descriptor.xml</descriptor>
+                    </descriptors>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <version>1.0</version>
+                <configuration>
+                    <rules>
+                        <requireMavenVersion>
+                            <version>[2.2,3)</version>
+                        </requireMavenVersion>
+                        <requireJavaVersion>
+                            <version>[1.5,)</version>
+                        </requireJavaVersion>
+                    </rules>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-source-plugin</artifactId>
+                <version>2.1.2</version>
+                <configuration>
+                    <includePom>true</includePom>
+                    <archive>
+                        <manifestEntries>
+                            <Built-By>${project.organization.name}</Built-By>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>attach-sources</id>
+                        <phase>verify</phase>
+                        <goals>
+                          <goal>jar-no-fork</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+        </plugins>
+
+        <resources>
+
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>**/version.properties</include>
+                </includes>
+            </resource>
+
+            <resource>
+                <directory>src/main/resources</directory>
+                <includes>
+                    <include>**/*.css</include>
+                    <include>**/*.html</include>
+                    <include>**/*.png</include>
+                    <include>**/*.properties</include>
+                    <include>**/*.txt</include>
+                    <include>**/*.xml</include>
+                    <include>**/*.xsd</include>
+                </includes>
+                <excludes>
+                    <exclude>**/version.properties</exclude>
+                </excludes>
+            </resource>
+
+        </resources>
+
+    </build>
+
+    <reporting>
+
+        <plugins>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-site-plugin</artifactId>
+                <version>2.2</version>
+                <configuration>
+                    <locales>ja</locales>
+                    <generateReports>true</generateReports>
+                    <generateSitemap>true</generateSitemap>
+                    <inputEncoding>${project.build.sourceEncoding}</inputEncoding>
+                    <outputEncoding>${project.reporting.outputEncoding}</outputEncoding>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <version>2.8</version>
+                <configuration>
+                    <show>protected</show>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <version>2.6</version>
+                <configuration>
+                    <configLocation>${checkstyle.config.location}</configLocation>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-pmd-plugin</artifactId>
+                <version>2.5</version>
+                <configuration>
+                    <targetJdk>${maven.compiler.target}</targetJdk>
+                    <rulesets>
+                        <ruleset>${project.myrepoconf}/pmdrules.xml</ruleset>
+                    </rulesets>
+                </configuration>
+                <reportSets>
+                    <reportSet>
+                        <reports>
+                            <report>pmd</report>
+                            <report>cpd</report>
+                        </reports>
+                    </reportSet>
+                </reportSets>
+            </plugin>
+
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>findbugs-maven-plugin</artifactId>
+                <version>2.3.2</version>
+                <configuration>
+                    <effort>Max</effort>
+                    <threshold>Low</threshold>
+                    <inputEncoding>${project.build.sourceEncoding}</inputEncoding>
+                    <outputEncoding>${project.reporting.outputEncoding}</outputEncoding>
+                    <!--excludeFilterFile/-->
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>cobertura-maven-plugin</artifactId>
+                <version>2.4</version>
+            </plugin>
+
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>javancss-maven-plugin</artifactId>
+                <version>2.0</version>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jxr-plugin</artifactId>
+                <version>2.2</version>
+            </plugin>
+
+        </plugins>
+
+    </reporting>
+
+    <profiles/>
+
+</project>
+
+<!-- EOF -->
diff --git a/src/main/assembly/descriptor.xml b/src/main/assembly/descriptor.xml
new file mode 100644 (file)
index 0000000..b744cbd
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<assembly
+  xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0
+  http://maven.apache.org/xsd/assembly-1.1.0.xsd"
+>
+
+<!--
+    SourceForge.JP用リリースファイル構成定義ファイル
+    Maven2 assembly用
+-->
+
+    <id>src</id>
+
+    <formats>
+        <format>zip</format>
+    </formats>
+
+    <fileSets>
+        <fileSet>
+            <includes>
+                <include>*.txt</include>
+                <include>pom.xml</include>
+                <include>build.xml</include>
+            </includes>
+            <useDefaultExcludes>true</useDefaultExcludes>
+        </fileSet>
+        <fileSet>
+            <directory>src/</directory>
+            <useDefaultExcludes>true</useDefaultExcludes>
+        </fileSet>
+    </fileSets>
+
+</assembly>
+
+<!-- EOF -->
diff --git a/src/main/config/checks.xml b/src/main/config/checks.xml
new file mode 100644 (file)
index 0000000..0b45e05
--- /dev/null
@@ -0,0 +1,392 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE module PUBLIC
+    "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
+    "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
+
+<!--
+    Checkstyle用チェック項目定義。
+
+    Checkstyle 5.0 以降向けに記述。
+
+    [ http://checkstyle.sourceforge.net/ ]
+
+    Copyright(c) 2010 olyutorskii
+-->
+
+
+<module name="Checker">
+
+    <property name="charset" value="UTF-8" />
+    <property name="localeCountry" value="JP" />
+    <property name="localeLanguage" value="ja" />
+    <property name="severity" value="error" />
+
+
+    <!-- Filters -->
+    <module name="SeverityMatchFilter" />
+    <module name="SuppressionFilter">
+        <property name="file" value="src/main/config/suppressions.xml" />
+    </module>
+    <module name="SuppressionCommentFilter" />
+    <module name="SuppressWithNearbyCommentFilter" />
+
+
+    <!-- Duplicate Code -->
+    <module name="StrictDuplicateCode">
+        <property name="min" value="20" />
+    </module>
+
+
+    <!-- Headers -->
+    <module name="RegexpHeader">
+        <property name="header" value="^/\*$\n^ \*( .*)?$\n^ \*/$\n" />
+        <property name="multiLines" value="2" />
+    </module>
+
+
+    <!-- Javadoc Comments -->
+    <module name="JavadocPackage" />
+
+
+    <!-- Miscellaneous -->
+    <module name="NewlineAtEndOfFile" />
+    <module name="Translation" />
+
+
+    <!-- Regexp -->
+    <module name="RegexpSingleline">
+        <property name="format" value="\s+$" />
+        <property name="minimum" value="0" />
+        <property name="maximum" value="0" />
+    </module>
+
+
+    <!-- Size Violations -->
+    <module name="FileLength" />
+
+
+    <!-- Whitespace -->
+    <module name="FileTabCharacter" />
+
+
+    <module name="TreeWalker">
+
+        <module name="FileContentsHolder" />
+
+
+    <!-- Annotations -->
+
+        <module name="AnnotationUseStyle" />
+        <module name="MissingDeprecated" />
+        <module name="MissingOverride" />
+        <module name="PackageAnnotation" />
+        <module name="SuppressWarnings" />
+
+
+    <!-- Block Checks -->
+
+        <module name="EmptyBlock" />
+        <module name="LeftCurly" />
+        <module name="NeedBraces">
+            <property name="tokens" value="LITERAL_DO" />
+        </module>
+        <module name="RightCurly" />
+        <module name="AvoidNestedBlocks" />
+
+
+    <!-- Class Design -->
+
+        <module name="VisibilityModifier" />
+        <module name="FinalClass" />
+        <module name="InterfaceIsType" />
+        <module name="HideUtilityClassConstructor" />
+<!--    <module name="DesignForExtension" />  -->
+        <module name="MutableException" />
+        <module name="ThrowsCount">
+            <property name="max" value="3" />
+        </module>
+
+
+    <!-- Coding -->
+
+        <module name="ArrayTrailingComma" />
+        <module name="AvoidInlineConditionals" />
+        <module name="CovariantEquals" />
+        <module name="DoubleCheckedLocking" />
+        <module name="EmptyStatement" />
+        <module name="EqualsAvoidNull" />
+        <module name="EqualsHashCode" />
+<!--    <module name="FinalLocalVariable" />  -->
+        <module name="HiddenField">
+            <property name="ignoreConstructorParameter" value="true" />
+            <property name="ignoreSetter" value="true" />
+            <property name="ignoreAbstractMethods" value="true" />
+        </module>
+        <module name="IllegalInstantiation" />
+        <module name="IllegalToken">
+            <property name="tokens" value="LITERAL_NATIVE, STATIC_IMPORT" />
+        </module>
+        <module name="IllegalTokenText">
+            <property name="tokens" value="NUM_INT, NUM_LONG" />
+            <property name="format" value="^0[^lx]" />
+            <property name="ignoreCase" value="true" />
+        </module>
+        <module name="InnerAssignment" />
+        <module name="MagicNumber" />
+        <module name="MissingSwitchDefault" />
+        <module name="ModifiedControlVariable" />
+        <module name="RedundantThrows">
+            <property name="allowUnchecked" value="true" />
+            <property name="allowSubclasses" value="true" />
+        </module>
+        <module name="SimplifyBooleanExpression" />
+        <module name="SimplifyBooleanReturn" />
+        <module name="StringLiteralEquality" />
+        <module name="NestedIfDepth" />
+        <module name="NestedTryDepth" />
+        <module name="NoClone" />
+        <module name="NoFinalizer" />
+        <module name="SuperClone" />
+        <module name="SuperFinalize" />
+        <module name="IllegalCatch" />
+        <module name="IllegalThrows" />
+        <module name="PackageDeclaration" />
+        <module name="JUnitTestCase" />
+        <module name="ReturnCount">
+            <property name="max" value="5" />
+        </module>
+        <module name="IllegalType" />
+        <module name="DeclarationOrder" />
+        <module name="ParameterAssignment" />
+<!--    <module name="ExplicitInitialization" />  -->
+        <module name="DefaultComesLast" />
+        <module name="MissingCtor" />
+        <module name="FallThrough" />
+        <module name="MultipleStringLiterals">
+            <property
+                name="ignoreStringsRegexp"
+                value="^&quot;&quot;$|^&quot;.&quot;$"
+            />
+        </module>
+        <module name="MultipleVariableDeclarations" />
+        <module name="UnnecessaryParentheses" />
+
+
+    <!-- Imports -->
+
+        <module name="AvoidStarImport" />
+        <module name="AvoidStaticImport" />
+        <module name="IllegalImport" />
+        <module name="RedundantImport" />
+        <module name="UnusedImports" />
+        <module name="ImportOrder" />
+<!--    <module name="ImportControl" />  -->
+
+
+    <!-- Javadoc Comments -->
+
+        <module name="JavadocType" />
+        <module name="JavadocMethod" />
+        <module name="JavadocVariable">
+            <property name="scope" value="protected" />
+        </module>
+        <module name="JavadocStyle">
+            <property
+                name="endOfSentenceFormat"
+                value="([。.?!][ \t\n\r\f&lt;])|([。.?!]$)" />
+            <property name="checkEmptyJavadoc" value="true" />
+            <property name="checkHtml" value="true" />
+        </module>
+<!--    <module name="WriteTag" />  -->
+
+
+    <!-- Metrics -->
+
+        <module name="BooleanExpressionComplexity" />
+        <module name="ClassDataAbstractionCoupling" />
+        <module name="ClassFanOutComplexity" />
+        <module name="CyclomaticComplexity" />
+        <module name="NPathComplexity" />
+        <module name="JavaNCSS" />
+
+
+    <!-- Miscellaneous -->
+
+        <module name="TodoComment">
+            <property name="format" value="TODO" />
+        </module>
+        <module name="UncommentedMain" />
+        <module name="UpperEll" />
+        <module name="ArrayTypeStyle" />
+<!--    <module name="FinalParameters" />  -->
+<!--    <module name="DescendantToken" />  -->
+<!--
+        <module name="Indentation">
+            <property name="caseIndent" value="0" />
+        </module>
+-->
+<!--    <module name="TrailingComment" />  -->
+        <module name="Regexp">
+            <property name="format" value="@author" />
+            <property name="illegalPattern" value="true" />
+        </module>
+        <module name="Regexp">
+            <property name="format" value="^ \* Copyright\(c\)" />
+        </module>
+        <module name="Regexp">
+            <property name="format" value="^ \* License : The MIT License" />
+            <property name="duplicateLimit" value="1" />
+        </module>
+
+
+    <!-- Modifiers -->
+
+        <module name="ModifierOrder" />
+        <module name="RedundantModifier" />
+
+
+    <!-- Naming Conventions -->
+
+        <module name="AbstractClassName">
+            <property
+                name="format"
+                value="^Abstract.*$|^.*Factory$|^.*Builder$|^.*Adapter$"
+            />
+        </module>
+        <module name="ClassTypeParameterName" />
+        <module name="ConstantName" />
+        <module name="LocalFinalVariableName">
+            <property name="format" value="^[a-z][_a-zA-Z0-9]*$" />
+        </module>
+        <module name="LocalVariableName">
+            <property name="format" value="^[a-z][_a-zA-Z0-9]*$" />
+        </module>
+        <module name="MemberName">
+            <property name="format" value="^[a-z][_a-zA-Z0-9]*$" />
+        </module>
+        <module name="MethodName" />
+        <module name="MethodTypeParameterName" />
+        <module name="PackageName" />
+        <module name="ParameterName">
+            <property name="format" value="^[a-z][_a-zA-Z0-9]*$" />
+        </module>
+        <module name="StaticVariableName" />
+        <module name="TypeName" />
+
+
+    <!-- Size Violations -->
+
+        <module name="ExecutableStatementCount" />
+        <module name="LineLength">
+            <property name="max" value="78" />
+        </module>
+        <module name="MethodLength" />
+        <module name="AnonInnerLength" />
+        <module name="ParameterNumber" />
+        <module name="OuterTypeNumber" />
+
+
+    <!-- Whitespace -->
+
+        <module name="GenericWhitespace" />
+        <module name="EmptyForInitializerPad" />
+        <module name="EmptyForIteratorPad" />
+        <module name="MethodParamPad">
+            <property
+                name="tokens"
+                value="CTOR_DEF, LITERAL_NEW, METHOD_DEF, SUPER_CTOR_CALL"
+            />
+        </module>
+        <module name="NoWhitespaceAfter">
+            <property name="allowLineBreaks" value="false" />
+            <property name="tokens" value="DEC, DOT, INC" />
+        </module>
+        <module name="NoWhitespaceBefore">
+            <property name="allowLineBreaks" value="false" />
+            <property name="tokens" value="POST_DEC, POST_INC" />
+        </module>
+        <module name="NoWhitespaceBefore">
+            <property name="allowLineBreaks" value="true" />
+            <property name="tokens" value="SEMI" />
+        </module>
+        <module name="OperatorWrap">
+            <property name="option" value="eol" />
+            <property
+                name="tokens"
+                value="ASSIGN,
+                       BAND_ASSIGN, BOR_ASSIGN, BXOR_ASSIGN,
+                       PLUS_ASSIGN, MINUS_ASSIGN,
+                       STAR_ASSIGN, DIV_ASSIGN, MOD_ASSIGN,
+                       SL_ASSIGN, SR_ASSIGN"
+            />
+        </module>
+        <module name="OperatorWrap">
+            <property name="option" value="nl" />
+            <property
+                name="tokens"
+                value="BAND, BOR, BXOR,
+                       MINUS, STAR, DIV, MOD,
+                       LAND, LOR,
+                       EQUAL"
+            />
+        </module>
+        <module name="ParenPad">
+            <property name="option" value="nospace" />
+            <property
+                name="tokens"
+                value="CTOR_CALL, METHOD_CALL, SUPER_CTOR_CALL"
+            />
+        </module>
+        <module name="TypecastParenPad" />
+        <module name="WhitespaceAfter">
+            <property name="tokens" value="COMMA, SEMI" />
+        </module>
+        <module name="WhitespaceAround">
+            <property
+                name="tokens"
+                value="ASSIGN,
+                       LAND, LOR,
+                       BAND, BOR, BXOR, BSR,
+                       BAND_ASSIGN, BOR_ASSIGN, BXOR_ASSIGN, BSR_ASSIGN,
+                       SL, SR,
+                       SL_ASSIGN, SR_ASSIGN,
+                       MINUS, STAR, DIV, MOD,
+                       PLUS_ASSIGN, MINUS_ASSIGN,
+                       STAR_ASSIGN, DIV_ASSIGN, MOD_ASSIGN,
+                       EQUAL, NOT_EQUAL, GT, GE, LT, LE,
+                       "
+            />
+        </module>
+
+
+<!-- 代用品で解決
+        <module name="Header" />
+        <module name="RegexpSingleline" />
+        <module name="RegexpMultiline" />
+        <module name="RegexpSinglelineJava" />
+-->
+
+<!-- バグ?
+        <module name="RequireThis" />
+-->
+<!-- 5.2 or later
+        <module name="InnerTypeLast" />
+-->
+
+<!-- 5.3 or later
+        <module name="NestedForDepth" />
+        <module name="OneStatementPerLine" />
+        <module name="OuterTypeFilename" />
+        <module name="MethodCount" />
+-->
+
+<!-- Obsolated
+        <module name="TabCharacter" />
+-->
+
+    </module>
+
+</module>
+
+<!-- EOF -->
diff --git a/src/main/config/pmdrules.xml b/src/main/config/pmdrules.xml
new file mode 100644 (file)
index 0000000..ad3dba9
--- /dev/null
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    PMD用ルールセット定義
+
+    PMD [ http://pmd.sourceforge.net/ ] 4.2.5 以降用に記述されています。
+
+    Copyright(c) 2010 olyutorskii
+-->
+
+<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"
+  name="Custom ruleset"
+>
+
+    <rule ref="rulesets/basic.xml">
+        <exclude name="UnnecessaryReturn" />
+    </rule>
+
+    <rule ref="rulesets/braces.xml">
+        <exclude name="IfElseStmtsMustUseBraces" />
+        <exclude name="IfStmtsMustUseBraces" />
+    </rule>
+
+    <rule ref="rulesets/codesize.xml">
+        <exclude name="TooManyMethods" />
+    </rule>
+    <rule ref="rulesets/codesize.xml/TooManyMethods">
+        <properties>
+            <property name="maxmethods" value="25"/>
+        </properties>
+    </rule>
+
+    <rule ref="rulesets/clone.xml" />
+
+    <rule ref="rulesets/controversial.xml">
+        <exclude name="NullAssignment" />
+        <exclude name="OnlyOneReturn" />
+        <exclude name="DefaultPackage" />
+        <exclude name="DataflowAnomalyAnalysis" />
+    </rule>
+
+    <rule ref="rulesets/coupling.xml" />
+
+    <rule ref="rulesets/design.xml">
+        <exclude name="ConfusingTernary" />
+        <exclude name="UnnecessaryLocalBeforeReturn" />
+    </rule>
+
+    <rule ref="rulesets/finalizers.xml" />
+
+    <rule ref="rulesets/imports.xml" />
+
+    <rule ref="rulesets/logging-java.xml" />
+
+    <rule ref="rulesets/migrating.xml" />
+    <rule ref="rulesets/migrating_to_15.xml" />
+
+    <rule ref="rulesets/naming.xml">
+        <exclude name="ShortVariable" />
+        <exclude name="LongVariable" />
+        <exclude name="AvoidFieldNameMatchingMethodName" />
+    </rule>
+    <rule ref="rulesets/naming.xml/LongVariable">
+        <properties>
+            <property name="minimum" value="25"/>
+        </properties>
+    </rule>
+
+    <rule ref="rulesets/optimizations.xml">
+        <exclude name="LocalVariableCouldBeFinal" />
+        <exclude name="MethodArgumentCouldBeFinal" />
+    </rule>
+
+    <rule ref="rulesets/strictexception.xml">
+        <exclude name="AvoidThrowingNullPointerException" />
+    </rule>
+
+    <rule ref="rulesets/strings.xml" />
+
+    <rule ref="rulesets/sunsecure.xml" />
+
+    <rule ref="rulesets/typeresolution.xml" />
+
+    <rule ref="rulesets/unusedcode.xml" />
+
+</ruleset>
+
+<!-- EOF -->
diff --git a/src/main/config/suppressions.xml b/src/main/config/suppressions.xml
new file mode 100644 (file)
index 0000000..1d736c1
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE suppressions PUBLIC
+    "-//Puppy Crawl//DTD Suppressions 1.1//EN"
+    "http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
+
+<!--
+    Checkstyle用警告抑止設定
+    ※ SuppressionFilter モジュール用
+
+    [ http://checkstyle.sourceforge.net/ ]
+
+    Copyright(c) 2010 olyutorskii
+-->
+
+
+<suppressions>
+
+<!--
+    <suppress files="" checks="ExecutableStatementCount" />
+
+    <suppress files="" checks="ClassDataAbstractionCoupling" />
+    <suppress files="" checks="ClassFanOutComplexity" />
+    <suppress files="" checks="CyclomaticComplexity" />
+    <suppress files="" checks="NPathComplexity" />
+    <suppress files="" checks="JavaNCSS" />
+-->
+
+<!--
+    <suppress files="" checks="MagicNumber" />
+    <suppress files="" checks="MultipleStringLiterals" />
+-->
+
+</suppressions>
+
+
+<!-- EOF -->
diff --git a/src/main/java/jp/sourceforge/jovsonz/JsArray.java b/src/main/java/jp/sourceforge/jovsonz/JsArray.java
new file mode 100644 (file)
index 0000000..df80a51
--- /dev/null
@@ -0,0 +1,311 @@
+/*
+ * JSON array value
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * JSON ARRAY型Valueを表す。
+ * 子要素の配列リストを反映する。
+ * <h1>表記例</h1>
+ * <pre>
+ * [
+ *     true ,
+ *     "ABC" ,
+ *     12.3
+ * ]
+ * </pre>
+ */
+public class JsArray
+        implements JsComposition<JsValue> {
+
+    private static final String ERRMSG_NOARRAYCOMMA =
+            "missing comma in ARRAY";
+    private static final String ERRMSG_NOELEM =
+            "missing element in ARRAY";
+
+    private final List<JsValue> valueList = new LinkedList<JsValue>();
+    private boolean changed = false;
+
+    /**
+     * コンストラクタ。
+     */
+    public JsArray(){
+        super();
+        return;
+    }
+
+    /**
+     * JSON文字列ソースからARRAY型Valueを読み込む。
+     * さらに子Valueへとパース解析が進む可能性がある。
+     * 別型の可能性のある先頭文字を読み込んだ場合、
+     * ソースに文字を読み戻した後nullが返される。
+     * @param source 文字列ソース
+     * @return ARRAY型Value。別型の可能性がある場合はnull。
+     * @throws IOException 入力エラー
+     * @throws JsParseException 不正な表現または意図しない入力終了
+     */
+    static JsArray parseArray(JsonSource source)
+            throws IOException, JsParseException {
+        char charHead = source.readOrDie();
+        if(charHead != '['){
+            source.unread(charHead);
+            return null;
+        }
+
+        JsArray result = new JsArray();
+
+        for(;;){
+            source.skipWhiteSpace();
+            char chData = source.readOrDie();
+            if(chData == ']') break;
+
+            if(result.isEmpty()){
+                source.unread(chData);
+            }else{
+                if(chData != ','){
+                    throw new JsParseException(ERRMSG_NOARRAYCOMMA,
+                                               source.getLineNumber() );
+                }
+            }
+
+            JsValue value = Json.parseValue(source);
+            if(value == null){
+                throw new JsParseException(ERRMSG_NOELEM,
+                                           source.getLineNumber() );
+            }
+
+            result.add(value);
+        }
+
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 常に{@link JsTypes#ARRAY}を返す。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public JsTypes getJsTypes(){
+        return JsTypes.ARRAY;
+    }
+
+    /**
+     * このValueおよび子孫に変更があったか判定する。
+     * 子要素の追加・削除が行われたか、
+     * もしくは子要素のいずれかに変更が認められれば、
+     * このARRAY型Valueに変更があったとみなされる。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public boolean hasChanged(){
+        if(this.changed) return true;
+
+        for(JsValue value : this.valueList){
+            if( ! (value instanceof JsComposition) ) continue;
+            JsComposition composition = (JsComposition) value;
+            if(composition.hasChanged()) return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * このValueおよび子孫に変更がなかったことにする。
+     */
+    @Override
+    public void setUnchanged(){
+        this.changed = false;
+
+        for(JsValue value : this.valueList){
+            if( ! (value instanceof JsComposition) ) continue;
+            JsComposition composition = (JsComposition) value;
+            composition.setUnchanged();
+        }
+
+        return;
+    }
+
+    /**
+     * 深さ優先探索を行い各種構造の出現をビジターに通知する。
+     * thisを通知した後、子Valueを順に訪問し、最後に閉じ括弧を通知する。
+     * @param visitor {@inheritDoc}
+     * @throws JsVisitException {@inheritDoc}
+     */
+    @Override
+    public void traverse(ValueVisitor visitor) throws JsVisitException{
+        visitor.visitValue(this);
+
+        for(JsValue value : this.valueList){
+            value.traverse(visitor);
+        }
+
+        visitor.visitCompositionClose(this);
+
+        return;
+    }
+
+    /**
+     * 配列要素数を返す。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int size(){
+        return this.valueList.size();
+    }
+
+    /**
+     * 配列が空か判定する。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public boolean isEmpty(){
+        return this.valueList.isEmpty();
+    }
+
+    /**
+     * 配列を空にする。
+     */
+    @Override
+    public void clear(){
+        if(this.valueList.size() > 0) this.changed = true;
+        this.valueList.clear();
+        return;
+    }
+
+    /**
+     * ハッシュ値を返す。
+     * 全ての子孫Valueのハッシュ値からその都度合成される。高コスト注意!。
+     * @return {@inheritDoc}
+     * @see java.util.List#hashCode()
+     */
+    @Override
+    public int hashCode(){
+        return this.valueList.hashCode();
+    }
+
+    /**
+     * 等価判定を行う。
+     * 双方の配列サイズが一致し
+     * その全ての子Valueでのequals()が等価と判断された場合のみ
+     * 等価と判断される。
+     * @param obj {@inheritDoc}
+     * @return {@inheritDoc}
+     * @see java.util.List#equals(Object)
+     */
+    @Override
+    public boolean equals(Object obj){
+        if(this == obj) return true;
+
+        if( ! (obj instanceof JsArray) ) return false;
+        JsArray array = (JsArray) obj;
+
+        return this.valueList.equals(array.valueList);
+    }
+
+    /**
+     * 配列にValueを追加する。
+     * 同じJsValueインスタンスを複数回追加することも可能。
+     * @param value JSON Value
+     * @throws NullPointerException 引数がnull
+     */
+    public void add(JsValue value) throws NullPointerException{
+        if(value == null) throw new NullPointerException();
+        this.valueList.add(value);
+        this.changed = true;
+        return;
+    }
+
+    /**
+     * 配列から指定された位置のValueを返す。
+     * @param index 0で始まる配列上の位置
+     * @return Value JSON Value
+     * @throws IndexOutOfBoundsException 不正な位置指定
+     */
+    public JsValue get(int index) throws IndexOutOfBoundsException{
+        return this.valueList.get(index);
+    }
+
+    /**
+     * 配列からValueを削除する。
+     * {@link java.util.List#remove(Object)}と異なり、
+     * 削除対象の検索に際して
+     * {@link java.lang.Object#equals(Object)}は使われない。
+     * 一致するインスタンスが複数存在する場合、
+     * 先頭に近いインスタンスのみ削除される。
+     * 一致するインスタンスが存在しなければなにもしない。
+     * @param value JSON Value
+     * @return 既存のValueが削除されたならtrue
+     */
+    // TODO 必要?
+    public boolean remove(JsValue value){
+        boolean removed = false;
+
+        Iterator<JsValue> it = this.valueList.iterator();
+        while(it.hasNext()){
+            JsValue elem = it.next();
+            if(elem == value){
+                it.remove();
+                this.changed = true;
+                removed = true;
+                break;
+            }
+        }
+
+        return removed;
+    }
+
+    /**
+     * 配列から指定位置のValueを削除する。
+     * @param index 0で始まる削除対象のインデックス値
+     * @return 削除されたValue
+     * @throws IndexOutOfBoundsException 不正なインデックス値
+     */
+    public JsValue remove(int index) throws IndexOutOfBoundsException{
+        JsValue removed = this.valueList.remove(index);
+        this.changed = true;
+        return removed;
+    }
+
+    /**
+     * Valueにアクセスするための反復子を提供する。
+     * この反復子での削除作業はできない。
+     * @return 反復子イテレータ
+     * @see UnmodIterator
+     */
+    public Iterator<JsValue> iterator(){
+        return UnmodIterator.unmodIterator(this.valueList);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 文字列表現を返す。
+     * JSON表記の全体もしくは一部としての利用も可能。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder text = new StringBuilder();
+
+        text.append("[");
+        boolean hasElem = false;
+        for(JsValue value : this.valueList){
+            if(hasElem) text.append(',');
+            text.append(value);
+            hasElem = true;
+        }
+        text.append("]");
+
+        return text.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/JsBoolean.java b/src/main/java/jp/sourceforge/jovsonz/JsBoolean.java
new file mode 100644 (file)
index 0000000..06e4a0f
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * JSON boolean value
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.io.IOException;
+
+/**
+ * JSON BOOLEAN型Valueを表す。
+ * 真偽値を反映する。
+ * インスタンスは2つしか存在しえない。
+ * <h1>表記例</h1>
+ * <pre>
+ * true
+ * false
+ * </pre>
+ */
+public final class JsBoolean
+        implements JsValue, Comparable<JsBoolean> {
+
+    /** 唯一の真値。 */
+    public static final JsBoolean TRUE  = new JsBoolean();
+    /** 唯一の偽値。 */
+    public static final JsBoolean FALSE = new JsBoolean();
+
+    /** 真の文字列表現。 */
+    public static final String TEXT_TRUE  = "true";
+    /** 偽の文字列表現。 */
+    public static final String TEXT_FALSE = "false";
+
+    /** 真のハッシュ値。 */
+    public static final int HASH_TRUE = Boolean.TRUE.hashCode();
+    /** 偽のハッシュ値。 */
+    public static final int HASH_FALSE = Boolean.FALSE.hashCode();
+
+    /**
+     * 隠しコンストラクタ。
+     * 2回しか呼ばれないはず。
+     */
+    private JsBoolean(){
+        super();
+        return;
+    }
+
+    /**
+     * JSON文字列ソースからBOOLEAN型Valueを読み込む。
+     * 別型の可能性のある先頭文字を読み込んだ場合、
+     * ソースに文字を読み戻した後nullが返される。
+     * @param source 文字列ソース
+     * @return BOOLEAN型Value。別型の可能性がある場合はnull。
+     * @throws IOException 入力エラー
+     * @throws JsParseException 不正トークンもしくは意図しない入力終了
+     */
+    static JsBoolean parseBoolean(JsonSource source)
+            throws IOException, JsParseException{
+        JsBoolean result = null;
+        boolean hasError = false;
+
+        char charHead = source.readOrDie();
+        switch(charHead){
+        case 't':
+            if(source.matchOrDie("rue")){
+                result = JsBoolean.TRUE;
+            }else{
+                hasError = true;
+            }
+            break;
+        case 'f':
+            if(source.matchOrDie("alse")){
+                result = JsBoolean.FALSE;
+            }else{
+                hasError = true;
+            }
+            break;
+        default:
+            source.unread(charHead);
+            break;
+        }
+
+        if(hasError){
+            throw new JsParseException(JsParseException.ERRMSG_INVALIDTOKEN,
+                                       source.getLineNumber() );
+        }
+
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 常に{@link JsTypes#BOOLEAN}を返す。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public JsTypes getJsTypes(){
+        return JsTypes.BOOLEAN;
+    }
+
+    /**
+     * 各種構造の出現をビジターに通知する。
+     * この実装ではthisの出現のみを通知する。
+     * @param visitor {@inheritDoc}
+     * @throws JsVisitException {@inheritDoc}
+     */
+    @Override
+    public void traverse(ValueVisitor visitor)
+            throws JsVisitException{
+        visitor.visitValue(this);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * ハッシュ値を返す。
+     * 真なら{@link #HASH_TRUE}、偽なら{@link #HASH_FALSE}を返す。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int hashCode(){
+        int result;
+        if(this == TRUE) result = HASH_TRUE;
+        else             result = HASH_FALSE;
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 等価判定を行う。
+     * @param obj {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object obj){
+        if(this == obj) return true;
+        if(obj instanceof JsBoolean) return false;
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     * BOOLEAN型Valueを順序付ける。
+     * ({@link #TRUE}、{@link #FALSE})の順に順序付けられる。
+     * @param value {@inheritDoc}
+     * @return {@inheritDoc}
+     * @throws NullPointerException 引数がnull
+     */
+    @Override
+    public int compareTo(JsBoolean value) throws NullPointerException{
+        if(value == null) throw new NullPointerException();
+
+        int result;
+        if(this == value)     result =  0;
+        else if(this == TRUE) result = -1;
+        else                  result = +1;
+
+        return result;
+    }
+
+    /**
+     * boolean値を反映したBOOLEAN型Valueを返す。
+     * @param bool boolean値
+     * @return BOOLEAN型Value
+     */
+    public static JsBoolean valueOf(boolean bool){
+        if(bool) return TRUE;
+        return FALSE;
+    }
+
+    /**
+     * boolean値を返す。
+     * @return boolean値
+     */
+    public boolean booleanValue(){
+        if(this == TRUE) return true;
+        return false;
+    }
+
+    /**
+     * 真か判定する。
+     * @return 真ならtrue
+     */
+    public boolean isTrue(){
+        if(this == TRUE) return true;
+        return false;
+    }
+
+    /**
+     * 偽か判定する。
+     * @return 偽ならtrue
+     */
+    public boolean isFalse(){
+        if(this != TRUE) return true;
+        return false;
+    }
+
+    /**
+     * 文字列表現を返す。
+     * JSON表記の一部としての利用も可能。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        if(this == TRUE) return TEXT_TRUE;
+        return TEXT_FALSE;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/JsComposition.java b/src/main/java/jp/sourceforge/jovsonz/JsComposition.java
new file mode 100644 (file)
index 0000000..e18ec8b
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * composition type value
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+/**
+ * 子要素を持つJSON型の抽象インタフェース。
+ * JSON最上位構造であるための必要条件。
+ * 子要素を持ちうるJSON型はOBJECT型かARRAY型のみ。
+ * @param <E> 反復子の要素型
+ */
+public interface JsComposition<E> extends JsValue, Iterable<E> {
+
+    /**
+     * 要素数を返す。
+     * <p>OBJECT型の場合は直下のPAIR総数。</p>
+     * <p>ARRAY型の場合は直下の子要素総数。</p>
+     * @return 要素数
+     */
+    int size();
+
+    /**
+     * 子要素が空か否か判定する。
+     * @return 要素がなければtrue
+     */
+    boolean isEmpty();
+
+    /**
+     * 子要素を空にする。
+     */
+    void clear();
+
+    /**
+     * このValueおよび子孫に変更があったか判定する。
+     * Value生成直後はfalseでなければならない。
+     * ロードしたデータに対し
+     * 再セーブの必要があるかどうかの判定などを目的とする。
+     * <p>変更が可能なValueはOBJECT型かARRAY型のみ。</p>
+     * @return 変更があればtrue
+     */
+    boolean hasChanged();
+
+    /**
+     * このValueおよび子孫に変更がなかったことにする。
+     */
+    void setUnchanged();
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/JsNull.java b/src/main/java/jp/sourceforge/jovsonz/JsNull.java
new file mode 100644 (file)
index 0000000..b0e1015
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * JSON null value
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.io.IOException;
+
+/**
+ * JSON NULL型Valueを表す。
+ * Javaのnullとは一切無関係。
+ * その実体はシングルトン。
+ * <h1>表記例</h1>
+ * <pre>
+ * null
+ * </pre>
+ */
+public final class JsNull
+        implements JsValue, Comparable<JsNull> {
+
+    /** ただ唯一のインスタンス。 */
+    public static final JsNull NULL = new JsNull();
+
+    /** 唯一の文字列表現。 */
+    public static final String TEXT = "null";
+
+    /** 唯一のハッシュ値。 */
+    public static final int ONLYHASH = 982451653; // 大きな素数;
+
+    /**
+     * 隠しコンストラクタ。
+     * 1回しか呼ばれないはず
+     */
+    private JsNull(){
+        super();
+        return;
+    }
+
+    /**
+     * JSON文字列ソースからNULL型Valueを読み込む。
+     * 別型の可能性のある先頭文字を読み込んだ場合、
+     * ソースに文字を読み戻した後nullが返される。
+     * @param source 文字列ソース
+     * @return NULL型Value。別型の可能性がある場合はnull。
+     * @throws IOException 入力エラー
+     * @throws JsParseException 不正トークンもしくは意図しない入力終了
+     */
+    static JsNull parseNull(JsonSource source)
+            throws IOException, JsParseException{
+        char charHead = source.readOrDie();
+
+        if(charHead != 'n'){
+            source.unread(charHead);
+            return null;
+        }
+
+        if( ! source.matchOrDie("ull") ){
+            throw new JsParseException(JsParseException.ERRMSG_INVALIDTOKEN,
+                                       source.getLineNumber() );
+        }
+
+        return JsNull.NULL;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 常に{@link JsTypes#NULL}を返す。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public JsTypes getJsTypes(){
+        return JsTypes.NULL;
+    }
+
+    /**
+     * 各種構造の出現をビジターに通知する。
+     * この実装ではthisの出現のみを通知する。
+     * @param visitor {@inheritDoc}
+     * @throws JsVisitException {@inheritDoc}
+     */
+    @Override
+    public void traverse(ValueVisitor visitor)
+            throws JsVisitException{
+        visitor.visitValue(this);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * ハッシュ値を返す。
+     * 常に{@value ONLYHASH}を返す。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int hashCode(){
+        return ONLYHASH;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 等価判定を行う。
+     * {@link #NULL}が渡された時のみtrueを返す。
+     * @param obj {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object obj){
+        if(this == obj) return true;
+        if(obj instanceof JsNull) return true;  // シングルトンには冗長
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     * NULL型Valueを順序付ける。シングルトン相手にほぼ無意味。
+     * null以外の引数には必ず0を返す。
+     * @param value {@inheritDoc}
+     * @return {@inheritDoc}
+     * @throws NullPointerException 引数がnull
+     */
+    @Override
+    public int compareTo(JsNull value) throws NullPointerException{
+        if(value == null) throw new NullPointerException();
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 文字列表現を返す。
+     * 常に文字列 {@value TEXT} を返す。
+     * JSON表記の一部としての利用も可能。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        return TEXT;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/JsNumber.java b/src/main/java/jp/sourceforge/jovsonz/JsNumber.java
new file mode 100644 (file)
index 0000000..8185715
--- /dev/null
@@ -0,0 +1,427 @@
+/*
+ * JSON number value
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.MathContext;
+import java.math.RoundingMode;
+
+/**
+ * JSON NUMBER型Valueを表す。
+ * 整数、実数を含めた数値を反映する。
+ * <p>
+ * 10を基数とした{@link java.math.BigDecimal}を実装ベースとする。
+ * ※ IEEE754浮動小数ではない。
+ * </p>
+ * <p>(1)と(1.0)はスケール値によって区別される</p>
+ * <h1>表記例</h1>
+ * <pre>
+ * -43
+ * 0.56
+ * 3.23E-06
+ * </pre>
+ * @see java.math.BigDecimal
+ */
+public class JsNumber
+        implements JsValue, Comparable<JsNumber> {
+
+    private static final MathContext DEF_MC =
+            new MathContext(0, RoundingMode.UNNECESSARY);
+
+    private static final String ERRMSG_INVFRAC =
+            "invalid fractional number";
+    private static final String ERRMSG_NONUMBER =
+            "no number";
+    private static final String ERRMSG_EXTRAZERO =
+            "extra zero found";
+
+    private final BigDecimal decimal;
+
+    /**
+     * コンストラクタ。
+     * @param val 初期整数値
+     */
+    public JsNumber(long val){
+        this(BigDecimal.valueOf(val));
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * <p>
+     * {@link java.math.BigDecimal#valueOf(double)}と同等の丸めが行われる。
+     * (1.0/10.0)を渡すと0.1相当になる。
+     * 必要に応じて{@link java.math.BigDecimal}を
+     * 引数に持つコンストラクタと使い分けること。
+     * </p>
+     * @param val 初期実数値
+     * @see java.math.BigDecimal#valueOf(double)
+     */
+    public JsNumber(double val){
+        this(BigDecimal.valueOf(val));
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * @param val 初期整数値
+     * @throws ArithmeticException 正確な結果を
+     * {@link java.math.BigDecimal}に納め切れない
+     */
+    public JsNumber(BigInteger val) throws ArithmeticException{
+        this(new BigDecimal(val, DEF_MC));
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * 書式は{@link java.math.BigDecimal#BigDecimal(String)}に準ずる。
+     * @param val 初期数値の文字列表記
+     * @throws NumberFormatException 不正な数値表記
+     * @throws ArithmeticException 正確な結果を
+     * {@link java.math.BigDecimal}に納め切れない
+     * @see java.math.BigDecimal#BigDecimal(String)
+     */
+    public JsNumber(CharSequence val)
+            throws NumberFormatException, ArithmeticException{
+        this(new BigDecimal(val.toString(), DEF_MC));
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * @param val 初期数値
+     * @throws NullPointerException 引数がnull
+     */
+    public JsNumber(BigDecimal val) throws NullPointerException{
+        super();
+        if(val == null) throw new NullPointerException();
+        this.decimal = val;
+        return;
+    }
+
+    /**
+     * 任意の文字がUnicodeのBasic-Latinの数字か否か判定する。
+     * @param ch 文字
+     * @return 数字ならtrue
+     * @see java.lang.Character#isDigit(char)
+     */
+    public static boolean isLatinDigit(char ch){
+        if('0' <= ch && ch <= '9') return true;
+        return false;
+    }
+
+    /**
+     * 文字ソースから符号付きの数字並びを読み込む。
+     * 先頭'+'符号は読み飛ばされる。
+     * 冒頭のゼロ'0'に続く数字を許すか否か指定が可能。
+     * <p>NUMBER型表記の整数部、小数部、指数部読み込みの下請けメソッド。</p>
+     * @param source 文字列ソース
+     * @param app 出力先
+     * @param allowZeroTrail 冒頭のゼロ'0'に続く数字を許すならtrue
+     * @return 引数と同じ出力先
+     * @throws IOException 入出力エラー
+     * @throws JsParseException 不正な書式もしくは意図しない入力終了
+     */
+    private static Appendable appendDigitText(JsonSource source,
+                                              Appendable app,
+                                              boolean allowZeroTrail)
+            throws IOException, JsParseException{
+        char head = source.readOrDie();
+        if     (head == '-') app.append('-');
+        else if(head != '+') source.unread(head);
+
+        boolean hasAppended = false;
+        boolean zeroStarted = false;    // 先頭は0か
+        for(;;){
+            if( ! source.hasMore() && hasAppended) break;
+
+            char readedCh = source.readOrDie();
+
+            if( ! isLatinDigit(readedCh) ){
+                if( ! hasAppended ){
+                    throw new JsParseException(ERRMSG_NONUMBER,
+                                               source.getLineNumber() );
+                }
+                source.unread(readedCh);
+                break;
+            }
+
+            if(hasAppended){
+                if(zeroStarted && ! allowZeroTrail){
+                    throw new JsParseException(ERRMSG_EXTRAZERO,
+                                               source.getLineNumber() );
+                }
+            }else{                       // 1st char
+                if(readedCh == '0'){
+                    zeroStarted = true;
+                }
+            }
+
+            app.append(readedCh);
+            hasAppended = true;
+        }
+
+        return app;
+    }
+
+    /**
+     * 文字ソースから、ピリオド「.」で始まるNUMBER型小数部を読み込む。
+     * 小数部がなければなにもせずに戻る。
+     * @param source 文字列ソース
+     * @param app 出力先
+     * @return 引数と同じ出力先
+     * @throws IOException 入出力エラー
+     * @throws JsParseException 不正な書式もしくは意図しない入力終了
+     */
+    private static Appendable appendFractionPart(JsonSource source,
+                                                 Appendable app )
+            throws IOException, JsParseException{
+        if( ! source.hasMore() ) return app;
+
+        char chData;
+
+        chData = source.readOrDie();
+        if(chData != '.'){
+            source.unread(chData);
+            return app;
+        }
+
+        app.append(".");
+
+        boolean hasAppended = false;
+        for(;;){
+            if( ! source.hasMore() && hasAppended) break;
+
+            chData = source.readOrDie();
+
+            if( ! isLatinDigit(chData) ){
+                if( ! hasAppended ){
+                    throw new JsParseException(ERRMSG_INVFRAC,
+                                               source.getLineNumber());
+                }
+                source.unread(chData);
+                break;
+            }
+
+            app.append(chData);
+            hasAppended = true;
+        }
+
+        return app;
+    }
+
+    /**
+     * 文字ソースから「e」もしくは「E」で始まるNUMBER型指数部を読み込む。
+     * 指数部がなければなにもせずに戻る。
+     * @param source 文字列ソース
+     * @param app 出力先
+     * @return 引数と同じ出力先
+     * @throws IOException 入出力エラー
+     * @throws JsParseException 不正な書式もしくは意図しない入力終了
+     */
+    private static Appendable appendExpPart(JsonSource source,
+                                            Appendable app )
+            throws IOException, JsParseException{
+        if( ! source.hasMore() ) return app;
+
+        char chData = source.readOrDie();
+        if(chData != 'e' && chData != 'E'){
+            source.unread(chData);
+            return app;
+        }
+
+        app.append('E');
+
+        appendDigitText(source, app, true);
+
+        return app;
+    }
+
+    /**
+     * JSON文字列ソースからNUMBER型Valueを読み込む。
+     * 別型の可能性のある先頭文字を読み込んだ場合、
+     * ソースに文字を読み戻した後nullが返される。
+     * @param source 文字列ソース
+     * @return NUMBER型Value。別型の可能性がある場合はnull。
+     * @throws IOException 入力エラー
+     * @throws JsParseException 不正な表記もしくは意図しない入力終了
+     */
+    static JsNumber parseNumber(JsonSource source)
+            throws IOException, JsParseException{
+        char charHead = source.readOrDie();
+        source.unread(charHead);
+        if( charHead != '-' && ! JsNumber.isLatinDigit(charHead) ){
+            return null;
+        }
+
+        StringBuilder numText = new StringBuilder();
+
+        appendDigitText   (source, numText, false);
+        appendFractionPart(source, numText);
+        appendExpPart     (source, numText);
+
+        JsNumber result = new JsNumber(numText);
+
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 常に{@link JsTypes#NUMBER}を返す。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public JsTypes getJsTypes(){
+        return JsTypes.NUMBER;
+    }
+
+    /**
+     * 各種構造の出現をビジターに通知する。
+     * この実装ではthisの出現のみを通知する。
+     * @param visitor {@inheritDoc}
+     * @throws JsVisitException {@inheritDoc}
+     */
+    @Override
+    public void traverse(ValueVisitor visitor)
+            throws JsVisitException{
+        visitor.visitValue(this);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * ハッシュ値を返す。
+     * {@link java.math.BigDecimal#hashCode()}と同じ値を返す。
+     * @return {@inheritDoc}
+     * @see java.math.BigDecimal#hashCode()
+     */
+    @Override
+    public int hashCode(){
+        return this.decimal.hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 等価判定を行う。
+     * {@link java.math.BigDecimal#equals(Object)}と同等の判断が行われる。
+     * 「1.2」と「0.12E+1」など、
+     * スケールの一致しない値は異なる値と見なされる。
+     * @param obj {@inheritDoc}
+     * @return {@inheritDoc}
+     * @see java.math.BigDecimal#equals(Object)
+     */
+    @Override
+    public boolean equals(Object obj){
+        if(this == obj) return true;
+        if( ! (obj instanceof JsNumber) ) return false;
+        JsNumber number = (JsNumber) obj;
+        return this.decimal.equals(number.decimal);
+    }
+
+    /**
+     * {@inheritDoc}
+     * NUMBER型Valueを昇順に順序付ける。
+     * 「1.2」と「0.12E+1」など、スケールが異なっても値が同じであれば
+     * 等しい値と見なされる。
+     * @param value {@inheritDoc}
+     * @return {@inheritDoc}
+     * @see java.math.BigDecimal#compareTo(BigDecimal)
+     */
+    @Override
+    public int compareTo(JsNumber value){
+        if(this == value) return 0;
+        return this.decimal.compareTo(value.decimal);
+    }
+
+    /**
+     * int型の数値を返す。
+     * 情報が失われる可能性がある。
+     * @return int型数値
+     * @see java.lang.Number#intValue()
+     * @see java.math.BigDecimal#intValue()
+     */
+    public int intValue(){
+        return this.decimal.intValue();
+    }
+
+    /**
+     * long型の数値を返す。
+     * 情報が失われる可能性がある。
+     * @return long型数値
+     * @see java.lang.Number#longValue()
+     * @see java.math.BigDecimal#longValue()
+     */
+    public long longValue(){
+        return this.decimal.longValue();
+    }
+
+    /**
+     * float型の数値を返す。
+     * 情報が失われる可能性がある。
+     * @return float型数値
+     * @see java.lang.Number#floatValue()
+     * @see java.math.BigDecimal#floatValue()
+     */
+    public float floatValue(){
+        return this.decimal.floatValue();
+    }
+
+    /**
+     * double型の数値を返す。
+     * 情報が失われる可能性がある。
+     * @return double型数値
+     * @see java.lang.Number#doubleValue()
+     * @see java.math.BigDecimal#doubleValue()
+     */
+    public double doubleValue(){
+        return this.decimal.doubleValue();
+    }
+
+    /**
+     * {@link java.math.BigDecimal}型の数値表現を返す。
+     * @return BigDecimal型数値
+     */
+    public BigDecimal decimalValue(){
+        return this.decimal;
+    }
+
+    /**
+     * スケール値を返す。
+     * このインスタンスが整数文字列表記に由来する場合、
+     * スケール値は0になるはず。
+     * <p>
+     * <ul>
+     * <li>"99"のスケール値は0
+     * <li>"99.0"のスケール値は1
+     * <li>"99.01"のスケール値は2
+     * <li>"99E+3"のスケール値は-3
+     * <li>"99.0E+3"のスケール値は-2
+     * </ul>
+     * </p>
+     * @return スケール値
+     * @see java.math.BigDecimal#scale()
+     */
+    public int scale(){
+        return this.decimal.scale();
+    }
+
+    /**
+     * 文字列表現を返す。
+     * {@link java.math.BigDecimal#toString()}に準ずる。
+     * JSON表記の一部としての利用も可能。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        return this.decimal.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/JsObject.java b/src/main/java/jp/sourceforge/jovsonz/JsObject.java
new file mode 100644 (file)
index 0000000..49cd49a
--- /dev/null
@@ -0,0 +1,374 @@
+/*
+ * JSON object value
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * JSON OBJECT型Valueを表す。
+ * PAIR名と子要素の組(PAIR)の集合を反映する。
+ * PAIR名の並び順に関しては未定義とする。
+ * <h1>表記例</h1>
+ * <pre>
+ * {
+ *     "Name" : "Joe" ,
+ *     "Age" : 19
+ * }
+ * </pre>
+ */
+public class JsObject
+        implements JsComposition<JsPair> {
+
+    private static final String ERRMSG_NOOBJECTCOMMA =
+            "missing comma in OBJECT";
+    private static final String ERRMSG_NOHASHNAME =
+            "no hash name in OBJECT";
+    private static final String ERRMSG_NOHASHSEP =
+            "missing hash separator(:) in OBJECT";
+    private static final String ERRMSG_NOHASHVAL =
+            "no hash value in OBJECT";
+
+    private final Map<String, JsPair> pairMap =
+            new TreeMap<String, JsPair>();
+    private final Collection<JsPair> pairCollection = this.pairMap.values();
+
+    private boolean changed = false;
+
+
+    /**
+     * コンストラクタ。
+     */
+    public JsObject(){
+        super();
+        return;
+    }
+
+    /**
+     * JSON文字列ソースからOBJECT型Valueを読み込む。
+     * さらに子Valueへとパース解析が進む可能性がある。
+     * 別型の可能性のある先頭文字を読み込んだ場合、
+     * ソースに文字を読み戻した後nullが返される。
+     * @param source 文字列ソース
+     * @return OBJECT型Value。別型の可能性がある場合はnull。
+     * @throws IOException 入力エラー
+     * @throws JsParseException 不正な表記もしくは意図しない入力終了
+     */
+    static JsObject parseObject(JsonSource source)
+            throws IOException, JsParseException{
+        char charHead = source.readOrDie();
+        if(charHead != '{'){
+            source.unread(charHead);
+            return null;
+        }
+
+        JsObject result = new JsObject();
+
+        for(;;){
+            source.skipWhiteSpace();
+            char chData = source.readOrDie();
+            if(chData == '}') break;
+
+            if(result.isEmpty()){
+                source.unread(chData);
+            }else{
+                if(chData != ','){
+                    throw new JsParseException(ERRMSG_NOOBJECTCOMMA,
+                                               source.getLineNumber() );
+                }
+                source.skipWhiteSpace();
+            }
+
+            JsString name = JsString.parseString(source);
+            if(name == null){
+                throw new JsParseException(ERRMSG_NOHASHNAME,
+                                           source.getLineNumber() );
+            }
+
+            source.skipWhiteSpace();
+            chData = source.readOrDie();
+            if(chData != ':'){
+                throw new JsParseException(ERRMSG_NOHASHSEP,
+                                           source.getLineNumber() );
+            }
+
+            JsValue value = Json.parseValue(source);
+            if(value == null){
+                throw new JsParseException(ERRMSG_NOHASHVAL,
+                                           source.getLineNumber() );
+            }
+
+            result.putValue(name.toRawString(), value);
+        }
+
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 常に{@link JsTypes#OBJECT}を返す。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public JsTypes getJsTypes(){
+        return JsTypes.OBJECT;
+    }
+
+    /**
+     * このValueおよび子孫に変更があったか判定する。
+     * PAIRの追加・削除が行われたか、
+     * もしくはPAIRのValue値いずれかに変更が認められれば、
+     * このOBJECT型Valueに変更があったとみなされる。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public boolean hasChanged(){
+        if(this.changed) return true;
+
+        for(JsPair pair : this){
+            JsValue value = pair.getValue();
+            if( ! (value instanceof JsComposition) ) continue;
+            JsComposition composition = (JsComposition) value;
+            if(composition.hasChanged()) return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * このValueおよび子孫に変更がなかったことにする。
+     */
+    @Override
+    public void setUnchanged(){
+        this.changed = false;
+
+        for(JsPair pair : this){
+            JsValue value = pair.getValue();
+            if( ! (value instanceof JsComposition) ) continue;
+            JsComposition composition = (JsComposition) value;
+            composition.setUnchanged();
+        }
+
+        return;
+    }
+
+    /**
+     * 深さ優先探索を行い各種構造の出現をビジターに通知する。
+     * thisを通知した後、PAIRの各名前およびValueを順に訪問し、
+     * 最後に閉じ括弧を通知する。
+     * PAIRの訪問順に関しては未定義。
+     * @param visitor {@inheritDoc}
+     * @throws JsVisitException {@inheritDoc}
+     */
+    @Override
+    public void traverse(ValueVisitor visitor) throws JsVisitException{
+        visitor.visitValue(this);
+
+        for(JsPair pair : this){
+            String name   = pair.getName();
+            JsValue value = pair.getValue();
+            visitor.visitPairName(name);
+            value.traverse(visitor);
+        }
+
+        visitor.visitCompositionClose(this);
+
+        return;
+    }
+
+    /**
+     * PAIR総数を返す。
+     * @return PAIR総数
+     */
+    @Override
+    public int size(){
+        return this.pairMap.size();
+    }
+
+    /**
+     * PAIR集合が空か判定する。
+     * @return 空ならtrue
+     */
+    @Override
+    public boolean isEmpty(){
+        return this.pairMap.isEmpty();
+    }
+
+    /**
+     * PAIR集合を空にする。
+     */
+    @Override
+    public void clear(){
+        if(this.pairMap.size() > 0) this.changed = true;
+        this.pairMap.clear();
+        return;
+    }
+
+    /**
+     * ハッシュ値を返す。
+     * 全てのPAIRのハッシュ値からその都度合成される。高コスト注意!。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int hashCode(){
+        return this.pairMap.hashCode();
+    }
+
+    /**
+     * 等価判定を行う。
+     * 双方のPAIR数が一致し、
+     * 全てのPAIR名およびそれに対応付けられたValueが一致した場合のみ
+     * 等価と判断される。
+     * @param obj {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object obj){
+        if(this == obj) return true;
+
+        if( ! (obj instanceof JsObject) ) return false;
+        JsObject composit = (JsObject) obj;
+
+        return this.pairMap.equals(composit.pairMap);
+    }
+
+    /**
+     * 名前とValueからPAIRを登録する。
+     * @param name 名前
+     * @param value Value
+     * @return 旧Value。同じ内容のPAIRがすでに存在していたらnull
+     * @throws NullPointerException 引数のいずれかがnull
+     */
+    public JsValue putValue(String name, JsValue value)
+            throws NullPointerException{
+        if(name  == null) throw new NullPointerException();
+        if(value == null) throw new NullPointerException();
+
+        JsValue oldValue = null;
+        JsPair oldPair = this.pairMap.get(name);
+        if(oldPair != null){
+            oldValue = oldPair.getValue();
+            if(value.equals(oldValue)) return null;
+        }
+
+        JsPair newPair = new JsPair(name, value);
+        this.pairMap.put(name, newPair);
+
+        this.changed = true;
+        return oldValue;
+    }
+
+    /**
+     * PAIR名からValueを取得する。
+     * @param name PAIR名
+     * @return 対応するValue。見つからなければnull
+     */
+    public JsValue getValue(String name){
+        JsPair pair = this.pairMap.get(name);
+        if(pair == null) return null;
+        JsValue value = pair.getValue();
+        return value;
+    }
+
+    /**
+     * PAIRを追加する。
+     * 同じPAIR名を持つPAIRは無条件に上書きされる。
+     * @param pair PAIR
+     */
+    public void putPair(JsPair pair){
+        this.pairMap.put(pair.getName(), pair);
+        return;
+    }
+
+    /**
+     * PAIR名からPAIRを返す。
+     * @param name PAIR名
+     * @return PAIR。見つからなければnull
+     */
+    public JsPair getPair(String name){
+        JsValue value = getValue(name);
+        if(value == null) return null;
+
+        return new JsPair(name, value);
+    }
+
+    /**
+     * 指定した名前のPAIRを削除する。
+     * @param name PAIR名
+     * @return 消されたPAIR。該当するPAIRがなければnull
+     */
+    public JsPair remove(String name){
+        JsPair oldPair = this.pairMap.remove(name);
+        if(oldPair != null) this.changed = true;
+
+        return oldPair;
+    }
+
+    /**
+     * 保持する全PAIRのPAIR名の集合を返す。
+     * @return すべての名前
+     */
+    public Set<String> nameSet(){
+        return this.pairMap.keySet();
+    }
+
+    /**
+     * PAIRのリストを返す。
+     * このリストを上書き操作しても影響はない。
+     * @return PAIRリスト
+     */
+    public List<JsPair> getPairList(){
+        List<JsPair> result = new ArrayList<JsPair>(this.pairMap.size());
+
+        for(JsPair pair : this){
+            result.add(pair);
+        }
+
+        return result;
+    }
+
+    /**
+     * PAIRにアクセスするための反復子を提供する。
+     * この反復子での削除作業はできない。
+     * PAIR出現順序は未定義。
+     * @return 反復子イテレータ
+     */
+    public Iterator<JsPair> iterator(){
+        return UnmodIterator.unmodIterator(this.pairCollection);
+    }
+
+    /**
+     * 文字列表現を返す。
+     * JSON表記の全体もしくは一部としての利用も可能。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder text = new StringBuilder();
+
+        text.append("{");
+
+        boolean hasElem = false;
+        for(JsPair pair : this){
+            if(hasElem) text.append(',');
+            text.append(pair);
+            hasElem = true;
+        }
+
+        text.append("}");
+
+        return text.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/JsPair.java b/src/main/java/jp/sourceforge/jovsonz/JsPair.java
new file mode 100644 (file)
index 0000000..4e4f5d6
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * JSON pair in object
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.io.IOException;
+
+/**
+ * OBJECT型Value内に列挙される、名前の付いたValueとの組(PAIR)。
+ * PAIRはValueではない。
+ * <p>
+ * <code>
+ * <pre>
+ * {
+ *     "PairName1" : 99.9 ,
+ *     "PairName2" : "textValue"
+ * }
+ * </pre>
+ * </code>
+ * </p>
+ */
+public class JsPair {
+
+    private final String name;
+    private final JsValue value;
+
+    /**
+     * コンストラクタ。
+     * @param name PAIR名
+     * @param value PAIR名に対応付けられるValue
+     * @throws NullPointerException 引数のいずれかがnull
+     */
+    public JsPair(String name, JsValue value)
+            throws NullPointerException{
+        super();
+
+        if(name  == null || value == null) throw new NullPointerException();
+
+        this.name = name;
+        this.value = value;
+
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * STRING型をValueに持つPAIRが生成される。
+     * @param name PAIR名
+     * @param text PAIR名に対応付けられる文字列データ。
+     * エスケープされる前段階の表記。
+     * @throws NullPointerException 引数がnull
+     */
+    public JsPair(String name, CharSequence text)
+            throws NullPointerException{
+        this(name, (JsValue) new JsString(text) );
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * BOOLEAN型をValueに持つPAIRが生成される。
+     * @param name PAIR名
+     * @param bool PAIR名に対応付けられる真偽値
+     * @throws NullPointerException PAIR名がnull
+     */
+    public JsPair(String name, boolean bool)
+            throws NullPointerException{
+        this(name, JsBoolean.valueOf(bool));
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * NUMBER型をValueに持つPAIRが生成される。
+     * @param name PAIR名
+     * @param number PAIR名に対応付けられる整数値
+     * @throws NullPointerException PAIR名がnull
+     */
+    public JsPair(String name, long number)
+            throws NullPointerException{
+        this(name, new JsNumber(number));
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * NUMBER型をValueに持つPAIRが生成される。
+     * @param name PAIR名
+     * @param number PAIR名に対応付けられる実数値
+     * @throws NullPointerException PAIR名がnull
+     */
+    public JsPair(String name, double number)
+            throws NullPointerException{
+        this(name, new JsNumber(number));
+        return;
+    }
+
+    /**
+     * PAIR名を返す。
+     * @return PAIR名
+     */
+    public String getName(){
+        return this.name;
+    }
+
+    /**
+     * Valueを返す。
+     * @return Value
+     */
+    public JsValue getValue(){
+        return this.value;
+    }
+
+    /**
+     * {@inheritDoc}
+     * ハッシュ値を返す。
+     * PAIR名とValue双方のハッシュ値から合成される。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int hashCode(){
+        int nameHash = this.name.hashCode();
+        int valHash = this.value.hashCode();
+        return nameHash ^ valHash;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 等価判定を行う。
+     * PAIR名とValue双方が一致する場合のみ真となる。
+     * @param obj {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object obj){
+        if(this == obj) return true;
+
+        if( ! (obj instanceof JsPair) ) return false;
+        JsPair target = (JsPair) obj;
+
+        if( ! this.name .equals(target.name)  ) return false;
+        if( ! this.value.equals(target.value) ) return false;
+
+        return true;
+    }
+
+    /**
+     * 文字列表現を返す。
+     * JSON表記の一部としての利用も可能。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder result = new StringBuilder();
+        try{
+            JsString.dumpString(result, this.name);
+        }catch(IOException e){
+            assert false;
+            throw new AssertionError(e);
+        }
+
+        result.append(':')
+            .append(this.value.toString());
+
+        return result.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/JsParseException.java b/src/main/java/jp/sourceforge/jovsonz/JsParseException.java
new file mode 100644 (file)
index 0000000..094da39
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * JSON parse error information
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+/**
+ * 入力文字列パース中断例外。
+ * JSON文字列ソースへのパース処理の中断時に投げられる。
+ */
+@SuppressWarnings("serial")
+public class JsParseException extends Exception {
+
+    static final String ERRMSG_INVALIDTOKEN =
+            "invalid JSON token";
+    static final String ERRMSG_INVALIDROOT =
+            "top root JSON value must be OBJECT or ARRAY";
+    static final String ERRMSG_NODATA =
+            "We need but no more JSON data";
+
+    private static final int LINE_UNKNOWN = 0;
+
+    private final int lineNumber;
+
+    /**
+     * コンストラクタ。
+     */
+    public JsParseException(){
+        this(null, LINE_UNKNOWN);
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * @param message 詳細メッセージ。不明な場合はnull
+     * @param lineNumber 行番号。不明な場合は0以下の値
+     */
+    public JsParseException(String message, int lineNumber){
+        this(message, (Throwable) null, lineNumber);
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * @param message 詳細メッセージ。不明な場合はnull
+     * @param cause 原因となった例外。不明な場合はnull
+     * @param lineNumber 行番号。不明な場合は0以下の値
+     */
+    public JsParseException(String message, Throwable cause, int lineNumber){
+        super(message, cause);
+        this.lineNumber = lineNumber;
+        return;
+    }
+
+    /**
+     * パースエラーの起きた行番号を返す。
+     * @return 行番号。不明な場合は0以下の値。
+     */
+    public int getLineNumber(){
+        return this.lineNumber;
+    }
+
+    /**
+     * 有効な行番号を保持しているか判定する。
+     * @return 有効な行番号(1以上)を保持していればtrue
+     */
+    public boolean hasValidLineNumber(){
+        if(this.lineNumber > 0) return true;
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 有効な行番号があれば一緒に出力される。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String getMessage(){
+        StringBuilder message = new StringBuilder();
+
+        String superMessage = super.getMessage();
+        if(superMessage != null){
+            message.append(superMessage);
+        }
+
+        if(hasValidLineNumber()){
+            if(message.length() > 0) message.append(' ');
+            message.append("[line:").append(this.lineNumber).append(']');
+        }
+
+        if(message.length() <= 0) return null;
+        return message.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/JsString.java b/src/main/java/jp/sourceforge/jovsonz/JsString.java
new file mode 100644 (file)
index 0000000..3f2ecc3
--- /dev/null
@@ -0,0 +1,380 @@
+/*
+ * JSON string value
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.io.IOException;
+
+/**
+ * JSON STRING型Valueを表す。
+ * Unicode文字列データを反映する。
+ * <h1>表記例</h1>
+ * <pre>
+ * "xyz"
+ * "漢"
+ * "foo\nbar"
+ * "{@literal \}u304a"
+ * ""
+ * </pre>
+ */
+public class JsString
+        implements JsValue, CharSequence, Comparable<JsString> {
+
+    private static final int HEX_BASE = 16;
+    private static final int NIBBLE_WIDE = 4;
+    private static final int NIBBLES_CHAR = Character.SIZE / NIBBLE_WIDE;
+
+    private static final String ERRMSG_INVESC = "invalid escape character";
+    private static final String ERRMSG_INVCTR = "invalid control character";
+
+    private final String rawText;
+
+    /**
+     * コンストラクタ。
+     * 長さ0の空文字が設定される。
+     */
+    public JsString(){
+        this("");
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * 引数はJSON書式ではない生文字列。
+     * @param rawSeq 生文字列
+     * @throws NullPointerException 引数がnull
+     */
+    public JsString(CharSequence rawSeq) throws NullPointerException{
+        super();
+        if(rawSeq == null) throw new NullPointerException();
+        this.rawText = rawSeq.toString();
+        return;
+    }
+
+    /**
+     * FFFF形式4桁で16進エスケープされた文字列を読み、
+     * char1文字にデコードする。
+     * @param source 文字列ソース
+     * @return 文字
+     * @throws IOException 入力エラー
+     * @throws JsParseException 不正表記もしくは意図しない入力終了
+     */
+    static char parseHexChar(JsonSource source)
+            throws IOException, JsParseException{
+        char hex1Ch = source.readOrDie();
+        char hex2Ch = source.readOrDie();
+        char hex3Ch = source.readOrDie();
+        char hex4Ch = source.readOrDie();
+
+        int digit1 = Character.digit(hex1Ch, HEX_BASE);
+        int digit2 = Character.digit(hex2Ch, HEX_BASE);
+        int digit3 = Character.digit(hex3Ch, HEX_BASE);
+        int digit4 = Character.digit(hex4Ch, HEX_BASE);
+
+        if(   digit1 < 0
+           || digit2 < 0
+           || digit3 < 0
+           || digit4 < 0 ){
+            throw new JsParseException(ERRMSG_INVESC, source.getLineNumber());
+        }
+
+        int digit = 0;
+        digit += digit1;
+        digit <<= NIBBLE_WIDE;
+        digit += digit2;
+        digit <<= NIBBLE_WIDE;
+        digit += digit3;
+        digit <<= NIBBLE_WIDE;
+        digit += digit4;
+
+        char result = (char) digit;
+
+        return result;
+    }
+
+    /**
+     * '\'に続くスペシャルキャラの読み込みを行う。
+     * @param source 文字列ソース
+     * @param app スペシャルキャラ格納文字列
+     * @throws IOException 入出力エラー
+     * @throws JsParseException "\z"などの不正なスペシャルキャラ
+     * もしくは意図しない入力終了
+     */
+    private static void parseSpecial(JsonSource source, Appendable app)
+            throws IOException, JsParseException{
+        char special;
+
+        char chData = source.readOrDie();
+        switch(chData){
+        case '"':  special = '"';  break;
+        case '\\': special = '\\'; break;
+        case '/':  special = '/';  break;
+        case 'b':  special = '\b'; break;
+        case 'f':  special = '\f'; break;
+        case 'n':  special = '\n'; break;
+        case 'r':  special = '\r'; break;
+        case 't':  special = '\t'; break;
+        case 'u':  special = parseHexChar(source); break;
+        default:
+            throw new JsParseException(ERRMSG_INVESC, source.getLineNumber());
+        }
+
+        app.append(special);
+
+        return;
+    }
+
+    /**
+     * JSON文字列ソースからSTRING型Valueを読み込む。
+     * 別型の可能性のある先頭文字を読み込んだ場合、
+     * ソースに文字を読み戻した後nullが返される。
+     * @param source 文字列ソース
+     * @return STRING型Value。別型の可能性がある場合はnull。
+     * @throws IOException 入力エラー
+     * @throws JsParseException 不正な表記もしくは意図しない入力終了
+     */
+    static JsString parseString(JsonSource source)
+            throws IOException, JsParseException{
+        char charHead = source.readOrDie();
+        if(charHead != '"'){
+            source.unread(charHead);
+            return null;
+        }
+
+        StringBuilder text = new StringBuilder();
+
+        for(;;){
+            char chData = source.readOrDie();
+            if(chData == '"') break;
+
+            if(chData == '\\'){
+                parseSpecial(source, text);
+            }else if(Character.isISOControl(chData)){
+                throw new JsParseException(ERRMSG_INVCTR,
+                                           source.getLineNumber());
+            }else{
+                text.append(chData);
+            }
+        }
+
+        JsString result = new JsString(text);
+
+        return result;
+    }
+
+    /**
+     * 任意の文字からエスケープ出力用シンボルを得る。
+     * このシンボルは'\'に続けて用いられる1文字である。
+     * 'u'を返す事はありえない。
+     * @param ch 任意の文字
+     * @return エスケープ出力用シンボル。
+     * 1文字エスケープの必要がない場合は'\0'
+     */
+    private static char escapeSymbol(char ch){
+        char result;
+        switch(ch){
+        case '"' : result = '"';  break;
+        case '\\': result = '\\'; break;
+        case '/' : result = '/';  break;
+        case '\b': result = 'b';  break;
+        case '\f': result = 'f';  break;
+        case '\n': result = 'n';  break;
+        case '\r': result = 'r';  break;
+        case '\t': result = 't';  break;
+        default:   result = '\0'; break;
+        }
+        return result;
+    }
+
+    /**
+     * 特殊文字をエスケープ出力する。
+     * 特殊文字でなければなにもしない。
+     * @param appout 出力先
+     * @param ch 文字
+     * @return 特殊文字出力がエスケープされた時にtrue
+     * @throws IOException 出力エラー
+     */
+    private static boolean dumpSpecialChar(Appendable appout, char ch)
+            throws IOException{
+        char esc1ch = escapeSymbol(ch);
+
+        if(esc1ch != '\0'){
+            appout.append('\\').append(esc1ch);
+        }else if(Character.isISOControl(ch)){
+            // TODO さらなる高速化が必要
+            String hex = "0000" + Integer.toHexString(ch);
+            hex = hex.substring(hex.length() - NIBBLES_CHAR);
+            appout.append("\\u").append(hex);
+        }else{
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * JSON STRING型Value形式で文字列を出力する。
+     * @param appout 文字出力
+     * @param seq 文字列
+     * @throws IOException 出力エラー
+     */
+    public static void dumpString(Appendable appout, CharSequence seq)
+            throws IOException{
+        appout.append('"');
+
+        int length = seq.length();
+        for(int pos = 0; pos < length; pos++){
+            char ch = seq.charAt(pos);
+            if( ! dumpSpecialChar(appout, ch) ){
+                appout.append(ch);
+            }
+        }
+
+        appout.append('"');
+
+        return;
+    }
+
+    /**
+     * JSON STRING型Value形式の文字列を返す。
+     * @param seq 生文字列
+     * @return STRING型表記に変換された文字列
+     */
+    // TODO いらない
+    public static StringBuilder escapeText(CharSequence seq){
+        StringBuilder result = new StringBuilder();
+        try{
+            dumpString(result, seq);
+        }catch(IOException e){
+            assert false;
+            throw new AssertionError(e);
+        }
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 常に{@link JsTypes#STRING}を返す。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public JsTypes getJsTypes(){
+        return JsTypes.STRING;
+    }
+
+    /**
+     * 各種構造の出現をビジターに通知する。
+     * この実装ではthisの出現のみを通知する。
+     * @param visitor {@inheritDoc}
+     * @throws JsVisitException {@inheritDoc}
+     */
+    @Override
+    public void traverse(ValueVisitor visitor)
+            throws JsVisitException{
+        visitor.visitValue(this);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * ハッシュ値を返す。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int hashCode(){
+        return this.rawText.hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 等価判定を行う。
+     * {@link java.lang.String#equals(Object)}に準ずる。
+     * @param obj {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object obj){
+        if(this == obj) return true;
+
+        if( ! (obj instanceof JsString) ) return false;
+        JsString string = (JsString) obj;
+
+        return this.rawText.equals(string.rawText);
+    }
+
+    /**
+     * {@inheritDoc}
+     * STRING型Valueを昇順に順序付ける。
+     * {@link java.lang.String#compareTo(String)}に準ずる。
+     * @param value {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int compareTo(JsString value){
+        if(this == value) return 0;
+        if(value == null) return +1;
+        return this.rawText.compareTo(value.rawText);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 指定位置の文字を返す。
+     * @param index {@inheritDoc}
+     * @return {@inheritDoc}
+     * @throws IndexOutOfBoundsException {@inheritDoc}
+     */
+    @Override
+    public char charAt(int index)
+            throws IndexOutOfBoundsException{
+        return this.rawText.charAt(index);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 文字列長(char値総数)を返す。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int length(){
+        return this.rawText.length();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 部分文字列を返す。
+     * @param start {@inheritDoc}
+     * @param end {@inheritDoc}
+     * @return {@inheritDoc}
+     * @throws IndexOutOfBoundsException {@inheritDoc}
+     */
+    @Override
+    public CharSequence subSequence(int start, int end)
+            throws IndexOutOfBoundsException{
+        return this.rawText.subSequence(start, end);
+    }
+
+    /**
+     * クォーテーションやエスケープ処理の施されていない生の文字列を返す。
+     * @return 生の文字列
+     */
+    public String toRawString(){
+        return this.rawText;
+    }
+
+    /**
+     * {@inheritDoc}
+     * クォーテーションとエスケープ処理の施された文字列表記を生成する。
+     * JSON表記の一部としての利用も可能。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public String toString(){
+        StringBuilder string = escapeText(this.rawText);
+        return string.toString();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/JsTypes.java b/src/main/java/jp/sourceforge/jovsonz/JsTypes.java
new file mode 100644 (file)
index 0000000..3e00a13
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * JSON types
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+/**
+ * JSON 各種型列挙。
+ */
+public enum JsTypes {
+
+    /** NUMBER型に対応。 */
+    NUMBER  (JsNumber .class),
+    /** STRING型に対応。 */
+    STRING  (JsString .class),
+    /** BOOLEAN型に対応。 */
+    BOOLEAN (JsBoolean.class),
+    /** ARRAY型に対応。 */
+    ARRAY   (JsArray  .class),
+    /** OBJECT型に対応。 */
+    OBJECT  (JsObject .class),
+    /** NULL型に対応。 */
+    NULL    (JsNull   .class),
+    ;
+
+    private static final JsTypes[] VALUE_ARRAY = values();
+
+    private final Class<? extends JsValue> klass;
+    private final boolean isComposition;
+
+    /**
+     * コンストラクタ。
+     * @param klass {@link java.lang.Class}型
+     */
+    private JsTypes(Class<? extends JsValue> klass){
+        this.klass = klass;
+
+        if(JsComposition.class.isAssignableFrom(this.klass)){
+            this.isComposition = true;
+        }else{
+            this.isComposition = false;
+        }
+
+        return;
+    }
+
+    /**
+     * {@link java.lang.Class}型から対応する型列挙を返す。
+     * @param carg 任意のjava.lang.Class型変数
+     * @return 型列挙。JSON型に由来しないクラスが指定されたときはnull
+     * @throws NullPointerException 引数がnull
+     */
+    public static JsTypes getJsTypes(Class<?> carg)
+            throws NullPointerException{
+        if(carg == null) throw new NullPointerException();
+
+        for(JsTypes types : VALUE_ARRAY){
+            if(types.klass == carg) return types;
+        }
+
+        return null;
+    }
+
+    /**
+     * 対応する{@link java.lang.Class}型を返す。
+     * @return java.lang.Class型
+     */
+    public Class<? extends JsValue> getJsClass(){
+        return this.klass;
+    }
+
+    /**
+     * このJSON型が子要素を持ちうるか判定する。
+     * 子要素を持ちうるJSON型はOBJECT型かARRAY型のみ。
+     * @return 子要素を持ちうるならtrue
+     */
+    public boolean isComposition(){
+        return this.isComposition;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/JsValue.java b/src/main/java/jp/sourceforge/jovsonz/JsValue.java
new file mode 100644 (file)
index 0000000..9635760
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * JSON value common interface
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+/**
+ * JSON各種Value共通インタフェース。
+ */
+public interface JsValue {
+
+    /**
+     * 対応するJSON型列挙を返す。
+     * @return JSON型列挙
+     */
+    JsTypes getJsTypes();
+
+    /**
+     * 深さ優先探索を行い各種構造の出現をビジターに通知する。
+     * @param visitor ビジター
+     * @throws JsVisitException ビジターにより
+     * トラバース中断が判断された時に投げられる。
+     */
+    void traverse(ValueVisitor visitor) throws JsVisitException;
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/JsVisitException.java b/src/main/java/jp/sourceforge/jovsonz/JsVisitException.java
new file mode 100644 (file)
index 0000000..e976c47
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * JSON traverse error exception
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+/**
+ * トラバース中断例外。
+ * JSONツリー構造のトラバース処理の中断時に投げられる。
+ * <p>
+ * トラバース処理内部でIOExceptionなどのチェック例外が発生した場合、
+ * チェーン例外機構({@link java.lang.Throwable#getCause()} etc.)
+ * を用いるのが望ましい。
+ * </p>
+ */
+@SuppressWarnings("serial")
+public class JsVisitException extends Exception {
+
+    /**
+     * コンストラクタ。
+     */
+    public JsVisitException(){
+        super();
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * @param message 詳細メッセージ。不明な場合はnull
+     */
+    public JsVisitException(String message){
+        super(message);
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * @param message 詳細メッセージ。不明な場合はnull
+     * @param cause 原因となった例外。不明な場合はnull
+     */
+    public JsVisitException(String message, Throwable cause){
+        super(message, cause);
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * @param cause 原因となった例外。不明な場合はnull
+     */
+    public JsVisitException(Throwable cause){
+        super(cause);
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/Json.java b/src/main/java/jp/sourceforge/jovsonz/Json.java
new file mode 100644 (file)
index 0000000..b946308
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * JSON utilities
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * JSON各種共通ユーティリティ。
+ */
+public final class Json {
+
+    /** MIME タイプ。 */
+    public static final String MIME_TYPE = "application/json";
+
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private Json(){
+        assert false;
+        throw new AssertionError();
+    }
+
+    /**
+     * JSON最上位構造から文字出力を開始する。
+     * @param appout 出力先
+     * @param topValue OBJECT型かARRAY型のValue
+     * @throws NullPointerException 引数がnull
+     * @throws JsVisitException 何らかの理由で処理中断
+     * @throws IOException 出力エラー
+     */
+    public static void dumpJson(Appendable appout, JsComposition topValue)
+            throws NullPointerException,
+                   JsVisitException,
+                   IOException {
+        if(appout == null || topValue == null){
+            throw new NullPointerException();
+        }
+
+        JsonAppender appender = new JsonAppender(appout);
+
+        try{
+            topValue.traverse(appender);
+        }catch(JsVisitException e){
+            assert appender.hasIOException();
+            throw appender.getIOException();
+        }
+
+        return;
+    }
+
+    /**
+     * JSONの各種Valueを文字ソースから読み取る。
+     * @param source 文字入力
+     * @return 各種Value。
+     * 0個以上連続するホワイトスペースと共にソースの終わりに達したときはnull
+     * @throws IOException 入力エラー
+     * @throws JsParseException パースエラー
+     */
+    static JsValue parseValue(JsonSource source)
+            throws IOException, JsParseException{
+        source.skipWhiteSpace();
+        if( ! source.hasMore() ) return null;
+
+        JsValue result;
+        result = JsObject .parseObject (source);
+        if(result != null) return result;
+        result = JsArray  .parseArray  (source);
+        if(result != null) return result;
+        result = JsString .parseString (source);
+        if(result != null) return result;
+        result = JsNull   .parseNull   (source);
+        if(result != null) return result;
+        result = JsBoolean.parseBoolean(source);
+        if(result != null) return result;
+        result = JsNumber .parseNumber (source);
+
+        if(result == null){
+            throw new JsParseException(JsParseException.ERRMSG_INVALIDTOKEN,
+                                       source.getLineNumber() );
+        }
+
+        return result;
+    }
+
+    /**
+     * JSONの最上位構造を文字ソースから読み取る。
+     * @param source 文字入力ソース
+     * @return JSON最上位構造。OBJECT型かARRAY型のいずれか。
+     * 入力が0個以上のホワイトスペースのみで埋められていた場合はnull。
+     * @throws IOException 入力エラー
+     * @throws JsParseException パースエラー
+     */
+    private static JsComposition parseJson(JsonSource source)
+            throws IOException, JsParseException{
+        JsValue topValue = parseValue(source);
+        if(topValue == null) return null;
+
+        if( ! (topValue instanceof JsComposition) ){
+            throw new JsParseException(JsParseException.ERRMSG_INVALIDROOT,
+                                       source.getLineNumber() );
+        }
+        JsComposition result = (JsComposition) topValue;
+
+        return result;
+    }
+
+    /**
+     * JSONの最上位構造を文字リーダから読み取る。
+     * @param source 文字入力リーダ
+     * @return JSON最上位構造。OBJECT型かARRAY型のいずれか。
+     * 入力が0個以上のホワイトスペースのみで埋められていた場合はnull。
+     * @throws IOException 入力エラー
+     * @throws JsParseException パースエラー
+     */
+    public static JsComposition parseJson(Reader source)
+            throws IOException, JsParseException{
+        JsonSource jsonSource = new JsonSource(source);
+        return parseJson(jsonSource);
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/JsonAppender.java b/src/main/java/jp/sourceforge/jovsonz/JsonAppender.java
new file mode 100644 (file)
index 0000000..cbba7ad
--- /dev/null
@@ -0,0 +1,443 @@
+/*
+ * JSON output
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.io.Flushable;
+import java.io.IOException;
+import java.util.EmptyStackException;
+import java.util.Stack;
+
+/**
+ * JSON文字出力用ビジター。
+ * <p>
+ * JSON Valueのトラバース時にこのビジターを指定すると、
+ * 事前に用意した文字出力先にJSONフォーマットで出力される。
+ * </p>
+ * <p>
+ * 出力に伴う{@link java.io.IOException}は
+ * {@link JsVisitException}のチェーン例外となる。
+ * </p>
+ * <p>
+ * 前回パースの成功/失敗に関わらず、
+ * インスタンスの再利用時の挙動は保証されない。
+ * </p>
+ */
+class JsonAppender implements ValueVisitor {
+
+    /** 改行。 */
+    public static final String NEWLINE = "\n";
+    /** インデント単位。 */
+    public static final String INDENT_UNIT = "\u0020\u0020";
+    /** Pair区切り。 */
+    public static final String PAIR_SEPARATOR = "\u0020:\u0020";
+    /** コンマ区切り。 */
+    public static final String COMMA = "\u0020,";
+    /** 空要素。 */
+    public static final String EMPTY = "\u0020";
+
+
+    private final Appendable appout;
+
+    private final Stack<DumpContext> contextStack =
+            new Stack<DumpContext>();
+
+    private IOException ioException = null;
+
+
+    /**
+     * コンストラクタ。
+     * @param appout 出力先
+     * @throws NullPointerException 引数がnull
+     */
+    public JsonAppender(Appendable appout)
+            throws NullPointerException{
+        super();
+        if(appout == null) throw new NullPointerException();
+        this.appout = appout;
+        return;
+    }
+
+    /**
+     * コンテキストをプッシュ退避する。
+     * @param composition 現在のコンテキスト
+     */
+    protected void pushComposition(JsComposition composition){
+        DumpContext context = new DumpContext(composition);
+        this.contextStack.push(context);
+        return;
+    }
+
+    /**
+     * コンテキストをポップ復帰する。
+     * @return スタックトップのコンテキスト
+     * @throws EmptyStackException スタック構造が空
+     */
+    protected JsComposition popComposition() throws EmptyStackException{
+        DumpContext context = this.contextStack.pop();
+        JsComposition composition = context.getComposition();
+        return composition;
+    }
+
+    /**
+     * ネスト構造の深さを返す。
+     * @return 0から始まる深さ
+     */
+    protected int nestDepth(){
+        return this.contextStack.size();
+    }
+
+    /**
+     * ネスト構造が空(深さ0)か判定する。
+     * @return 空ならtrue
+     */
+    protected boolean isNestEmpty(){
+        return this.contextStack.isEmpty();
+    }
+
+    /**
+     * ネスト後、一つでも子要素が出力されたか判定する。
+     * @return 子要素が出力されていればtrue
+     */
+    protected boolean hasChildDumped(){
+        if(isNestEmpty()) return false;
+        boolean result = this.contextStack.peek().hasChildDumped();
+        return result;
+    }
+
+    /**
+     * 現時点でのネストに対し、子要素が一つ以上出力済みであると設定する。
+     */
+    protected void setChildDumped(){
+        if(isNestEmpty()) return;
+        this.contextStack.peek().setChildDumped();
+        return;
+    }
+
+    /**
+     * 現在のコンテキストがARRAY型配列要素出力中の状態か否か判定する。
+     * @return 現在のコンテキストがARRAY型配列要素出力中ならtrue
+     */
+    protected boolean isArrayContext(){
+        if(isNestEmpty()) return false;
+
+        DumpContext context = this.contextStack.peek();
+        JsComposition composition = context.getComposition();
+        JsTypes type = composition.getJsTypes();
+        if(type != JsTypes.ARRAY) return false;
+
+        return true;
+    }
+
+    /**
+     * 1文字出力。
+     * @param ch 文字
+     * @throws JsVisitException 出力エラー。
+     * @see java.lang.Appendable#append(char)
+     */
+    protected void append(char ch) throws JsVisitException{
+        try{
+            this.appout.append(ch);
+        }catch(IOException e){
+            this.ioException = e;
+            throw new JsVisitException(e);
+        }
+        return;
+    }
+
+    /**
+     * 文字列出力。
+     * @param seq 文字列
+     * @throws JsVisitException 出力エラー。
+     * @see java.lang.Appendable#append(CharSequence)
+     */
+    protected void append(CharSequence seq) throws JsVisitException{
+        try{
+            this.appout.append(seq);
+        }catch(IOException e){
+            this.ioException = e;
+            throw new JsVisitException(e);
+        }
+        return;
+    }
+
+    /**
+     * 可能であれば出力先をフラッシュする。
+     * @throws JsVisitException 出力エラー
+     * @see java.io.Flushable
+     */
+    protected void flush() throws JsVisitException{
+        try{
+            if(this.appout instanceof Flushable){
+                ((Flushable)this.appout).flush();
+            }
+        }catch(IOException e){
+            this.ioException = e;
+            throw new JsVisitException(e);
+        }
+        return;
+    }
+
+    /**
+     * トラバース中断の原因となったIOExceptionを返す。
+     * @return トラバース中断の原因となったIOException。なければnull。
+     */
+    public IOException getIOException(){
+        return this.ioException;
+    }
+
+    /**
+     * トラバース中断の原因となったIOExceptionがあるか判定する。
+     * @return トラバース中断の原因となったIOExceptionがあればtrue
+     */
+    public boolean hasIOException(){
+        if(this.ioException != null) return true;
+        return false;
+    }
+
+    /**
+     * pairの名前を出力する。
+     * @param name pair名
+     * @throws JsVisitException 出力エラー
+     */
+    protected void putPairName(String name) throws JsVisitException{
+        try{
+            JsString.dumpString(this.appout, name);
+        }catch(IOException e){
+            this.ioException = e;
+            throw new JsVisitException(e);
+        }
+        return;
+    }
+
+    /**
+     * pair区切りコロンを出力する。
+     * @throws JsVisitException 出力エラー
+     */
+    protected void putPairSeparator() throws JsVisitException{
+        append(PAIR_SEPARATOR);
+        return;
+    }
+
+    /**
+     * 要素間区切りコンマを出力する。
+     * JSONでは最後の要素の後にコンマを出力してはいけない。
+     * @throws JsVisitException 出力エラー
+     */
+    protected void putComma() throws JsVisitException{
+        append(COMMA);
+        return;
+    }
+
+    /**
+     * 改行を出力する。
+     * @throws JsVisitException 出力エラー。
+     */
+    protected void putNewLine() throws JsVisitException{
+        append(NEWLINE);
+        return;
+    }
+
+    /**
+     * インデントを出力する。
+     * @throws JsVisitException 出力エラー
+     */
+    protected void putIndent() throws JsVisitException{
+        int level = nestDepth();
+        for(int ct = 1; ct <= level; ct++){
+            append(INDENT_UNIT);
+        }
+        return;
+    }
+
+    /**
+     * OBJECT及びARRAY型の最初の要素の前部分を出力する。
+     * @throws JsVisitException 出力エラー
+     */
+    protected void putBefore1stElement() throws JsVisitException{
+        putNewLine();
+        putIndent();
+        return;
+    }
+
+    /**
+     * OBJECT及びARRAY型の要素間区切りを出力する。
+     * @throws JsVisitException 出力エラー
+     */
+    protected void putBetweenElement() throws JsVisitException{
+        putComma();
+        putNewLine();
+        putIndent();
+        return;
+    }
+
+    /**
+     * OBJECT及びARRAY型の最後の要素の後部分を出力する。
+     * @throws JsVisitException 出力エラー
+     */
+    protected void putAfterLastElement() throws JsVisitException{
+        putNewLine();
+        putIndent();
+        return;
+    }
+
+    /**
+     * OBJECT及びARRAY型の空要素を出力する。
+     * @throws JsVisitException 出力エラー
+     */
+    protected void putEmptyElement() throws JsVisitException{
+        append(EMPTY);
+        return;
+    }
+
+    /**
+     * パース前の出力を行う。
+     * @throws JsVisitException 出力エラー
+     */
+    protected void putBeforeParse() throws JsVisitException{
+        //NOTHING
+        return;
+    }
+
+    /**
+     * パース後の出力を行う。
+     * @throws JsVisitException 出力エラー
+     */
+    protected void putAfterParse() throws JsVisitException{
+        putNewLine();
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * Valueの出力を行う。
+     * @param value {@inheritDoc}
+     * @throws JsVisitException {@inheritDoc}
+     */
+    @Override
+    public void visitValue(JsValue value)
+            throws JsVisitException{
+        if(isNestEmpty()) putBeforeParse();
+
+        if(isArrayContext()){
+            if(hasChildDumped()) putBetweenElement();
+            else                 putBefore1stElement();
+        }
+
+        JsTypes type = value.getJsTypes();
+        switch(type){
+        case OBJECT: append('{');              break;
+        case ARRAY:  append('[');              break;
+        default:     append(value.toString()); break;
+        }
+        setChildDumped();
+
+        if(type.isComposition()){
+            assert value instanceof JsComposition;
+            JsComposition composition = (JsComposition) value;
+            pushComposition(composition);
+        }
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * OBJECT内の各pairの名前を出力する。
+     * @param pairName {@inheritDoc}
+     * @throws JsVisitException {@inheritDoc}
+     */
+    @Override
+    public void visitPairName(String pairName)
+            throws JsVisitException{
+        if(hasChildDumped()) putBetweenElement();
+        else                 putBefore1stElement();
+
+        putPairName(pairName);
+        putPairSeparator();
+
+        setChildDumped();
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 閉じ括弧を出力する。
+     * @param closed {@inheritDoc}
+     * @throws JsVisitException {@inheritDoc}
+     */
+    @Override
+    public void visitCompositionClose(JsComposition closed)
+            throws JsVisitException{
+        boolean hasDumped = hasChildDumped();
+        JsComposition composition = popComposition();
+
+        if(hasDumped) putAfterLastElement();
+        else          putEmptyElement();
+
+        char closeBrace;
+        switch(composition.getJsTypes()){
+        case OBJECT: closeBrace = '}'; break;
+        case ARRAY:  closeBrace = ']'; break;
+        default: assert false; throw new AssertionError();
+        }
+        append(closeBrace);
+
+        if(isNestEmpty()){
+            putAfterParse();
+            flush();
+        }
+
+        return;
+    }
+
+    /**
+     * ネストされた各JSON集約型コンテキストの出力状況。
+     */
+    private static class DumpContext{
+        private final JsComposition composition;
+        private boolean childDumped;
+
+        /**
+         * コンストラクタ。
+         * 子要素が出力された事実は無い状態で始まる。
+         * @param composition レベルに対応するOBJECTもしくはARRAY型Value
+         */
+        DumpContext(JsComposition composition){
+            this.composition = composition;
+            this.childDumped = false;
+            return;
+        }
+
+        /**
+         * このレベルに対応するJSON集約型を返す。
+         * @return OBJECTもしくはARRAY型Value
+         */
+        JsComposition getComposition(){
+            return this.composition;
+        }
+
+        /**
+         * このレベルで子要素出力が行われたか判定する。
+         * @return 子要素出力が行われていたならtrue
+         */
+        boolean hasChildDumped(){
+            return this.childDumped;
+        }
+
+        /**
+         * このレベルで子要素出力が行われた事実を設定する。
+         */
+        void setChildDumped(){
+            this.childDumped = true;
+            return;
+        }
+
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/JsonSource.java b/src/main/java/jp/sourceforge/jovsonz/JsonSource.java
new file mode 100644 (file)
index 0000000..1cb3566
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ * JSON stream source
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+
+/**
+ * JSONデータ用入力ソース。
+ * 先読みした文字のプッシュバック機能と行番号のカウント機能を有する。
+ * 行番号は1から始まる。
+ * 行と行はLF('\n')で区切られるものとする。(※CRは無視)
+ * @see java.io.PushbackReader
+ * @see java.io.LineNumberReader
+ */
+class JsonSource implements Closeable {
+
+    /** プッシュバック可能な文字数。 */
+    private static final int PUSHBACK_TOKENS = 10;
+
+    private static final char LINEFEED = '\n';  // LF(0x0a)
+
+    private static final String ERRMSG_OVERFLOW =
+            "Pushback buffer overflow";
+    private static final String ERRMSG_CLOSED =
+            "Stream closed";
+
+    static{
+        assert "\\uXXXX".length() < PUSHBACK_TOKENS;
+    }
+
+    private final Reader reader;
+
+    // プッシュバック用文字スタック構造。
+    private final char[] charStack = new char[PUSHBACK_TOKENS];
+    private int stackPt = 0;
+
+    private int lineNumber = 1;
+
+    private boolean closed = false;
+
+    /**
+     * コンストラクタ。
+     * @param reader 文字入力リーダー
+     * @throws NullPointerException 引数がnull
+     */
+    public JsonSource(Reader reader) throws NullPointerException{
+        super();
+        if(reader == null) throw new NullPointerException();
+        this.reader = reader;
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * 任意の文字列を入力ソースとする。
+     * @param text 文字列
+     * @see java.io.StringReader
+     */
+    public JsonSource(CharSequence text){
+        this(new StringReader(text.toString()));
+        return;
+    }
+
+    /**
+     * JSON規格のwhitespace文字を判定する。
+     * @param ch 判定対象文字
+     * @return whitespaceならtrue
+     */
+    public static boolean isWhitespace(char ch){
+        switch(ch){
+        case '\u0020':
+        case '\t':
+        case '\r':
+        case '\n':
+            return true;
+        default:
+            break;
+        }
+        return false;
+    }
+
+    /**
+     * JSON規格のwhitespace文字を判定する。
+     * @param ch 判定対象文字。
+     * 上位16bitがゼロでなければwhitespaceと判定されない。
+     * @return whitespaceならtrue。引数が負の場合はfalse。
+     */
+    public static boolean isWhitespace(int ch){
+        if((int)Character.MIN_VALUE > ch) return false;
+        if((int)Character.MAX_VALUE < ch) return false;
+        return isWhitespace((char)ch);
+    }
+
+    /**
+     * プッシュバック可能な残り文字数を返す。
+     * @return プッシュバック可能な残り文字数
+     */
+    public int getPushBackSpared(){
+        return PUSHBACK_TOKENS - this.stackPt;
+    }
+
+    /**
+     * 現時点での行番号を返す。
+     * @return 1から始まる行番号
+     */
+    public int getLineNumber(){
+        return this.lineNumber;
+    }
+
+    /**
+     * 1文字読み込む。
+     * @return 読み込んだ文字。入力が終わっている場合は負の値。
+     * @throws IOException 入力エラー
+     * @see java.io.Reader#read()
+     */
+    public int read() throws IOException{
+        if(this.closed) throw new IOException(ERRMSG_CLOSED);
+
+        int chData;
+        if(this.stackPt > 0){
+            chData = (int) this.charStack[--this.stackPt];
+        }else{
+            chData = this.reader.read();
+        }
+
+        if(chData == (int)LINEFEED) this.lineNumber++;
+
+        return chData;
+    }
+
+    /**
+     * 入力末端ではないと仮定して1文字読み込む。
+     * @return 読み込んだ文字。
+     * @throws IOException 入力エラー
+     * @throws JsParseException 入力が終わっている
+     */
+    public char readOrDie() throws IOException, JsParseException{
+        int chData = read();
+        if(chData < 0){
+            throw new JsParseException(JsParseException.ERRMSG_NODATA,
+                                       this.lineNumber);
+        }
+        return (char)chData;
+    }
+
+    /**
+     * 入力が文字列とマッチするか判定する。
+     * 失敗しても読み戻しは行われない。
+     * 長さ0の文字列は必ずマッチに成功する。
+     * @param seq マッチ対象文字列
+     * @return マッチすればtrue
+     * @throws IOException 入力エラー
+     * @throws JsParseException 入力が終わっている。
+     */
+    public boolean matchOrDie(CharSequence seq)
+            throws IOException, JsParseException{
+        int length = seq.length();
+        for(int pt = 0; pt < length; pt++){
+            if(readOrDie() != seq.charAt(pt)) return false;
+        }
+        return true;
+    }
+
+    /**
+     * 1文字読み戻す。
+     * 行数カウントへも反映される。
+     * @param ch 読み戻す文字
+     * @throws IOException バッファあふれもしくはクローズ済み
+     */
+    public void unread(char ch) throws IOException{
+        if(this.closed) throw new IOException(ERRMSG_CLOSED);
+
+        if(this.stackPt >= PUSHBACK_TOKENS){
+            throw new IOException(ERRMSG_OVERFLOW);
+        }
+
+        this.charStack[this.stackPt++] = ch;
+
+        if(ch == LINEFEED) this.lineNumber--;
+
+        return;
+    }
+
+    /**
+     * 1文字読み戻す。
+     * char型にキャストした引数が次回読み込まれる。
+     * 行数カウントへも反映される。
+     * @param ch 読み戻す文字。負の符号を含む上位16bitは無視される。
+     * @throws IOException バッファあふれもしくはクローズ済み
+     */
+    public void unread(int ch) throws IOException{
+        unread((char) ch);
+        return;
+    }
+
+    /**
+     * whitespace文字を読み飛ばす。
+     * @throws IOException 入力エラー
+     */
+    public void skipWhiteSpace() throws IOException{
+        for(;;){
+            int chData = read();
+            if(chData < 0) break;
+            if( ! isWhitespace(chData) ){
+                unread(chData);
+                break;
+            }
+        }
+
+        return;
+    }
+
+    /**
+     * まだ読み込めるデータがあるか判定する。
+     * @return まだ読めるデータがあればtrue
+     * @throws IOException IO入力エラー
+     */
+    public boolean hasMore() throws IOException{
+        int chData = read();
+        if(chData < 0) return false;
+        unread(chData);
+        return true;
+    }
+
+    /**
+     * コンストラクタで指定されたReaderを閉じる。
+     * クローズ後の読み込みおよび読み戻し動作は全て例外を投げる。
+     * @throws IOException 入出力エラー
+     * @see java.io.Closeable
+     */
+    public void close() throws IOException{
+        this.closed = true;
+        this.stackPt = 0;
+        this.reader.close();
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/UnmodIterator.java b/src/main/java/jp/sourceforge/jovsonz/UnmodIterator.java
new file mode 100644 (file)
index 0000000..2d42f95
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * unmodifiable iterator wrap
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * 既存の{@link java.util.Iterator}および{@link java.lang.Iterable}に対し、
+ * 削除のできない変更操作不可なIteratorラッパを提供する。
+ * @param <E> コレクション内の要素型
+ */
+public class UnmodIterator<E> implements Iterator<E> {
+
+    private final Iterator<E> rawIterator;
+
+    /**
+     * コンストラクタ。
+     * @param iterator ラップ元Iterator
+     * @throws NullPointerException 引数がnull
+     */
+    public UnmodIterator(Iterator<E> iterator) throws NullPointerException{
+        super();
+        if(iterator == null) throw new NullPointerException();
+        this.rawIterator = iterator;
+        return;
+    }
+
+    /**
+     * 削除操作不可なラップIteratorを生成する。
+     * @param <E> コレクション内の要素型
+     * @param iterator ラップ元Iterator
+     * @return 変更操作不可なIterator
+     * @throws NullPointerException 引数がnull
+     */
+    public static <E> Iterator<E> wrapUnmod(Iterator<E> iterator)
+            throws NullPointerException{
+        if(iterator == null) throw new NullPointerException();
+        return new UnmodIterator<E>(iterator);
+    }
+
+    /**
+     * 削除操作不可なラップIterableを生成する。
+     * @param <E> コレクション内の要素型
+     * @param iterable ラップ元Iterable
+     * @return 変更操作不可なIteratorを生成するIterable
+     * @throws NullPointerException 引数がnull
+     */
+    public static <E> Iterable<E> wrapUnmod(Iterable<E> iterable)
+            throws NullPointerException{
+        if(iterable == null) throw new NullPointerException();
+        final Iterable<E> innerArg = iterable;
+        return new Iterable<E>(){
+            public Iterator<E> iterator(){
+                Iterator<E> iterator = innerArg.iterator();
+                return new UnmodIterator<E>(iterator);
+            }
+        };
+    }
+
+    /**
+     * Iterableに由来する削除操作不可なラップIteratorを生成する。
+     * @param <E> コレクション内の要素型
+     * @param iterable Iterable
+     * @return 変更操作不可なIterator
+     * @throws NullPointerException 引数がnull
+     */
+    public static <E> Iterator<E> unmodIterator(Iterable<E> iterable)
+            throws NullPointerException{
+        if(iterable == null) throw new NullPointerException();
+        Iterator<E> iterator = iterable.iterator();
+        return new UnmodIterator<E>(iterator);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 反復子に次の要素があるか判定する。
+     * @return {@inheritDoc}
+     */
+    @Override
+    public boolean hasNext(){
+        return this.rawIterator.hasNext();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 反復子の次の要素を取得する。
+     * @return {@inheritDoc}
+     * @throws NoSuchElementException これ以上要素はない。
+     */
+    @Override
+    public E next() throws NoSuchElementException{
+        return this.rawIterator.next();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 必ず失敗し例外を投げる。
+     * @throws UnsupportedOperationException
+     */
+    @Override
+    public void remove() throws UnsupportedOperationException{
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/ValueVisitor.java b/src/main/java/jp/sourceforge/jovsonz/ValueVisitor.java
new file mode 100644 (file)
index 0000000..99ba49a
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * JSON value visitor
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+/**
+ * JSONツリー上の各Valueへの深さ優先ビジター共通インタフェース。
+ */
+public interface ValueVisitor {
+
+    /**
+     * Value登場の通知を受け取る。
+     * @param value JSON Value
+     * @throws JsVisitException ビジターがトラバース中止を判断した際に
+     * 投げられる。
+     */
+    void visitValue(JsValue value) throws JsVisitException;
+
+    /**
+     * OBJECT型内部のPAIR名登場の通知を受け取る。
+     * PAIRの示すValueの出現する直前に通知が行われる。
+     * @param pairName PAIR名
+     * @throws JsVisitException ビジターがトラバース中止を判断した際に
+     * 投げられる。
+     */
+    void visitPairName(String pairName) throws JsVisitException;
+
+    /**
+     * 括弧構造終了の通知を受け取る。
+     * <p>括弧構造を持つJSON型は、OBJECT型かARRAY型のみ。</p>
+     * @param composition OBJECT型かARRAY型のいずれかのValue
+     * @throws JsVisitException ビジターがトラバース中止を判断した際に
+     * 投げられる。
+     */
+    void visitCompositionClose(JsComposition composition)
+            throws JsVisitException;
+
+}
diff --git a/src/main/java/jp/sourceforge/jovsonz/package-info.java b/src/main/java/jp/sourceforge/jovsonz/package-info.java
new file mode 100644 (file)
index 0000000..7176733
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Jovsonz library package comment
+ *
+ * This source-file is something special for SunJDK5.0 Javadoc or later.
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+/**
+ * Jovsonz is a Java-library for JSON read/write purpose.
+ * Jovsonz library was derived from Jindolf project.
+ *
+ * <hr>
+ *
+ * <p>
+ * The MIT License
+ * </p>
+ * <p>
+ * Copyright(c) 2009 olyutorskii
+ * </p>
+ * <p>
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ * </p>
+ * <p>
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * </p>
+ * <p>
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ * </p>
+ *
+ * <hr>
+ *
+ * <h1>JSON基本型</h1>
+ * <dl>
+ * <dt>OBJECT型</dt>
+ * <dd>{  "Name" : "Joe",  "Age" : 88  }</dd>
+ * <dt>ARRAY型</dt>
+ * <dd>[ true, "ABC", 23 ]</dd>
+ * <dt>STRING型</dt>
+ * <dd>"ABC"</dd>
+ * <dt>NUMBER型</dt>
+ * <dd>-12.34</dd>
+ * <dt>BOOLEAN型</dt>
+ * <dd>true</dd>
+ * <dt>NULL型</dt>
+ * <dd>null</dd>
+ * </dl>
+ *
+ * @see <a href="http://json.org/">Introducing JSON</a>
+ * @see <a href="http://json.org/json-ja.html">JSONの紹介</a>
+ * @see <a href="http://www.ietf.org/rfc/rfc4627.txt">RFC4627</a>
+ * @see <a href=
+ * "http://ja.wikipedia.org/wiki/JavaScript_Object_Notation">
+ * Wikipedia(ja)解説</a>
+ * @see <a href=
+ * "http://en.wikipedia.org/wiki/JavaScript_Object_Notation">
+ * Wikipedia(en)</a>
+ */
+
+package jp.sourceforge.jovsonz;
+
+/* EOF */
diff --git a/src/main/resources/jp/sourceforge/jovsonz/resources/version.properties b/src/main/resources/jp/sourceforge/jovsonz/resources/version.properties
new file mode 100644 (file)
index 0000000..debc29b
--- /dev/null
@@ -0,0 +1,7 @@
+# Version definition
+#   [ with Maven resource filtering ]
+
+library.name = ${pom.name}
+library.version = ${pom.version}
+
+# EOF #
diff --git a/src/test/java/jp/sourceforge/jovsonz/JsArrayTest.java b/src/test/java/jp/sourceforge/jovsonz/JsArrayTest.java
new file mode 100644 (file)
index 0000000..878d7f5
--- /dev/null
@@ -0,0 +1,503 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class JsArrayTest {
+
+    public JsArrayTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception{
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception{
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of parseArray method, of class JsArray.
+     */
+    @Test
+    public void testParseArray() throws Exception{
+        System.out.println("parseArray");
+
+        JsonSource source;
+        JsArray array;
+
+        source = new JsonSource("[]");
+        array = JsArray.parseArray(source);
+        assertEquals(0, array.size());
+
+        source = new JsonSource("[true]");
+        array = JsArray.parseArray(source);
+        assertEquals(1, array.size());
+        assertEquals(JsBoolean.TRUE, array.get(0));
+
+        source = new JsonSource("[true,false]");
+        array = JsArray.parseArray(source);
+        assertEquals(2, array.size());
+        assertEquals(JsBoolean.TRUE, array.get(0));
+        assertEquals(JsBoolean.FALSE, array.get(1));
+
+        source = new JsonSource("\n[\rtrue\t, false\n]\r");
+        array = JsArray.parseArray(source);
+        assertNull(array);
+
+        source = new JsonSource("[\rtrue\t, false\n]\r");
+        array = JsArray.parseArray(source);
+        assertEquals(2, array.size());
+        assertEquals(JsBoolean.TRUE, array.get(0));
+        assertEquals(JsBoolean.FALSE, array.get(1));
+
+        try{
+            source = new JsonSource("[,]");
+            array = JsArray.parseArray(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("[true,]");
+            array = JsArray.parseArray(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("[true#]");
+            array = JsArray.parseArray(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("[true,");
+            array = JsArray.parseArray(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("[true");
+            array = JsArray.parseArray(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        source = new JsonSource("true]");
+        array = JsArray.parseArray(source);
+        assertNull(array);
+
+        return;
+    }
+
+    /**
+     * Test of add method, of class JsArray.
+     */
+    @Test
+    public void testAdd(){
+        System.out.println("add");
+
+        JsArray array = new JsArray();
+
+        JsNumber number = new JsNumber("1.23");
+        assertEquals(0, array.size());
+        array.add(number);
+        assertEquals(1, array.size());
+        array.add(number);
+        assertEquals(2, array.size());
+
+        return;
+    }
+
+    /**
+     * Test of get method, of class JsArray.
+     */
+    @Test
+    public void testGet(){
+        System.out.println("get");
+
+        JsArray array = new JsArray();
+
+        JsValue val1 = new JsNumber("1.23");
+        JsValue val2 = new JsString("abc");
+
+        array.add(val1);
+        array.add(val2);
+
+        assertEquals(val1, array.get(0));
+        assertEquals(val2, array.get(1));
+
+        try{
+            array.get(2);
+            fail();
+        }catch(IndexOutOfBoundsException e){
+            // NOTHING
+        }
+
+        return;
+    }
+
+    /**
+     * Test of clear method, of class JsArray.
+     */
+    @Test
+    public void testClear(){
+        System.out.println("clear");
+
+        JsArray array = new JsArray();
+
+        JsValue val1 = new JsNumber("1.23");
+        JsValue val2 = new JsString("abc");
+
+        array.add(val1);
+        array.add(val2);
+        assertEquals(2, array.size());
+
+        array.clear();
+        assertEquals(0, array.size());
+
+        try{
+            array.get(0);
+            fail();
+        }catch(IndexOutOfBoundsException e){
+            // NOTHING
+        }
+
+        array = new JsArray();
+        array.add(JsNull.NULL);
+        assertEquals(1, array.size());
+        assertTrue(array.hasChanged());
+        array.setUnchanged();
+        assertFalse(array.hasChanged());
+        array.clear();
+        assertEquals(0, array.size());
+        assertTrue(array.hasChanged());
+        array.setUnchanged();
+        array.clear();
+        assertEquals(0, array.size());
+        assertFalse(array.hasChanged());
+
+        return;
+    }
+
+    /**
+     * Test of remove method, of class JsArray.
+     */
+    @Test
+    public void testRemove_JsValue(){
+        System.out.println("remove");
+
+        JsArray array = new JsArray();
+
+        JsValue val1 = new JsNumber("1.23");
+        JsValue val2 = new JsString("abc");
+        JsValue val3 = JsBoolean.TRUE;
+
+        array.add(val1);
+        array.add(val2);
+        assertEquals(2, array.size());
+
+        assertTrue(array.remove(val1));
+        assertEquals(1, array.size());
+        assertEquals(val2, array.get(0));
+
+        assertFalse(array.remove(val3));
+        assertEquals(1, array.size());
+
+        return;
+    }
+
+    /**
+     * Test of remove method, of class JsArray.
+     */
+    @Test
+    public void testRemove_int(){
+        System.out.println("remove");
+
+        JsArray array = new JsArray();
+
+        JsValue val1 = new JsNumber("1.23");
+        JsValue val2 = new JsString("abc");
+        JsValue val3 = JsBoolean.TRUE;
+
+        array.add(val1);
+        array.add(val2);
+        array.add(val3);
+        assertEquals(3, array.size());
+
+        assertEquals(val1, array.remove(0));
+        assertEquals(2, array.size());
+        assertEquals(val2, array.get(0));
+
+        assertEquals(val3, array.remove(1));
+        assertEquals(1, array.size());
+        assertEquals(val2, array.get(0));
+
+        return;
+    }
+
+    /**
+     * Test of size method, of class JsArray.
+     */
+    @Test
+    public void testSize(){
+        System.out.println("size");
+
+        JsArray array = new JsArray();
+        assertEquals(0, array.size());
+        assertTrue(array.isEmpty());
+
+        JsValue val1 = new JsNumber("1.23");
+
+        array.add(val1);
+        assertEquals(1, array.size());
+        assertFalse(array.isEmpty());
+
+        return;
+    }
+
+    /**
+     * Test of iterator method, of class JsArray.
+     */
+    @Test
+    public void testIterator(){
+        System.out.println("iterator");
+
+        JsArray array = new JsArray();
+
+        JsValue val1 = new JsNumber("1.23");
+        JsValue val2 = new JsString("abc");
+
+        array.add(val1);
+        array.add(val2);
+
+        Iterator<JsValue> it = array.iterator();
+
+        assertTrue(it.hasNext());
+        assertEquals(val1, it.next());
+
+        assertTrue(it.hasNext());
+        assertEquals(val2, it.next());
+
+        assertFalse(it.hasNext());
+
+        return;
+    }
+
+    /**
+     * Test of hashCode method, of class JsArray.
+     */
+    @Test
+    public void testHashCode(){
+        System.out.println("hashCode");
+
+        JsArray array1 = new JsArray();
+        JsArray array2 = new JsArray();
+
+        assertEquals(array1.hashCode(), array2.hashCode());
+
+        array1.add(new JsString("abc"));
+        array2.add(new JsString("abc"));
+
+        assertEquals(array1.hashCode(), array2.hashCode());
+
+        return;
+    }
+
+    /**
+     * Test of equals method, of class JsArray.
+     */
+    @Test
+    public void testEquals(){
+        System.out.println("equals");
+
+        JsArray array1 = new JsArray();
+        JsArray array2 = new JsArray();
+
+        assertTrue(array1.equals(array2));
+
+        array1.add(new JsString("abc"));
+        array2.add(new JsString("abc"));
+
+        assertTrue(array1.equals(array2));
+
+        array1.add(new JsString("xyz"));
+        array2.add(new JsString("XYZ"));
+
+        assertFalse(array1.equals(array2));
+
+        JsArray nullVal = null;
+
+        assertFalse(array1.equals(nullVal));
+
+        assertFalse(array1.equals(""));
+
+        return;
+    }
+
+    /**
+     * Test of toString method, of class JsArray.
+     */
+    @Test
+    public void testToString(){
+        System.out.println("toString");
+
+        JsArray array = new JsArray();
+
+        assertEquals("[]", array.toString());
+
+        array.add(JsBoolean.TRUE);
+        assertEquals("[true]", array.toString());
+
+        array.add(JsBoolean.FALSE);
+        assertEquals("[true,false]", array.toString());
+
+        array.add(new JsArray());
+        assertEquals("[true,false,[]]", array.toString());
+
+        return;
+    }
+
+    /**
+     * Test of traverse method, of class JsArray.
+     */
+    @Test
+    public void testTraverse() throws Exception{
+        System.out.println("traverse");
+
+        JsArray array = new JsArray();
+        JsValue val1 = new JsNumber("12");
+        JsValue val2 = new JsString("AB");
+        array.add(val1);
+        array.add(val2);
+
+        final List<Object> visited = new LinkedList<Object>();
+
+        try{
+            array.traverse(new ValueVisitor(){
+                public void visitValue(JsValue value)
+                        throws JsVisitException{
+                    visited.add(value);
+                    return;
+                }
+
+                public void visitPairName(String name)
+                        throws JsVisitException{
+                    visited.add(name);
+                    return;
+                }
+
+                public void visitCompositionClose(JsComposition composite)
+                        throws JsVisitException{
+                    visited.add(composite);
+                    return;
+                }
+            });
+        }catch(JsVisitException e){
+            fail();
+        }
+
+        assertEquals(4, visited.size());
+        assertEquals(array, visited.get(0));
+        assertEquals(val1, visited.get(1));
+        assertEquals(val2, visited.get(2));
+        assertEquals(array, visited.get(3));
+
+        return;
+    }
+
+    /**
+     * Test of hasChanged method, of class JsArray.
+     */
+    @Test
+    public void testHasChanged(){
+        System.out.println("hasChanged");
+
+        JsArray array = new JsArray();
+        assertFalse(array.hasChanged());
+
+        array.add(new JsNumber("0"));
+        assertTrue(array.hasChanged());
+
+        array.setUnchanged();
+        assertFalse(array.hasChanged());
+
+        JsArray child = new JsArray();
+        array.add(child);
+        array.setUnchanged();
+        assertFalse(array.hasChanged());
+
+        child.add(JsNull.NULL);
+        assertTrue(array.hasChanged());
+        array.setUnchanged();
+        assertFalse(array.hasChanged());
+
+        return;
+    }
+
+    /**
+     * Test of setUnchanged method, of class JsArray.
+     */
+    @Test
+    public void testSetUnchanged(){
+        System.out.println("setUnchanged");
+
+        JsArray array = new JsArray();
+        JsArray child = new JsArray();
+        array.add(child);
+
+        child.add(JsNull.NULL);
+        assertTrue(child.hasChanged());
+
+        array.setUnchanged();
+        assertFalse(child.hasChanged());
+
+        return;
+    }
+
+    /**
+     * Test of getJsTypes method, of class JsArray.
+     */
+    @Test
+    public void testGetJsTypes() {
+        System.out.println("getJsTypes");
+
+        JsArray instance = new JsArray();
+
+        assertEquals(JsTypes.ARRAY, instance.getJsTypes());
+
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jovsonz/JsBooleanTest.java b/src/test/java/jp/sourceforge/jovsonz/JsBooleanTest.java
new file mode 100644 (file)
index 0000000..2753630
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.util.SortedSet;
+import java.util.TreeSet;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class JsBooleanTest {
+
+    public JsBooleanTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception{
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception{
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of traverse method, of class JsBoolean.
+     */
+    @Test
+    public void testTraverse(){
+        System.out.println("traverse");
+
+        try{
+            JsBoolean.TRUE.traverse(new ValueVisitor(){
+                int ct = 0;
+
+                public void visitValue(JsValue value)
+                        throws JsVisitException{
+                    assertEquals(JsBoolean.TRUE, value);
+                    assertTrue(this.ct++ <= 0);
+                }
+
+                public void visitPairName(String name)
+                        throws JsVisitException{
+                    throw new JsVisitException();
+                }
+
+                public void visitCompositionClose(JsComposition composite)
+                        throws JsVisitException{
+                    throw new JsVisitException();
+                }
+            });
+        }catch(JsVisitException e){
+            fail();
+        }
+
+        try{
+            JsBoolean.FALSE.traverse(new ValueVisitor(){
+                int ct = 0;
+
+                public void visitValue(JsValue value)
+                        throws JsVisitException{
+                    assertEquals(JsBoolean.FALSE, value);
+                    assertTrue(this.ct++ <= 0);
+                }
+
+                public void visitPairName(String name)
+                        throws JsVisitException{
+                    throw new JsVisitException();
+                }
+
+                public void visitCompositionClose(JsComposition composite)
+                        throws JsVisitException{
+                    throw new JsVisitException();
+                }
+            });
+        }catch(JsVisitException e){
+            fail();
+        }
+
+        return;
+    }
+
+    /**
+     * Test of valueOf method, of class JsBoolean.
+     */
+    @Test
+    public void testValueOf(){
+        System.out.println("valueOf");
+        assertEquals(JsBoolean.TRUE, JsBoolean.valueOf(true));
+        assertEquals(JsBoolean.FALSE, JsBoolean.valueOf(false));
+        return;
+    }
+
+    /**
+     * Test of booleanValue method, of class JsBoolean.
+     */
+    @Test
+    public void testBooleanValue(){
+        System.out.println("booleanValue");
+        assertTrue(JsBoolean.TRUE.booleanValue());
+        assertFalse(JsBoolean.FALSE.booleanValue());
+        return;
+    }
+
+    /**
+     * Test of isTrue method, of class JsBoolean.
+     */
+    @Test
+    public void testIsTrue(){
+        System.out.println("isTrue");
+        assertTrue(JsBoolean.TRUE.isTrue());
+        assertFalse(JsBoolean.FALSE.isTrue());
+        return;
+    }
+
+    /**
+     * Test of isFalse method, of class JsBoolean.
+     */
+    @Test
+    public void testIsFalse(){
+        System.out.println("isFalse");
+        assertFalse(JsBoolean.TRUE.isFalse());
+        assertTrue(JsBoolean.FALSE.isFalse());
+        return;
+    }
+
+    /**
+     * Test of hashCode method, of class JsBoolean.
+     */
+    @Test
+    public void testHashCode(){
+        System.out.println("hashCode");
+        assertEquals(JsBoolean.TRUE.hashCode(), JsBoolean.TRUE.hashCode());
+        assertEquals(JsBoolean.FALSE.hashCode(), JsBoolean.FALSE.hashCode());
+        // NOTHING
+        return;
+    }
+
+    /**
+     * Test of equals method, of class JsBoolean.
+     */
+    @Test
+    public void testEquals(){
+        System.out.println("equals");
+
+        JsBoolean nullVal = null;
+
+        assertTrue(JsBoolean.TRUE.equals(JsBoolean.TRUE));
+        assertFalse(JsBoolean.TRUE.equals(JsBoolean.FALSE));
+        assertFalse(JsBoolean.TRUE.equals(JsNull.NULL));
+        assertFalse(JsBoolean.TRUE.equals(nullVal));
+
+        assertFalse(JsBoolean.FALSE.equals(JsBoolean.TRUE));
+        assertTrue(JsBoolean.FALSE.equals(JsBoolean.FALSE));
+        assertFalse(JsBoolean.TRUE.equals(JsNull.NULL));
+        assertFalse(JsBoolean.FALSE.equals(nullVal));
+
+        return;
+    }
+
+    /**
+     * Test of compareTo method, of class JsBoolean.
+     */
+    @Test
+    public void testCompareTo(){
+        System.out.println("compareTo");
+        assertEquals(0, JsBoolean.TRUE.compareTo(JsBoolean.TRUE));
+        assertEquals(0, JsBoolean.FALSE.compareTo(JsBoolean.FALSE));
+        assertTrue(0 > JsBoolean.TRUE.compareTo(JsBoolean.FALSE));
+        assertTrue(0 < JsBoolean.FALSE.compareTo(JsBoolean.TRUE));
+
+        try{
+            JsBoolean.TRUE.compareTo(null);
+            fail();
+        }catch(NullPointerException e){
+            // NOTHING
+        }
+
+        try{
+            JsBoolean.FALSE.compareTo(null);
+            fail();
+        }catch(NullPointerException e){
+            // NOTHING
+        }
+
+        SortedSet<JsBoolean> set = new TreeSet<JsBoolean>();
+
+        set.clear();
+        set.add(JsBoolean.TRUE);
+        set.add(JsBoolean.FALSE);
+        assertEquals(JsBoolean.TRUE, set.first());
+        assertEquals(JsBoolean.FALSE, set.last());
+        set.clear();
+        set.add(JsBoolean.FALSE);
+        set.add(JsBoolean.TRUE);
+        assertEquals(JsBoolean.TRUE, set.first());
+        assertEquals(JsBoolean.FALSE, set.last());
+
+        return;
+    }
+
+    /**
+     * Test of toString method, of class JsBoolean.
+     */
+    @Test
+    public void testToString(){
+        System.out.println("toString");
+        assertEquals("true", JsBoolean.TRUE.toString());
+        assertEquals("false", JsBoolean.FALSE.toString());
+        return;
+    }
+
+    /**
+     * Test of toString method, of class JsBoolean.
+     */
+    @Test
+    public void testEtc(){
+        System.out.println("etc.");
+        assertNotNull(JsBoolean.TRUE);
+        assertNotNull(JsBoolean.FALSE);
+        assertTrue(JsBoolean.TRUE instanceof JsBoolean);
+        assertTrue(JsBoolean.FALSE instanceof JsBoolean);
+        return;
+    }
+
+    /**
+     * Test of getJsTypes method, of class JsBoolean.
+     */
+    @Test
+    public void testGetJsTypes() {
+        System.out.println("getJsTypes");
+
+        assertEquals(JsTypes.BOOLEAN, JsBoolean.TRUE.getJsTypes());
+
+        return;
+    }
+
+    /**
+     * Test of parseBoolean method, of class JsBoolean.
+     */
+    @Test
+    public void testParseBoolean() throws Exception{
+        System.out.println("parseBoolean");
+
+        JsonSource source;
+        JsBoolean result;
+
+        source = new JsonSource("true");
+        result = JsBoolean.parseBoolean(source);
+        assertEquals(JsBoolean.TRUE, result);
+
+        source = new JsonSource("false");
+        result = JsBoolean.parseBoolean(source);
+        assertEquals(JsBoolean.FALSE, result);
+
+        source = new JsonSource("X");
+        result = JsBoolean.parseBoolean(source);
+        assertNull(result);
+
+        try{
+            source = new JsonSource("tX");
+            result = JsBoolean.parseBoolean(source);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        try{
+            source = new JsonSource("trX");
+            result = JsBoolean.parseBoolean(source);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        try{
+            source = new JsonSource("truX");
+            result = JsBoolean.parseBoolean(source);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        try{
+            source = new JsonSource("fX");
+            result = JsBoolean.parseBoolean(source);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        try{
+            source = new JsonSource("faX");
+            result = JsBoolean.parseBoolean(source);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        try{
+            source = new JsonSource("falX");
+            result = JsBoolean.parseBoolean(source);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        try{
+            source = new JsonSource("falsX");
+            result = JsBoolean.parseBoolean(source);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jovsonz/JsNullTest.java b/src/test/java/jp/sourceforge/jovsonz/JsNullTest.java
new file mode 100644 (file)
index 0000000..5e0f8e5
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class JsNullTest {
+
+    public JsNullTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception{
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception{
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of etc of class JsNull.
+     */
+    @Test
+    public void testEtc(){
+        System.out.println("etc");
+        assertNotNull(JsNull.NULL);
+        assertTrue(JsNull.NULL instanceof JsNull);
+        return;
+    }
+
+    /**
+     * Test of traverse method, of class JsNull.
+     */
+    @Test
+    public void testTraverse(){
+        System.out.println("traverse");
+        try{
+            JsNull.NULL.traverse(new ValueVisitor(){
+                int ct = 0;
+
+                public void visitValue(JsValue value)
+                        throws JsVisitException{
+                    assertEquals(JsNull.NULL, value);
+                    assertTrue(this.ct++ <= 0);
+                }
+
+                public void visitPairName(String name)
+                        throws JsVisitException{
+                    throw new JsVisitException();
+                }
+
+                public void visitCompositionClose(JsComposition composite)
+                        throws JsVisitException{
+                    throw new JsVisitException();
+                }
+            });
+        }catch(JsVisitException e){
+            fail();
+        }
+        return;
+    }
+
+    /**
+     * Test of compareTo method, of class JsNull.
+     */
+    @Test
+    public void testCompareTo(){
+        System.out.println("compareTo");
+        assertEquals(0, JsNull.NULL.compareTo(JsNull.NULL));
+        try{
+            JsNull.NULL.compareTo(null);
+            fail();
+        }catch(NullPointerException e){
+            // NOTHING
+        }
+        return;
+    }
+
+    /**
+     * Test of toString method, of class JsNull.
+     */
+    @Test
+    public void testToString(){
+        System.out.println("toString");
+        assertEquals("null", JsNull.NULL.toString());
+        return;
+    }
+
+    /**
+     * Test of getJsTypes method, of class JsNull.
+     */
+    @Test
+    public void testGetJsTypes() {
+        System.out.println("getJsTypes");
+
+        JsNull instance = JsNull.NULL;
+
+        assertEquals(JsTypes.NULL, instance.getJsTypes());
+
+        return;
+    }
+
+    /**
+     * Test of equals method, of class JsNull.
+     */
+    @Test
+    public void testEquals(){
+        System.out.println("equals");
+
+        assertTrue(JsNull.NULL.equals(JsNull.NULL));
+
+        JsNull nullVal = null;
+        assertFalse(JsNull.NULL.equals(nullVal));
+
+        assertFalse(JsNull.NULL.equals(""));
+
+        return;
+    }
+
+    /**
+     * Test of hashCode method, of class JsNull.
+     */
+    @Test
+    public void testHashCode(){
+        System.out.println("hashCode");
+
+        assertEquals(JsNull.NULL.hashCode(), JsNull.NULL.hashCode());
+
+        return;
+    }
+
+    /**
+     * Test of parseNull method, of class JsNull.
+     */
+    @Test
+    public void testParseNull() throws Exception{
+        System.out.println("parseNull");
+
+        JsonSource source;
+        JsNull result;
+
+        source = new JsonSource("null");
+        result = JsNull.parseNull(source);
+        assertEquals(JsNull.NULL, result);
+
+        source = new JsonSource("X");
+        result = JsNull.parseNull(source);
+        assertNull(result);
+
+        try{
+            source = new JsonSource("nX");
+            result = JsNull.parseNull(source);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        try{
+            source = new JsonSource("nuX");
+            result = JsNull.parseNull(source);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        try{
+            source = new JsonSource("nulX");
+            result = JsNull.parseNull(source);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jovsonz/JsNumberTest.java b/src/test/java/jp/sourceforge/jovsonz/JsNumberTest.java
new file mode 100644 (file)
index 0000000..7eb5356
--- /dev/null
@@ -0,0 +1,536 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class JsNumberTest {
+
+    public JsNumberTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception{
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception{
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of parseNumber method, of class JsNumber.
+     */
+    @Test
+    public void testParseNumber() throws Exception{
+        System.out.println("parseNumber");
+
+        JsonSource source;
+        JsNumber number;
+
+        source = new JsonSource("0");
+        number = JsNumber.parseNumber(source);
+        assertEquals("0", number.toString());
+
+        source = new JsonSource("-0");
+        number = JsNumber.parseNumber(source);
+        assertEquals("0", number.toString());
+
+        source = new JsonSource("12");
+        number = JsNumber.parseNumber(source);
+        assertEquals("12", number.toString());
+
+        source = new JsonSource("-12");
+        number = JsNumber.parseNumber(source);
+        assertEquals("-12", number.toString());
+
+        source = new JsonSource("+12");
+        number = JsNumber.parseNumber(source);
+        assertNull(number);
+
+        try{
+            source = new JsonSource("12.");
+            number = JsNumber.parseNumber(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("12.@");
+            number = JsNumber.parseNumber(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        source = new JsonSource("12.34");
+        number = JsNumber.parseNumber(source);
+        assertEquals("12.34", number.toString());
+
+        source = new JsonSource("12.0");
+        number = JsNumber.parseNumber(source);
+        assertEquals("12.0", number.toString());
+
+        source = new JsonSource("12.00");
+        number = JsNumber.parseNumber(source);
+        assertEquals("12.00", number.toString());
+
+        source = new JsonSource("12.003");
+        number = JsNumber.parseNumber(source);
+        assertEquals("12.003", number.toString());
+
+        source = new JsonSource("12.0030");
+        number = JsNumber.parseNumber(source);
+        assertEquals("12.0030", number.toString());
+
+        try{
+            source = new JsonSource("09");
+            number = JsNumber.parseNumber(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        source = new JsonSource("12e34");
+        number = JsNumber.parseNumber(source);
+        assertEquals("1.2E+35", number.toString());
+
+        source = new JsonSource("12E34");
+        number = JsNumber.parseNumber(source);
+        assertEquals("1.2E+35", number.toString());
+
+        source = new JsonSource("12e+34");
+        number = JsNumber.parseNumber(source);
+        assertEquals("1.2E+35", number.toString());
+
+        source = new JsonSource("12e-34");
+        number = JsNumber.parseNumber(source);
+        assertEquals("1.2E-33", number.toString());
+
+        source = new JsonSource("12e0034");
+        number = JsNumber.parseNumber(source);
+        assertEquals("1.2E+35", number.toString());
+
+        try{
+            source = new JsonSource("12e");
+            number = JsNumber.parseNumber(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("12e+");
+            number = JsNumber.parseNumber(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("12e-");
+            number = JsNumber.parseNumber(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("12e#");
+            number = JsNumber.parseNumber(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        source = new JsonSource("-12.34e-056");
+        number = JsNumber.parseNumber(source);
+        assertEquals("-1.234E-55", number.toString());
+
+        return;
+    }
+
+    /**
+     * Test of constructor, of class JsNumber.
+     */
+    @Test
+    public void testConstructors() throws Exception{
+        System.out.println("constructor");
+
+        JsNumber number;
+        BigDecimal decimal;
+
+        number = new JsNumber(99L);
+        decimal = number.decimalValue();
+        assertEquals(new BigInteger("99"), decimal.unscaledValue());
+        assertEquals(0, decimal.scale());
+
+        number = new JsNumber(99.0);
+        decimal = number.decimalValue();
+        assertEquals(new BigInteger("990"), decimal.unscaledValue());
+        assertEquals(1, decimal.scale());
+
+        number = new JsNumber(new BigInteger("99"));
+        decimal = number.decimalValue();
+        assertEquals(new BigInteger("99"), decimal.unscaledValue());
+        assertEquals(0, decimal.scale());
+
+        number = new JsNumber("99.9");
+        decimal = number.decimalValue();
+        assertEquals(new BigInteger("999"), decimal.unscaledValue());
+        assertEquals(1, decimal.scale());
+
+        number = new JsNumber(new BigDecimal("99.9"));
+        decimal = number.decimalValue();
+        assertEquals(new BigInteger("999"), decimal.unscaledValue());
+        assertEquals(1, decimal.scale());
+
+        number = new JsNumber(1.0 / 10.0);
+        decimal = number.decimalValue();
+        assertEquals(new BigInteger("1"), decimal.unscaledValue());
+        assertEquals(1, decimal.scale());
+
+        number = new JsNumber(new BigDecimal(1.0 / 10.0));
+        decimal = number.decimalValue();
+        assertEquals(
+                new BigInteger(
+                "1000000000000000055511151231257827021181583404541015625"),
+                decimal.unscaledValue());
+        assertEquals(55, decimal.scale());
+
+        try{
+            number = new JsNumber((BigDecimal)null);
+            fail();
+        }catch(NullPointerException e){
+            //GOOD
+        }
+
+        return;
+    }
+
+    /**
+     * Test of traverse method, of class JsNumber.
+     */
+    @Test
+    public void testTraverse(){
+        System.out.println("traverse");
+
+        JsNumber number = new JsNumber("0");
+
+        try{
+            number.traverse(new ValueVisitor(){
+                int ct = 0;
+
+                public void visitValue(JsValue value)
+                        throws JsVisitException{
+                    assertEquals(new JsNumber("0"), value);
+                    assertTrue(this.ct++ <= 0);
+                }
+
+                public void visitPairName(String name)
+                        throws JsVisitException{
+                    throw new JsVisitException();
+                }
+
+                public void visitCompositionClose(JsComposition composite)
+                        throws JsVisitException{
+                    throw new JsVisitException();
+                }
+            });
+        }catch(JsVisitException e){
+            fail();
+        }
+
+        return;
+    }
+
+    /**
+     * Test of decimalValue method, of class JsNumber.
+     */
+    @Test
+    public void testDecimalValue(){
+        System.out.println("decimalValue");
+
+        JsNumber number = new JsNumber("-123.456e+1");
+        BigDecimal decimal = number.decimalValue();
+
+        assertEquals(new BigDecimal("-123.456e+1"), decimal);
+        assertEquals(2, decimal.scale());
+        assertEquals(new BigInteger("-123456"), decimal.unscaledValue());
+
+        return;
+    }
+
+    /**
+     * Test of intValue method, of class JsNumber.
+     */
+    @Test
+    public void testIntValue(){
+        System.out.println("intValue");
+
+        assertEquals(0, new JsNumber("0").intValue());
+        assertEquals(99, new JsNumber("99.9").intValue());
+        assertEquals(-99, new JsNumber("-99.9").intValue());
+        assertEquals(2147483647, new JsNumber("2147483647").intValue());
+
+        return;
+    }
+
+    /**
+     * Test of longValue method, of class JsNumber.
+     */
+    @Test
+    public void testLongValue(){
+        System.out.println("longValue");
+
+        assertEquals(0L, new JsNumber("0").longValue());
+        assertEquals(99L, new JsNumber("99.9").longValue());
+        assertEquals(-99L, new JsNumber("-99.9").longValue());
+        assertEquals(999999999999L, new JsNumber("999999999999").longValue());
+
+        return;
+    }
+
+    /**
+     * Test of floatValue method, of class JsNumber.
+     */
+    @Test
+    public void testFloatValue(){
+        System.out.println("floatValue");
+
+        assertEquals(1.25f, new JsNumber("1.25").floatValue(), 0.0);
+        assertEquals(1.25f, new JsNumber("125E-2").floatValue(), 0.0);
+
+        return;
+    }
+
+    /**
+     * Test of doubleValue method, of class JsNumber.
+     */
+    @Test
+    public void testDoubleValue(){
+        System.out.println("doubleValue");
+
+        assertEquals(1.25, new JsNumber("1.25").doubleValue(), 0.0);
+        assertEquals(1.25, new JsNumber("125E-2").doubleValue(), 0.0);
+
+        return;
+    }
+
+    /**
+     * Test of hashCode method, of class JsNumber.
+     */
+    @Test
+    public void testHashCode(){
+        System.out.println("hashCode");
+
+        assertEquals(new JsNumber("1").hashCode(), new JsNumber("1").hashCode());
+        assertEquals(new JsNumber("1.23").hashCode(), new JsNumber("123e-2").hashCode());
+
+        return;
+    }
+
+    /**
+     * Test of equals method, of class JsNumber.
+     */
+    @Test
+    public void testEquals(){
+        System.out.println("equals");
+
+        JsNumber nullVal = null;
+
+        assertTrue(new JsNumber("1").equals(new JsNumber("1")));
+        assertFalse(new JsNumber("1").equals(new JsNumber("2")));
+        assertFalse(new JsNumber("1").equals(nullVal));
+        assertFalse(new JsNumber("1").equals(""));
+
+        assertTrue(new JsNumber("1.23").equals(new JsNumber("123e-2")));
+        assertFalse(new JsNumber("1.0").equals(new JsNumber("1.00")));
+
+        return;
+    }
+
+    /**
+     * Test of compareTo method, of class JsNumber.
+     */
+    @Test
+    public void testCompareTo(){
+        System.out.println("compareTo");
+
+        assertTrue(0 > new JsNumber("-1").compareTo(new JsNumber("1")));
+        assertTrue(0 < new JsNumber("1").compareTo(new JsNumber("-1")));
+        assertTrue(new JsNumber("1").compareTo(new JsNumber("1")) == 0);
+
+        assertTrue(0 > new JsNumber("1").compareTo(new JsNumber("2")));
+        assertTrue(0 < new JsNumber("9").compareTo(new JsNumber("8")));
+
+        assertTrue(new JsNumber("1.23").compareTo(new JsNumber("123e-2")) == 0);
+        assertTrue(new JsNumber("1.0").compareTo(new JsNumber("1.00")) == 0);
+
+        JsNumber number = new JsNumber("99");
+        assertTrue(number.compareTo(number) == 0);
+
+        return;
+    }
+
+    /**
+     * Test of toString method, of class JsNumber.
+     */
+    @Test
+    public void testToString(){
+        System.out.println("toString");
+
+        JsNumber number;
+
+        number = new JsNumber("0");
+        assertEquals("0", number.toString());
+        number = new JsNumber("+0");
+        assertEquals("0", number.toString());
+        number = new JsNumber("-0");
+        assertEquals("0", number.toString());
+
+        number = new JsNumber("1");
+        assertEquals("1", number.toString());
+        number = new JsNumber("+1");
+        assertEquals("1", number.toString());
+        number = new JsNumber("-1");
+        assertEquals("-1", number.toString());
+
+        number = new JsNumber("0.0");
+        assertEquals("0.0", number.toString());
+
+        number = new JsNumber("1.0");
+        assertEquals("1.0", number.toString());
+
+        number = new JsNumber("1.00");
+        assertEquals("1.00", number.toString());
+
+        number = new JsNumber("0.1");
+        assertEquals("0.1", number.toString());
+
+        number = new JsNumber("0.10");
+        assertEquals("0.10", number.toString());
+
+        number = new JsNumber("0.000001");
+        assertEquals("0.000001", number.toString());
+
+        number = new JsNumber("0.0000001");
+        assertEquals("1E-7", number.toString());
+
+        number = new JsNumber("123e0");
+        assertEquals("123", number.toString());
+
+        number = new JsNumber("123e1");
+        assertEquals("1.23E+3", number.toString());
+
+        number = new JsNumber("123E1");
+        assertEquals("1.23E+3", number.toString());
+
+        number = new JsNumber("123e+1");
+        assertEquals("1.23E+3", number.toString());
+
+        number = new JsNumber("123e-1");
+        assertEquals("12.3", number.toString());
+
+        number = new JsNumber("123e-8");
+        assertEquals("0.00000123", number.toString());
+
+        number = new JsNumber("123e-9");
+        assertEquals("1.23E-7", number.toString());
+
+        return;
+    }
+
+    /**
+     * Test of getJsTypes method, of class JsNumber.
+     */
+    @Test
+    public void testGetJsTypes() {
+        System.out.println("getJsTypes");
+
+        JsNumber instance = new JsNumber(0);
+
+        assertEquals(JsTypes.NUMBER, instance.getJsTypes());
+
+        return;
+    }
+
+    /**
+     * Test of isLatinDigit method, of class JsNumber.
+     */
+    @Test
+    public void testIsLatinDigit(){
+        System.out.println("isLatinDigit");
+
+        assertTrue(JsNumber.isLatinDigit('0'));
+        assertTrue(JsNumber.isLatinDigit('1'));
+        assertTrue(JsNumber.isLatinDigit('2'));
+        assertTrue(JsNumber.isLatinDigit('3'));
+        assertTrue(JsNumber.isLatinDigit('4'));
+        assertTrue(JsNumber.isLatinDigit('5'));
+        assertTrue(JsNumber.isLatinDigit('6'));
+        assertTrue(JsNumber.isLatinDigit('7'));
+        assertTrue(JsNumber.isLatinDigit('8'));
+        assertTrue(JsNumber.isLatinDigit('9'));
+
+        assertFalse(JsNumber.isLatinDigit('A'));
+        assertFalse(JsNumber.isLatinDigit('+'));
+        assertFalse(JsNumber.isLatinDigit('-'));
+        assertFalse(JsNumber.isLatinDigit('.'));
+        assertFalse(JsNumber.isLatinDigit('\u0000'));
+        assertFalse(JsNumber.isLatinDigit('\uffff'));
+
+        return;
+    }
+
+    /**
+     * Test of scale method, of class JsNumber.
+     */
+    @Test
+    public void testScale() throws Exception{
+        System.out.println("scale");
+
+        JsNumber number;
+        JsonSource source;
+
+        source = new JsonSource("10");
+        number = JsNumber.parseNumber(source);
+        assertEquals(0, number.scale());
+
+        source = new JsonSource("10.0");
+        number = JsNumber.parseNumber(source);
+        assertEquals(1, number.scale());
+
+        source = new JsonSource("10.0E+3");
+        number = JsNumber.parseNumber(source);
+        assertEquals(-2, number.scale());
+
+        source = new JsonSource("10E+3");
+        number = JsNumber.parseNumber(source);
+        assertEquals(-3, number.scale());
+
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jovsonz/JsObjectTest.java b/src/test/java/jp/sourceforge/jovsonz/JsObjectTest.java
new file mode 100644 (file)
index 0000000..de35604
--- /dev/null
@@ -0,0 +1,560 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class JsObjectTest {
+
+    public JsObjectTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception{
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception{
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of parseObject method, of class JsObject.
+     */
+    @Test
+    public void testParseObject() throws Exception{
+        System.out.println("parseObject");
+
+        JsonSource source;
+        JsObject object;
+
+        source = new JsonSource("{}");
+        object = JsObject.parseObject(source);
+        assertEquals(0, object.size());
+
+        source = new JsonSource("{\"A\":true}");
+        object = JsObject.parseObject(source);
+        assertEquals(1, object.size());
+        assertEquals(JsBoolean.TRUE, object.getPair("A").getValue());
+
+        source = new JsonSource("{\"A\":true,\"B\":false}");
+        object = JsObject.parseObject(source);
+        assertEquals(2, object.size());
+        assertEquals(JsBoolean.TRUE, object.getPair("A").getValue());
+        assertEquals(JsBoolean.FALSE, object.getPair("B").getValue());
+
+        source = new JsonSource("\n{\r\"A\"\t: true,\"B\":false\n}\r");
+        object = JsObject.parseObject(source);
+        assertNull(object);
+
+        source = new JsonSource("{\r\"A\"\t: true,\"B\":false\n}\r");
+        object = JsObject.parseObject(source);
+        assertEquals(2, object.size());
+        assertEquals(JsBoolean.TRUE, object.getPair("A").getValue());
+        assertEquals(JsBoolean.FALSE, object.getPair("B").getValue());
+
+        try{
+            source = new JsonSource("{,}");
+            object = JsObject.parseObject(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("{true,}");
+            object = JsObject.parseObject(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("{true");
+            object = JsObject.parseObject(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("{\"A\",");
+            object = JsObject.parseObject(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("{\"A\":");
+            object = JsObject.parseObject(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("{\"A\":#");
+            object = JsObject.parseObject(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("{\"A\":true#");
+            object = JsObject.parseObject(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("{\"A\":true,");
+            object = JsObject.parseObject(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("{\"A\":true,#");
+            object = JsObject.parseObject(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        source = new JsonSource("true}");
+        object = JsObject.parseObject(source);
+        assertNull(object);
+
+        return;
+    }
+
+    /**
+     * Test of putValue method, of class JsObject.
+     */
+    @Test
+    public void testPutGetValue(){
+        System.out.println("putValue");
+
+        JsObject object = new JsObject();
+        assertEquals(0, object.size());
+
+        object.putValue("x", JsNull.NULL);
+        assertEquals(1, object.size());
+        assertEquals(JsNull.NULL, object.getValue("x"));
+        assertEquals(null, object.getValue("y"));
+
+        object.putValue("y", JsBoolean.TRUE);
+        assertEquals(2, object.size());
+        assertEquals(JsBoolean.TRUE, object.getValue("y"));
+
+        object.putValue("x", JsBoolean.FALSE);
+        assertEquals(2, object.size());
+        assertEquals(JsBoolean.FALSE, object.getValue("x"));
+
+        try{
+            object.putValue("x", null);
+            fail();
+        }catch(NullPointerException e){
+            //GOOD
+        }
+
+        try{
+            object.putValue(null, JsNull.NULL);
+            fail();
+        }catch(NullPointerException e){
+            //GOOD
+        }
+
+        return;
+    }
+
+    /**
+     * Test of putPair method, of class JsObject.
+     */
+    @Test
+    public void testPutGetPair(){
+        System.out.println("putPair");
+
+        JsObject object = new JsObject();
+        assertEquals(0, object.size());
+
+        JsPair pair = new JsPair("x", JsNull.NULL);
+        object.putPair(pair);
+        assertEquals(1, object.size());
+        assertEquals(JsNull.NULL, object.getValue("x"));
+
+        JsPair pair2 = object.getPair("x");
+        assertNotNull(pair2);
+        assertEquals("x", pair2.getName());
+        assertEquals(JsNull.NULL, pair2.getValue());
+
+        assertNull(object.getPair("y"));
+
+        return;
+    }
+
+    /**
+     * Test of clear method, of class JsObject.
+     */
+    @Test
+    public void testClear(){
+        System.out.println("clear");
+
+        JsObject object = new JsObject();
+        assertEquals(0, object.size());
+
+        object.putValue("x", JsNull.NULL);
+        assertEquals(1, object.size());
+
+        object.clear();
+        assertEquals(0, object.size());
+
+        object = new JsObject();
+        object.putValue("x", JsNull.NULL);
+        assertEquals(1, object.size());
+        assertTrue(object.hasChanged());
+        object.setUnchanged();
+        assertFalse(object.hasChanged());
+        object.clear();
+        assertEquals(0, object.size());
+        assertTrue(object.hasChanged());
+        object.setUnchanged();
+        object.clear();
+        assertEquals(0, object.size());
+        assertFalse(object.hasChanged());
+
+        return;
+    }
+
+    /**
+     * Test of remove method, of class JsObject.
+     */
+    @Test
+    public void testRemove(){
+        System.out.println("remove");
+
+        JsObject object = new JsObject();
+
+        object.putValue("x", JsNull.NULL);
+        assertEquals(1, object.size());
+
+        assertNotNull(object.getValue("x"));
+
+        assertEquals(JsNull.NULL, object.remove("x").getValue());
+        assertEquals(0, object.size());
+        assertNull(object.getValue("x"));
+
+        assertNull(object.remove("y"));
+
+        return;
+    }
+
+    /**
+     * Test of nameSet method, of class JsObject.
+     */
+    @Test
+    public void testNameSet(){
+        System.out.println("nameSet");
+
+        JsObject object = new JsObject();
+        Set<String> set;
+
+        set = object.nameSet();
+        assertEquals(0, set.size());
+
+        object.putValue("y", JsNull.NULL);
+        object.putValue("z", JsNull.NULL);
+        object.putValue("x", JsNull.NULL);
+
+        set = object.nameSet();
+        assertEquals(3, set.size());
+        Object[] names = set.toArray();
+
+        assertEquals("x", names[0]);
+        assertEquals("y", names[1]);
+        assertEquals("z", names[2]);
+
+        return;
+    }
+
+    /**
+     * Test of getPairList method, of class JsObject.
+     */
+    @Test
+    public void testGetPairList(){
+        System.out.println("getPairList");
+
+        JsObject object = new JsObject();
+        List<JsPair> list;
+
+        list = object.getPairList();
+        assertEquals(0, list.size());
+
+        object.putValue("y", JsNull.NULL);
+        object.putValue("z", JsBoolean.TRUE);
+        object.putValue("x", JsBoolean.FALSE);
+
+        list = object.getPairList();
+        assertEquals(3, list.size());
+
+        assertEquals("x", list.get(0).getName());
+        assertEquals("y", list.get(1).getName());
+        assertEquals("z", list.get(2).getName());
+        assertEquals(JsBoolean.FALSE, list.get(0).getValue());
+        assertEquals(JsNull.NULL, list.get(1).getValue());
+        assertEquals(JsBoolean.TRUE, list.get(2).getValue());
+
+        return;
+    }
+
+    /**
+     * Test of iterator method, of class JsObject.
+     */
+    @Test
+    public void testIterator(){
+        System.out.println("iterator");
+
+        JsObject object = new JsObject();
+        object.putValue("y", JsBoolean.FALSE);
+        object.putValue("x", JsBoolean.TRUE);
+
+        Iterator<JsPair> it = object.iterator();
+
+        assertTrue(it.hasNext());
+        assertEquals(JsBoolean.TRUE, it.next().getValue());
+        assertTrue(it.hasNext());
+        assertEquals(JsBoolean.FALSE, it.next().getValue());
+        assertFalse(it.hasNext());
+
+        return;
+    }
+
+    /**
+     * Test of size method, of class JsObject.
+     */
+    @Test
+    public void testSize(){
+        System.out.println("size");
+
+        JsObject object = new JsObject();
+        assertEquals(0, object.size());
+        assertTrue(object.isEmpty());
+
+        object.putValue("x", JsBoolean.TRUE);
+        assertEquals(1, object.size());
+        assertFalse(object.isEmpty());
+
+        object.putValue("y", JsBoolean.FALSE);
+        assertEquals(2, object.size());
+        assertFalse(object.isEmpty());
+
+        return;
+    }
+
+    /**
+     * Test of hashCode method, of class JsObject.
+     */
+    @Test
+    public void testHashCode(){
+        System.out.println("hashCode");
+
+        JsObject obj1 = new JsObject();
+        JsObject obj2 = new JsObject();
+        assertEquals(obj1.hashCode(), obj2.hashCode());
+
+        obj1.putValue("x", new JsNumber("99"));
+        obj2.putValue("x", new JsNumber("99"));
+        assertEquals(obj1.hashCode(), obj2.hashCode());
+
+        return;
+    }
+
+    /**
+     * Test of equals method, of class JsObject.
+     */
+    @Test
+    public void testEquals(){
+        System.out.println("equals");
+
+        JsObject obj1 = new JsObject();
+        JsObject obj2 = new JsObject();
+        assertTrue(obj1.equals(obj2));
+
+        obj1.putValue("x", new JsNumber("99"));
+        obj2.putValue("x", new JsNumber("99"));
+        assertTrue(obj1.equals(obj2));
+
+        obj1.putValue("x", new JsNumber("99"));
+        obj2.putValue("x", new JsNumber("999"));
+        assertFalse(obj1.equals(obj2));
+
+        JsObject nullVal = null;
+        assertFalse(obj1.equals(nullVal));
+        assertFalse(obj1.equals(""));
+
+        return;
+    }
+
+    /**
+     * Test of toString method, of class JsObject.
+     */
+    @Test
+    public void testToString(){
+        System.out.println("toString");
+
+        JsObject object = new JsObject();
+
+        assertEquals("{}", object.toString());
+
+        object.putValue("x", JsBoolean.TRUE);
+        assertEquals("{\"x\":true}", object.toString());
+
+        object.putValue("y", JsBoolean.FALSE);
+        assertEquals("{\"x\":true,\"y\":false}", object.toString());
+
+        object.putValue("z", new JsObject());
+        assertEquals("{\"x\":true,\"y\":false,\"z\":{}}", object.toString());
+
+        return;
+    }
+
+    /**
+     * Test of traverse method, of class JsObject.
+     */
+    @Test
+    public void testTraverse() throws Exception{
+        System.out.println("traverse");
+
+        JsObject obj = new JsObject();
+        JsValue val1 = new JsNumber("12");
+        JsValue val2 = new JsString("AB");
+        obj.putValue("x", val1);
+        obj.putValue("y", val2);
+
+        final List<Object> visited = new LinkedList<Object>();
+
+        try{
+            obj.traverse(new ValueVisitor(){
+                public void visitValue(JsValue value)
+                        throws JsVisitException{
+                    visited.add(value);
+                    return;
+                }
+
+                public void visitPairName(String name)
+                        throws JsVisitException{
+                    visited.add(name);
+                    return;
+                }
+
+                public void visitCompositionClose(JsComposition composite)
+                        throws JsVisitException{
+                    visited.add(composite);
+                    return;
+                }
+            });
+        }catch(JsVisitException e){
+            fail();
+        }
+
+        assertEquals(6, visited.size());
+        assertEquals(obj, visited.get(0));
+        assertEquals("x", visited.get(1));
+        assertEquals(val1, visited.get(2));
+        assertEquals("y", visited.get(3));
+        assertEquals(val2, visited.get(4));
+        assertEquals(obj, visited.get(5));
+
+        return;
+    }
+
+    /**
+     * Test of hasChanged method, of class JsObject.
+     */
+    @Test
+    public void testHasChanged(){
+        System.out.println("hasChanged");
+
+        JsObject obj = new JsObject();
+        assertFalse(obj.hasChanged());
+
+        obj.putValue("x", JsNull.NULL);
+        assertTrue(obj.hasChanged());
+
+        obj.setUnchanged();
+        assertFalse(obj.hasChanged());
+
+        JsObject child = new JsObject();
+        obj.putValue("y", child);
+        obj.setUnchanged();
+        assertFalse(obj.hasChanged());
+
+        child.putValue("z", JsBoolean.TRUE);
+        assertTrue(obj.hasChanged());
+        obj.setUnchanged();
+        assertFalse(obj.hasChanged());
+
+        return;
+    }
+
+    /**
+     * Test of setUnchanged method, of class JsObject.
+     */
+    @Test
+    public void testSetUnchanged(){
+        System.out.println("setUnchanged");
+
+        JsObject obj = new JsObject();
+        JsObject child = new JsObject();
+        obj.putValue("x", child);
+
+        child.putValue("y", JsNull.NULL);
+        assertTrue(child.hasChanged());
+
+        obj.setUnchanged();
+        assertFalse(child.hasChanged());
+
+        return;
+    }
+
+    /**
+     * Test of getJsTypes method, of class JsObject.
+     */
+    @Test
+    public void testGetJsTypes() {
+        System.out.println("getJsTypes");
+
+        JsObject instance = new JsObject();
+
+        assertEquals(JsTypes.OBJECT, instance.getJsTypes());
+
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jovsonz/JsPairTest.java b/src/test/java/jp/sourceforge/jovsonz/JsPairTest.java
new file mode 100644 (file)
index 0000000..803ee68
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class JsPairTest {
+
+    public JsPairTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception{
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception{
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of Constructor, of class JsPair.
+     */
+    @Test
+    public void testConstructor(){
+        System.out.println("constructor");
+
+        JsPair pair = null;
+
+        try{
+            pair = new JsPair(null, 3);
+            fail();
+        }catch(NullPointerException e){
+            // NOTHING
+        }
+
+        try{
+            pair = new JsPair("three", (String)null);
+            fail();
+        }catch(NullPointerException e){
+            // NOTHING
+        }
+
+        try{
+            pair = new JsPair(null, (String)null);
+            fail();
+        }catch(NullPointerException e){
+            // NOTHING
+        }
+
+        try{
+            pair = new JsPair("three", (JsValue)null);
+            fail();
+        }catch(NullPointerException e){
+            // NOTHING
+        }
+
+        try{
+            pair = new JsPair(null, (JsValue)null);
+            fail();
+        }catch(NullPointerException e){
+            // NOTHING
+        }
+
+        assertNull(pair);
+
+        return;
+    }
+
+    /**
+     * Test of getName method, of class JsPair.
+     */
+    @Test
+    public void testGetName(){
+        System.out.println("getName");
+
+        JsPair pair;
+
+        pair = new JsPair("", JsNull.NULL);
+        assertEquals("", pair.getName());
+
+        pair = new JsPair("a", JsNull.NULL);
+        assertEquals("a", pair.getName());
+
+        return;
+    }
+
+    /**
+     * Test of getValue method, of class JsPair.
+     */
+    @Test
+    public void testGetValue(){
+        System.out.println("getValue");
+
+        JsPair pair;
+
+        pair = new JsPair("x", JsNull.NULL);
+        assertEquals(JsNull.NULL, pair.getValue());
+
+        pair = new JsPair("x", "abc");
+        assertEquals(new JsString("abc"), pair.getValue());
+
+        pair = new JsPair("x", true);
+        assertEquals(JsBoolean.TRUE, pair.getValue());
+
+        pair = new JsPair("x", false);
+        assertEquals(JsBoolean.FALSE, pair.getValue());
+
+        pair = new JsPair("x", 999999999999L);
+        assertEquals(new JsNumber("999999999999"), pair.getValue());
+
+        pair = new JsPair("x", 1.25);
+        assertEquals(new JsNumber("1.25"), pair.getValue());
+
+        return;
+    }
+
+    /**
+     * Test of toString method, of class JsPair.
+     */
+    @Test
+    public void testToString(){
+        System.out.println("toString");
+
+        JsPair pair;
+
+        pair = new JsPair("x", JsNull.NULL);
+        assertEquals("\"x\":null", pair.toString());
+
+        pair = new JsPair("", JsNull.NULL);
+        assertEquals("\"\":null", pair.toString());
+
+        return;
+    }
+
+    /**
+     * Test of equals method, of class JsPair.
+     */
+    @Test
+    public void testEquals(){
+        System.out.println("equals");
+
+        JsPair pair1;
+        JsPair pair2;
+        JsPair nullVal = null;
+
+        pair1 = new JsPair("three", 3);
+        pair2 = new JsPair("three", 3);
+
+        assertFalse(pair1.equals(nullVal));
+        assertTrue(pair1.equals(pair1));
+        assertTrue(pair1.equals(pair2));
+        assertFalse(pair1.equals(new Object()));
+
+        pair2 = new JsPair("three", 4);
+        assertFalse(pair1.equals(pair2));
+
+        pair2 = new JsPair("two", 3);
+        assertFalse(pair1.equals(pair2));
+
+        pair2 = new JsPair("four", 4);
+        assertFalse(pair1.equals(pair2));
+
+        return;
+    }
+
+    /**
+     * Test of hashCode method, of class JsPair.
+     */
+    @Test
+    public void testHashCode(){
+        System.out.println("hashCode");
+
+        JsPair pair1;
+        JsPair pair2;
+
+        pair1 = new JsPair("three", 3);
+        pair2 = new JsPair("three", 3);
+
+        assertEquals(pair1.hashCode(), pair2.hashCode());
+
+        int hash1 = pair1.hashCode();
+        int hash2 = pair1.hashCode();
+
+        assertEquals(hash1, hash2);
+
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jovsonz/JsParseExceptionTest.java b/src/test/java/jp/sourceforge/jovsonz/JsParseExceptionTest.java
new file mode 100644 (file)
index 0000000..3b81940
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class JsParseExceptionTest {
+
+    public JsParseExceptionTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception{
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception{
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of getLineNumber method, of class JsParseException.
+     */
+    @Test
+    public void testGetLineNumber(){
+        System.out.println("getLineNumber");
+
+        JsParseException ex;
+
+        ex = new JsParseException();
+        assertTrue(1 > ex.getLineNumber());
+        assertFalse(ex.hasValidLineNumber());
+
+        ex = new JsParseException("abc", 99);
+        assertEquals(99, ex.getLineNumber());
+        assertTrue(ex.hasValidLineNumber());
+
+        ex = new JsParseException("abc", new Throwable(), 99);
+        assertEquals(99, ex.getLineNumber());
+        assertTrue(ex.hasValidLineNumber());
+
+        return;
+    }
+
+    /**
+     * Test of getMessage method, of class JsParseException.
+     */
+    @Test
+    public void testGetMessage(){
+        System.out.println("getMessage");
+
+        JsParseException ex;
+
+        ex = new JsParseException();
+        assertNull(ex.getMessage());
+
+        ex = new JsParseException("abc", 99);
+        assertEquals("abc [line:99]", ex.getMessage());
+
+        ex = new JsParseException("abc", new Throwable(), 99);
+        assertEquals("abc [line:99]", ex.getMessage());
+
+        ex = new JsParseException(null, new Throwable(), 99);
+        assertEquals("[line:99]", ex.getMessage());
+
+        ex = new JsParseException(null, new Throwable(), 0);
+        assertNull(ex.getMessage());
+
+        ex = new JsParseException("abc", new Throwable(), 0);
+        assertEquals("abc", ex.getMessage());
+
+        return;
+    }
+
+    /**
+     * Test of getCause method, of class JsParseException.
+     */
+    @Test
+    public void testGetCause(){
+        System.out.println("getMessage");
+
+        JsParseException ex;
+
+        ex = new JsParseException();
+        assertNull(ex.getCause());
+
+        ex = new JsParseException("abc", 99);
+        assertNull(ex.getCause());
+
+        Throwable cause = new Throwable();
+        ex = new JsParseException("abc", cause, 99);
+        assertTrue(cause == ex.getCause());
+
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jovsonz/JsStringTest.java b/src/test/java/jp/sourceforge/jovsonz/JsStringTest.java
new file mode 100644 (file)
index 0000000..7e86b77
--- /dev/null
@@ -0,0 +1,508 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.util.SortedSet;
+import java.util.TreeSet;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class JsStringTest {
+
+    public JsStringTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception{
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception{
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of Constructor, of class JsString.
+     */
+    @Test
+    public void testConstructor() throws Exception{
+        System.out.println("constructor");
+
+        JsString string;
+
+        string = new JsString();
+        assertEquals("", string.toRawString());
+
+        string = new JsString("");
+        assertEquals("", string.toRawString());
+
+        string = new JsString("x");
+        assertEquals("x", string.toRawString());
+
+        string = new JsString("\u001f");
+        assertEquals("\u001f", string.toRawString());
+        assertEquals("\"\\u001f\"", string.toString());
+
+        try{
+            string = new JsString(null);
+            fail();
+        }catch(NullPointerException e){
+            //NOTHING
+        }
+
+        return;
+    }
+
+    /**
+     * Test of parseHexChar method, of class JsString.
+     */
+    @Test
+    public void testParseHexChar() throws Exception{
+        System.out.println("parseHexChar");
+
+        JsonSource source;
+        char ch;
+
+        source = new JsonSource("0000");
+        ch = JsString.parseHexChar(source);
+        assertEquals('\u0000', ch);
+
+        source = new JsonSource("ffff");
+        ch = JsString.parseHexChar(source);
+        assertEquals('\uffff', ch);
+
+        source = new JsonSource("FFFF");
+        ch = JsString.parseHexChar(source);
+        assertEquals('\uffff', ch);
+
+        source = new JsonSource("dead");
+        ch = JsString.parseHexChar(source);
+        assertEquals('\udead', ch);
+
+        source = new JsonSource("abcde");
+        ch = JsString.parseHexChar(source);
+        assertEquals('\uabcd', ch);
+
+        try{
+            source = new JsonSource("000,");
+            ch = JsString.parseHexChar(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        return;
+    }
+
+    /**
+     * Test of parseString method, of class JsString.
+     */
+    @Test
+    public void testParseString() throws Exception{
+        System.out.println("parseString");
+
+        JsonSource source;
+        JsString string;
+
+        source = new JsonSource("\"abc\"");
+        string = JsString.parseString(source);
+        assertEquals("abc", string.toRawString());
+
+        source = new JsonSource("\"あいう\"");
+        string = JsString.parseString(source);
+        assertEquals("あいう", string.toRawString());
+
+        source = new JsonSource("\"\\\"\\\\\\/\"");
+        string = JsString.parseString(source);
+        assertEquals("\"\\/", string.toRawString());
+
+        source = new JsonSource("\"\\b\\f\\n\\r\\t\"");
+        string = JsString.parseString(source);
+        assertEquals("\b\f\n\r\t", string.toRawString());
+
+        source = new JsonSource("\"\\uabcd\\uCDEF\"");
+        string = JsString.parseString(source);
+        assertEquals("\uabcd\ucdef", string.toRawString());
+
+        source = new JsonSource("abc\"");
+        string = JsString.parseString(source);
+        assertNull(string);
+
+        try{
+            source = new JsonSource("\"abc");
+            string = JsString.parseString(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("\"\\#\"");
+            string = JsString.parseString(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("\"\\u#999\"");
+            string = JsString.parseString(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("\"\\u9#99\"");
+            string = JsString.parseString(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("\"\\u99#9\"");
+            string = JsString.parseString(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("\"\\u999#\"");
+            string = JsString.parseString(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        try{
+            source = new JsonSource("\"abc\nxyz\"");
+            string = JsString.parseString(source);
+            fail();
+        }catch(JsParseException e){
+            // NOTHING
+        }
+
+        return;
+    }
+
+    /**
+     * Test of dumpString method, of class JsString.
+     */
+    @Test
+    public void testDumpString() throws Exception{
+        System.out.println("writeText");
+
+        Appendable appout;
+        JsString string;
+
+        appout = new StringBuilder();
+        string = new JsString();
+        JsString.dumpString(appout, string);
+        assertEquals("\"\"", appout.toString());
+
+        appout = new StringBuilder();
+        string = new JsString("abc");
+        JsString.dumpString(appout, string);
+        assertEquals("\"abc\"", appout.toString());
+
+        appout = new StringBuilder();
+        string = new JsString("\"");
+        JsString.dumpString(appout, string);
+        assertEquals("\"\\\"\"", appout.toString());
+
+        appout = new StringBuilder();
+        string = new JsString("\\");
+        JsString.dumpString(appout, string);
+        assertEquals("\"\\\\\"", appout.toString());
+
+        appout = new StringBuilder();
+        string = new JsString("/");
+        JsString.dumpString(appout, string);
+        assertEquals("\"\\/\"", appout.toString());
+
+        appout = new StringBuilder();
+        string = new JsString("\b");
+        JsString.dumpString(appout, string);
+        assertEquals("\"\\b\"", appout.toString());
+
+        appout = new StringBuilder();
+        string = new JsString("\f");
+        JsString.dumpString(appout, string);
+        assertEquals("\"\\f\"", appout.toString());
+
+        appout = new StringBuilder();
+        string = new JsString("\n");
+        JsString.dumpString(appout, string);
+        assertEquals("\"\\n\"", appout.toString());
+
+        appout = new StringBuilder();
+        string = new JsString("\r");
+        JsString.dumpString(appout, string);
+        assertEquals("\"\\r\"", appout.toString());
+
+        appout = new StringBuilder();
+        string = new JsString("\t");
+        JsString.dumpString(appout, string);
+        assertEquals("\"\\t\"", appout.toString());
+
+        appout = new StringBuilder();
+        string = new JsString("\u0001");
+        JsString.dumpString(appout, string);
+        assertEquals("\"\\u0001\"", appout.toString());
+
+        appout = new StringBuilder();
+        string = new JsString("あ");
+        JsString.dumpString(appout, string);
+        assertEquals("\"あ\"", appout.toString());
+
+        return;
+    }
+
+    /**
+     * Test of escapeText method, of class JsString.
+     */
+    @Test
+    public void testEscapeText(){
+        System.out.println("escapeText");
+
+        assertEquals("\"A\"", JsString.escapeText("A").toString());
+
+        return;
+    }
+
+    /**
+     * Test of traverse method, of class JsString.
+     */
+    @Test
+    public void testTraverse(){
+        System.out.println("traverse");
+
+        JsString string = new JsString("A");
+
+        try{
+            string.traverse(new ValueVisitor(){
+                int ct = 0;
+
+                public void visitValue(JsValue value)
+                        throws JsVisitException{
+                    assertEquals(new JsString("A"), value);
+                    assertTrue(this.ct++ <= 0);
+                }
+
+                public void visitPairName(String name)
+                        throws JsVisitException{
+                    throw new JsVisitException();
+                }
+
+                public void visitCompositionClose(JsComposition composite)
+                        throws JsVisitException{
+                    throw new JsVisitException();
+                }
+            });
+        }catch(JsVisitException e){
+            fail();
+        }
+
+        return;
+    }
+
+    /**
+     * Test of charAt method, of class JsString.
+     */
+    @Test
+    public void testCharAt(){
+        System.out.println("charAt");
+
+        JsString string;
+
+        string = new JsString("abcde");
+        assertEquals('b', string.charAt(1));
+
+        try{
+            string.charAt(999);
+            fail();
+        }catch(IndexOutOfBoundsException e){
+            // NOTHING
+        }
+
+        return;
+    }
+
+    /**
+     * Test of length method, of class JsString.
+     */
+    @Test
+    public void testLength(){
+        System.out.println("length");
+
+        assertEquals(0, new JsString().length());
+        assertEquals(0, new JsString("").length());
+        assertEquals(1, new JsString("A").length());
+        assertEquals(2, new JsString("AB").length());
+        assertEquals(3, new JsString("A\"B").length());
+
+        return;
+    }
+
+    /**
+     * Test of subSequence method, of class JsString.
+     */
+    @Test
+    public void testSubSequence(){
+        System.out.println("subSequence");
+
+        JsString string;
+
+        string = new JsString("abcde");
+        assertEquals("bcd", string.subSequence(1, 4).toString());
+        assertEquals("", string.subSequence(1, 1).toString());
+
+        try{
+            string.subSequence(1,999);
+            fail();
+        }catch(IndexOutOfBoundsException e){
+            // NOTHING
+        }
+
+        return;
+    }
+
+    /**
+     * Test of hashCode method, of class JsString.
+     */
+    @Test
+    public void testHashCode(){
+        System.out.println("hashCode");
+        assertEquals(new JsString("A").hashCode(), new JsString("A").hashCode());
+        return;
+    }
+
+    /**
+     * Test of equals method, of class JsString.
+     */
+    @Test
+    public void testEquals(){
+        System.out.println("equals");
+
+        assertTrue(new JsString("A").equals(new JsString("A")));
+        assertFalse(new JsString("A").equals(new JsString("a")));
+        JsString nullVal = null;
+        assertFalse(new JsString("A").equals(nullVal));
+
+        assertFalse(new JsString("A").equals(""));
+
+        return;
+    }
+
+    /**
+     * Test of compareTo method, of class JsString.
+     */
+    @Test
+    public void testCompareTo(){
+        System.out.println("compareTo");
+
+        assertTrue(0 == new JsString("A").compareTo(new JsString("A")));
+        assertTrue(0 > new JsString("A").compareTo(new JsString("a")));
+        assertTrue(0 < new JsString("a").compareTo(new JsString("A")));
+        assertTrue(0 < new JsString("A").compareTo(null));
+
+        SortedSet<JsString> set = new TreeSet<JsString>();
+
+        set.clear();
+        set.add(new JsString("A"));
+        set.add(new JsString("a"));
+        assertEquals(new JsString("A"), set.first());
+        assertEquals(new JsString("a"), set.last());
+
+        set.clear();
+        set.add(new JsString("a"));
+        set.add(new JsString("A"));
+        assertEquals(new JsString("A"), set.first());
+        assertEquals(new JsString("a"), set.last());
+
+        JsString string = new JsString("A");
+        assertEquals(0, string.compareTo(string));
+
+        return;
+    }
+
+    /**
+     * Test of toString method, of class JsString.
+     */
+    @Test
+    public void testToString(){
+        System.out.println("toString");
+
+        assertEquals("\"\"", new JsString("").toString());
+        assertEquals("\"abc\"", new JsString("abc").toString());
+        assertEquals("\"\\\"\"", new JsString("\"").toString());
+        assertEquals("\"\\\\\"", new JsString("\\").toString());
+        assertEquals("\"\\/\"", new JsString("/").toString());
+        assertEquals("\"\\b\"", new JsString("\b").toString());
+        assertEquals("\"\\f\"", new JsString("\f").toString());
+        assertEquals("\"\\n\"", new JsString("\n").toString());
+        assertEquals("\"\\r\"", new JsString("\r").toString());
+        assertEquals("\"\\t\"", new JsString("\t").toString());
+        assertEquals("\"\\u0001\"", new JsString("\u0001").toString());
+        assertEquals("\"あ\"", new JsString("あ").toString());
+
+        return;
+    }
+
+    /**
+     * Test of toRawString method, of class JsString.
+     */
+    @Test
+    public void testToRawString(){
+        System.out.println("toRawString");
+
+        assertEquals("", new JsString("").toRawString());
+        assertEquals("abc", new JsString("abc").toRawString());
+        assertEquals("\"", new JsString("\"").toRawString());
+        assertEquals("\\", new JsString("\\").toRawString());
+        assertEquals("/", new JsString("/").toRawString());
+        assertEquals("\b", new JsString("\b").toRawString());
+        assertEquals("\f", new JsString("\f").toRawString());
+        assertEquals("\n", new JsString("\n").toRawString());
+        assertEquals("\r", new JsString("\r").toRawString());
+        assertEquals("\t", new JsString("\t").toRawString());
+        assertEquals("\u0001", new JsString("\u0001").toRawString());
+        assertEquals("あ", new JsString("あ").toRawString());
+
+        return;
+    }
+
+    /**
+     * Test of getJsTypes method, of class JsString.
+     */
+    @Test
+    public void testGetJsTypes() {
+        System.out.println("getJsTypes");
+
+        JsString instance = new JsString();
+
+        assertEquals(JsTypes.STRING, instance.getJsTypes());
+
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jovsonz/JsTypesTest.java b/src/test/java/jp/sourceforge/jovsonz/JsTypesTest.java
new file mode 100644 (file)
index 0000000..590a0fa
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class JsTypesTest {
+
+    public JsTypesTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of values method, of class JsTypes.
+     */
+    @Test
+    public void testValues() {
+        System.out.println("values");
+        JsTypes[] result = JsTypes.values();
+        assertEquals(6, result.length);
+        assertEquals(JsTypes.NUMBER,  result[0]);
+        assertEquals(JsTypes.STRING,  result[1]);
+        assertEquals(JsTypes.BOOLEAN, result[2]);
+        assertEquals(JsTypes.ARRAY,   result[3]);
+        assertEquals(JsTypes.OBJECT,  result[4]);
+        assertEquals(JsTypes.NULL,    result[5]);
+        return;
+    }
+
+    /**
+     * Test of valueOf method, of class JsTypes.
+     */
+    @Test
+    public void testValueOf() {
+        System.out.println("valueOf");
+
+        assertEquals(JsTypes.NUMBER,  JsTypes.valueOf("NUMBER"));
+        assertEquals(JsTypes.STRING,  JsTypes.valueOf("STRING"));
+        assertEquals(JsTypes.BOOLEAN, JsTypes.valueOf("BOOLEAN"));
+        assertEquals(JsTypes.ARRAY,   JsTypes.valueOf("ARRAY"));
+        assertEquals(JsTypes.OBJECT,  JsTypes.valueOf("OBJECT"));
+        assertEquals(JsTypes.NULL,    JsTypes.valueOf("NULL"));
+
+        return;
+    }
+
+    /**
+     * Test of getJsTypes method, of class JsTypes.
+     */
+    @Test
+    public void testGetJsTypes() {
+        System.out.println("getJsTypes");
+
+        assertEquals(JsTypes.NUMBER,  JsTypes.getJsTypes(JsNumber.class));
+        assertEquals(JsTypes.STRING,  JsTypes.getJsTypes(JsString.class));
+        assertEquals(JsTypes.BOOLEAN, JsTypes.getJsTypes(JsBoolean.class));
+        assertEquals(JsTypes.ARRAY,   JsTypes.getJsTypes(JsArray.class));
+        assertEquals(JsTypes.OBJECT,  JsTypes.getJsTypes(JsObject.class));
+        assertEquals(JsTypes.NULL,    JsTypes.getJsTypes(JsNull.class));
+
+        assertNull(JsTypes.getJsTypes(Object.class));
+
+        try{
+            JsTypes.getJsTypes(null);
+            fail();
+        }catch(NullPointerException e){
+            //GOOD
+        }
+
+        return;
+    }
+
+    /**
+     * Test of getJsClass method, of class JsTypes.
+     */
+    @Test
+    public void testGetJsClass() {
+        System.out.println("getJsClass");
+
+        assertEquals(JsNumber.class, JsTypes.NUMBER.getJsClass());
+        assertEquals(JsString.class, JsTypes.STRING.getJsClass());
+        assertEquals(JsBoolean.class, JsTypes.BOOLEAN.getJsClass());
+        assertEquals(JsArray.class, JsTypes.ARRAY.getJsClass());
+        assertEquals(JsObject.class, JsTypes.OBJECT.getJsClass());
+        assertEquals(JsNull.class, JsTypes.NULL.getJsClass());
+
+        return;
+    }
+
+    /**
+     * Test of isComposition method, of class JsTypes.
+     */
+    @Test
+    public void testIsComposition(){
+        System.out.println("isComposition");
+
+        assertTrue(JsTypes.OBJECT.isComposition());
+        assertTrue(JsTypes.ARRAY.isComposition());
+
+        assertFalse(JsTypes.NUMBER.isComposition());
+        assertFalse(JsTypes.STRING.isComposition());
+        assertFalse(JsTypes.BOOLEAN.isComposition());
+        assertFalse(JsTypes.NULL.isComposition());
+
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jovsonz/JsVisitExceptionTest.java b/src/test/java/jp/sourceforge/jovsonz/JsVisitExceptionTest.java
new file mode 100644 (file)
index 0000000..6e89b1c
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class JsVisitExceptionTest {
+
+    public JsVisitExceptionTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception{
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception{
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    @Test
+    public void testConstructor(){
+        System.out.println("constructor");
+
+        JsVisitException ex;
+
+        ex = new JsVisitException();
+        assertNull(ex.getMessage());
+        assertNull(ex.getCause());
+
+        ex = new JsVisitException((String)null);
+        assertNull(ex.getMessage());
+
+        ex = new JsVisitException("");
+        assertEquals("", ex.getMessage());
+
+        ex = new JsVisitException("abc");
+        assertEquals("abc", ex.getMessage());
+
+        Throwable cause = new Throwable("cause");
+
+        ex = new JsVisitException((Throwable)null);
+        assertNull(ex.getMessage());
+
+        ex = new JsVisitException(cause);
+        assertEquals(cause, ex.getCause());
+
+        ex = new JsVisitException(null, null);
+        assertNull(ex.getMessage());
+        assertNull(ex.getCause());
+
+        ex = new JsVisitException("abc", null);
+        assertEquals("abc", ex.getMessage());
+        assertNull(ex.getCause());
+
+        ex = new JsVisitException(null, cause);
+        assertNull(ex.getMessage());
+        assertEquals(cause, ex.getCause());
+
+        ex = new JsVisitException("abc", cause);
+        assertEquals("abc", ex.getMessage());
+        assertEquals(cause, ex.getCause());
+
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jovsonz/JsonAppenderTest.java b/src/test/java/jp/sourceforge/jovsonz/JsonAppenderTest.java
new file mode 100644 (file)
index 0000000..68543de
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.EmptyStackException;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class JsonAppenderTest {
+
+    public JsonAppenderTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception{
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception{
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of Constructor, of class JsonAppender.
+     */
+    @Test
+    public void testConstructor(){
+        System.out.println("constructor");
+
+        JsonAppender appender;
+
+        try{
+            appender = new JsonAppender(null);
+            fail();
+        }catch(NullPointerException e){
+            //GOOD
+        }
+
+        appender = new JsonAppender(new StringBuilder());
+        assertFalse(appender.hasIOException());
+        assertNull(appender.getIOException());
+
+        return;
+    }
+
+    /**
+     * Test of pushComposition method, of class JsonAppender.
+     */
+    @Test
+    public void testPushPopComposition(){
+        System.out.println("pushComposition");
+
+        JsonAppender appender = new JsonAppender(new StringBuilder());
+
+        try{
+            appender.popComposition();
+            fail();
+        }catch(EmptyStackException e){
+            //GOOD
+        }
+        assertEquals(0, appender.nestDepth());
+        assertTrue(appender.isNestEmpty());
+
+        JsObject obj1 = new JsObject();
+        JsObject obj2 = new JsObject();
+        JsArray array1 = new JsArray();
+        JsArray array2 = new JsArray();
+
+        appender.pushComposition(obj1);
+        appender.pushComposition(array1);
+        appender.pushComposition(obj2);
+        appender.pushComposition(array2);
+
+        assertEquals(4, appender.nestDepth());
+        assertFalse(appender.isNestEmpty());
+
+        assertEquals(array2, appender.popComposition());
+        assertEquals(obj2, appender.popComposition());
+        assertEquals(array1, appender.popComposition());
+        assertEquals(obj1, appender.popComposition());
+
+        try{
+            appender.popComposition();
+            fail();
+        }catch(EmptyStackException e){
+            //GOOD
+        }
+        assertEquals(0, appender.nestDepth());
+        assertTrue(appender.isNestEmpty());
+
+        return;
+    }
+
+    /**
+     * Test of hasChildDumped setChildDumped method, of class JsonAppender.
+     */
+    @Test
+    public void testHasSetChildDumped(){
+        System.out.println("hasChildDumped");
+
+        JsonAppender appender;
+
+        appender = new JsonAppender(new StringBuilder());
+        assertFalse(appender.hasChildDumped());
+        appender.setChildDumped();
+        assertFalse(appender.hasChildDumped());
+
+        appender.pushComposition(new JsObject());
+        assertFalse(appender.hasChildDumped());
+        appender.setChildDumped();
+        assertTrue(appender.hasChildDumped());
+
+        appender.pushComposition(new JsObject());
+        assertFalse(appender.hasChildDumped());
+
+        appender.popComposition();
+        assertTrue(appender.hasChildDumped());
+
+        return;
+    }
+
+    /**
+     * Test of isArrayContext method, of class JsonAppender.
+     */
+    @Test
+    public void testIsArrayContext(){
+        System.out.println("isArrayContext");
+        return;
+    }
+
+    /**
+     * Test of getIOException method, of class JsonAppender.
+     */
+    @Test
+    public void testGetIOException() throws Exception{
+        System.out.println("getIOException");
+
+        Reader reader = new StringReader("[1,2,3,4,5]");
+        JsComposition root = Json.parseJson(reader);
+
+        Appendable app = new TroubleAppender(3);
+        JsonAppender appender = new JsonAppender(app);
+
+        assertFalse(appender.hasIOException());
+        assertNull(appender.getIOException());
+
+        try{
+            appender.append("abcde");
+            fail();
+        }catch(JsVisitException e){
+            assertTrue(appender.hasIOException());
+            assertEquals(e.getCause(), appender.getIOException());
+        }
+
+        return;
+    }
+
+    /**
+     * Test of putPairName method, of class JsonAppender.
+     */
+    @Test
+    public void testPutPairName() throws Exception{
+        System.out.println("putPairName");
+
+        Appendable app;
+        JsonAppender appender;
+
+        app = new TroubleAppender(1);
+        appender = new JsonAppender(app);
+
+        try{
+            appender.putPairName("A");
+        }catch(JsVisitException e){
+            assertTrue(appender.hasIOException());
+            assertEquals(e.getCause(), appender.getIOException());
+        }
+
+        return;
+    }
+
+    /**
+     * Test of append method, of class JsonAppender.
+     */
+    @Test
+    public void testAppend() throws Exception{
+        System.out.println("append");
+
+        Appendable app;
+        JsonAppender appender;
+
+        app = new TroubleAppender(3);
+        appender = new JsonAppender(app);
+        appender.append('0');
+        appender.append('1');
+        appender.append('2');
+        try{
+            appender.append('3');
+        }catch(JsVisitException e){
+            assertTrue(appender.hasIOException());
+            assertEquals(e.getCause(), appender.getIOException());
+        }
+
+        app = new TroubleAppender(3);
+        appender = new JsonAppender(app);
+        try{
+            appender.append("1234");
+        }catch(JsVisitException e){
+            assertTrue(appender.hasIOException());
+            assertEquals(e.getCause(), appender.getIOException());
+        }
+
+        return;
+    }
+
+    /**
+     * Test of flush method, of class JsonAppender.
+     */
+    @Test
+    public void testFlush() throws Exception{
+        System.out.println("flush");
+
+        Appendable app = new TroubleAppender(3);
+        JsonAppender appender = new JsonAppender(app);
+
+        try{
+            appender.flush();
+            fail();
+        }catch(JsVisitException e){
+            assertTrue(appender.hasIOException());
+            assertEquals(e.getCause(), appender.getIOException());
+        }
+
+        return;
+    }
+
+    /**
+     * Test of hasIOException method, of class JsonAppender.
+     */
+    @Test
+    public void testHasIOException(){
+        System.out.println("hasIOException");
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jovsonz/JsonSourceTest.java b/src/test/java/jp/sourceforge/jovsonz/JsonSourceTest.java
new file mode 100644 (file)
index 0000000..79b34c4
--- /dev/null
@@ -0,0 +1,616 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class JsonSourceTest {
+
+    public JsonSourceTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception{
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception{
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of constructor, of class JsonSource.
+     */
+    @Test
+    public void testConstructor() throws Exception{
+        System.out.println("constructor");
+
+        JsonSource source;
+        Reader reader;
+
+        reader = new StringReader("abc");
+        source = new JsonSource(reader);
+        assertEquals('a', source.read());
+        assertEquals('b', source.read());
+        assertEquals('c', source.read());
+        assertEquals(-1, source.read());
+
+        source = new JsonSource("abc");
+        assertEquals('a', source.read());
+        assertEquals('b', source.read());
+        assertEquals('c', source.read());
+        assertEquals(-1, source.read());
+
+        try{
+            source = new JsonSource((Reader)null);
+            fail();
+        }catch(NullPointerException e){
+            //GOOD
+        }
+
+        try{
+            source = new JsonSource((String)null);
+            fail();
+        }catch(NullPointerException e){
+            //GOOD
+        }
+
+        return;
+    }
+
+    /**
+     * Test of getLineNumber method, of class JsonSource.
+     */
+    @Test
+    public void testGetLineNumber() throws Exception{
+        System.out.println("getLineNumber");
+
+        JsonSource source;
+
+        source = new JsonSource("a\nbc\r\nd\n\n");
+        assertEquals(1, source.getLineNumber());
+        assertEquals('a', source.read());
+        assertEquals(1, source.getLineNumber());
+        assertEquals('\n', source.read());
+        assertEquals(2, source.getLineNumber());
+        assertEquals('b', source.read());
+        assertEquals(2, source.getLineNumber());
+        assertEquals('c', source.read());
+        assertEquals(2, source.getLineNumber());
+        assertEquals('\r', source.read());
+        assertEquals(2, source.getLineNumber());
+        assertEquals('\n', source.read());
+        assertEquals(3, source.getLineNumber());
+        assertEquals('d', source.read());
+        assertEquals(3, source.getLineNumber());
+        assertEquals('\n', source.read());
+        assertEquals(4, source.getLineNumber());
+        assertEquals('\n', source.read());
+        assertEquals(5, source.getLineNumber());
+        assertEquals(-1, source.read());
+        assertEquals(5, source.getLineNumber());
+
+        source = new JsonSource("\nX");
+        assertEquals(1, source.getLineNumber());
+        assertEquals('\n', source.read());
+        assertEquals(2, source.getLineNumber());
+        assertEquals('X', source.read());
+        assertEquals(2, source.getLineNumber());
+        assertEquals(-1, source.read());
+        assertEquals(2, source.getLineNumber());
+
+        source = new JsonSource("");
+        assertEquals(1, source.getLineNumber());
+        assertEquals(-1, source.read());
+
+        return;
+    }
+
+    /**
+     * Test of read method, of class JsonSource.
+     */
+    @Test
+    public void testRead() throws Exception{
+        System.out.println("read");
+
+        JsonSource source;
+        Reader reader;
+
+        reader = new StringReader("abc");
+        source = new JsonSource(reader);
+        assertEquals('a', source.read());
+        assertEquals('b', source.read());
+        assertEquals('c', source.read());
+        assertEquals(-1, source.read());
+
+        source = new JsonSource("abc");
+        assertEquals('a', source.read());
+        assertEquals('b', source.read());
+        assertEquals('c', source.read());
+        assertEquals(-1, source.read());
+
+        source = new JsonSource("X\u0000\u3000\uffffZ");
+        assertEquals('X', source.read());
+        assertEquals('\u0000', source.read());
+        assertEquals('\u3000', source.read());
+        assertEquals('\uffff', source.read());
+        assertEquals('Z', source.read());
+        assertEquals(-1, source.read());
+
+        // CJK UNIFIED IDEOGRAPH-2000B 𠀋
+        source = new JsonSource("X\ud840\udc0bZ");
+        assertEquals('X', source.read());
+        assertEquals('\ud840', source.read());
+        assertEquals('\udc0b', source.read());
+        assertEquals('Z', source.read());
+        assertEquals(-1, source.read());
+
+        source = new JsonSource("");
+        assertEquals(-1, source.read());
+
+        reader = new TroubleReader("abc", 1);
+        source = new JsonSource(reader);
+        assertEquals('a', source.read());
+        try{
+            source.read();
+            fail();
+        }catch(IOException e){
+            // GOOD!
+        }catch(Throwable e){
+            fail();
+        }
+
+        return;
+    }
+
+    /**
+     * Test of readOrDie method, of class JsonSource.
+     */
+    @Test
+    public void testReadOrDie() throws Exception{
+        System.out.println("readOrDie");
+
+        JsonSource source;
+
+        source = new JsonSource("ab\nc");
+        assertEquals('a', source.readOrDie());
+        assertEquals('b', source.readOrDie());
+        assertEquals('\n', source.readOrDie());
+        assertEquals('c', source.readOrDie());
+        try{
+            source.readOrDie();
+            fail();
+        }catch(JsParseException e){
+            assertEquals(2, e.getLineNumber());
+            assertEquals("We need but no more JSON data [line:2]",
+                         e.getMessage());
+        }catch(Throwable e){
+            fail();
+        }
+
+        Reader reader = new TroubleReader("abc", 1);
+        source = new JsonSource(reader);
+        assertEquals('a', source.readOrDie());
+        try{
+            source.readOrDie();
+            fail();
+        }catch(IOException e){
+            // GOOD!
+        }catch(Throwable e){
+            fail();
+        }
+
+        return;
+    }
+
+    /**
+     * Test of matchOrDie method, of class JsonSource.
+     */
+    @Test
+    public void testMatchOrDie() throws Exception{
+        System.out.println("matchOrDie");
+
+        JsonSource source;
+
+        source = new JsonSource("ABC");
+        assertTrue(source.matchOrDie("ABC"));
+
+        source = new JsonSource("ABC");
+        assertFalse(source.matchOrDie("XYZ"));
+
+        source = new JsonSource("ABC");
+        assertTrue(source.matchOrDie("A"));
+
+        source = new JsonSource("ABC");
+        assertTrue(source.matchOrDie(""));
+
+        source = new JsonSource("ABC");
+        try{
+            source.matchOrDie("ABCD");
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        return;
+    }
+
+    /**
+     * Test of unread method, of class JsonSource.
+     */
+    @Test
+    public void testUnread() throws Exception{
+        System.out.println("unread");
+
+        JsonSource source;
+        Reader reader;
+
+        reader = new StringReader("abc");
+        source = new JsonSource(reader);
+        assertEquals('a', source.read());
+        assertEquals('b', source.read());
+        source.unread('X');
+        source.unread('Y');
+        assertEquals('Y', source.read());
+        assertEquals('X', source.read());
+        assertEquals('c', source.read());
+        assertEquals(-1, source.read());
+
+        reader = new StringReader("a\nb\nc");
+        source = new JsonSource(reader);
+        assertEquals('a', source.read());
+        assertEquals('\n', source.read());
+        assertEquals('b', source.read());
+        assertEquals('\n', source.read());
+        assertEquals(3, source.getLineNumber());
+        source.unread('\n');
+        assertEquals(2, source.getLineNumber());
+        assertEquals('\n', source.read());
+        assertEquals(3, source.getLineNumber());
+
+        reader = new StringReader("abc");
+        source = new JsonSource(reader);
+        assertEquals('a', source.read());
+        assertEquals('b', source.read());
+        source.unread(-1);
+        assertEquals((char)-1, source.read());
+        assertEquals('c', source.read());
+        assertEquals(-1, source.read());
+
+        reader = new StringReader("X");
+        source = new JsonSource(reader);
+        source.unread('Y');
+        assertEquals('Y', source.read());
+        assertEquals('X', source.read());
+        assertEquals(-1, source.read());
+
+        reader = new StringReader("X");
+        source = new JsonSource(reader);
+        int spared = source.getPushBackSpared();
+        for(int ct = 1; ct <= spared; ct++){
+            source.unread('Y');
+        }
+        for(int ct = 1; ct <= spared; ct++){
+            assertEquals('Y', source.read());
+        }
+        assertEquals('X', source.read());
+        assertEquals(-1, source.read());
+
+        reader = new StringReader("X");
+        source = new JsonSource(reader);
+        while(source.getPushBackSpared() > 0){
+            source.unread('Y');
+        }
+        try{
+            source.unread('Y');
+            fail();
+        }catch(IOException e){
+            assertEquals("Pushback buffer overflow", e.getMessage());
+        }catch(Throwable e){
+            fail();
+        }
+
+        return;
+    }
+
+    /**
+     * Test of unread method, of class JsonSource.
+     */
+    @Test
+    public void testUnread_int() throws Exception{
+        System.out.println("unread");
+
+        JsonSource source;
+        Reader reader;
+
+        reader = new StringReader("abc");
+        source = new JsonSource(reader);
+        assertEquals('a', source.readOrDie());
+        assertEquals('b', source.readOrDie());
+        source.unread((int) 'X');
+        assertEquals('X', source.readOrDie());
+        assertEquals('c', source.readOrDie());
+        assertEquals(-1, source.read());
+
+        reader = new StringReader("");
+        source = new JsonSource(reader);
+        assertEquals(-1, source.read());
+        source.unread((int) 'X');
+        assertEquals('X', source.readOrDie());
+
+        reader = new StringReader("ab");
+        source = new JsonSource(reader);
+        assertEquals('a', source.readOrDie());
+        source.unread((int) 'X');
+        source.unread((int) 'Y');
+        assertEquals('Y', source.readOrDie());
+        assertEquals('X', source.readOrDie());
+        assertEquals('b', source.readOrDie());
+        assertEquals(-1, source.read());
+
+        reader = new StringReader("");
+        source = new JsonSource(reader);
+        source.unread((int) '\0');
+        assertEquals('\0', source.readOrDie());
+        source.unread(0xffff);
+        assertEquals('\uffff', source.readOrDie());
+        source.unread(0x1ffff);
+        assertEquals('\uffff', source.readOrDie());
+        source.unread(0x1ffff);
+        assertEquals(0xffff, source.read());
+        source.unread(0xffffffff);
+        assertEquals(0xffff, source.read());
+        source.unread(-1);
+        assertEquals(0xffff, source.read());
+
+        source.unread(-1);
+        assertEquals(0xffff, source.readOrDie());
+
+        return;
+    }
+
+    /**
+     * Test of unread method, of class JsonSource.
+     */
+    @Test
+    public void testUnread_char() throws Exception{
+        System.out.println("unread");
+
+        JsonSource source;
+        Reader reader;
+
+        reader = new StringReader("abc");
+        source = new JsonSource(reader);
+        assertEquals('a', source.readOrDie());
+        assertEquals('b', source.readOrDie());
+        source.unread('X');
+        assertEquals('X', source.readOrDie());
+        assertEquals('c', source.readOrDie());
+        assertEquals(-1, source.read());
+
+        reader = new StringReader("");
+        source = new JsonSource(reader);
+        assertEquals(-1, source.read());
+        source.unread('X');
+        assertEquals('X', source.readOrDie());
+
+        reader = new StringReader("ab");
+        source = new JsonSource(reader);
+        assertEquals('a', source.readOrDie());
+        source.unread('X');
+        source.unread('Y');
+        assertEquals('Y', source.readOrDie());
+        assertEquals('X', source.readOrDie());
+        assertEquals('b', source.readOrDie());
+        assertEquals(-1, source.read());
+
+        reader = new StringReader("");
+        source = new JsonSource(reader);
+        source.unread('\0');
+        assertEquals('\0', source.readOrDie());
+        source.unread((char) 0xffff);
+        assertEquals('\uffff', source.readOrDie());
+
+        return;
+    }
+
+    /**
+     * Test of close method, of class JsonSource.
+     */
+    @Test
+    public void testClose() throws Exception{
+        System.out.println("close");
+
+        JsonSource source;
+        Reader reader;
+
+        reader = new StringReader("abc");
+        source = new JsonSource(reader);
+        source.close();
+
+        try{
+            source.read();
+            fail();
+        }catch(IOException e){
+            assertEquals("Stream closed", e.getMessage());
+        }
+
+        try{
+            source.unread('X');
+            fail();
+        }catch(IOException e){
+            assertEquals("Stream closed", e.getMessage());
+        }
+
+        return;
+    }
+
+    /**
+     * Test of getPushBackSpared method, of class JsonSource.
+     */
+    @Test
+    public void testGetPushBackSpared() throws Exception{
+        System.out.println("getPushBackSpared");
+
+        JsonSource source;
+        Reader reader;
+
+        reader = new StringReader("abc");
+        source = new JsonSource(reader);
+
+        assertTrue(source.getPushBackSpared() > 0);
+
+        while(source.getPushBackSpared() > 0){
+            source.unread('X');
+        }
+
+        try{
+            source.unread('X');
+            fail();
+        }catch(IOException e){
+            //NOTHING
+        }
+
+        assertEquals(0, source.getPushBackSpared());
+        source.close();
+        assertTrue(source.getPushBackSpared() > 0);
+
+        return;
+    }
+
+    /**
+     * Test of isWhitespace method, of class JsonSource.
+     */
+    @Test
+    public void testIsWhitespace_char(){
+        System.out.println("isWhitespace");
+
+        assertTrue(JsonSource.isWhitespace('\t'));
+        assertTrue(JsonSource.isWhitespace('\r'));
+        assertTrue(JsonSource.isWhitespace('\n'));
+        assertTrue(JsonSource.isWhitespace('\u0020'));
+
+        assertFalse(JsonSource.isWhitespace('A'));
+        assertFalse(JsonSource.isWhitespace('\u3000'));
+        assertFalse(JsonSource.isWhitespace('\0'));
+        assertFalse(JsonSource.isWhitespace((char) -1));
+
+        return;
+    }
+
+    /**
+     * Test of isWhitespace method, of class JsonSource.
+     */
+    @Test
+    public void testIsWhitespace_int(){
+        System.out.println("isWhitespace");
+
+        assertTrue(JsonSource.isWhitespace((int) '\t'));
+        assertTrue(JsonSource.isWhitespace((int) '\r'));
+        assertTrue(JsonSource.isWhitespace((int) '\n'));
+        assertTrue(JsonSource.isWhitespace((int) '\u0020'));
+        assertTrue(JsonSource.isWhitespace(0x0020));
+
+        assertFalse(JsonSource.isWhitespace((int) 'A'));
+        assertFalse(JsonSource.isWhitespace((int) '\u3000'));
+        assertFalse(JsonSource.isWhitespace((int) '\0'));
+        assertFalse(JsonSource.isWhitespace(-1));
+
+        assertFalse(JsonSource.isWhitespace(0xffff));
+        assertFalse(JsonSource.isWhitespace(0x1ffff));
+        assertFalse(JsonSource.isWhitespace(0xffff0020));
+
+        return;
+    }
+
+    /**
+     * Test of skipWhiteSpace method, of class JsonSource.
+     */
+    @Test
+    public void testSkipWhiteSpace() throws Exception{
+        System.out.println("skipWhiteSpace");
+
+        JsonSource source;
+        Reader reader;
+
+        reader = new StringReader("abc");
+        source = new JsonSource(reader);
+        source.skipWhiteSpace();
+        assertEquals('a', source.read());
+
+        reader = new StringReader(" abc");
+        source = new JsonSource(reader);
+        source.skipWhiteSpace();
+        assertEquals('a', source.read());
+
+        reader = new StringReader("\t\r\n\u0020abc");
+        source = new JsonSource(reader);
+        source.skipWhiteSpace();
+        assertEquals('a', source.read());
+
+        reader = new StringReader(" ");
+        source = new JsonSource(reader);
+        source.skipWhiteSpace();
+        assertEquals(-1, source.read());
+
+        reader = new StringReader("");
+        source = new JsonSource(reader);
+        source.skipWhiteSpace();
+        assertEquals(-1, source.read());
+
+        return;
+    }
+
+    /**
+     * Test of hasMore method, of class JsonSource.
+     */
+    @Test
+    public void testHasMore() throws Exception{
+        System.out.println("hasMore");
+
+        JsonSource source;
+        Reader reader;
+
+        reader = new StringReader("abc");
+        source = new JsonSource(reader);
+        assertTrue(source.hasMore());
+        assertEquals('a', source.read());
+        assertTrue(source.hasMore());
+        assertEquals('b', source.read());
+        assertTrue(source.hasMore());
+        assertEquals('c', source.read());
+        assertFalse(source.hasMore());
+        source.unread('X');
+        assertTrue(source.hasMore());
+        assertEquals('X', source.read());
+        assertFalse(source.hasMore());
+
+        reader = new StringReader("");
+        source = new JsonSource(reader);
+        assertFalse(source.hasMore());
+
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jovsonz/JsonTest.java b/src/test/java/jp/sourceforge/jovsonz/JsonTest.java
new file mode 100644 (file)
index 0000000..709e0f9
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.io.Reader;
+import java.io.IOException;
+import java.io.StringReader;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class JsonTest {
+
+    public JsonTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception{
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception{
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of dumpJson method, of class Json.
+     */
+    @Test
+    public void testDumpJson() throws Exception{
+        System.out.println("dumpJson");
+
+        String SP2 = "\u0020\u0020";
+        String SP4 = SP2 + SP2;
+        String HASHSEP = "\u0020:\u0020";
+
+        Reader reader;
+        JsComposition root;
+        StringBuilder dump = new StringBuilder();
+
+        reader = new StringReader("{}");
+        root = Json.parseJson(reader);
+        dump.setLength(0);
+        Json.dumpJson(dump, root);
+        assertEquals("{ }\n", dump.toString());
+
+        reader = new StringReader("[]");
+        root = Json.parseJson(reader);
+        dump.setLength(0);
+        Json.dumpJson(dump, root);
+        assertEquals("[ ]\n", dump.toString());
+
+        reader = new StringReader("[1]");
+        root = Json.parseJson(reader);
+        dump.setLength(0);
+        Json.dumpJson(dump, root);
+        assertEquals("[\n"
+                + SP2+"1\n"
+                + "]\n", dump.toString());
+
+        reader = new StringReader("[1,2]");
+        root = Json.parseJson(reader);
+        dump.setLength(0);
+        Json.dumpJson(dump, root);
+        assertEquals("[\n"
+                + SP2+"1 ,\n"
+                + SP2+"2\n"
+                + "]\n", dump.toString());
+
+        reader = new StringReader("[1,[2],3]");
+        root = Json.parseJson(reader);
+        dump.setLength(0);
+        Json.dumpJson(dump, root);
+        assertEquals("[\n"
+                + SP2+"1 ,\n"
+                + SP2+"[\n"
+                + SP4+"2\n"
+                + SP2+"] ,\n"
+                + SP2+"3\n"
+                + "]\n", dump.toString());
+
+        reader = new StringReader("[1,{\"A\":\"a\"}]");
+        root = Json.parseJson(reader);
+        dump.setLength(0);
+        Json.dumpJson(dump, root);
+        assertEquals("[\n"
+                + SP2+"1 ,\n"
+                + SP2+"{\n"
+                + SP4+"\"A\""+HASHSEP+"\"a\"\n"
+                + SP2+"}\n"
+                + "]\n", dump.toString());
+
+        reader = new StringReader("{\"A\":\"a\"}");
+        root = Json.parseJson(reader);
+        dump.setLength(0);
+        Json.dumpJson(dump, root);
+        assertEquals("{\n"
+                + SP2+"\"A\""+HASHSEP+"\"a\"\n"
+                + "}\n", dump.toString());
+
+        reader = new StringReader("{\"A\":\"a\",\"B\":\"b\"}");
+        root = Json.parseJson(reader);
+        dump.setLength(0);
+        Json.dumpJson(dump, root);
+        assertEquals("{\n"
+                + SP2+"\"A\""+HASHSEP+"\"a\" ,\n"
+                + SP2+"\"B\""+HASHSEP+"\"b\"\n"
+                + "}\n", dump.toString());
+
+        reader = new StringReader("{\"A\":{}}");
+        root = Json.parseJson(reader);
+        dump.setLength(0);
+        Json.dumpJson(dump, root);
+        assertEquals("{\n"
+                + SP2+"\"A\""+HASHSEP+"{\u0020}\n"
+                + "}\n", dump.toString());
+
+        reader = new StringReader("{\"A\":{\"B\":\"b\"}}");
+        root = Json.parseJson(reader);
+        dump.setLength(0);
+        Json.dumpJson(dump, root);
+        assertEquals("{\n"
+                + SP2+"\"A\""+HASHSEP+"{\n"
+                + SP4+"\"B\""+HASHSEP+"\"b\"\n"
+                + SP2+"}\n"
+                + "}\n", dump.toString());
+
+        reader = new StringReader("{\"A\":[]}");
+        root = Json.parseJson(reader);
+        dump.setLength(0);
+        Json.dumpJson(dump, root);
+        assertEquals("{\n"
+                + SP2+"\"A\""+HASHSEP+"[\u0020]\n"
+                + "}\n", dump.toString());
+
+        reader = new StringReader("{\"A\":[1,2]}");
+        root = Json.parseJson(reader);
+        dump.setLength(0);
+        Json.dumpJson(dump, root);
+        assertEquals("{\n"
+                + SP2+"\"A\""+HASHSEP+"[\n"
+                + SP4+"1 ,\n"
+                + SP4+"2\n"
+                + SP2+"]\n"
+                + "}\n", dump.toString());
+
+        reader = new StringReader("["
+                + "true,false,null,\"string\",-0.5"
+                + "]");
+        root = Json.parseJson(reader);
+        dump.setLength(0);
+        Json.dumpJson(dump, root);
+        assertEquals("[\n"
+                + SP2+"true ,\n"
+                + SP2+"false ,\n"
+                + SP2+"null ,\n"
+                + SP2+"\"string\" ,\n"
+                + SP2+"-0.5\n"
+                + "]\n", dump.toString());
+
+        try{
+            Json.dumpJson(null, new JsObject());
+            fail();
+        }catch(NullPointerException e){
+            //GOOD
+        }
+
+        try{
+            Json.dumpJson(new StringBuilder(), null);
+            fail();
+        }catch(NullPointerException e){
+            //GOOD
+        }
+
+        reader = new StringReader("[1,2,3]");
+        root = Json.parseJson(reader);
+        TroubleAppender app = new TroubleAppender(3);
+        try{
+            Json.dumpJson(app, root);
+            fail();
+        }catch(IOException e){
+            //GOOD
+        }
+
+        return;
+    }
+
+    /**
+     * Test of parseValue method, of class Json.
+     */
+    @Test
+    public void testParseValue() throws Exception{
+        System.out.println("parseValue");
+
+        JsonSource source;
+        JsValue value;
+
+        source = new JsonSource("true");
+        value = Json.parseValue(source);
+        assertEquals(JsBoolean.TRUE, value);
+
+        source = new JsonSource("false");
+        value = Json.parseValue(source);
+        assertEquals(JsBoolean.FALSE, value);
+
+        source = new JsonSource("null");
+        value = Json.parseValue(source);
+        assertEquals(JsNull.NULL, value);
+
+        source = new JsonSource("-0.5");
+        value = Json.parseValue(source);
+        assertEquals(JsTypes.NUMBER, value.getJsTypes());
+        assertEquals(-0.5, ((JsNumber)value).doubleValue(), 0.0);
+
+        source = new JsonSource("\"ABC\"");
+        value = Json.parseValue(source);
+        assertEquals(JsTypes.STRING, value.getJsTypes());
+        assertEquals("ABC", ((JsString)value).toRawString());
+
+        source = new JsonSource("[1,2,3]");
+        value = Json.parseValue(source);
+        assertEquals(JsTypes.ARRAY, value.getJsTypes());
+        assertEquals(3, ((JsArray)value).size());
+
+        source = new JsonSource("{\"A\":1,\"B\":2,\"C\":3}");
+        value = Json.parseValue(source);
+        assertEquals(JsTypes.OBJECT, value.getJsTypes());
+        assertEquals(3, ((JsObject)value).size());
+
+        source = new JsonSource("");
+        value = Json.parseValue(source);
+        assertNull(value);
+
+        source = new JsonSource(" ");
+        value = Json.parseValue(source);
+        assertNull(value);
+
+        try{
+            source = new JsonSource("#");
+            value = Json.parseValue(source);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        return;
+    }
+
+    /**
+     * Test of parseJson method, of class Json.
+     */
+    @Test
+    public void testParseJson() throws Exception{
+        System.out.println("parseJson");
+
+        Reader reader;
+        JsComposition root;
+
+        reader = new StringReader("{}");
+        root = Json.parseJson(reader);
+        assertNotNull(root);
+        assertEquals(JsTypes.OBJECT, root.getJsTypes());
+        assertEquals(0, root.size());
+
+        reader = new StringReader("{\"name\":\"value\"}");
+        root = Json.parseJson(reader);
+        assertNotNull(root);
+        assertEquals(JsTypes.OBJECT, root.getJsTypes());
+        assertEquals(1, root.size());
+
+        reader = new StringReader(" { \"name\" : \"value\" } ");
+        root = Json.parseJson(reader);
+        assertNotNull(root);
+        assertEquals(JsTypes.OBJECT, root.getJsTypes());
+        assertEquals(1, root.size());
+
+        reader = new StringReader("[]");
+        root = Json.parseJson(reader);
+        assertNotNull(root);
+        assertEquals(JsTypes.ARRAY, root.getJsTypes());
+        assertEquals(0, root.size());
+
+        reader = new StringReader("[1,2,3]");
+        root = Json.parseJson(reader);
+        assertNotNull(root);
+        assertEquals(JsTypes.ARRAY, root.getJsTypes());
+        assertEquals(3, root.size());
+
+        reader = new StringReader(" [ 1 , 2 , 3 ] ");
+        root = Json.parseJson(reader);
+        assertNotNull(root);
+        assertEquals(JsTypes.ARRAY, root.getJsTypes());
+        assertEquals(3, root.size());
+
+        reader = new StringReader("");
+        root = Json.parseJson(reader);
+        assertNull(root);
+
+        reader = new StringReader(" ");
+        root = Json.parseJson(reader);
+        assertNull(root);
+
+        try{
+            reader = new StringReader("true");
+            root = Json.parseJson(reader);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        try{
+            reader = new StringReader("false");
+            root = Json.parseJson(reader);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        try{
+            reader = new StringReader("null");
+            root = Json.parseJson(reader);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        try{
+            reader = new StringReader("\"ABC\"");
+            root = Json.parseJson(reader);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        try{
+            reader = new StringReader("-0.5");
+            root = Json.parseJson(reader);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        try{
+            reader = new StringReader("#");
+            root = Json.parseJson(reader);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        try{
+            reader = new StringReader(" [ 1 , 2 , 3 ");
+            root = Json.parseJson(reader);
+            fail();
+        }catch(JsParseException e){
+            //GOOD
+        }
+
+        try{
+            reader = new TroubleReader(" [ 1 , 2 , 3 ] ", 3);
+            root = Json.parseJson(reader);
+            fail();
+        }catch(IOException e){
+            //GOOD
+        }
+
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jovsonz/TroubleAppender.java b/src/test/java/jp/sourceforge/jovsonz/TroubleAppender.java
new file mode 100644 (file)
index 0000000..57fd5d4
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.io.Flushable;
+import java.io.IOException;
+
+/**
+ *
+ */
+public class TroubleAppender implements Appendable, Flushable {
+
+    private final StringBuilder content = new StringBuilder();
+    private final long limit;
+
+    public TroubleAppender(long limit){
+        super();
+        this.limit = limit;
+        return;
+    }
+
+    private void checkLimit() throws IOException{
+        if(this.limit < this.content.length()) throw new IOException();
+        return;
+    }
+
+    public Appendable append(CharSequence csq)
+            throws IOException{
+        this.content.append(csq);
+        checkLimit();
+        return this;
+    }
+
+    public Appendable append(CharSequence csq, int start, int end)
+            throws IOException{
+        this.content.append(csq, start, end);
+        checkLimit();
+        return this;
+    }
+
+    public Appendable append(char c)
+            throws IOException{
+        this.content.append(c);
+        checkLimit();
+        return this;
+    }
+
+    public void flush() throws IOException{
+        throw new IOException();
+    }
+
+    @Override
+    public String toString(){
+        return this.content.toString();
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jovsonz/TroubleReader.java b/src/test/java/jp/sourceforge/jovsonz/TroubleReader.java
new file mode 100644 (file)
index 0000000..1b475ba
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.CharBuffer;
+
+/**
+ * limit文字まで正常に読み込め、
+ * 以降の読み出しで自動的にIOExceptionを投げるStringReader。
+ */
+public class TroubleReader extends StringReader{
+
+    private final int limit;
+    private int ct = 0;
+
+    public TroubleReader(String text, int limit){
+        super(text);
+        this.limit = limit;
+        return;
+    }
+
+    @Override
+    public int read() throws IOException{
+        if(this.ct >= this.limit) throw new IOException();
+        this.ct++;
+        return super.read();
+    }
+
+    @Override
+    public int read(char[] cbuf){
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int read(char[] cbuf, int off, int len){
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int read(CharBuffer target){
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long skip(long ns){
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/jovsonz/UnmodIteratorTest.java b/src/test/java/jp/sourceforge/jovsonz/UnmodIteratorTest.java
new file mode 100644 (file)
index 0000000..80f7044
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jovsonz;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class UnmodIteratorTest {
+
+    public UnmodIteratorTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception{
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception{
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    private void assert3ListAndIterator(List list, Iterator unmod){
+        assertEquals(3, list.size());
+
+        assertTrue(unmod.hasNext());
+        assertEquals(list.get(0), unmod.next());
+
+        assertTrue(unmod.hasNext());
+        assertEquals(list.get(1), unmod.next());
+
+        try{
+            unmod.remove();
+            fail();
+        }catch(UnsupportedOperationException e){
+            //NOTHING
+        }
+
+        assertTrue(unmod.hasNext());
+        assertEquals(list.get(2), unmod.next());
+
+        assertFalse(unmod.hasNext());
+
+        try{
+            unmod.next();
+            fail();
+        }catch(NoSuchElementException e){
+            //NOTHING
+        }
+
+        return;
+    }
+
+    /**
+     * Test of Constructor, of class UnmodIterator.
+     */
+    @Test
+    public void testConstructor(){
+        System.out.println("constructor");
+
+        List<String> list;
+        Iterator<String> it;
+
+        list = new LinkedList<String>();
+        list.add("A");
+        list.add("B");
+        list.add("C");
+
+        it = list.iterator();
+        UnmodIterator<String> unmod = new UnmodIterator<String>(it);
+
+        assert3ListAndIterator(list, unmod);
+
+        try{
+            unmod = new UnmodIterator<String>(null);
+            fail();
+        }catch(NullPointerException e){
+            //GOOD
+        }
+
+        return;
+    }
+
+    /**
+     * Test of wrapUnmod method, of class UnmodIterator.
+     */
+    @Test
+    public void testWrapUnmod_Iterator(){
+        System.out.println("wrapUnmod");
+
+        List<String> list;
+        Iterator<String> it;
+
+        list = new LinkedList<String>();
+        list.add("A");
+        list.add("B");
+        list.add("C");
+
+        it = list.iterator();
+        Iterator<String> unmod = UnmodIterator.wrapUnmod(it);
+
+        assert3ListAndIterator(list, unmod);
+
+        try{
+            unmod = UnmodIterator.wrapUnmod((Iterator<String>)null);
+            fail();
+        }catch(NullPointerException e){
+            //GOOD
+        }
+
+        return;
+    }
+
+    /**
+     * Test of wrapUnmod method, of class UnmodIterator.
+     */
+    @Test
+    public void testWrapUnmod_Iterable(){
+        System.out.println("wrapUnmod");
+
+        List<String> list;
+
+        list = new LinkedList<String>();
+        list.add("A");
+        list.add("B");
+        list.add("C");
+
+        Iterable<String> unmod = UnmodIterator.wrapUnmod(list);
+
+        assert3ListAndIterator(list, unmod.iterator());
+
+        try{
+            unmod = UnmodIterator.wrapUnmod((Iterable<String>)null);
+            fail();
+        }catch(NullPointerException e){
+            //GOOD
+        }
+
+        return;
+    }
+
+    /**
+     * Test of unmodIterator method, of class UnmodIterator.
+     */
+    @Test
+    public void testUnmodIterator(){
+        System.out.println("unmodIterator");
+
+        List<String> list;
+
+        list = new LinkedList<String>();
+        list.add("A");
+        list.add("B");
+        list.add("C");
+
+        Iterator<String> unmod = UnmodIterator.unmodIterator(list);
+
+        assert3ListAndIterator(list, unmod);
+
+        try{
+            unmod = UnmodIterator.unmodIterator(null);
+            fail();
+        }catch(NullPointerException e){
+            //GOOD
+        }
+
+        return;
+    }
+
+    /**
+     * Test of hasNext method, of class UnmodIterator.
+     */
+    @Test
+    public void testHasNext(){
+        System.out.println("hasNext");
+        return;
+    }
+
+    /**
+     * Test of next method, of class UnmodIterator.
+     */
+    @Test
+    public void testNext(){
+        System.out.println("next");
+        return;
+    }
+
+    /**
+     * Test of remove method, of class UnmodIterator.
+     */
+    @Test
+    public void testRemove(){
+        System.out.println("remove");
+        return;
+    }
+
+}