4 * License : The MIT License
5 * Copyright(c) 2009 olyutorskii
8 package jp.sourceforge.jindolf.parser;
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;
22 * バイトストリームを入力とする文字列デコーダ。
24 * <p>入力バイトストリームから文字列(charシーケンス)をデコードし、
26 * 文字デコードハンドラ<code>{@link DecodeHandler}</code>に通知する。
28 * <p>このクラスは、「制御の反転」(Inversion of Control)を用いて
29 * <code>{@link java.nio.charset.CharsetDecoder}</code>呼び出しの
34 * <code>{@link java.io.InputStreamReader}</code>の
37 * <p>マルチスレッドからの同一インスタンスへの操作は非対応。
39 * @see java.nio.charset.CharsetDecoder
41 public class StreamDecoder{
43 /** デフォルト入力バッファサイズ(={@value}bytes)。 */
44 public static final int BYTEBUF_DEFSZ = 4 * 1024;
45 /** デフォルト出力バッファサイズ(={@value}chars)。 */
46 public static final int CHARBUF_DEFSZ = 4 * 1024;
48 private static final int DEF_ERRBUFLEN = 4;
51 private final CharsetDecoder decoder;
53 private final ByteBuffer byteBuffer;
54 private final CharBuffer charBuffer;
56 private ReadableByteChannel channel;
58 private DecodeHandler decodeHandler;
60 private byte[] errorData = new byte[DEF_ERRBUFLEN];
66 * バッファサイズは入出力ともデフォルト値が用いられる。
68 * @param decoder 文字列デコーダ。
69 * 異常系に関するアクション応答設定は変更される。
70 * @throws NullPointerException デコーダにnullを渡した。
72 public StreamDecoder(CharsetDecoder decoder) throws NullPointerException{
73 this(decoder, BYTEBUF_DEFSZ, CHARBUF_DEFSZ);
80 * @param decoder 文字列デコーダ。
81 * 異常系に関するアクション応答設定は変更される。
82 * @param inbufSz 入力バッファサイズ(byte単位)
83 * @param outbufSz 出力バッファサイズ(char単位)
84 * @throws NullPointerException デコーダにnullを渡した。
85 * @throws IllegalArgumentException バッファサイズが0以下。
87 public StreamDecoder(CharsetDecoder decoder,
90 throws NullPointerException,
91 IllegalArgumentException {
94 if(decoder == null) throw new NullPointerException();
96 if(inbufSz <= 0 || outbufSz <= 0){
97 throw new IllegalArgumentException();
100 this.decoder = decoder;
101 this.byteBuffer = ByteBuffer.allocate(inbufSz);
102 this.charBuffer = CharBuffer.allocate(outbufSz);
114 private void initDecoderImpl(){
115 this.byteBuffer.clear().flip();
116 this.charBuffer.clear();
118 this.decoder.onMalformedInput (CodingErrorAction.REPORT);
119 this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
120 this.decoder.reset();
122 Arrays.fill(this.errorData, (byte) 0x00);
130 protected void initDecoder(){
138 * <p>デコード結果はここで指定したハンドラに通知される。
140 * <p>nullオブジェクトを指定することも可能だが、
143 * @param decodeHandler デコードハンドラ
145 public void setDecodeHandler(DecodeHandler decodeHandler){
146 this.decodeHandler = decodeHandler;
155 protected ByteBuffer getByteBuffer(){
156 return this.byteBuffer;
164 protected CharBuffer getCharBuffer(){
165 return this.charBuffer;
169 * チャネルからの入力を読み進め入力バッファに詰め込む。
171 * <p>前回デコード処理の読み残しはバッファ前方に詰め直される。
173 * <p>入力バッファに空きがない状態で呼んではいけない。
177 * ※入力バッファに空きがありチャネルがブロックモードの場合、
179 * @throws java.io.IOException 入力エラー
181 protected int fillByteBuffer() throws IOException{
182 this.byteBuffer.compact();
183 assert this.byteBuffer.hasRemaining();
185 int length = this.channel.read(this.byteBuffer);
188 this.byteBuffer.flip();
199 * <p>既に出力バッファが空だった場合、何もしない。
201 * @throws DecodeException ハンドラによるデコードエラー
203 protected void notifyText() throws DecodeException{
204 if(this.charBuffer.position() <= 0){
208 this.charBuffer.flip();
209 this.decodeHandler.charContent(this.charBuffer);
210 this.charBuffer.clear();
216 * デコードハンドラにデコードエラーを通知する。
218 * @param result デコード結果
219 * @throws DecodeException ハンドラによるデコードエラー
220 * @throws IOException 入力エラー
222 protected void notifyError(CoderResult result)
225 int length = chopErrorSequence(result);
226 this.decodeHandler.decodingError(this.errorData, 0, length);
231 * 入力バッファからデコードエラーの原因となったバイト列を読み進める。
233 * <p>{@link #errorData}の先頭にバイト列がコピーされ、
236 * <p>入力バッファはエラーの長さの分だけ読み進められる。
238 * @param result デコードエラー
240 * @throws IOException 入力エラー。
241 * ※このメソッドを継承する場合、必要に応じて先読みをしてもよいし、
242 * その結果生じたIO例外を投げてもよい。
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
257 * @param size 再アサイン量。バイト長。
259 protected void reassignErrorData(int size){
260 int oldLength = this.errorData.length;
261 if(oldLength >= size) return;
264 if(oldLength * 2 > newSize) newSize = oldLength * 2;
266 byte[] newData = Arrays.copyOf(this.errorData, newSize);
267 this.errorData = newData;
273 * バイト入力ストリームを文字列デコードする。
275 * <p>デコード作業の状況に応じてハンドラへの各種通知が行われる。
277 * <p>入力ストリーム末端に到達するとデコード作業は終わり、
280 * @param istream 入力ストリーム
281 * @throws IOException 入力エラー
282 * @throws DecodeException ハンドラによるデコードエラー
284 public void decode(InputStream istream)
287 // このチャネルは必ずブロックモードのはず
288 this.channel = Channels.newChannel(istream);
293 this.channel.close();
303 * @throws IOException 入力エラー
304 * @throws DecodeException ハンドラによるデコードエラー
306 protected void decodeChannel()
311 this.decodeHandler.startDecoding(this.decoder);
314 boolean isEndOfInput;
316 ioLength = fillByteBuffer();
317 isEndOfInput = ioLength < 0;
320 CoderResult decodeResult =
321 this.decoder.decode(this.byteBuffer,
325 if(decodeResult.isError()){
327 decodeResult = modifyErrorLength(decodeResult);
328 notifyError(decodeResult);
333 if(decodeResult.isOverflow()){
338 assert decodeResult.isUnderflow();
347 ioLength = fillByteBuffer();
348 isEndOfInput = ioLength < 0;
353 CoderResult flushResult;
355 flushResult = this.decoder.flush(this.charBuffer);
356 assert ! flushResult.isError();
358 }while( ! flushResult.isUnderflow() );
360 this.decodeHandler.endDecoding();
366 * エラーの長さ(バイト列長)を修正する。
368 * <p>文字コード毎の事情に特化した異常系実装を目的とする。
369 * デフォルト実装では引数をそのまま返す。
372 * さらに長いエラー情報を再構成してもよい。
374 * <p>エラー情報を前方に縮小することで、
375 * エラーとして扱われるはずだったバイト列後部は
376 * 再度デコード処理の対象として扱われる。
378 * @param result 修正元エラー情報。
379 * @return 修正後エラー情報。引数と同じ場合もありうる。
381 * @throws IOException バイト列読み込みエラー
383 protected CoderResult modifyErrorLength(CoderResult result)
385 CoderResult newResult = result;
390 * 不適切な入力バッファサイズ由来の無限ループを検出し例外を投げる。
394 * @throws DecodeException 無限ループが検出された
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();
401 StringBuilder text = new StringBuilder();
402 text.append("too small input buffer (");
403 text.append(bufSz).append("bytes) for ");
406 throw new DecodeException(text.toString());