OSDN Git Service

54828465d6eb293ea65504ecc0542481545d0df0
[jindolf/JinParser.git] / src / main / java / jp / sourceforge / jindolf / parser / StreamDecoder.java
1 /*
2  * stream decoder
3  *
4  * License : The MIT License
5  * Copyright(c) 2009 olyutorskii
6  */
7
8 package jp.sourceforge.jindolf.parser;
9
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.nio.ByteBuffer;
13 import java.nio.CharBuffer;
14 import java.nio.channels.Channels;
15 import java.nio.channels.ReadableByteChannel;
16 import java.nio.charset.CharsetDecoder;
17 import java.nio.charset.CoderResult;
18 import java.nio.charset.CodingErrorAction;
19 import java.util.Arrays;
20
21 /**
22  * バイトストリームからの文字デコーダ。
23  * 入力バイトストリームをデコードし、デコード結果およびデコードエラーを
24  * 文字デコードハンドラ{@link DecodeHandler}に通知する。
25  * このクラスは、
26  * デコードエラー詳細を察知できない{@link java.io.InputStreamReader}の
27  * 代替品として設計された。
28  * マルチスレッド対応はしていない。
29  */
30 public class StreamDecoder{
31
32     /** デフォルト入力バッファサイズ(={@value}bytes)。 */
33     public static final int BYTEBUF_DEFSZ = 4 * 1024;
34     /** デフォルト出力バッファサイズ(={@value}chars)。 */
35     public static final int CHARBUF_DEFSZ = 4 * 1024;
36
37
38     private final CharsetDecoder decoder;
39
40     private ReadableByteChannel channel;
41     private final ByteBuffer byteBuffer;
42     private final CharBuffer charBuffer;
43
44     private boolean isEndOfInput;
45     private boolean isFlushing;
46
47     private DecodeHandler decodeHandler;
48
49     // エンコーディングによっては長さに見直しが必要
50     private byte[] errorData = new byte[4];
51
52
53     /**
54      * コンストラクタ。
55      * @param decoder デコーダ
56      */
57     public StreamDecoder(CharsetDecoder decoder){
58         this(decoder, BYTEBUF_DEFSZ, CHARBUF_DEFSZ);
59         return;
60     }
61
62     /**
63      * コンストラクタ。
64      * @param decoder デコーダ
65      * @param inbufSz 入力バッファサイズ
66      * @param outbufSz 出力バッファサイズ
67      * @throws NullPointerException デコーダにnullを渡した。
68      * @throws IllegalArgumentException バッファサイズが負。
69      */
70     public StreamDecoder(CharsetDecoder decoder,
71                            int inbufSz,
72                            int outbufSz )
73             throws NullPointerException,
74                    IllegalArgumentException {
75         super();
76
77         if(decoder == null) throw new NullPointerException();
78
79         if(inbufSz <= 0 || outbufSz <= 0){
80             throw new IllegalArgumentException();
81         }
82
83         this.decoder = decoder;
84         this.byteBuffer = ByteBuffer.allocate(inbufSz);
85         this.charBuffer = CharBuffer.allocate(outbufSz);
86         this.channel = null;
87
88         initDecoderImpl();
89
90         return;
91     }
92
93     /**
94      * デコーダの初期化下請。
95      */
96     private void initDecoderImpl(){
97         this.byteBuffer.clear().flip();
98         this.charBuffer.clear();
99
100         this.decoder.onMalformedInput     (CodingErrorAction.REPORT);
101         this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
102         this.decoder.reset();
103
104         this.isEndOfInput = false;
105         this.isFlushing = false;
106
107         Arrays.fill(this.errorData, (byte) 0x00);
108
109         return;
110     }
111
112     /**
113      * デコーダの初期化。
114      */
115     protected void initDecoder(){
116         initDecoderImpl();
117         return;
118     }
119
120     /**
121      * 入力バッファを返す。
122      * @return 入力バッファ
123      */
124     protected ByteBuffer getByteBuffer(){
125         return this.byteBuffer;
126     }
127
128     /**
129      * 出力バッファを返す。
130      * @return 出力バッファ
131      */
132     protected CharBuffer getCharBuffer(){
133         return this.charBuffer;
134     }
135
136     /**
137      * デコードハンドラの設定。
138      * nullオブジェクトを指定しても構わないが、
139      * その場合パース時に例外を起こす。
140      * @param decodeHandler デコードハンドラ
141      */
142     public void setDecodeHandler(DecodeHandler decodeHandler){
143         this.decodeHandler = decodeHandler;
144         return;
145     }
146
147     /**
148      * デコードエラー格納配列の再アサイン。
149      * 配列の内容は保持される。
150      * 決して縮小することは無い。
151      * メモ:java.util.Arrays#copyOf()はJRE1.5にない。
152      * @param size 再アサイン量。バイト長。
153      */
154     protected void reassignErrorData(int size){
155         int oldLength = this.errorData.length;
156         if(oldLength >= size) return;
157         int newSize = size;
158         if(oldLength * 2 > newSize) newSize = oldLength * 2;
159         byte[] newData = new byte[newSize];
160         System.arraycopy(this.errorData, 0, newData, 0, oldLength);
161         this.errorData = newData;
162         return;
163     }
164
165     /**
166      * デコードハンドラに文字列を渡す。
167      * @throws DecodeException デコードエラー
168      */
169     protected void flushContent() throws DecodeException{
170         if(this.charBuffer.position() <= 0){
171             return;
172         }
173
174         this.charBuffer.flip();
175         this.decodeHandler.charContent(this.charBuffer);
176         this.charBuffer.clear();
177
178         return;
179     }
180
181     /**
182      * デコードハンドラにデコードエラーを渡す。
183      * @param result デコード結果
184      * @throws DecodeException デコードエラー
185      * @throws IOException 入力エラー
186      */
187     protected void putDecodeError(CoderResult result)
188             throws IOException,
189                    DecodeException{
190         int length = chopErrorSequence(result);
191         this.decodeHandler.decodingError(this.errorData, 0, length);
192         return;
193     }
194
195     /**
196      * デコードエラーの原因バイト列を抽出する。
197      * {@link #errorData}の先頭にバイト列が格納され、バイト長が返される。
198      * @param result デコード結果
199      * @return 原因バイト列の長さ
200      * @throws IOException 入力エラー。
201      *     ※このメソッドを継承する場合、必要に応じて先読みをしてもよいし、
202      *     その結果生じたIO例外を投げてもよい。
203      */
204     protected int chopErrorSequence(CoderResult result) throws IOException{
205         int errorLength = result.length();
206         reassignErrorData(errorLength);
207         this.byteBuffer.get(this.errorData, 0, errorLength);  // 相対get
208         return errorLength;
209     }
210
211     /**
212      * チャンネルからの入力を読み進める。
213      * 前回の読み残しはバッファ前方に詰め直される。
214      * @return 入力バイト数。
215      * @throws java.io.IOException 入出力エラー
216      */
217     protected int readByteBuffer() throws IOException{
218         this.byteBuffer.compact();
219
220         int length = this.channel.read(this.byteBuffer);
221         if(length <= 0){
222             this.isEndOfInput = true;
223         }
224
225         this.byteBuffer.flip();
226
227         return length;
228     }
229
230     /**
231      * バイトストリームのデコードを開始する。
232      * @param istream 入力ストリーム
233      * @throws IOException 入出力エラー
234      * @throws DecodeException デコードエラー
235      */
236     public void decode(InputStream istream)
237             throws IOException,
238                    DecodeException {
239         this.channel = Channels.newChannel(istream);
240
241         try{
242             decodeChannel();
243         }finally{
244             this.channel.close();
245             this.channel = null;
246             istream.close();
247         }
248
249         return;
250     }
251
252     /**
253      * 内部チャネルのデコードを開始する。
254      * @throws IOException 入出力エラー
255      * @throws DecodeException デコードエラー
256      */
257     protected void decodeChannel()
258             throws IOException,
259                    DecodeException {
260         initDecoder();
261
262         this.decodeHandler.startDecoding(this.decoder);
263
264         for(;;){
265             CoderResult result;
266             if(this.isFlushing){
267                 result = this.decoder.flush(this.charBuffer);
268             }else{
269                 result = this.decoder.decode(this.byteBuffer,
270                                              this.charBuffer,
271                                              this.isEndOfInput);
272             }
273
274             if(result.isError()){
275                 flushContent();
276                 putDecodeError(result);
277             }else if(result.isOverflow()){      // 出力バッファが一杯
278                 flushContent();
279             }else if(result.isUnderflow()){     // 入力バッファが空
280                 if( ! this.isEndOfInput ){
281                     readByteBuffer();
282                     continue;
283                 }
284
285                 if( ! this.isFlushing ){
286                     this.isFlushing = true;
287                     continue;
288                 }
289
290                 flushContent();
291                 break;
292             }else{
293                 assert false;
294             }
295         }
296
297         this.decodeHandler.endDecoding();
298
299         return;
300     }
301
302 }