--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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