OSDN Git Service

コメント修正
[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  * <p>入力バイトストリームから文字列(charシーケンス)をデコードし、
25  * デコード結果およびデコード異常系を
26  * 文字デコードハンドラ<code>{@link DecodeHandler}</code>に通知する。
27  *
28  * <p>このクラスは、「制御の反転」(Inversion of Control)を用いて
29  * <code>{@link java.nio.charset.CharsetDecoder}</code>呼び出しの
30  * 煩雑さを隠蔽するために設計された。
31  *
32  * <p>このクラスは、
33  * デコード異常系詳細を察知できない
34  * <code>{@link java.io.InputStreamReader}</code>の
35  * 代替品として設計された。
36  *
37  * <p>マルチスレッドからの同一インスタンスへの操作は非対応。
38  *
39  * @see java.nio.charset.CharsetDecoder
40  */
41 public class StreamDecoder{
42
43     /** デフォルト入力バッファサイズ(={@value}bytes)。 */
44     public static final int BYTEBUF_DEFSZ = 4 * 1024;
45     /** デフォルト出力バッファサイズ(={@value}chars)。 */
46     public static final int CHARBUF_DEFSZ = 4 * 1024;
47
48     private static final int DEF_ERRBUFLEN = 4;
49
50
51     private final CharsetDecoder decoder;
52
53     private final ByteBuffer byteBuffer;
54     private final CharBuffer charBuffer;
55
56     private ReadableByteChannel channel;
57
58     private DecodeHandler decodeHandler;
59
60     private byte[] errorData = new byte[DEF_ERRBUFLEN];
61
62
63     /**
64      * コンストラクタ。
65      *
66      * バッファサイズは入出力ともデフォルト値が用いられる。
67      *
68      * @param decoder 文字列デコーダ。
69      * 異常系に関するアクション応答設定は変更される。
70      * @throws NullPointerException デコーダにnullを渡した。
71      */
72     public StreamDecoder(CharsetDecoder decoder) throws NullPointerException{
73         this(decoder, BYTEBUF_DEFSZ, CHARBUF_DEFSZ);
74         return;
75     }
76
77     /**
78      * コンストラクタ。
79      *
80      * @param decoder 文字列デコーダ。
81      * 異常系に関するアクション応答設定は変更される。
82      * @param inbufSz 入力バッファサイズ(byte単位)
83      * @param outbufSz 出力バッファサイズ(char単位)
84      * @throws NullPointerException デコーダにnullを渡した。
85      * @throws IllegalArgumentException バッファサイズが0以下。
86      */
87     public StreamDecoder(CharsetDecoder decoder,
88                            int inbufSz,
89                            int outbufSz )
90             throws NullPointerException,
91                    IllegalArgumentException {
92         super();
93
94         if(decoder == null) throw new NullPointerException();
95
96         if(inbufSz <= 0 || outbufSz <= 0){
97             throw new IllegalArgumentException();
98         }
99
100         this.decoder = decoder;
101         this.byteBuffer = ByteBuffer.allocate(inbufSz);
102         this.charBuffer = CharBuffer.allocate(outbufSz);
103         this.channel = null;
104
105         initDecoderImpl();
106
107         return;
108     }
109
110
111     /**
112      * デコーダの初期化下請。
113      */
114     private void initDecoderImpl(){
115         this.byteBuffer.clear().flip();
116         this.charBuffer.clear();
117
118         this.decoder.onMalformedInput     (CodingErrorAction.REPORT);
119         this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
120         this.decoder.reset();
121
122         Arrays.fill(this.errorData, (byte) 0x00);
123
124         return;
125     }
126
127     /**
128      * デコーダの初期化。
129      */
130     protected void initDecoder(){
131         initDecoderImpl();
132         return;
133     }
134
135     /**
136      * デコードハンドラの設定。
137      *
138      * <p>デコード結果はここで指定したハンドラに通知される。
139      *
140      * <p>nullオブジェクトを指定することも可能だが、
141      * その場合デコード時に例外を起こす。
142      *
143      * @param decodeHandler デコードハンドラ
144      */
145     public void setDecodeHandler(DecodeHandler decodeHandler){
146         this.decodeHandler = decodeHandler;
147         return;
148     }
149
150     /**
151      * 入力バッファを返す。
152      *
153      * @return 入力バッファ
154      */
155     protected ByteBuffer getByteBuffer(){
156         return this.byteBuffer;
157     }
158
159     /**
160      * 出力バッファを返す。
161      *
162      * @return 出力バッファ
163      */
164     protected CharBuffer getCharBuffer(){
165         return this.charBuffer;
166     }
167
168     /**
169      * チャネルからの入力を読み進め入力バッファに詰め込む。
170      *
171      * <p>前回デコード処理の読み残しはバッファ前方に詰め直される。
172      *
173      * <p>入力バッファに空きがない状態で呼んではいけない。
174      *
175      * @return 入力バイト数。
176      *     入力末端に達したときは負の値。
177      *     ※入力バッファに空きがありチャネルがブロックモードの場合、
178      *     返り値0はありえない。
179      * @throws java.io.IOException 入力エラー
180      */
181     protected int fillByteBuffer() throws IOException{
182         this.byteBuffer.compact();
183         assert this.byteBuffer.hasRemaining();
184
185         int length = this.channel.read(this.byteBuffer);
186         assert length != 0;
187
188         this.byteBuffer.flip();
189
190         return length;
191     }
192
193     /**
194      * 出力バッファの全出力を読み進め、
195      * デコードハンドラに文字列を通知する。
196      *
197      * <p>出力バッファはクリアされる。
198      *
199      * <p>既に出力バッファが空だった場合、何もしない。
200      *
201      * @throws DecodeException ハンドラによるデコードエラー
202      */
203     protected void notifyText() throws DecodeException{
204         if(this.charBuffer.position() <= 0){
205             return;
206         }
207
208         this.charBuffer.flip();
209         this.decodeHandler.charContent(this.charBuffer);
210         this.charBuffer.clear();
211
212         return;
213     }
214
215     /**
216      * デコードハンドラにデコードエラーを通知する。
217      *
218      * @param result デコード結果
219      * @throws DecodeException ハンドラによるデコードエラー
220      * @throws IOException 入力エラー
221      */
222     protected void notifyError(CoderResult result)
223             throws IOException,
224                    DecodeException{
225         int length = chopErrorSequence(result);
226         this.decodeHandler.decodingError(this.errorData, 0, length);
227         return;
228     }
229
230     /**
231      * 入力バッファからデコードエラーの原因となったバイト列を読み進める。
232      *
233      * <p>{@link #errorData}の先頭にバイト列がコピーされ、
234      * バイト長が返される。
235      *
236      * <p>入力バッファはエラーの長さの分だけ読み進められる。
237      *
238      * @param result デコードエラー
239      * @return 原因バイト列の長さ
240      * @throws IOException 入力エラー。
241      *     ※このメソッドを継承する場合、必要に応じて先読みをしてもよいし、
242      *     その結果生じたIO例外を投げてもよい。
243      */
244     protected int chopErrorSequence(CoderResult result) throws IOException{
245         int errorLength = result.length();
246         reassignErrorData(errorLength);
247         this.byteBuffer.get(this.errorData, 0, errorLength);  // 相対get
248         return errorLength;
249     }
250
251     /**
252      * デコードエラー格納配列の再アサイン。
253      *
254      * <p>旧配列の内容は保持される。
255      * 決して縮小することは無い。
256      *
257      * @param size 再アサイン量。バイト長。
258      */
259     protected void reassignErrorData(int size){
260         int oldLength = this.errorData.length;
261         if(oldLength >= size) return;
262
263         int newSize = size;
264         if(oldLength * 2 > newSize) newSize = oldLength * 2;
265
266         byte[] newData = Arrays.copyOf(this.errorData, newSize);
267         this.errorData = newData;
268
269         return;
270     }
271
272     /**
273      * バイト入力ストリームを文字列デコードする。
274      *
275      * <p>デコード作業の状況に応じてハンドラへの各種通知が行われる。
276      *
277      * <p>入力ストリーム末端に到達するとデコード作業は終わり、
278      * 入力ストリームは閉じられる。
279      *
280      * @param istream 入力ストリーム
281      * @throws IOException 入力エラー
282      * @throws DecodeException ハンドラによるデコードエラー
283      */
284     public void decode(InputStream istream)
285             throws IOException,
286                    DecodeException {
287         // このチャネルは必ずブロックモードのはず
288         this.channel = Channels.newChannel(istream);
289
290         try{
291             decodeChannel();
292         }finally{
293             this.channel.close();
294             this.channel = null;
295         }
296
297         return;
298     }
299
300     /**
301      * 内部チャネルのデコードを開始する。
302      *
303      * @throws IOException 入力エラー
304      * @throws DecodeException ハンドラによるデコードエラー
305      */
306     protected void decodeChannel()
307             throws IOException,
308                    DecodeException {
309         initDecoder();
310
311         this.decodeHandler.startDecoding(this.decoder);
312
313         int ioLength;
314         boolean isEndOfInput;
315
316         ioLength = fillByteBuffer();
317         isEndOfInput = ioLength < 0;
318
319         for(;;){
320             CoderResult decodeResult =
321                     this.decoder.decode(this.byteBuffer,
322                                         this.charBuffer,
323                                         isEndOfInput);
324             // デコードエラー出現
325             if(decodeResult.isError()){
326                 notifyText();
327                 decodeResult = modifyErrorLength(decodeResult);
328                 notifyError(decodeResult);
329                 continue;
330             }
331
332             // 出力バッファが一杯
333             if(decodeResult.isOverflow()){
334                 notifyText();
335                 continue;
336             }
337
338             assert decodeResult.isUnderflow();
339
340             // デコード掃き出し開始
341             if(isEndOfInput){
342                 break;
343             }
344
345             // 入力バッファのデータが不足
346             checkInfLoop();
347             ioLength = fillByteBuffer();
348             isEndOfInput = ioLength < 0;
349         }
350
351         notifyText();
352
353         CoderResult flushResult;
354         do{
355             flushResult = this.decoder.flush(this.charBuffer);
356             assert ! flushResult.isError();
357             notifyText();
358         }while( ! flushResult.isUnderflow() );
359
360         this.decodeHandler.endDecoding();
361
362         return;
363     }
364
365     /**
366      * エラーの長さ(バイト列長)を修正する。
367      *
368      * <p>文字コード毎の事情に特化した異常系実装を目的とする。
369      * デフォルト実装では引数をそのまま返す。
370      *
371      * <p>バイト列を先読みすることで、
372      * さらに長いエラー情報を再構成してもよい。
373      *
374      * <p>エラー情報を前方に縮小することで、
375      * エラーとして扱われるはずだったバイト列後部は
376      * 再度デコード処理の対象として扱われる。
377      *
378      * @param result 修正元エラー情報。
379      * @return 修正後エラー情報。引数と同じ場合もありうる。
380      * (修正がない場合など)
381      * @throws IOException バイト列読み込みエラー
382      */
383     protected CoderResult modifyErrorLength(CoderResult result)
384             throws IOException{
385         CoderResult newResult = result;
386         return newResult;
387     }
388
389     /**
390      * 不適切な入力バッファサイズ由来の無限ループを検出し例外を投げる。
391      *
392      * <p>検出しなければ何もしない。
393      *
394      * @throws DecodeException 無限ループが検出された
395      */
396     private void checkInfLoop() throws DecodeException{
397         if(this.byteBuffer.position() == 0){
398             int bufSz = this.byteBuffer.capacity();
399             String csName = this.decoder.charset().name();
400
401             StringBuilder text = new StringBuilder();
402             text.append("too small input buffer (");
403             text.append(bufSz).append("bytes) for ");
404             text.append(csName);
405
406             throw new DecodeException(text.toString());
407         }
408         return;
409     }
410
411 }