4 * License : The MIT License
5 * Copyright(c) 2009 olyutorskii
8 package jp.sourceforge.jovsonz;
10 import java.io.Flushable;
11 import java.io.IOException;
12 import java.util.EmptyStackException;
13 import java.util.Stack;
18 * JSON Valueのトラバース時にこのビジターを指定すると、
19 * 事前に用意した文字出力先にJSONフォーマットで出力される。
22 * 出力に伴う{@link java.io.IOException}は
23 * {@link JsVisitException}のチェーン例外となる。
27 * インスタンスの再利用時の挙動は保証されない。
30 class JsonAppender implements ValueVisitor {
33 public static final String NEWLINE = "\n";
35 public static final String INDENT_UNIT = "\u0020\u0020";
37 public static final String PAIR_SEPARATOR = "\u0020:\u0020";
39 public static final String COMMA = "\u0020,";
41 public static final String EMPTY = "\u0020";
44 private final Appendable appout;
46 private final Stack<DumpContext> contextStack =
47 new Stack<DumpContext>();
49 private IOException ioException = null;
55 * @throws NullPointerException 引数がnull
57 public JsonAppender(Appendable appout)
58 throws NullPointerException{
60 if(appout == null) throw new NullPointerException();
67 * @param composition 現在のコンテキスト
69 protected void pushComposition(JsComposition composition){
70 DumpContext context = new DumpContext(composition);
71 this.contextStack.push(context);
77 * @return スタックトップのコンテキスト
78 * @throws EmptyStackException スタック構造が空
80 protected JsComposition popComposition() throws EmptyStackException{
81 DumpContext context = this.contextStack.pop();
82 JsComposition composition = context.getComposition();
90 protected int nestDepth(){
91 return this.contextStack.size();
98 protected boolean isNestEmpty(){
99 return this.contextStack.isEmpty();
103 * ネスト後、一つでも子要素が出力されたか判定する。
104 * @return 子要素が出力されていればtrue
106 protected boolean hasChildDumped(){
107 if(isNestEmpty()) return false;
108 boolean result = this.contextStack.peek().hasChildDumped();
113 * 現時点でのネストに対し、子要素が一つ以上出力済みであると設定する。
115 protected void setChildDumped(){
116 if(isNestEmpty()) return;
117 this.contextStack.peek().setChildDumped();
122 * 現在のコンテキストがARRAY型配列要素出力中の状態か否か判定する。
123 * @return 現在のコンテキストがARRAY型配列要素出力中ならtrue
125 protected boolean isArrayContext(){
126 if(isNestEmpty()) return false;
128 DumpContext context = this.contextStack.peek();
129 JsComposition composition = context.getComposition();
130 JsTypes type = composition.getJsTypes();
131 if(type != JsTypes.ARRAY) return false;
139 * @throws JsVisitException 出力エラー。
140 * @see java.lang.Appendable#append(char)
142 protected void append(char ch) throws JsVisitException{
144 this.appout.append(ch);
145 }catch(IOException e){
146 this.ioException = e;
147 throw new JsVisitException(e);
155 * @throws JsVisitException 出力エラー。
156 * @see java.lang.Appendable#append(CharSequence)
158 protected void append(CharSequence seq) throws JsVisitException{
160 this.appout.append(seq);
161 }catch(IOException e){
162 this.ioException = e;
163 throw new JsVisitException(e);
170 * @throws JsVisitException 出力エラー
171 * @see java.io.Flushable
173 protected void flush() throws JsVisitException{
175 if(this.appout instanceof Flushable){
176 ((Flushable)this.appout).flush();
178 }catch(IOException e){
179 this.ioException = e;
180 throw new JsVisitException(e);
186 * トラバース中断の原因となったIOExceptionを返す。
187 * @return トラバース中断の原因となったIOException。なければnull。
189 public IOException getIOException(){
190 return this.ioException;
194 * トラバース中断の原因となったIOExceptionがあるか判定する。
195 * @return トラバース中断の原因となったIOExceptionがあればtrue
197 public boolean hasIOException(){
198 if(this.ioException != null) return true;
205 * @throws JsVisitException 出力エラー
207 protected void putPairName(String name) throws JsVisitException{
209 JsString.dumpString(this.appout, name);
210 }catch(IOException e){
211 this.ioException = e;
212 throw new JsVisitException(e);
219 * @throws JsVisitException 出力エラー
221 protected void putPairSeparator() throws JsVisitException{
222 append(PAIR_SEPARATOR);
228 * JSONでは最後の要素の後にコンマを出力してはいけない。
229 * @throws JsVisitException 出力エラー
231 protected void putComma() throws JsVisitException{
238 * @throws JsVisitException 出力エラー。
240 protected void putNewLine() throws JsVisitException{
247 * @throws JsVisitException 出力エラー
249 protected void putIndent() throws JsVisitException{
250 int level = nestDepth();
251 for(int ct = 1; ct <= level; ct++){
258 * OBJECT及びARRAY型の最初の要素の前部分を出力する。
259 * @throws JsVisitException 出力エラー
261 protected void putBefore1stElement() throws JsVisitException{
268 * OBJECT及びARRAY型の要素間区切りを出力する。
269 * @throws JsVisitException 出力エラー
271 protected void putBetweenElement() throws JsVisitException{
279 * OBJECT及びARRAY型の最後の要素の後部分を出力する。
280 * @throws JsVisitException 出力エラー
282 protected void putAfterLastElement() throws JsVisitException{
289 * OBJECT及びARRAY型の空要素を出力する。
290 * @throws JsVisitException 出力エラー
292 protected void putEmptyElement() throws JsVisitException{
299 * @throws JsVisitException 出力エラー
301 protected void putBeforeParse() throws JsVisitException{
308 * @throws JsVisitException 出力エラー
310 protected void putAfterParse() throws JsVisitException{
318 * @param value {@inheritDoc}
319 * @throws JsVisitException {@inheritDoc}
322 public void visitValue(JsValue value)
323 throws JsVisitException{
324 if(isNestEmpty()) putBeforeParse();
326 if(isArrayContext()){
327 if(hasChildDumped()) putBetweenElement();
328 else putBefore1stElement();
331 JsTypes type = value.getJsTypes();
333 case OBJECT: append('{'); break;
334 case ARRAY: append('['); break;
335 default: append(value.toString()); break;
339 if(type.isComposition()){
340 assert value instanceof JsComposition;
341 JsComposition composition = (JsComposition) value;
342 pushComposition(composition);
350 * OBJECT内の各pairの名前を出力する。
351 * @param pairName {@inheritDoc}
352 * @throws JsVisitException {@inheritDoc}
355 public void visitPairName(String pairName)
356 throws JsVisitException{
357 if(hasChildDumped()) putBetweenElement();
358 else putBefore1stElement();
360 putPairName(pairName);
371 * @param closed {@inheritDoc}
372 * @throws JsVisitException {@inheritDoc}
375 public void visitCompositionClose(JsComposition closed)
376 throws JsVisitException{
377 boolean hasDumped = hasChildDumped();
378 JsComposition composition = popComposition();
380 if(hasDumped) putAfterLastElement();
381 else putEmptyElement();
384 switch(composition.getJsTypes()){
385 case OBJECT: closeBrace = '}'; break;
386 case ARRAY: closeBrace = ']'; break;
387 default: assert false; throw new AssertionError();
400 * ネストされた各JSON集約型コンテキストの出力状況。
402 private static class DumpContext{
403 private final JsComposition composition;
404 private boolean childDumped;
408 * 子要素が出力された事実は無い状態で始まる。
409 * @param composition レベルに対応するOBJECTもしくはARRAY型Value
411 DumpContext(JsComposition composition){
412 this.composition = composition;
413 this.childDumped = false;
418 * このレベルに対応するJSON集約型を返す。
419 * @return OBJECTもしくはARRAY型Value
421 JsComposition getComposition(){
422 return this.composition;
426 * このレベルで子要素出力が行われたか判定する。
427 * @return 子要素出力が行われていたならtrue
429 boolean hasChildDumped(){
430 return this.childDumped;
434 * このレベルで子要素出力が行われた事実を設定する。
436 void setChildDumped(){
437 this.childDumped = true;