OSDN Git Service

cbba7ad9d873277eb705a9173e2085a78ac843ef
[jovsonz/Jovsonz.git] / src / main / java / jp / sourceforge / jovsonz / JsonAppender.java
1 /*
2  * JSON output
3  *
4  * License : The MIT License
5  * Copyright(c) 2009 olyutorskii
6  */
7
8 package jp.sourceforge.jovsonz;
9
10 import java.io.Flushable;
11 import java.io.IOException;
12 import java.util.EmptyStackException;
13 import java.util.Stack;
14
15 /**
16  * JSON文字出力用ビジター。
17  * <p>
18  * JSON Valueのトラバース時にこのビジターを指定すると、
19  * 事前に用意した文字出力先にJSONフォーマットで出力される。
20  * </p>
21  * <p>
22  * 出力に伴う{@link java.io.IOException}は
23  * {@link JsVisitException}のチェーン例外となる。
24  * </p>
25  * <p>
26  * 前回パースの成功/失敗に関わらず、
27  * インスタンスの再利用時の挙動は保証されない。
28  * </p>
29  */
30 class JsonAppender implements ValueVisitor {
31
32     /** 改行。 */
33     public static final String NEWLINE = "\n";
34     /** インデント単位。 */
35     public static final String INDENT_UNIT = "\u0020\u0020";
36     /** Pair区切り。 */
37     public static final String PAIR_SEPARATOR = "\u0020:\u0020";
38     /** コンマ区切り。 */
39     public static final String COMMA = "\u0020,";
40     /** 空要素。 */
41     public static final String EMPTY = "\u0020";
42
43
44     private final Appendable appout;
45
46     private final Stack<DumpContext> contextStack =
47             new Stack<DumpContext>();
48
49     private IOException ioException = null;
50
51
52     /**
53      * コンストラクタ。
54      * @param appout 出力先
55      * @throws NullPointerException 引数がnull
56      */
57     public JsonAppender(Appendable appout)
58             throws NullPointerException{
59         super();
60         if(appout == null) throw new NullPointerException();
61         this.appout = appout;
62         return;
63     }
64
65     /**
66      * コンテキストをプッシュ退避する。
67      * @param composition 現在のコンテキスト
68      */
69     protected void pushComposition(JsComposition composition){
70         DumpContext context = new DumpContext(composition);
71         this.contextStack.push(context);
72         return;
73     }
74
75     /**
76      * コンテキストをポップ復帰する。
77      * @return スタックトップのコンテキスト
78      * @throws EmptyStackException スタック構造が空
79      */
80     protected JsComposition popComposition() throws EmptyStackException{
81         DumpContext context = this.contextStack.pop();
82         JsComposition composition = context.getComposition();
83         return composition;
84     }
85
86     /**
87      * ネスト構造の深さを返す。
88      * @return 0から始まる深さ
89      */
90     protected int nestDepth(){
91         return this.contextStack.size();
92     }
93
94     /**
95      * ネスト構造が空(深さ0)か判定する。
96      * @return 空ならtrue
97      */
98     protected boolean isNestEmpty(){
99         return this.contextStack.isEmpty();
100     }
101
102     /**
103      * ネスト後、一つでも子要素が出力されたか判定する。
104      * @return 子要素が出力されていればtrue
105      */
106     protected boolean hasChildDumped(){
107         if(isNestEmpty()) return false;
108         boolean result = this.contextStack.peek().hasChildDumped();
109         return result;
110     }
111
112     /**
113      * 現時点でのネストに対し、子要素が一つ以上出力済みであると設定する。
114      */
115     protected void setChildDumped(){
116         if(isNestEmpty()) return;
117         this.contextStack.peek().setChildDumped();
118         return;
119     }
120
121     /**
122      * 現在のコンテキストがARRAY型配列要素出力中の状態か否か判定する。
123      * @return 現在のコンテキストがARRAY型配列要素出力中ならtrue
124      */
125     protected boolean isArrayContext(){
126         if(isNestEmpty()) return false;
127
128         DumpContext context = this.contextStack.peek();
129         JsComposition composition = context.getComposition();
130         JsTypes type = composition.getJsTypes();
131         if(type != JsTypes.ARRAY) return false;
132
133         return true;
134     }
135
136     /**
137      * 1文字出力。
138      * @param ch 文字
139      * @throws JsVisitException 出力エラー。
140      * @see java.lang.Appendable#append(char)
141      */
142     protected void append(char ch) throws JsVisitException{
143         try{
144             this.appout.append(ch);
145         }catch(IOException e){
146             this.ioException = e;
147             throw new JsVisitException(e);
148         }
149         return;
150     }
151
152     /**
153      * 文字列出力。
154      * @param seq 文字列
155      * @throws JsVisitException 出力エラー。
156      * @see java.lang.Appendable#append(CharSequence)
157      */
158     protected void append(CharSequence seq) throws JsVisitException{
159         try{
160             this.appout.append(seq);
161         }catch(IOException e){
162             this.ioException = e;
163             throw new JsVisitException(e);
164         }
165         return;
166     }
167
168     /**
169      * 可能であれば出力先をフラッシュする。
170      * @throws JsVisitException 出力エラー
171      * @see java.io.Flushable
172      */
173     protected void flush() throws JsVisitException{
174         try{
175             if(this.appout instanceof Flushable){
176                 ((Flushable)this.appout).flush();
177             }
178         }catch(IOException e){
179             this.ioException = e;
180             throw new JsVisitException(e);
181         }
182         return;
183     }
184
185     /**
186      * トラバース中断の原因となったIOExceptionを返す。
187      * @return トラバース中断の原因となったIOException。なければnull。
188      */
189     public IOException getIOException(){
190         return this.ioException;
191     }
192
193     /**
194      * トラバース中断の原因となったIOExceptionがあるか判定する。
195      * @return トラバース中断の原因となったIOExceptionがあればtrue
196      */
197     public boolean hasIOException(){
198         if(this.ioException != null) return true;
199         return false;
200     }
201
202     /**
203      * pairの名前を出力する。
204      * @param name pair名
205      * @throws JsVisitException 出力エラー
206      */
207     protected void putPairName(String name) throws JsVisitException{
208         try{
209             JsString.dumpString(this.appout, name);
210         }catch(IOException e){
211             this.ioException = e;
212             throw new JsVisitException(e);
213         }
214         return;
215     }
216
217     /**
218      * pair区切りコロンを出力する。
219      * @throws JsVisitException 出力エラー
220      */
221     protected void putPairSeparator() throws JsVisitException{
222         append(PAIR_SEPARATOR);
223         return;
224     }
225
226     /**
227      * 要素間区切りコンマを出力する。
228      * JSONでは最後の要素の後にコンマを出力してはいけない。
229      * @throws JsVisitException 出力エラー
230      */
231     protected void putComma() throws JsVisitException{
232         append(COMMA);
233         return;
234     }
235
236     /**
237      * 改行を出力する。
238      * @throws JsVisitException 出力エラー。
239      */
240     protected void putNewLine() throws JsVisitException{
241         append(NEWLINE);
242         return;
243     }
244
245     /**
246      * インデントを出力する。
247      * @throws JsVisitException 出力エラー
248      */
249     protected void putIndent() throws JsVisitException{
250         int level = nestDepth();
251         for(int ct = 1; ct <= level; ct++){
252             append(INDENT_UNIT);
253         }
254         return;
255     }
256
257     /**
258      * OBJECT及びARRAY型の最初の要素の前部分を出力する。
259      * @throws JsVisitException 出力エラー
260      */
261     protected void putBefore1stElement() throws JsVisitException{
262         putNewLine();
263         putIndent();
264         return;
265     }
266
267     /**
268      * OBJECT及びARRAY型の要素間区切りを出力する。
269      * @throws JsVisitException 出力エラー
270      */
271     protected void putBetweenElement() throws JsVisitException{
272         putComma();
273         putNewLine();
274         putIndent();
275         return;
276     }
277
278     /**
279      * OBJECT及びARRAY型の最後の要素の後部分を出力する。
280      * @throws JsVisitException 出力エラー
281      */
282     protected void putAfterLastElement() throws JsVisitException{
283         putNewLine();
284         putIndent();
285         return;
286     }
287
288     /**
289      * OBJECT及びARRAY型の空要素を出力する。
290      * @throws JsVisitException 出力エラー
291      */
292     protected void putEmptyElement() throws JsVisitException{
293         append(EMPTY);
294         return;
295     }
296
297     /**
298      * パース前の出力を行う。
299      * @throws JsVisitException 出力エラー
300      */
301     protected void putBeforeParse() throws JsVisitException{
302         //NOTHING
303         return;
304     }
305
306     /**
307      * パース後の出力を行う。
308      * @throws JsVisitException 出力エラー
309      */
310     protected void putAfterParse() throws JsVisitException{
311         putNewLine();
312         return;
313     }
314
315     /**
316      * {@inheritDoc}
317      * Valueの出力を行う。
318      * @param value {@inheritDoc}
319      * @throws JsVisitException {@inheritDoc}
320      */
321     @Override
322     public void visitValue(JsValue value)
323             throws JsVisitException{
324         if(isNestEmpty()) putBeforeParse();
325
326         if(isArrayContext()){
327             if(hasChildDumped()) putBetweenElement();
328             else                 putBefore1stElement();
329         }
330
331         JsTypes type = value.getJsTypes();
332         switch(type){
333         case OBJECT: append('{');              break;
334         case ARRAY:  append('[');              break;
335         default:     append(value.toString()); break;
336         }
337         setChildDumped();
338
339         if(type.isComposition()){
340             assert value instanceof JsComposition;
341             JsComposition composition = (JsComposition) value;
342             pushComposition(composition);
343         }
344
345         return;
346     }
347
348     /**
349      * {@inheritDoc}
350      * OBJECT内の各pairの名前を出力する。
351      * @param pairName {@inheritDoc}
352      * @throws JsVisitException {@inheritDoc}
353      */
354     @Override
355     public void visitPairName(String pairName)
356             throws JsVisitException{
357         if(hasChildDumped()) putBetweenElement();
358         else                 putBefore1stElement();
359
360         putPairName(pairName);
361         putPairSeparator();
362
363         setChildDumped();
364
365         return;
366     }
367
368     /**
369      * {@inheritDoc}
370      * 閉じ括弧を出力する。
371      * @param closed {@inheritDoc}
372      * @throws JsVisitException {@inheritDoc}
373      */
374     @Override
375     public void visitCompositionClose(JsComposition closed)
376             throws JsVisitException{
377         boolean hasDumped = hasChildDumped();
378         JsComposition composition = popComposition();
379
380         if(hasDumped) putAfterLastElement();
381         else          putEmptyElement();
382
383         char closeBrace;
384         switch(composition.getJsTypes()){
385         case OBJECT: closeBrace = '}'; break;
386         case ARRAY:  closeBrace = ']'; break;
387         default: assert false; throw new AssertionError();
388         }
389         append(closeBrace);
390
391         if(isNestEmpty()){
392             putAfterParse();
393             flush();
394         }
395
396         return;
397     }
398
399     /**
400      * ネストされた各JSON集約型コンテキストの出力状況。
401      */
402     private static class DumpContext{
403         private final JsComposition composition;
404         private boolean childDumped;
405
406         /**
407          * コンストラクタ。
408          * 子要素が出力された事実は無い状態で始まる。
409          * @param composition レベルに対応するOBJECTもしくはARRAY型Value
410          */
411         DumpContext(JsComposition composition){
412             this.composition = composition;
413             this.childDumped = false;
414             return;
415         }
416
417         /**
418          * このレベルに対応するJSON集約型を返す。
419          * @return OBJECTもしくはARRAY型Value
420          */
421         JsComposition getComposition(){
422             return this.composition;
423         }
424
425         /**
426          * このレベルで子要素出力が行われたか判定する。
427          * @return 子要素出力が行われていたならtrue
428          */
429         boolean hasChildDumped(){
430             return this.childDumped;
431         }
432
433         /**
434          * このレベルで子要素出力が行われた事実を設定する。
435          */
436         void setChildDumped(){
437             this.childDumped = true;
438             return;
439         }
440
441     }
442
443 }