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;
23 * 入力バイトストリームをデコードし、デコード結果およびデコードエラーを
24 * 文字デコードハンドラ{@link DecodeHandler}に通知する。
26 * デコードエラー詳細を察知できない{@link java.io.InputStreamReader}の
30 public class StreamDecoder{
32 /** デフォルト入力バッファサイズ(={@value}bytes)。 */
33 public static final int BYTEBUF_DEFSZ = 4 * 1024;
34 /** デフォルト出力バッファサイズ(={@value}chars)。 */
35 public static final int CHARBUF_DEFSZ = 4 * 1024;
38 private final CharsetDecoder decoder;
40 private ReadableByteChannel channel;
41 private final ByteBuffer byteBuffer;
42 private final CharBuffer charBuffer;
44 private boolean isEndOfInput;
45 private boolean isFlushing;
47 private DecodeHandler decodeHandler;
49 // エンコーディングによっては長さに見直しが必要
50 private byte[] errorData = new byte[4];
57 public StreamDecoder(CharsetDecoder decoder){
58 this(decoder, BYTEBUF_DEFSZ, CHARBUF_DEFSZ);
65 * @param inbufSz 入力バッファサイズ
66 * @param outbufSz 出力バッファサイズ
67 * @throws NullPointerException デコーダにnullを渡した。
68 * @throws IllegalArgumentException バッファサイズが負。
70 public StreamDecoder(CharsetDecoder decoder,
73 throws NullPointerException,
74 IllegalArgumentException {
77 if(decoder == null) throw new NullPointerException();
79 if(inbufSz <= 0 || outbufSz <= 0){
80 throw new IllegalArgumentException();
83 this.decoder = decoder;
84 this.byteBuffer = ByteBuffer.allocate(inbufSz);
85 this.charBuffer = CharBuffer.allocate(outbufSz);
96 private void initDecoderImpl(){
97 this.byteBuffer.clear().flip();
98 this.charBuffer.clear();
100 this.decoder.onMalformedInput (CodingErrorAction.REPORT);
101 this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
102 this.decoder.reset();
104 this.isEndOfInput = false;
105 this.isFlushing = false;
107 Arrays.fill(this.errorData, (byte) 0x00);
115 protected void initDecoder(){
124 protected ByteBuffer getByteBuffer(){
125 return this.byteBuffer;
132 protected CharBuffer getCharBuffer(){
133 return this.charBuffer;
138 * nullオブジェクトを指定しても構わないが、
140 * @param decodeHandler デコードハンドラ
142 public void setDecodeHandler(DecodeHandler decodeHandler){
143 this.decodeHandler = decodeHandler;
151 * メモ:java.util.Arrays#copyOf()はJRE1.5にない。
152 * @param size 再アサイン量。バイト長。
154 protected void reassignErrorData(int size){
155 int oldLength = this.errorData.length;
156 if(oldLength >= size) return;
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;
167 * @throws DecodeException デコードエラー
169 protected void flushContent() throws DecodeException{
170 if(this.charBuffer.position() <= 0){
174 this.charBuffer.flip();
175 this.decodeHandler.charContent(this.charBuffer);
176 this.charBuffer.clear();
182 * デコードハンドラにデコードエラーを渡す。
183 * @param result デコード結果
184 * @throws DecodeException デコードエラー
185 * @throws IOException 入力エラー
187 protected void putDecodeError(CoderResult result)
190 int length = chopErrorSequence(result);
191 this.decodeHandler.decodingError(this.errorData, 0, length);
196 * デコードエラーの原因バイト列を抽出する。
197 * {@link #errorData}の先頭にバイト列が格納され、バイト長が返される。
198 * @param result デコード結果
200 * @throws IOException 入力エラー。
201 * ※このメソッドを継承する場合、必要に応じて先読みをしてもよいし、
202 * その結果生じたIO例外を投げてもよい。
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
213 * 前回の読み残しはバッファ前方に詰め直される。
215 * @throws java.io.IOException 入出力エラー
217 protected int readByteBuffer() throws IOException{
218 this.byteBuffer.compact();
220 int length = this.channel.read(this.byteBuffer);
222 this.isEndOfInput = true;
225 this.byteBuffer.flip();
231 * バイトストリームのデコードを開始する。
232 * @param istream 入力ストリーム
233 * @throws IOException 入出力エラー
234 * @throws DecodeException デコードエラー
236 public void decode(InputStream istream)
239 this.channel = Channels.newChannel(istream);
244 this.channel.close();
254 * @throws IOException 入出力エラー
255 * @throws DecodeException デコードエラー
257 protected void decodeChannel()
262 this.decodeHandler.startDecoding(this.decoder);
267 result = this.decoder.flush(this.charBuffer);
269 result = this.decoder.decode(this.byteBuffer,
274 if(result.isError()){
276 putDecodeError(result);
277 }else if(result.isOverflow()){ // 出力バッファが一杯
279 }else if(result.isUnderflow()){ // 入力バッファが空
280 if( ! this.isEndOfInput ){
285 if( ! this.isFlushing ){
286 this.isFlushing = true;
297 this.decodeHandler.endDecoding();