4 * Copyright(c) 2009 olyutorskii
\r
5 * $Id: StreamDecoder.java 894 2009-11-04 07:26:59Z olyutorskii $
\r
8 package jp.sourceforge.jindolf.parser;
\r
10 import java.io.IOException;
\r
11 import java.io.InputStream;
\r
12 import java.nio.ByteBuffer;
\r
13 import java.nio.CharBuffer;
\r
14 import java.nio.channels.Channels;
\r
15 import java.nio.channels.ReadableByteChannel;
\r
16 import java.nio.charset.CharsetDecoder;
\r
17 import java.nio.charset.CoderResult;
\r
18 import java.nio.charset.CodingErrorAction;
\r
19 import java.util.Arrays;
\r
22 * バイトストリームからの文字デコーダ。
\r
23 * 入力バイトストリームをデコードし、デコード結果およびデコードエラーを
\r
24 * 文字デコードハンドラ{@link DecodeHandler}に通知する。
\r
26 * デコードエラー詳細を察知できない{@link java.io.InputStreamReader}の
\r
30 public class StreamDecoder{
\r
32 /** デフォルト入力バッファサイズ(={@value}bytes)。 */
\r
33 public static final int BYTEBUF_DEFSZ = 4 * 1024;
\r
34 /** デフォルト出力バッファサイズ(={@value}chars)。 */
\r
35 public static final int CHARBUF_DEFSZ = 4 * 1024;
\r
37 private final CharsetDecoder decoder;
\r
39 private ReadableByteChannel channel;
\r
40 private final ByteBuffer byteBuffer;
\r
41 private final CharBuffer charBuffer;
\r
43 private boolean isEndOfInput = false;
\r
44 private boolean isFlushing = false;
\r
46 private DecodeHandler decodeHandler;
\r
47 private byte[] errorData = new byte[4];
\r
51 * @param decoder デコーダ
\r
53 public StreamDecoder(CharsetDecoder decoder){
\r
54 this(decoder, BYTEBUF_DEFSZ, CHARBUF_DEFSZ);
\r
60 * @param decoder デコーダ
\r
61 * @param inbuf_sz 入力バッファサイズ
\r
62 * @param outbuf_sz 出力バッファサイズ
\r
63 * @throws NullPointerException デコーダにnullを渡した。
\r
64 * @throws IllegalArgumentException バッファサイズが負。
\r
66 public StreamDecoder(CharsetDecoder decoder,
\r
69 throws NullPointerException,
\r
70 IllegalArgumentException {
\r
73 if(decoder == null) throw new NullPointerException();
\r
75 if(inbuf_sz <= 0 || outbuf_sz <= 0){
\r
76 throw new IllegalArgumentException();
\r
79 this.decoder = decoder;
\r
80 this.byteBuffer = ByteBuffer.allocate(inbuf_sz);
\r
81 this.charBuffer = CharBuffer.allocate(outbuf_sz);
\r
82 this.channel = null;
\r
92 private void initDecoderImpl(){
\r
93 this.byteBuffer.clear().flip();
\r
94 this.charBuffer.clear();
\r
96 this.decoder.onMalformedInput (CodingErrorAction.REPORT);
\r
97 this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
\r
98 this.decoder.reset();
\r
100 this.isEndOfInput = false;
\r
101 this.isFlushing = false;
\r
103 Arrays.fill(this.errorData, (byte)0x00);
\r
111 protected void initDecoder(){
\r
120 protected ByteBuffer getByteBuffer(){
\r
121 return this.byteBuffer;
\r
128 protected CharBuffer getCharBuffer(){
\r
129 return this.charBuffer;
\r
134 * nullオブジェクトを指定しても構わないが、
\r
136 * @param decodeHandler デコードハンドラ
\r
138 public void setDecodeHandler(DecodeHandler decodeHandler){
\r
139 this.decodeHandler = decodeHandler;
\r
144 * デコードエラー格納配列の再アサイン。
\r
147 * メモ:java.util.Arrays#copyOf()はJRE1.5にない。
\r
148 * @param size 再アサイン量。バイト長。
\r
150 protected void reassignErrorData(int size){
\r
151 int oldLength = this.errorData.length;
\r
152 if(oldLength >= size) return;
\r
153 int newSize = size;
\r
154 if(oldLength * 2 > newSize) newSize = oldLength * 2;
\r
155 byte[] newData = new byte[newSize];
\r
156 System.arraycopy(this.errorData, 0, newData, 0, oldLength);
\r
157 this.errorData = newData;
\r
163 * @throws DecodeException デコードエラー
\r
165 protected void flushContent() throws DecodeException{
\r
166 if(this.charBuffer.position() <= 0){
\r
170 this.charBuffer.flip();
\r
171 this.decodeHandler.charContent(this.charBuffer);
\r
172 this.charBuffer.clear();
\r
178 * デコードハンドラにデコードエラーを渡す。
\r
179 * @param result デコード結果
\r
180 * @throws DecodeException デコードエラー
\r
181 * @throws IOException 入力エラー
\r
183 protected void putDecodeError(CoderResult result)
\r
184 throws IOException,
\r
186 int length = chopErrorSequence(result);
\r
187 this.decodeHandler.decodingError(this.errorData, 0, length);
\r
192 * デコードエラーの原因バイト列を抽出する。
\r
193 * {@link #errorData}の先頭にバイト列が格納され、バイト長が返される。
\r
194 * @param result デコード結果
\r
195 * @return 原因バイト列の長さ
\r
196 * @throws IOException 入力エラー。
\r
197 * ※このメソッドを継承する場合、必要に応じて先読みをしてもよいし、
\r
198 * その結果生じたIO例外を投げてもよい。
\r
200 protected int chopErrorSequence(CoderResult result) throws IOException{
\r
201 int errorLength = result.length();
\r
202 reassignErrorData(errorLength);
\r
203 this.byteBuffer.get(this.errorData, 0, errorLength); // 相対get
\r
204 return errorLength;
\r
208 * チャンネルからの入力を読み進める。
\r
209 * 前回の読み残しはバッファ前方に詰め直される。
\r
211 * @throws java.io.IOException 入出力エラー
\r
213 protected int readByteBuffer() throws IOException{
\r
214 this.byteBuffer.compact();
\r
216 int length = this.channel.read(this.byteBuffer);
\r
218 this.isEndOfInput = true;
\r
221 this.byteBuffer.flip();
\r
227 * バイトストリームのデコードを開始する。
\r
228 * @param istream 入力ストリーム
\r
229 * @throws IOException 入出力エラー
\r
230 * @throws DecodeException デコードエラー
\r
232 public void decode(InputStream istream)
\r
233 throws IOException,
\r
235 this.channel = Channels.newChannel(istream);
\r
240 this.channel.close();
\r
241 this.channel = null;
\r
249 * 内部チャネルのデコードを開始する。
\r
250 * @throws IOException 入出力エラー
\r
251 * @throws DecodeException デコードエラー
\r
253 protected void decodeChannel()
\r
254 throws IOException,
\r
258 this.decodeHandler.startDecoding(this.decoder);
\r
261 CoderResult result;
\r
262 if(this.isFlushing){
\r
263 result = this.decoder.flush(this.charBuffer);
\r
265 result = this.decoder.decode(this.byteBuffer,
\r
267 this.isEndOfInput);
\r
270 if(result.isError()){
\r
272 putDecodeError(result);
\r
273 }else if(result.isOverflow()){ // 出力バッファが一杯
\r
275 }else if(result.isUnderflow()){ // 入力バッファが空
\r
276 if( ! this.isEndOfInput ){
\r
281 if( ! this.isFlushing ){
\r
282 this.isFlushing = true;
\r
293 this.decodeHandler.endDecoding();
\r