OSDN Git Service

@Override追加
[jindolf/Jindolf.git] / src / main / java / jp / sourceforge / jindolf / json / JsonAppender.java
1 /*\r
2  * JSON string output\r
3  *\r
4  * License : The MIT License\r
5  * Copyright(c) 2009 olyutorskii\r
6  */\r
7 \r
8 package jp.sourceforge.jindolf.json;\r
9 \r
10 import java.io.Flushable;\r
11 import java.io.IOException;\r
12 import java.util.Stack;\r
13 \r
14 /**\r
15  * JSON文字出力用ビジター。\r
16  * JSON Valueのトラバース時にこのビジターを指定すると、\r
17  * 事前に用意した文字出力先にJSONフォーマットで出力される\r
18  */\r
19 class JsonAppender\r
20         implements ValueVisitor,\r
21                    Flushable {\r
22 \r
23     private static final String NEWLINE = "\n";\r
24     private static final String INDENT_UNIT = "\u0020\u0020";\r
25     private static final String HASH_SEPARATOR = "\u0020:\u0020";\r
26     private static final String ELEM_DELIMITOR = "\u0020,";\r
27 \r
28 \r
29     private final Appendable appout;\r
30 \r
31     private final Stack<JsValue> valueStack = new Stack<JsValue>();\r
32     private final Stack<Boolean> hasChildStack = new Stack<Boolean>();\r
33 \r
34     private boolean afterPairName = false;\r
35 \r
36 \r
37     /**\r
38      * コンストラクタ。\r
39      * @param appout 出力先\r
40      */\r
41     public JsonAppender(Appendable appout){\r
42         super();\r
43         this.appout = appout;\r
44         return;\r
45     }\r
46 \r
47     /**\r
48      * 1文字出力。\r
49      * @param ch 文字\r
50      * @throws IOException 出力エラー\r
51      */\r
52     protected void append(char ch) throws IOException{\r
53         this.appout.append(ch);\r
54         return;\r
55     }\r
56 \r
57     /**\r
58      * 文字列出力。\r
59      * @param seq 文字列\r
60      * @throws IOException 出力エラー\r
61      */\r
62     protected void append(CharSequence seq) throws IOException{\r
63         this.appout.append(seq);\r
64         return;\r
65     }\r
66 \r
67     /**\r
68      * 最後の改行を出力した後、可能であれば出力先をフラッシュする。\r
69      * @throws IOException 出力エラー\r
70      */\r
71     public void flush() throws IOException{\r
72         putNewLine();\r
73         if(this.appout instanceof Flushable){\r
74             ((Flushable)this.appout).flush();\r
75         }\r
76         return;\r
77     }\r
78 \r
79     /**\r
80      * 改行を出力する。\r
81      * @throws IOException 出力エラー\r
82      */\r
83     protected void putNewLine()\r
84             throws IOException{\r
85         append(NEWLINE);\r
86         return;\r
87     }\r
88 \r
89     /**\r
90      * インデントを出力する。\r
91      * @throws IOException 出力エラー\r
92      */\r
93     protected void indentOut() throws IOException{\r
94         int level = stackLength();\r
95         for(int ct = 1; ct <= level; ct++){\r
96             append(INDENT_UNIT);\r
97         }\r
98         return;\r
99     }\r
100 \r
101     /**\r
102      * 要素間区切りコンマを出力する。\r
103      * JSONでは最後の要素の後にコンマを出力してはいけない。\r
104      * @throws IOException 出力エラー\r
105      */\r
106     protected void putElemDelimitor()\r
107             throws IOException{\r
108         append(ELEM_DELIMITOR);\r
109         return;\r
110     }\r
111 \r
112     /**\r
113      * pairの名前を出力する。\r
114      * @param name pair名\r
115      * @throws IOException 出力エラー\r
116      */\r
117     protected void putPairName(String name)\r
118             throws IOException{\r
119         JsString.writeText(this.appout, name);\r
120         return;\r
121     }\r
122 \r
123     /**\r
124      * pair区切りコロンを出力する。\r
125      * @throws IOException 出力エラー\r
126      */\r
127     protected void putPairSeparator()\r
128             throws IOException{\r
129         append(HASH_SEPARATOR);\r
130         return;\r
131     }\r
132 \r
133     /**\r
134      * 一段ネストする。\r
135      * @param value JSON Value\r
136      * @throws IllegalArgumentException 引数がObjectでもArrayでもなかった\r
137      */\r
138     protected void pushValue(JsValue value)\r
139             throws IllegalArgumentException{\r
140         if( ! (value instanceof JsObject) && ! (value instanceof JsArray) ){\r
141             throw new IllegalArgumentException();\r
142         }\r
143 \r
144         this.valueStack.push(value);\r
145         this.hasChildStack.push(false);\r
146 \r
147         return;\r
148     }\r
149 \r
150     /**\r
151      * ネストを一段解除する。\r
152      * @return 最後にネストしていたValue\r
153      */\r
154     protected JsValue popValue(){\r
155         this.hasChildStack.pop();\r
156         return this.valueStack.pop();\r
157     }\r
158 \r
159     /**\r
160      * ネストのスタック段数を返す。\r
161      * @return 段数\r
162      */\r
163     protected int stackLength(){\r
164         return this.valueStack.size();\r
165     }\r
166 \r
167     /**\r
168      * ネスト後、一つでも子要素が出力されたか判定する。\r
169      * @return 子要素が出力されていればtrue\r
170      */\r
171     protected boolean hasChildOut(){\r
172         if(stackLength() <= 0) return false;\r
173         return this.hasChildStack.peek();\r
174     }\r
175 \r
176     /**\r
177      * 現時点でのネストに対し、子要素が一つ以上出力済みであると設定する。\r
178      */\r
179     protected void setChildOut(){\r
180         if(stackLength() <= 0) return;\r
181         this.hasChildStack.pop();\r
182         this.hasChildStack.push(true);\r
183     }\r
184 \r
185     /**\r
186      * {@inheritDoc}\r
187      * Valueの出力を行う。\r
188      * @param value {@inheritDoc}\r
189      * @throws JsVisitException {@inheritDoc}\r
190      */\r
191     @Override\r
192     public void visitValue(JsValue value)\r
193             throws JsVisitException{\r
194         try{\r
195             if( ! this.afterPairName ){\r
196                 if(hasChildOut()){\r
197                     putElemDelimitor();\r
198                 }\r
199                 putNewLine();\r
200                 indentOut();\r
201             }\r
202             this.afterPairName = false;\r
203 \r
204             setChildOut();\r
205 \r
206             if(value instanceof JsObject){\r
207                 append('{');\r
208                 pushValue(value);\r
209             }else if(value instanceof JsArray){\r
210                 append('[');\r
211                 pushValue(value);\r
212             }else{\r
213                 append(value.toString());\r
214             }\r
215         }catch(IOException e){\r
216             throw new JsVisitException(e);\r
217         }\r
218 \r
219         return;\r
220     }\r
221 \r
222     /**\r
223      * {@inheritDoc}\r
224      * pairの名前を出力する。\r
225      * @param name {@inheritDoc}\r
226      * @throws JsVisitException {@inheritDoc}\r
227      */\r
228     @Override\r
229     public void visitPairName(String name)\r
230             throws JsVisitException{\r
231         try{\r
232             if(hasChildOut()){\r
233                 putElemDelimitor();\r
234             }\r
235             putNewLine();\r
236             indentOut();\r
237             putPairName(name);\r
238             putPairSeparator();\r
239         }catch(IOException e){\r
240             throw new JsVisitException(e);\r
241         }\r
242 \r
243         setChildOut();\r
244         this.afterPairName = true;\r
245 \r
246         return;\r
247     }\r
248 \r
249     /**\r
250      * {@inheritDoc}\r
251      * 閉じ括弧を出力する。\r
252      * @param composite {@inheritDoc}\r
253      * @throws JsVisitException {@inheritDoc}\r
254      */\r
255     @Override\r
256     public void visitCollectionClose(JsValue composite)\r
257             throws JsVisitException{\r
258         boolean hasChild = hasChildOut();\r
259 \r
260         JsValue value = popValue();\r
261 \r
262         try{\r
263             if(hasChild){\r
264                 putNewLine();\r
265                 indentOut();\r
266             }else{\r
267                 append('\u0020');\r
268             }\r
269 \r
270             if(value instanceof JsObject){\r
271                 append('}');\r
272             }else if(value instanceof JsArray){\r
273                 append(']');\r
274             }else{\r
275                 assert false;\r
276                 throw new JsVisitException();\r
277             }\r
278         }catch(IOException e){\r
279             throw new JsVisitException(e);\r
280         }\r
281 \r
282         return;\r
283     }\r
284 \r
285 }\r