OSDN Git Service

from subversion repository
authorOlyutorskii <olyutorskii@users.osdn.me>
Thu, 26 Aug 2010 07:38:16 +0000 (16:38 +0900)
committerOlyutorskii <olyutorskii@users.osdn.me>
Thu, 26 Aug 2010 07:38:16 +0000 (16:38 +0900)
16 files changed:
src/main/java/jp/sourceforge/jindolf/json/AbstractJsValue.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/json/JsArray.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/json/JsBoolean.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/json/JsNull.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/json/JsNumber.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/json/JsObject.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/json/JsPair.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/json/JsParseException.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/json/JsString.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/json/JsValue.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/json/JsVisitException.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/json/Json.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/json/JsonAppender.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/json/JsonReader.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/json/ValueVisitor.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/json/package-info.java [new file with mode: 0644]

diff --git a/src/main/java/jp/sourceforge/jindolf/json/AbstractJsValue.java b/src/main/java/jp/sourceforge/jindolf/json/AbstractJsValue.java
new file mode 100644 (file)
index 0000000..b86fa9c
--- /dev/null
@@ -0,0 +1,50 @@
+/*\r
+ * JSON abstract value\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: AbstractJsValue.java 900 2009-11-16 15:48:22Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r
+\r
+/**\r
+ * JSON 各種Value共通実装。\r
+ * 継承必須。\r
+ */\r
+public class AbstractJsValue implements JsValue{\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     */\r
+    protected AbstractJsValue(){\r
+        super();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param visitor {@inheritDoc}\r
+     * @throws JsVisitException {@inheritDoc}\r
+     */\r
+    public void traverse(ValueVisitor visitor)\r
+            throws JsVisitException{\r
+        visitor.visitValue(this);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    public boolean hasChanged(){\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     */\r
+    public void setUnchanged(){\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsArray.java b/src/main/java/jp/sourceforge/jindolf/json/JsArray.java
new file mode 100644 (file)
index 0000000..d523bec
--- /dev/null
@@ -0,0 +1,231 @@
+/*\r
+ * JSON array value\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: JsArray.java 914 2009-11-24 11:16:36Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r
+\r
+import java.io.IOException;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+\r
+/**\r
+ * JSON 配列Value。\r
+ */\r
+public class JsArray\r
+        extends AbstractJsValue\r
+        implements Iterable<JsValue> {\r
+\r
+    /**\r
+     * JSON Arrayを文字ストリームからパースする。\r
+     * @param reader 文字入力\r
+     * @return JSON Array。入力終了ならnull\r
+     * @throws IOException 入力エラー\r
+     * @throws JsParseException パースエラー\r
+     */\r
+    static JsArray parseArray(JsonReader reader)\r
+            throws IOException,\r
+                   JsParseException {\r
+        int chData;\r
+\r
+        Json.skipWhiteSpace(reader);\r
+        chData = reader.read();\r
+        if(chData < '\u0000') return null;\r
+        if(chData != '[') throw new JsParseException();\r
+\r
+        JsArray result = new JsArray();\r
+\r
+        for(;;){\r
+            Json.skipWhiteSpace(reader);\r
+            chData = reader.read();\r
+            if(chData < '\u0000') throw new JsParseException();\r
+            if(chData == ']') break;\r
+\r
+            if(result.size() <= 0){\r
+                reader.unread(chData);\r
+            }else{\r
+                if(chData != ',') throw new JsParseException();\r
+                Json.skipWhiteSpace(reader);\r
+            }\r
+\r
+            JsValue value = Json.parseValue(reader);\r
+            if(value == null){\r
+                throw new JsParseException();\r
+            }\r
+\r
+            result.add(value);\r
+        }\r
+\r
+        return result;\r
+    }\r
+\r
+\r
+    private final List<JsValue> valueList = new ArrayList<JsValue>();\r
+    private boolean changed = false;\r
+\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     */\r
+    public JsArray(){\r
+        super();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * JSON Valueを追加する。\r
+     * @param value JSON Value\r
+     */\r
+    public void add(JsValue value){\r
+        this.valueList.add(value);\r
+        this.changed = true;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 指定された位置のValueを返す。\r
+     * @param index 0で始まる位置\r
+     * @return Value\r
+     * @throws IndexOutOfBoundsException 不正な位置指定\r
+     */\r
+    public JsValue get(int index) throws IndexOutOfBoundsException{\r
+        return this.valueList.get(index);\r
+    }\r
+\r
+    /**\r
+     * 空にする。\r
+     */\r
+    public void clear(){\r
+        if(this.valueList.size() > 0) this.changed = true;\r
+        this.valueList.clear();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * JSON Valueを削除する。\r
+     * @param value JSON Value\r
+     * @return 既存のValueが削除されたならtrue\r
+     */\r
+    public boolean remove(JsValue value){\r
+        boolean removed = this.valueList.remove(value);\r
+        if(removed) this.changed = true;\r
+        return removed;\r
+    }\r
+\r
+    /**\r
+     * Value総数を返す。\r
+     * @return 総数\r
+     */\r
+    public int size(){\r
+        return this.valueList.size();\r
+    }\r
+\r
+    /**\r
+     * Valueにアクセスするための反復子を提供する。\r
+     * この反復子での削除作業はできない。\r
+     * @return 反復子イテレータ\r
+     */\r
+    public Iterator<JsValue> iterator(){\r
+        Collection<JsValue> unmodColl =\r
+                Collections.unmodifiableCollection(this.valueList);\r
+        return unmodColl.iterator();\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public int hashCode(){\r
+        return this.valueList.hashCode();\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param obj {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public boolean equals(Object obj){\r
+        if(obj == null) return false;\r
+        if(this == obj) return true;\r
+\r
+        if( ! (obj instanceof JsArray) ) return false;\r
+        JsArray array = (JsArray) obj;\r
+\r
+        return this.valueList.equals(array.valueList);\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public String toString(){\r
+        StringBuilder text = new StringBuilder();\r
+\r
+        text.append("[");\r
+        boolean hasElem = false;\r
+        for(JsValue value : this.valueList){\r
+            if(hasElem) text.append(',');\r
+            text.append(value);\r
+            hasElem = true;\r
+        }\r
+        text.append("]");\r
+\r
+        return text.toString();\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param visitor {@inheritDoc}\r
+     * @throws JsVisitException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void traverse(ValueVisitor visitor) throws JsVisitException{\r
+        visitor.visitValue(this);\r
+\r
+        for(JsValue value : this.valueList){\r
+            value.traverse(visitor);\r
+        }\r
+\r
+        visitor.visitCollectionClose(this);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public boolean hasChanged(){\r
+        if(this.changed) return true;\r
+\r
+        for(JsValue value : this.valueList){\r
+            if(value.hasChanged()) return true;\r
+        }\r
+\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void setUnchanged(){\r
+        this.changed = false;\r
+\r
+        for(JsValue value : this.valueList){\r
+            value.setUnchanged();\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsBoolean.java b/src/main/java/jp/sourceforge/jindolf/json/JsBoolean.java
new file mode 100644 (file)
index 0000000..7e84bea
--- /dev/null
@@ -0,0 +1,118 @@
+/*\r
+ * JSON boolean value\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: JsBoolean.java 913 2009-11-24 11:15:14Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r
+\r
+/**\r
+ * JSON 真偽Value。\r
+ */\r
+public final class JsBoolean\r
+        extends AbstractJsValue\r
+        implements Comparable<JsBoolean> {\r
+\r
+    /** 真。 */\r
+    public static final JsBoolean TRUE  = new JsBoolean();\r
+    /** 偽。 */\r
+    public static final JsBoolean FALSE = new JsBoolean();\r
+\r
+    /**\r
+     * boolean値から真偽Valueを返す。\r
+     * @param bool boolean値\r
+     * @return TRUEかFALSE\r
+     */\r
+    public static JsBoolean valueOf(boolean bool){\r
+        if(bool) return TRUE;\r
+        return FALSE;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * 2回しか呼ばれないはず。\r
+     */\r
+    private JsBoolean(){\r
+        super();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * boolean値を返す。\r
+     * @return boolean値\r
+     */\r
+    public boolean booleanValue(){\r
+        if(this == TRUE) return true;\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * 真か判定する。\r
+     * @return 真ならtrue\r
+     */\r
+    public boolean isTrue(){\r
+        if(this == TRUE) return true;\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * 偽か判定する。\r
+     * @return 偽ならtrue\r
+     */\r
+    public boolean isFalse(){\r
+        if(this == FALSE) return true;\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public int hashCode(){\r
+        if(this.isTrue()) return Boolean.TRUE.hashCode();\r
+        return Boolean.FALSE.hashCode();\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param obj {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public boolean equals(Object obj){\r
+        if(obj == null) return false;\r
+        if(this == obj) return true;\r
+\r
+        if( ! (obj instanceof JsBoolean) ) return false;\r
+\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param value {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    public int compareTo(JsBoolean value){\r
+        if(value == null) throw new NullPointerException();\r
+        if(this == value) return 0;\r
+\r
+        if     (this.isTrue()  && value.isFalse()) return -1;\r
+        else if(this.isFalse() && value.isTrue() ) return +1;\r
+\r
+        return 0;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public String toString(){\r
+        if(this.isTrue()) return "true";\r
+        return "false";\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsNull.java b/src/main/java/jp/sourceforge/jindolf/json/JsNull.java
new file mode 100644 (file)
index 0000000..75d0166
--- /dev/null
@@ -0,0 +1,70 @@
+/*\r
+ * JSON null value\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: JsNull.java 958 2009-12-14 13:21:00Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r
+\r
+/**\r
+ * JSON Null Value。\r
+ * その実体はシングルトン\r
+ */\r
+public final class JsNull\r
+        extends AbstractJsValue\r
+        implements Comparable<JsNull> {\r
+\r
+    /** ただ唯一のインスタンス。 */\r
+    public static final JsNull NULL = new JsNull();\r
+\r
+    /**\r
+     * 隠しコンストラクタ。\r
+     * 1回しか呼ばれないはず\r
+     */\r
+    private JsNull(){\r
+        super();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param value {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    public int compareTo(JsNull value){\r
+        if(value == null) throw new NullPointerException();\r
+        return 0;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param obj {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public boolean equals(Object obj){\r
+        if( ! (obj instanceof JsNull) ) return false;\r
+        if(obj != this) return false;\r
+        return true;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public int hashCode(){\r
+        return 7777;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public String toString(){\r
+        return "null";\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsNumber.java b/src/main/java/jp/sourceforge/jindolf/json/JsNumber.java
new file mode 100644 (file)
index 0000000..aa521b1
--- /dev/null
@@ -0,0 +1,325 @@
+/*\r
+ * JSON number value\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: JsNumber.java 915 2009-11-24 11:19:48Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r
+\r
+import java.io.IOException;\r
+import java.math.BigDecimal;\r
+import java.math.BigInteger;\r
+\r
+/**\r
+ * JSON 数値Value。\r
+ * 10を基数としたjava.math.BigDecimalを実装ベースとする\r
+ * IEEE754浮動小数ではない。\r
+ */\r
+public class JsNumber\r
+        extends AbstractJsValue\r
+        implements Comparable<JsNumber> {\r
+\r
+    /**\r
+     * 文字ストリームから符号付きの数字並びを読み込む。\r
+     * +符号は読み飛ばされる。\r
+     * 冒頭の連続する0はそのまま読まれる。\r
+     * @param reader 文字入力\r
+     * @param app 出力先\r
+     * @param allowZeroTrail 冒頭の2つ以上連続するゼロを許すならtrue\r
+     * @return 引数と同じ出力先\r
+     * @throws IOException 入出力エラー\r
+     * @throws JsParseException パースエラーもしくは入力終了\r
+     */\r
+    private static Appendable appendDigitText(JsonReader reader,\r
+                                                Appendable app,\r
+                                                boolean allowZeroTrail)\r
+            throws IOException, JsParseException{\r
+        int chData;\r
+\r
+        chData = reader.read();\r
+        if     (chData < '\u0000') throw new JsParseException();\r
+        else if(chData == '-') app.append('-');\r
+        else if(chData != '+') reader.unread(chData);\r
+\r
+        boolean hasAppended = false;\r
+        boolean zeroStarted = false;\r
+        for(;;){\r
+            chData = reader.read();\r
+            if(chData < '\u0000') throw new JsParseException();\r
+\r
+            if('0' <= chData && chData <= '9'){\r
+                app.append((char)chData);\r
+\r
+                if(zeroStarted && ! allowZeroTrail){\r
+                    throw new JsParseException();\r
+                }\r
+\r
+                if(chData == '0' &&  ! hasAppended ){\r
+                    zeroStarted = true;\r
+                }\r
+\r
+                hasAppended = true;\r
+            }else{\r
+                if( ! hasAppended ) throw new JsParseException();\r
+                reader.unread(chData);\r
+                break;\r
+            }\r
+        }\r
+\r
+        return app;\r
+    }\r
+\r
+    /**\r
+     * 文字ストリームから符号付きの数字並びを読み込む。\r
+     * +符号はパースエラーとなる。\r
+     * @param reader 文字入力\r
+     * @param app 出力先\r
+     * @return 引数と同じ出力先\r
+     * @throws IOException 入出力エラー\r
+     * @throws JsParseException パースエラーもしくは入力終了\r
+     */\r
+    private static Appendable appendIntegerPart(JsonReader reader,\r
+                                                  Appendable app )\r
+            throws IOException, JsParseException{\r
+        int chData;\r
+\r
+        chData = reader.read();\r
+        if(chData < '\u0000') throw new JsParseException();\r
+        if(chData == '+') throw new JsParseException();\r
+        reader.unread(chData);\r
+\r
+        appendDigitText(reader, app, false);\r
+\r
+        return app;\r
+    }\r
+\r
+    /**\r
+     * 文字ストリームから「.」で始まる小数部を読み込む。\r
+     * 小数部がなければなにもせずに戻る。\r
+     * @param reader 文字入力\r
+     * @param app 出力先\r
+     * @return 引数と同じ出力先\r
+     * @throws IOException 入出力エラー\r
+     * @throws JsParseException パースエラーもしくは入力終了\r
+     */\r
+    private static Appendable appendFractionPart(JsonReader reader,\r
+                                                    Appendable app )\r
+            throws IOException, JsParseException{\r
+        int chData;\r
+\r
+        chData = reader.read();\r
+        if(chData < '\u0000') throw new JsParseException();\r
+        if(chData != '.'){\r
+            reader.unread(chData);\r
+            return app;\r
+        }\r
+\r
+        app.append(".");\r
+\r
+        boolean hasAppended = false;\r
+        for(;;){\r
+            chData = reader.read();\r
+            if(chData < '\u0000') throw new JsParseException();\r
+\r
+            if('0' <= chData && chData <= '9'){\r
+                app.append((char)chData);\r
+                hasAppended = true;\r
+            }else{\r
+                if( ! hasAppended ) throw new JsParseException();\r
+                reader.unread(chData);\r
+                break;\r
+            }\r
+        }\r
+\r
+        return app;\r
+    }\r
+\r
+    /**\r
+     * 文字ストリームから「e」もしくは「E」で始まる指数部を読み込む。\r
+     * 指数部がなければなにもせずに戻る。\r
+     * @param reader 文字入力\r
+     * @param app 出力先\r
+     * @return 引数と同じ出力先\r
+     * @throws IOException 入出力エラー\r
+     * @throws JsParseException パースエラーもしくは入力終了\r
+     */\r
+    private static Appendable appendExpPart(JsonReader reader,\r
+                                              Appendable app )\r
+            throws IOException, JsParseException{\r
+        int chData;\r
+\r
+        chData = reader.read();\r
+        if(chData < '\u0000') throw new JsParseException();\r
+        if(chData != 'e' && chData != 'E'){\r
+            reader.unread(chData);\r
+            return app;\r
+        }\r
+\r
+        app.append('E');\r
+\r
+        appendDigitText(reader, app, true);\r
+\r
+        return app;\r
+    }\r
+\r
+    /**\r
+     * 文字ストリームからJSON数値Valueを読み込む。\r
+     * @param reader 文字入力\r
+     * @return 数値Value\r
+     * @throws IOException 入力エラー\r
+     * @throws JsParseException パースエラーもしくは入力終了\r
+     */\r
+    static JsNumber parseNumber(JsonReader reader)\r
+            throws IOException, JsParseException{\r
+        Json.skipWhiteSpace(reader);\r
+\r
+        StringBuilder numText = new StringBuilder();\r
+        appendIntegerPart (reader, numText);\r
+        appendFractionPart(reader, numText);\r
+        appendExpPart     (reader, numText);\r
+        JsNumber result = new JsNumber(numText);\r
+\r
+        return result;\r
+    }\r
+\r
+    private BigDecimal decimal;\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param val 初期数値\r
+     */\r
+    public JsNumber(long val){\r
+        this(BigDecimal.valueOf(val));\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param val 初期数値\r
+     */\r
+    public JsNumber(double val){\r
+        this(BigDecimal.valueOf(val));\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param val 初期数値\r
+     */\r
+    public JsNumber(BigInteger val){\r
+        this(new BigDecimal(val));\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * 書式はjava.math.BigDecinal#BigDecimal(String)に準ずる。\r
+     * @param val 初期数値の文字列表記\r
+     * @throws NumberFormatException 不正な数値表記\r
+     */\r
+    public JsNumber(CharSequence val) throws NumberFormatException{\r
+        this(new BigDecimal(val.toString()));\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param val 初期数値\r
+     * @throws NullPointerException 引数がnull\r
+     */\r
+    public JsNumber(BigDecimal val) throws NullPointerException{\r
+        super();\r
+        if(val == null) throw new NullPointerException();\r
+        this.decimal = val;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * BigDecimal型の数値を返す。\r
+     * @return BigDecimal型数値\r
+     */\r
+    public BigDecimal getBigDecimal(){\r
+        return this.decimal;\r
+    }\r
+\r
+    /**\r
+     * int型の数値を返す。\r
+     * @return int型数値\r
+     */\r
+    public int intValue(){\r
+        return this.decimal.intValue();\r
+    }\r
+\r
+    /**\r
+     * long型の数値を返す。\r
+     * @return long型数値\r
+     */\r
+    public long longValue(){\r
+        return this.decimal.longValue();\r
+    }\r
+\r
+    /**\r
+     * float型の数値を返す。\r
+     * @return float型数値\r
+     */\r
+    public float floatValue(){\r
+        return this.decimal.floatValue();\r
+    }\r
+\r
+    /**\r
+     * double型の数値を返す。\r
+     * @return double型数値\r
+     */\r
+    public double doubleValue(){\r
+        return this.decimal.doubleValue();\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public int hashCode(){\r
+        return this.decimal.hashCode();\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * 「1.2」と「0.12E+1」など、スケールの一致しない値は異なる値と見なされる。\r
+     * @param obj {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public boolean equals(Object obj){\r
+        if(obj == null) return false;\r
+        if(this == obj) return true;\r
+        if( ! (obj instanceof JsNumber) ) return false;\r
+        JsNumber number = (JsNumber) obj;\r
+        return this.decimal.equals(number.decimal);\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * 「1.2」と「0.12E+1」など、スケールが異なっても値が同じであれば\r
+     * 等しいと見なされる。\r
+     * @param value {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    public int compareTo(JsNumber value){\r
+        if(this == value) return 0;\r
+        return this.decimal.compareTo(value.decimal);\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * java.math.BigDecimal#toString()に準ずる。\r
+     * ※ JSON規格のパーサで解釈できるはず。\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public String toString(){\r
+        return this.decimal.toString();\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsObject.java b/src/main/java/jp/sourceforge/jindolf/json/JsObject.java
new file mode 100644 (file)
index 0000000..a2805f0
--- /dev/null
@@ -0,0 +1,304 @@
+/*\r
+ * JSON object value\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: JsObject.java 914 2009-11-24 11:16:36Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r
+\r
+import java.io.IOException;\r
+import java.util.ArrayList;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Set;\r
+import java.util.TreeMap;\r
+\r
+/**\r
+ * JSON オブジェクト Value。\r
+ */\r
+public class JsObject\r
+        extends AbstractJsValue\r
+        implements Iterable<JsPair> {\r
+\r
+    /**\r
+     * JSON Objectを文字ストリームからパースする。\r
+     * @param reader 文字入力\r
+     * @return JSON Object。入力終了ならnull\r
+     * @throws IOException 入力エラー\r
+     * @throws JsParseException パースエラー\r
+     */\r
+    static JsObject parseObject(JsonReader reader)\r
+            throws IOException, JsParseException{\r
+        int chData;\r
+\r
+        Json.skipWhiteSpace(reader);\r
+        chData = reader.read();\r
+        if(chData < '\u0000') return null;\r
+        if(chData != '{') throw new JsParseException();\r
+\r
+        JsObject result = new JsObject();\r
+\r
+        for(;;){\r
+            Json.skipWhiteSpace(reader);\r
+            chData = reader.read();\r
+            if(chData < '\u0000') throw new JsParseException();\r
+            if(chData == '}') break;\r
+\r
+            if(result.size() <= 0){\r
+                reader.unread(chData);\r
+            }else{\r
+                if(chData != ',') throw new JsParseException();\r
+                Json.skipWhiteSpace(reader);\r
+            }\r
+\r
+            JsString name = JsString.parseString(reader);\r
+            if(name == null){\r
+                throw new JsParseException();\r
+            }\r
+\r
+            Json.skipWhiteSpace(reader);\r
+            chData = reader.read();\r
+            if(chData < '\u0000') throw new JsParseException();\r
+            if(chData != ':') throw new JsParseException();\r
+            Json.skipWhiteSpace(reader);\r
+\r
+            JsValue value = Json.parseValue(reader);\r
+            if(value == null){\r
+                throw new JsParseException();\r
+            }\r
+\r
+            result.putValue(name.toRawString(), value);\r
+        }\r
+\r
+        return result;\r
+    }\r
+\r
+\r
+    private final Map<String, JsValue> valueMap =\r
+            new TreeMap<String, JsValue>();\r
+    private boolean changed = false;\r
+\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     */\r
+    public JsObject(){\r
+        super();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 名前とValueからpairを登録する。\r
+     * @param name 名前\r
+     * @param value Value\r
+     * @return 旧Value。同じ内容のpairがすでに存在していたらnull\r
+     * @throws NullPointerException 引数のいずれかがnull\r
+     */\r
+    public JsValue putValue(String name, JsValue value)\r
+            throws NullPointerException{\r
+        if(name  == null) throw new NullPointerException();\r
+        if(value == null) throw new NullPointerException();\r
+\r
+        JsValue oldValue = this.valueMap.get(name);\r
+        if(value.equals(oldValue)) return null;\r
+\r
+        JsValue old = this.valueMap.put(name, value);\r
+        this.changed = true;\r
+        return old;\r
+    }\r
+\r
+    /**\r
+     * 名前からValueを取得する。\r
+     * @param name 名前\r
+     * @return 対応するValue。見つからなければnull\r
+     */\r
+    public JsValue getValue(String name){\r
+        return this.valueMap.get(name);\r
+    }\r
+\r
+    /**\r
+     * JSON pairを追加する。\r
+     * @param pair JSON pair\r
+     */\r
+    public void putPair(JsPair pair){\r
+        putValue(pair.getName(), pair.getValue());\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 名前からJSON pairを返す。\r
+     * @param name 名前\r
+     * @return JSON Pair。見つからなければnull\r
+     */\r
+    public JsPair getPair(String name){\r
+        JsValue value = getValue(name);\r
+        if(value == null) return null;\r
+\r
+        return new JsPair(name, value);\r
+    }\r
+\r
+    /**\r
+     * 空にする。\r
+     */\r
+    public void clear(){\r
+        if(this.valueMap.size() > 0) this.changed = true;\r
+        this.valueMap.clear();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 指定した名前のpairを削除する。\r
+     * @param name pairの名前\r
+     * @return 消されたValue。該当するpairがなければnull\r
+     */\r
+    public JsValue remove(String name){\r
+        JsValue old = this.valueMap.remove(name);\r
+        if(old != null) this.changed = true;\r
+        return old;\r
+    }\r
+\r
+    /**\r
+     * 保持する全pairの名前の集合を返す。\r
+     * @return すべての名前\r
+     */\r
+    public Set<String> nameSet(){\r
+        return this.valueMap.keySet();\r
+    }\r
+\r
+    /**\r
+     * pairのリストを返す。\r
+     * 格納順は名前順。\r
+     * @return pairリスト\r
+     */\r
+    public List<JsPair> getPairList(){\r
+        List<JsPair> result = new ArrayList<JsPair>();\r
+\r
+        for(String name : nameSet()){\r
+            JsPair pair = getPair(name);\r
+            result.add(pair);\r
+        }\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * pairにアクセスするための反復子を提供する。\r
+     * この反復子での削除作業はできない。\r
+     * @return 反復子イテレータ\r
+     */\r
+    public Iterator<JsPair> iterator(){\r
+        return getPairList().iterator();\r
+    }\r
+\r
+    /**\r
+     * pair総数を返す。\r
+     * @return pair総数\r
+     */\r
+    public int size(){\r
+        return this.valueMap.size();\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public int hashCode(){\r
+        return this.valueMap.hashCode();\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param obj {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public boolean equals(Object obj){\r
+        if(obj == null) return false;\r
+        if(this == obj) return true;\r
+\r
+        if( ! (obj instanceof JsObject) ) return false;\r
+        JsObject composit = (JsObject) obj;\r
+\r
+        return this.valueMap.equals(composit.valueMap);\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public String toString(){\r
+        StringBuilder text = new StringBuilder();\r
+\r
+        text.append("{");\r
+        boolean hasElem = false;\r
+        for(JsPair pair : this){\r
+            if(hasElem) text.append(',');\r
+            try{\r
+                JsString.writeText(text, pair.getName());\r
+            }catch(IOException e){\r
+                assert false;\r
+            }\r
+            text.append(':')\r
+                .append(pair.getValue());\r
+            hasElem = true;\r
+        }\r
+        text.append("}");\r
+\r
+        return text.toString();\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param visitor {@inheritDoc}\r
+     * @throws JsVisitException {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void traverse(ValueVisitor visitor) throws JsVisitException{\r
+        visitor.visitValue(this);\r
+\r
+        for(JsPair pair : this){\r
+            String name   = pair.getName();\r
+            JsValue value = pair.getValue();\r
+            visitor.visitPairName(name);\r
+            value.traverse(visitor);\r
+        }\r
+\r
+        visitor.visitCollectionClose(this);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public boolean hasChanged(){\r
+        if(this.changed) return true;\r
+\r
+        for(JsValue value : this.valueMap.values()){\r
+            if(value.hasChanged()) return true;\r
+        }\r
+\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void setUnchanged(){\r
+        this.changed = false;\r
+\r
+        for(JsValue value : this.valueMap.values()){\r
+            value.setUnchanged();\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsPair.java b/src/main/java/jp/sourceforge/jindolf/json/JsPair.java
new file mode 100644 (file)
index 0000000..d10b039
--- /dev/null
@@ -0,0 +1,123 @@
+/*\r
+ * JSON pair in object\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: JsPair.java 920 2009-11-24 14:34:58Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r
+\r
+import java.io.IOException;\r
+\r
+/**\r
+ * JSON オブジェクトValue内に列挙される、名前の付いたValueとの組。\r
+ * 後での変更は不可能。\r
+ */\r
+public class JsPair{\r
+\r
+    private final String name;\r
+    private final JsValue value;\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param name 名前\r
+     * @param value JSON Value\r
+     * @throws NullPointerException 名前もしくはValueがnull\r
+     */\r
+    public JsPair(String name, JsValue value)\r
+            throws NullPointerException{\r
+        super();\r
+\r
+        if(name  == null || value == null) throw new NullPointerException();\r
+\r
+        this.name = name;\r
+        this.value = value;\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param name 名前\r
+     * @param text 文字列\r
+     * @throws NullPointerException 名前がnull\r
+     */\r
+    public JsPair(String name, CharSequence text)\r
+            throws NullPointerException{\r
+        this(name, (JsValue) new JsString(text) );\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param name 名前\r
+     * @param bool 真偽\r
+     * @throws NullPointerException 名前がnull\r
+     */\r
+    public JsPair(String name, boolean bool)\r
+            throws NullPointerException{\r
+        this(name, JsBoolean.valueOf(bool));\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param name 名前\r
+     * @param number 数値\r
+     * @throws NullPointerException 名前がnull\r
+     */\r
+    public JsPair(String name, long number)\r
+            throws NullPointerException{\r
+        this(name, new JsNumber(number));\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param name 名前\r
+     * @param number 数値\r
+     * @throws NullPointerException 名前がnull\r
+     */\r
+    public JsPair(String name, double number)\r
+            throws NullPointerException{\r
+        this(name, new JsNumber(number));\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 名前を返す。\r
+     * @return 名前\r
+     */\r
+    public String getName(){\r
+        return this.name;\r
+    }\r
+\r
+    /**\r
+     * JSON Valueを返す。\r
+     * @return JSON Value\r
+     */\r
+    public JsValue getValue(){\r
+        return this.value;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public String toString(){\r
+        StringBuilder text = new StringBuilder();\r
+\r
+        try{\r
+            JsString.writeText(text, this.name);\r
+        }catch(IOException e){\r
+            assert false; // NEVER!\r
+        }\r
+\r
+        text.append(':')\r
+            .append(this.value);\r
+\r
+        return text.toString();\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsParseException.java b/src/main/java/jp/sourceforge/jindolf/json/JsParseException.java
new file mode 100644 (file)
index 0000000..a516cec
--- /dev/null
@@ -0,0 +1,33 @@
+/*\r
+ * JSON parse error information\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: JsParseException.java 900 2009-11-16 15:48:22Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r
+\r
+/**\r
+ * JSON パースの異常系情報。\r
+ */\r
+@SuppressWarnings("serial")\r
+public class JsParseException extends Exception{\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     */\r
+    public JsParseException(){\r
+        super();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param th 原因となった例外\r
+     */\r
+    public JsParseException(Throwable th){\r
+        super(th);\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsString.java b/src/main/java/jp/sourceforge/jindolf/json/JsString.java
new file mode 100644 (file)
index 0000000..07dfc57
--- /dev/null
@@ -0,0 +1,266 @@
+/*\r
+ * JSON string value\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: JsString.java 917 2009-11-24 13:17:07Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r
+\r
+import java.io.IOException;\r
+\r
+/**\r
+ * JSON 文字列Value。\r
+ */\r
+public class JsString\r
+        extends AbstractJsValue\r
+        implements CharSequence, Comparable<JsString> {\r
+\r
+    /**\r
+     * FFFF形式4桁で16進エスケープされた文字列を読み、\r
+     * 1文字にデコードする。\r
+     * @param reader 文字入力\r
+     * @return 文字\r
+     * @throws IOException 入力エラー\r
+     * @throws JsParseException パースエラーもしくは入力終了\r
+     */\r
+    static char parseHexChar(JsonReader reader)\r
+            throws IOException, JsParseException{\r
+        int hex1 = reader.read();\r
+        int hex2 = reader.read();\r
+        int hex3 = reader.read();\r
+        int hex4 = reader.read();\r
+        if(hex4 < '\u0000') throw new JsParseException();\r
+\r
+        char hex1Ch = (char) hex1;\r
+        char hex2Ch = (char) hex2;\r
+        char hex3Ch = (char) hex3;\r
+        char hex4Ch = (char) hex4;\r
+\r
+        int digit1 = Character.digit(hex1Ch, 16);\r
+        int digit2 = Character.digit(hex2Ch, 16);\r
+        int digit3 = Character.digit(hex3Ch, 16);\r
+        int digit4 = Character.digit(hex4Ch, 16);\r
+\r
+        if(digit1 < 0) throw new JsParseException();\r
+        if(digit2 < 0) throw new JsParseException();\r
+        if(digit3 < 0) throw new JsParseException();\r
+        if(digit4 < 0) throw new JsParseException();\r
+\r
+        int digit = 0;\r
+        digit += digit1;\r
+        digit <<= 4;\r
+        digit += digit2;\r
+        digit <<= 4;\r
+        digit += digit3;\r
+        digit <<= 4;\r
+        digit += digit4;\r
+\r
+        char result = (char) digit;\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * ダブルクォーテーションで囲まれた文字列を読み込む。\r
+     * @param reader 文字入力\r
+     * @return 文字列Value\r
+     * @throws IOException 入力エラー\r
+     * @throws JsParseException パースエラーもしくは入力終了\r
+     */\r
+    static JsString parseString(JsonReader reader)\r
+            throws IOException, JsParseException{\r
+        int chData;\r
+\r
+        Json.skipWhiteSpace(reader);\r
+        chData = reader.read();\r
+        if(chData < '\u0000') return null;\r
+        if(chData != '"') throw new JsParseException();\r
+\r
+        StringBuilder text = new StringBuilder();\r
+\r
+        for(;;){\r
+            chData = reader.read();\r
+            if(chData < '\u0000') throw new JsParseException();\r
+            if(chData == '"') break;\r
+\r
+            if(chData == '\\'){\r
+                chData = reader.read();\r
+                if(chData < '\u0000') throw new JsParseException();\r
+                if     (chData == '"' ) text.append('"');\r
+                else if(chData == '\\') text.append('\\');\r
+                else if(chData == '/' ) text.append('/');\r
+                else if(chData == 'b' ) text.append('\b');\r
+                else if(chData == 'f' ) text.append('\f');\r
+                else if(chData == 'n' ) text.append('\n');\r
+                else if(chData == 'r' ) text.append('\r');\r
+                else if(chData == 't' ) text.append('\t');\r
+                else if(chData == 'u')  text.append(parseHexChar(reader));\r
+                else                    throw new JsParseException();\r
+            }else{\r
+                text.append((char)chData);\r
+            }\r
+        }\r
+\r
+        JsString result = new JsString(text);\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * JSON 文字列Value形式で文字列を出力する。\r
+     * @param appout 文字出力\r
+     * @param seq 文字列\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public static void writeText(Appendable appout, CharSequence seq)\r
+            throws IOException{\r
+        appout.append('"');\r
+\r
+        int length = seq.length();\r
+        for(int pos = 0; pos < length; pos++){\r
+            char ch = seq.charAt(pos);\r
+\r
+            switch(ch){\r
+            case '"' : appout.append('\\').append('"');  break;\r
+            case '\\': appout.append('\\').append('\\'); break;\r
+            case '/' : appout.append('\\').append('/');  break;\r
+            case '\b': appout.append('\\').append('b');  break;\r
+            case '\f': appout.append('\\').append('f');  break;\r
+            case '\n': appout.append('\\').append('n');  break;\r
+            case '\r': appout.append('\\').append('r');  break;\r
+            case '\t': appout.append('\\').append('t');  break;\r
+            default:\r
+                if(Character.isISOControl(ch)){\r
+                    String hex = "0000" + Integer.toHexString(ch);\r
+                    hex = hex.substring(hex.length() - 4);\r
+                    appout.append("\\u").append(hex);\r
+                }else{\r
+                    appout.append(ch);\r
+                }\r
+                break;\r
+            }\r
+        }\r
+\r
+        appout.append('"');\r
+\r
+        return;\r
+    }\r
+\r
+    private final String text;\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * 空文字が設定される。\r
+     */\r
+    public JsString(){\r
+        this(null);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * 引数はJSON書式ではない。\r
+     * @param seq 文字列。nullなら空文字が設定される。\r
+     */\r
+    public JsString(CharSequence seq){\r
+        super();\r
+        if(seq == null){\r
+            this.text = "";\r
+        }else{\r
+            this.text = seq.toString();\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param index {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     * @throws IndexOutOfBoundsException {@inheritDoc}\r
+     */\r
+    public char charAt(int index)\r
+            throws IndexOutOfBoundsException{\r
+        return this.text.charAt(index);\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    public int length(){\r
+        return this.text.length();\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param start {@inheritDoc}\r
+     * @param end {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     * @throws IndexOutOfBoundsException {@inheritDoc}\r
+     */\r
+    public CharSequence subSequence(int start, int end)\r
+            throws IndexOutOfBoundsException{\r
+        return this.text.subSequence(start, end);\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public int hashCode(){\r
+        return this.text.hashCode();\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param obj {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public boolean equals(Object obj){\r
+        if(obj == null) return false;\r
+        if(this == obj) return true;\r
+\r
+        if( ! (obj instanceof JsString) ) return false;\r
+        JsString string = (JsString) obj;\r
+\r
+        return this.text.equals(string.text);\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param value {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    public int compareTo(JsString value){\r
+        if(this == value) return 0;\r
+        if(value == null) return +1;\r
+        return this.text.compareTo(value.text);\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    @Override\r
+    public String toString(){\r
+        StringBuilder string = new StringBuilder();\r
+        try{\r
+            writeText(string, this.text);\r
+        }catch(IOException e){\r
+            assert false;\r
+        }\r
+        return string.toString();\r
+    }\r
+\r
+    /**\r
+     * クォーテーションされていない生の文字列を返す。\r
+     * @return 生の文字列\r
+     */\r
+    public String toRawString(){\r
+        return this.text;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsValue.java b/src/main/java/jp/sourceforge/jindolf/json/JsValue.java
new file mode 100644 (file)
index 0000000..6739fe9
--- /dev/null
@@ -0,0 +1,33 @@
+/*\r
+ * JSON value common interface\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: JsValue.java 899 2009-11-16 15:13:52Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r
+\r
+/**\r
+ * JSON 各種Value共通インタフェース。\r
+ */\r
+public interface JsValue{\r
+\r
+    /**\r
+     * 深さ優先探索を行い各種構造の出現をビジターに通知する。\r
+     * @param visitor ビジター\r
+     * @throws JsVisitException トラバース中断。\r
+     */\r
+    void traverse(ValueVisitor visitor) throws JsVisitException;\r
+\r
+    /**\r
+     * このValueおよび子孫に変更があったか判定する。\r
+     * @return 変更があればtrue\r
+     */\r
+    boolean hasChanged();\r
+\r
+    /**\r
+     * このValueおよび子孫に変更がなかったことにする。\r
+     */\r
+    void setUnchanged();\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsVisitException.java b/src/main/java/jp/sourceforge/jindolf/json/JsVisitException.java
new file mode 100644 (file)
index 0000000..39dd2a2
--- /dev/null
@@ -0,0 +1,33 @@
+/*\r
+ * JSON traverse error exception\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: JsVisitException.java 900 2009-11-16 15:48:22Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r
+\r
+/**\r
+ * トラバース中断例外。\r
+ */\r
+@SuppressWarnings("serial")\r
+public class JsVisitException extends Exception{\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     */\r
+    public JsVisitException(){\r
+        super();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param th 原因となった例外\r
+     */\r
+    public JsVisitException(Throwable th){\r
+        super(th);\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/json/Json.java b/src/main/java/jp/sourceforge/jindolf/json/Json.java
new file mode 100644 (file)
index 0000000..8eb9052
--- /dev/null
@@ -0,0 +1,193 @@
+/*\r
+ * JSON utilities\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: Json.java 914 2009-11-24 11:16:36Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r
+\r
+import java.io.IOException;\r
+import java.io.Reader;\r
+\r
+/**\r
+ * JSON各種共通ユーティリティ。\r
+ */\r
+public final class Json{\r
+\r
+    /**\r
+     * JSON最上位構造から文字出力を開始する。\r
+     * @param appout 出力先\r
+     * @param value JSONのObjectかArray\r
+     * @throws IOException 出力エラー\r
+     * @throws IllegalArgumentException 出力対象がObjectでもArrayでもない。\r
+     */\r
+    public static void writeJsonTop(Appendable appout, JsValue value)\r
+            throws IOException,\r
+                   IllegalArgumentException {\r
+        if( ! (value instanceof JsObject) && ! (value instanceof JsArray) ){\r
+            throw new IllegalArgumentException();\r
+        }\r
+\r
+        JsonAppender appender = new JsonAppender(appout);\r
+\r
+        try{\r
+            value.traverse(appender);\r
+        }catch(JsVisitException e){\r
+            Throwable cause = e.getCause();\r
+            if(cause instanceof IOException){\r
+                throw (IOException) cause;\r
+            }else if(cause instanceof RuntimeException){\r
+                throw (RuntimeException) cause;\r
+            }else if(cause instanceof Error){\r
+                throw (Error) cause;\r
+            }else{\r
+                assert false;\r
+                return;\r
+            }\r
+        }\r
+\r
+        appender.flush();\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * JSON規格のwhitespace文字を判定する。\r
+     * @param ch 判定対象文字\r
+     * @return whitespaceならtrue\r
+     */\r
+    public static boolean isWhitespace(char ch){\r
+        if(ch == '\t'    ) return true;\r
+        if(ch == '\r'    ) return true;\r
+        if(ch == '\n'    ) return true;\r
+        if(ch == '\u0020') return true;\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * whitespace文字を読み飛ばす。\r
+     * @param reader 文字入力\r
+     * @throws IOException 入力エラー\r
+     */\r
+    static void skipWhiteSpace(JsonReader reader)\r
+            throws IOException{\r
+        for(;;){\r
+            int chData = reader.read();\r
+            if(chData < '\u0000') break;\r
+            if( ! isWhitespace((char)chData) ){\r
+                reader.unread(chData);\r
+                break;\r
+            }\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 各種定数(true,false,null)を文字ストリームから読み取る。\r
+     * 長さ0の文字定数には無条件でfalseを返す。\r
+     * @param reader 文字入力\r
+     * @param text 文字定数\r
+     * @return 文字定数が文字入力に現れればtrue。\r
+     * 見つからないもしくはストリームの終わりに達したときはfalse\r
+     * @throws IOException 入力エラー\r
+     * @throws IllegalArgumentException 文字定数が長すぎる\r
+     */\r
+    static boolean parseConst(JsonReader reader,\r
+                               CharSequence text)\r
+            throws IOException,\r
+                   IllegalArgumentException {\r
+        int textLength = text.length();\r
+        if(textLength <= 0) return false;\r
+        if(textLength >= JsonReader.PUSHBACK_TOKENS){\r
+            throw new IllegalArgumentException();\r
+        }\r
+\r
+        int[] backData = new int[textLength - 1];\r
+        int readed = 0;\r
+\r
+        for(;;){\r
+            int chData = reader.read();\r
+            if(chData != text.charAt(readed)){\r
+                if(chData >= '\u0000') reader.unread(chData);\r
+                for(int pos = readed - 1; pos >= 0; pos--){\r
+                    reader.unread(backData[pos]);\r
+                }\r
+                break;\r
+            }\r
+\r
+            if(readed >= backData.length) return true;\r
+\r
+            backData[readed++] = chData;\r
+        }\r
+\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * JSONの各種Valueを文字ストリームから読み取る。\r
+     * @param reader 文字入力\r
+     * @return 各種Value\r
+     * @throws IOException 入力エラー\r
+     * @throws JsParseException パースエラー\r
+     */\r
+    public static JsValue parseValue(Reader reader)\r
+            throws IOException, JsParseException{\r
+        JsonReader jsreader;\r
+        if(reader instanceof JsonReader){\r
+            jsreader = (JsonReader) reader;\r
+        }else{\r
+            jsreader = new JsonReader(reader);\r
+        }\r
+\r
+        return parseValue(jsreader);\r
+    }\r
+\r
+    /**\r
+     * JSONの各種Valueを文字ストリームから読み取る。\r
+     * @param reader 文字入力\r
+     * @return 各種Value。ストリームの終わりに達したときはnull\r
+     * @throws IOException 入力エラー\r
+     * @throws JsParseException パースエラー\r
+     */\r
+    static JsValue parseValue(JsonReader reader)\r
+            throws IOException, JsParseException{\r
+        skipWhiteSpace(reader);\r
+\r
+        if(parseConst(reader, JsNull.NULL.toString())){\r
+            return JsNull.NULL;\r
+        }else if(parseConst(reader, JsBoolean.TRUE.toString())){\r
+            return JsBoolean.TRUE;\r
+        }else if(parseConst(reader, JsBoolean.FALSE.toString())){\r
+            return JsBoolean.FALSE;\r
+        }\r
+\r
+        int head = reader.read();\r
+        if(head < '\u0000') return null;\r
+\r
+        if( head == '-' || ('0' <= head && head <= '9') ){\r
+            reader.unread(head);\r
+            return JsNumber.parseNumber(reader);\r
+        }else if(head == '{'){\r
+            reader.unread(head);\r
+            return JsObject.parseObject(reader);\r
+        }else if(head == '['){\r
+            reader.unread(head);\r
+            return JsArray.parseArray(reader);\r
+        }else if(head == '"'){\r
+            reader.unread(head);\r
+            return JsString.parseString(reader);\r
+        }\r
+\r
+        throw new JsParseException();\r
+    }\r
+\r
+    /**\r
+     * 隠しコンストラクタ。\r
+     */\r
+    private Json(){\r
+        assert false;\r
+        throw new AssertionError();\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsonAppender.java b/src/main/java/jp/sourceforge/jindolf/json/JsonAppender.java
new file mode 100644 (file)
index 0000000..dd919eb
--- /dev/null
@@ -0,0 +1,282 @@
+/*\r
+ * JSON string output\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: JsonAppender.java 919 2009-11-24 14:32:50Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r
+\r
+import java.io.Flushable;\r
+import java.io.IOException;\r
+import java.util.Stack;\r
+\r
+/**\r
+ * JSON文字出力用ビジター。\r
+ * JSON Valueのトラバース時にこのビジターを指定すると、\r
+ * 事前に用意した文字出力先にJSONフォーマットで出力される\r
+ */\r
+class JsonAppender\r
+        implements ValueVisitor,\r
+                   Flushable {\r
+\r
+    private static final String NEWLINE = "\n";\r
+    private static final String INDENT_UNIT = "\u0020\u0020";\r
+    private static final String HASH_SEPARATOR = "\u0020:\u0020";\r
+    private static final String ELEM_DELIMITOR = "\u0020,";\r
+\r
+\r
+    private final Appendable appout;\r
+\r
+    private final Stack<JsValue> valueStack = new Stack<JsValue>();\r
+    private final Stack<Boolean> hasChildStack = new Stack<Boolean>();\r
+\r
+    private boolean afterPairName = false;\r
+\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param appout 出力先\r
+     */\r
+    public JsonAppender(Appendable appout){\r
+        super();\r
+        this.appout = appout;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 1文字出力。\r
+     * @param ch 文字\r
+     * @throws IOException 出力エラー\r
+     */\r
+    protected void append(char ch) throws IOException{\r
+        this.appout.append(ch);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 文字列出力。\r
+     * @param seq 文字列\r
+     * @throws IOException 出力エラー\r
+     */\r
+    protected void append(CharSequence seq) throws IOException{\r
+        this.appout.append(seq);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 最後の改行を出力した後、可能であれば出力先をフラッシュする。\r
+     * @throws IOException 出力エラー\r
+     */\r
+    public void flush() throws IOException{\r
+        putNewLine();\r
+        if(this.appout instanceof Flushable){\r
+            ((Flushable)this.appout).flush();\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 改行を出力する。\r
+     * @throws IOException 出力エラー\r
+     */\r
+    protected void putNewLine()\r
+            throws IOException{\r
+        append(NEWLINE);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * インデントを出力する。\r
+     * @throws IOException 出力エラー\r
+     */\r
+    protected void indentOut() throws IOException{\r
+        int level = stackLength();\r
+        for(int ct = 1; ct <= level; ct++){\r
+            append(INDENT_UNIT);\r
+        }\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 要素間区切りコンマを出力する。\r
+     * JSONでは最後の要素の後にコンマを出力してはいけない。\r
+     * @throws IOException 出力エラー\r
+     */\r
+    protected void putElemDelimitor()\r
+            throws IOException{\r
+        append(ELEM_DELIMITOR);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * pairの名前を出力する。\r
+     * @param name pair名\r
+     * @throws IOException 出力エラー\r
+     */\r
+    protected void putPairName(String name)\r
+            throws IOException{\r
+        JsString.writeText(this.appout, name);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * pair区切りコロンを出力する。\r
+     * @throws IOException 出力エラー\r
+     */\r
+    protected void putPairSeparator()\r
+            throws IOException{\r
+        append(HASH_SEPARATOR);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 一段ネストする。\r
+     * @param value JSON Value\r
+     * @throws IllegalArgumentException 引数がObjectでもArrayでもなかった\r
+     */\r
+    protected void pushValue(JsValue value)\r
+            throws IllegalArgumentException{\r
+        if( ! (value instanceof JsObject) && ! (value instanceof JsArray) ){\r
+            throw new IllegalArgumentException();\r
+        }\r
+\r
+        this.valueStack.push(value);\r
+        this.hasChildStack.push(false);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * ネストを一段解除する。\r
+     * @return 最後にネストしていたValue\r
+     */\r
+    protected JsValue popValue(){\r
+        this.hasChildStack.pop();\r
+        return this.valueStack.pop();\r
+    }\r
+\r
+    /**\r
+     * ネストのスタック段数を返す。\r
+     * @return 段数\r
+     */\r
+    protected int stackLength(){\r
+        return this.valueStack.size();\r
+    }\r
+\r
+    /**\r
+     * ネスト後、一つでも子要素が出力されたか判定する。\r
+     * @return 子要素が出力されていればtrue\r
+     */\r
+    protected boolean hasChildOut(){\r
+        if(stackLength() <= 0) return false;\r
+        return this.hasChildStack.peek();\r
+    }\r
+\r
+    /**\r
+     * 現時点でのネストに対し、子要素が一つ以上出力済みであると設定する。\r
+     */\r
+    protected void setChildOut(){\r
+        if(stackLength() <= 0) return;\r
+        this.hasChildStack.pop();\r
+        this.hasChildStack.push(true);\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * Valueの出力を行う。\r
+     * @param value {@inheritDoc}\r
+     * @throws JsVisitException {@inheritDoc}\r
+     */\r
+    public void visitValue(JsValue value)\r
+            throws JsVisitException{\r
+        try{\r
+            if( ! this.afterPairName ){\r
+                if(hasChildOut()){\r
+                    putElemDelimitor();\r
+                }\r
+                putNewLine();\r
+                indentOut();\r
+            }\r
+            this.afterPairName = false;\r
+\r
+            setChildOut();\r
+\r
+            if(value instanceof JsObject){\r
+                append('{');\r
+                pushValue(value);\r
+            }else if(value instanceof JsArray){\r
+                append('[');\r
+                pushValue(value);\r
+            }else{\r
+                append(value.toString());\r
+            }\r
+        }catch(IOException e){\r
+            throw new JsVisitException(e);\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * pairの名前を出力する。\r
+     * @param name {@inheritDoc}\r
+     * @throws JsVisitException {@inheritDoc}\r
+     */\r
+    public void visitPairName(String name)\r
+            throws JsVisitException{\r
+        try{\r
+            if(hasChildOut()){\r
+                putElemDelimitor();\r
+            }\r
+            putNewLine();\r
+            indentOut();\r
+            putPairName(name);\r
+            putPairSeparator();\r
+        }catch(IOException e){\r
+            throw new JsVisitException(e);\r
+        }\r
+\r
+        setChildOut();\r
+        this.afterPairName = true;\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * 閉じ括弧を出力する。\r
+     * @param composite {@inheritDoc}\r
+     * @throws JsVisitException {@inheritDoc}\r
+     */\r
+    public void visitCollectionClose(JsValue composite)\r
+            throws JsVisitException{\r
+        boolean hasChild = hasChildOut();\r
+\r
+        JsValue value = popValue();\r
+\r
+        try{\r
+            if(hasChild){\r
+                putNewLine();\r
+                indentOut();\r
+            }else{\r
+                append('\u0020');\r
+            }\r
+\r
+            if(value instanceof JsObject){\r
+                append('}');\r
+            }else if(value instanceof JsArray){\r
+                append(']');\r
+            }else{\r
+                assert false;\r
+                throw new JsVisitException();\r
+            }\r
+        }catch(IOException e){\r
+            throw new JsVisitException(e);\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsonReader.java b/src/main/java/jp/sourceforge/jindolf/json/JsonReader.java
new file mode 100644 (file)
index 0000000..c25dfdd
--- /dev/null
@@ -0,0 +1,38 @@
+/*\r
+ * JSON raeder\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: JsonReader.java 914 2009-11-24 11:16:36Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r
+\r
+import java.io.PushbackReader;\r
+import java.io.Reader;\r
+\r
+/**\r
+ * JSONデータ用入力文字ストリーム。\r
+ */\r
+class JsonReader extends PushbackReader{\r
+\r
+    /** 入力ストリームに必要なプッシュバック文字数。 */\r
+    public static final int PUSHBACK_TOKENS = 10;\r
+\r
+    static{\r
+        assert JsBoolean.TRUE .toString().length() < PUSHBACK_TOKENS;\r
+        assert JsBoolean.FALSE.toString().length() < PUSHBACK_TOKENS;\r
+        assert JsNull   .NULL .toString().length() < PUSHBACK_TOKENS;\r
+        assert "\\uXXXX"                 .length() < PUSHBACK_TOKENS;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param reader 文字入力\r
+     */\r
+    public JsonReader(Reader reader){\r
+        super(reader, PUSHBACK_TOKENS);\r
+        return;\r
+    }\r
+\r
+    // TODO エラー報告用に行数、文字数をカウント\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/json/ValueVisitor.java b/src/main/java/jp/sourceforge/jindolf/json/ValueVisitor.java
new file mode 100644 (file)
index 0000000..4d7a092
--- /dev/null
@@ -0,0 +1,36 @@
+/*\r
+ * JSON value visitor\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: ValueVisitor.java 900 2009-11-16 15:48:22Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r
+\r
+/**\r
+ * Valueへのビジター共通インタフェース。\r
+ */\r
+public interface ValueVisitor{\r
+\r
+    /**\r
+     * Value登場の通知を受け取る。\r
+     * @param value JSON Value\r
+     * @throws JsVisitException トラバース中止\r
+     */\r
+    void visitValue(JsValue value) throws JsVisitException;\r
+\r
+    /**\r
+     * pair名登場の通知を受け取る。\r
+     * @param name pair名\r
+     * @throws JsVisitException トラバース中止\r
+     */\r
+    void visitPairName(String name) throws JsVisitException;\r
+\r
+    /**\r
+     * 括弧終了の通知を受け取る。\r
+     * @param composite JSON Object か JSON Array\r
+     * @throws JsVisitException トラバース中止\r
+     */\r
+    void visitCollectionClose(JsValue composite) throws JsVisitException;\r
+\r
+}\r
diff --git a/src/main/java/jp/sourceforge/jindolf/json/package-info.java b/src/main/java/jp/sourceforge/jindolf/json/package-info.java
new file mode 100644 (file)
index 0000000..02b0260
--- /dev/null
@@ -0,0 +1,55 @@
+/*\r
+ * JSON Library パッケージコメント\r
+ *\r
+ * このファイルは、SunJDK5.0以降に含まれるJavadoc用に用意された、\r
+ * 特別な名前を持つソースファイルです。\r
+ * このファイルはソースコードを含まず、\r
+ * パッケージコメントとパッケージ宣言のみが含まれます。\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: package-info.java 899 2009-11-16 15:13:52Z olyutorskii $\r
+ */\r
+\r
+/**\r
+ * このライブラリは\r
+ * JSON形式のデータファイルの読み書きを行うために作られました。\r
+ *\r
+ * <hr>\r
+ *\r
+ * <p>\r
+ * The MIT License\r
+ * </p>\r
+ * <p>\r
+ * Copyright(c) 2009 olyutorskii\r
+ * </p>\r
+ * <p>\r
+ * Permission is hereby granted, free of charge, to any person obtaining a\r
+ * copy of this software and associated documentation files (the "Software"),\r
+ * to deal in the Software without restriction, including without limitation\r
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,\r
+ * and/or sell copies of the Software, and to permit persons to whom the\r
+ * Software is furnished to do so, subject to the following conditions:\r
+ * </p>\r
+ * <p>\r
+ * The above copyright notice and this permission notice shall be included in\r
+ * all copies or substantial portions of the Software.\r
+ * </p>\r
+ * <p>\r
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\r
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r
+ * OTHER DEALINGS IN THE SOFTWARE.\r
+ * </p>\r
+ *\r
+ * <hr>\r
+ *\r
+ * @see <a href="http://json.org/json-ja.html">JSONの紹介</a>\r
+ * @see <a href="http://www.ietf.org/rfc/rfc4627.txt">RFC4627</a>\r
+ * @see <a href="http://ja.wikipedia.org/wiki/JavaScript_Object_Notation">\r
+ * Wikipedia解説</a>\r
+ */\r
+\r
+package jp.sourceforge.jindolf.json;\r