-/*\r
- * stream decoder\r
- *\r
- * Copyright(c) 2009 olyutorskii\r
- * $Id: StreamDecoder.java 894 2009-11-04 07:26:59Z olyutorskii $\r
- */\r
-\r
-package jp.sourceforge.jindolf.parser;\r
-\r
-import java.io.IOException;\r
-import java.io.InputStream;\r
-import java.nio.ByteBuffer;\r
-import java.nio.CharBuffer;\r
-import java.nio.channels.Channels;\r
-import java.nio.channels.ReadableByteChannel;\r
-import java.nio.charset.CharsetDecoder;\r
-import java.nio.charset.CoderResult;\r
-import java.nio.charset.CodingErrorAction;\r
-import java.util.Arrays;\r
-\r
-/**\r
- * バイトストリームからの文字デコーダ。\r
- * 入力バイトストリームをデコードし、デコード結果およびデコードエラーを\r
- * 文字デコードハンドラ{@link DecodeHandler}に通知する。\r
- * このクラスは、\r
- * デコードエラー詳細を察知できない{@link java.io.InputStreamReader}の\r
- * 代替品として設計された。\r
- * マルチスレッド対応はしていない。\r
- */\r
-public class StreamDecoder{\r
-\r
- /** デフォルト入力バッファサイズ(={@value}bytes)。 */\r
- public static final int BYTEBUF_DEFSZ = 4 * 1024;\r
- /** デフォルト出力バッファサイズ(={@value}chars)。 */\r
- public static final int CHARBUF_DEFSZ = 4 * 1024;\r
-\r
- private final CharsetDecoder decoder;\r
-\r
- private ReadableByteChannel channel;\r
- private final ByteBuffer byteBuffer;\r
- private final CharBuffer charBuffer;\r
-\r
- private boolean isEndOfInput = false;\r
- private boolean isFlushing = false;\r
-\r
- private DecodeHandler decodeHandler;\r
- private byte[] errorData = new byte[4];\r
-\r
- /**\r
- * コンストラクタ。\r
- * @param decoder デコーダ\r
- */\r
- public StreamDecoder(CharsetDecoder decoder){\r
- this(decoder, BYTEBUF_DEFSZ, CHARBUF_DEFSZ);\r
- return;\r
- }\r
-\r
- /**\r
- * コンストラクタ。\r
- * @param decoder デコーダ\r
- * @param inbuf_sz 入力バッファサイズ\r
- * @param outbuf_sz 出力バッファサイズ\r
- * @throws NullPointerException デコーダにnullを渡した。\r
- * @throws IllegalArgumentException バッファサイズが負。\r
- */\r
- public StreamDecoder(CharsetDecoder decoder,\r
- int inbuf_sz,\r
- int outbuf_sz )\r
- throws NullPointerException,\r
- IllegalArgumentException {\r
- super();\r
-\r
- if(decoder == null) throw new NullPointerException();\r
-\r
- if(inbuf_sz <= 0 || outbuf_sz <= 0){\r
- throw new IllegalArgumentException();\r
- }\r
-\r
- this.decoder = decoder;\r
- this.byteBuffer = ByteBuffer.allocate(inbuf_sz);\r
- this.charBuffer = CharBuffer.allocate(outbuf_sz);\r
- this.channel = null;\r
-\r
- initDecoderImpl();\r
-\r
- return;\r
- }\r
-\r
- /**\r
- * デコーダの初期化下請。\r
- */\r
- private void initDecoderImpl(){\r
- this.byteBuffer.clear().flip();\r
- this.charBuffer.clear();\r
-\r
- this.decoder.onMalformedInput (CodingErrorAction.REPORT);\r
- this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);\r
- this.decoder.reset();\r
-\r
- this.isEndOfInput = false;\r
- this.isFlushing = false;\r
-\r
- Arrays.fill(this.errorData, (byte)0x00);\r
-\r
- return;\r
- }\r
-\r
- /**\r
- * デコーダの初期化。\r
- */\r
- protected void initDecoder(){\r
- initDecoderImpl();\r
- return;\r
- }\r
-\r
- /**\r
- * 入力バッファを返す。\r
- * @return 入力バッファ\r
- */\r
- protected ByteBuffer getByteBuffer(){\r
- return this.byteBuffer;\r
- }\r
-\r
- /**\r
- * 出力バッファを返す。\r
- * @return 出力バッファ\r
- */\r
- protected CharBuffer getCharBuffer(){\r
- return this.charBuffer;\r
- }\r
-\r
- /**\r
- * デコードハンドラの設定。\r
- * nullオブジェクトを指定しても構わないが、\r
- * その場合パース時に例外を起こす。\r
- * @param decodeHandler デコードハンドラ\r
- */\r
- public void setDecodeHandler(DecodeHandler decodeHandler){\r
- this.decodeHandler = decodeHandler;\r
- return;\r
- }\r
-\r
- /**\r
- * デコードエラー格納配列の再アサイン。\r
- * 配列の内容は保持される。\r
- * 決して縮小することは無い。\r
- * メモ:java.util.Arrays#copyOf()はJRE1.5にない。\r
- * @param size 再アサイン量。バイト長。\r
- */\r
- protected void reassignErrorData(int size){\r
- int oldLength = this.errorData.length;\r
- if(oldLength >= size) return;\r
- int newSize = size;\r
- if(oldLength * 2 > newSize) newSize = oldLength * 2;\r
- byte[] newData = new byte[newSize];\r
- System.arraycopy(this.errorData, 0, newData, 0, oldLength);\r
- this.errorData = newData;\r
- return;\r
- }\r
-\r
- /**\r
- * デコードハンドラに文字列を渡す。\r
- * @throws DecodeException デコードエラー\r
- */\r
- protected void flushContent() throws DecodeException{\r
- if(this.charBuffer.position() <= 0){\r
- return;\r
- }\r
-\r
- this.charBuffer.flip();\r
- this.decodeHandler.charContent(this.charBuffer);\r
- this.charBuffer.clear();\r
-\r
- return;\r
- }\r
-\r
- /**\r
- * デコードハンドラにデコードエラーを渡す。\r
- * @param result デコード結果\r
- * @throws DecodeException デコードエラー\r
- * @throws IOException 入力エラー\r
- */\r
- protected void putDecodeError(CoderResult result)\r
- throws IOException,\r
- DecodeException{\r
- int length = chopErrorSequence(result);\r
- this.decodeHandler.decodingError(this.errorData, 0, length);\r
- return;\r
- }\r
-\r
- /**\r
- * デコードエラーの原因バイト列を抽出する。\r
- * {@link #errorData}の先頭にバイト列が格納され、バイト長が返される。\r
- * @param result デコード結果\r
- * @return 原因バイト列の長さ\r
- * @throws IOException 入力エラー。\r
- * ※このメソッドを継承する場合、必要に応じて先読みをしてもよいし、\r
- * その結果生じたIO例外を投げてもよい。\r
- */\r
- protected int chopErrorSequence(CoderResult result) throws IOException{\r
- int errorLength = result.length();\r
- reassignErrorData(errorLength);\r
- this.byteBuffer.get(this.errorData, 0, errorLength); // 相対get\r
- return errorLength;\r
- }\r
-\r
- /**\r
- * チャンネルからの入力を読み進める。\r
- * 前回の読み残しはバッファ前方に詰め直される。\r
- * @return 入力バイト数。\r
- * @throws java.io.IOException 入出力エラー\r
- */\r
- protected int readByteBuffer() throws IOException{\r
- this.byteBuffer.compact();\r
-\r
- int length = this.channel.read(this.byteBuffer);\r
- if(length <= 0){\r
- this.isEndOfInput = true;\r
- }\r
-\r
- this.byteBuffer.flip();\r
-\r
- return length;\r
- }\r
-\r
- /**\r
- * バイトストリームのデコードを開始する。\r
- * @param istream 入力ストリーム\r
- * @throws IOException 入出力エラー\r
- * @throws DecodeException デコードエラー\r
- */\r
- public void decode(InputStream istream)\r
- throws IOException,\r
- DecodeException {\r
- this.channel = Channels.newChannel(istream);\r
-\r
- try{\r
- decodeChannel();\r
- }finally{\r
- this.channel.close();\r
- this.channel = null;\r
- istream.close();\r
- }\r
-\r
- return;\r
- }\r
-\r
- /**\r
- * 内部チャネルのデコードを開始する。\r
- * @throws IOException 入出力エラー\r
- * @throws DecodeException デコードエラー\r
- */\r
- protected void decodeChannel()\r
- throws IOException,\r
- DecodeException {\r
- initDecoder();\r
-\r
- this.decodeHandler.startDecoding(this.decoder);\r
-\r
- for(;;){\r
- CoderResult result;\r
- if(this.isFlushing){\r
- result = this.decoder.flush(this.charBuffer);\r
- }else{\r
- result = this.decoder.decode(this.byteBuffer,\r
- this.charBuffer,\r
- this.isEndOfInput);\r
- }\r
-\r
- if(result.isError()){\r
- flushContent();\r
- putDecodeError(result);\r
- }else if(result.isOverflow()){ // 出力バッファが一杯\r
- flushContent();\r
- }else if(result.isUnderflow()){ // 入力バッファが空\r
- if( ! this.isEndOfInput ){\r
- readByteBuffer();\r
- continue;\r
- }\r
-\r
- if( ! this.isFlushing ){\r
- this.isFlushing = true;\r
- continue;\r
- }\r
-\r
- flushContent();\r
- break;\r
- }else{\r
- assert false;\r
- }\r
- }\r
-\r
- this.decodeHandler.endDecoding();\r
-\r
- return;\r
- }\r
-\r
-}\r
+/*
+ * stream decoder
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sourceforge.jindolf.parser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+import java.util.Arrays;
+
+/**
+ * バイトストリームからの文字デコーダ。
+ * 入力バイトストリームをデコードし、デコード結果およびデコードエラーを
+ * 文字デコードハンドラ{@link DecodeHandler}に通知する。
+ * このクラスは、
+ * デコードエラー詳細を察知できない{@link java.io.InputStreamReader}の
+ * 代替品として設計された。
+ * マルチスレッド対応はしていない。
+ */
+public class StreamDecoder{
+
+ /** デフォルト入力バッファサイズ(={@value}bytes)。 */
+ public static final int BYTEBUF_DEFSZ = 4 * 1024;
+ /** デフォルト出力バッファサイズ(={@value}chars)。 */
+ public static final int CHARBUF_DEFSZ = 4 * 1024;
+
+
+ private final CharsetDecoder decoder;
+
+ private ReadableByteChannel channel;
+ private final ByteBuffer byteBuffer;
+ private final CharBuffer charBuffer;
+
+ private boolean isEndOfInput;
+ private boolean isFlushing;
+
+ private DecodeHandler decodeHandler;
+
+ // エンコーディングによっては長さに見直しが必要
+ private byte[] errorData = new byte[4];
+
+
+ /**
+ * コンストラクタ。
+ * @param decoder デコーダ
+ */
+ public StreamDecoder(CharsetDecoder decoder){
+ this(decoder, BYTEBUF_DEFSZ, CHARBUF_DEFSZ);
+ return;
+ }
+
+ /**
+ * コンストラクタ。
+ * @param decoder デコーダ
+ * @param inbufSz 入力バッファサイズ
+ * @param outbufSz 出力バッファサイズ
+ * @throws NullPointerException デコーダにnullを渡した。
+ * @throws IllegalArgumentException バッファサイズが負。
+ */
+ public StreamDecoder(CharsetDecoder decoder,
+ int inbufSz,
+ int outbufSz )
+ throws NullPointerException,
+ IllegalArgumentException {
+ super();
+
+ if(decoder == null) throw new NullPointerException();
+
+ if(inbufSz <= 0 || outbufSz <= 0){
+ throw new IllegalArgumentException();
+ }
+
+ this.decoder = decoder;
+ this.byteBuffer = ByteBuffer.allocate(inbufSz);
+ this.charBuffer = CharBuffer.allocate(outbufSz);
+ this.channel = null;
+
+ initDecoderImpl();
+
+ return;
+ }
+
+ /**
+ * デコーダの初期化下請。
+ */
+ private void initDecoderImpl(){
+ this.byteBuffer.clear().flip();
+ this.charBuffer.clear();
+
+ this.decoder.onMalformedInput (CodingErrorAction.REPORT);
+ this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
+ this.decoder.reset();
+
+ this.isEndOfInput = false;
+ this.isFlushing = false;
+
+ Arrays.fill(this.errorData, (byte) 0x00);
+
+ return;
+ }
+
+ /**
+ * デコーダの初期化。
+ */
+ protected void initDecoder(){
+ initDecoderImpl();
+ return;
+ }
+
+ /**
+ * 入力バッファを返す。
+ * @return 入力バッファ
+ */
+ protected ByteBuffer getByteBuffer(){
+ return this.byteBuffer;
+ }
+
+ /**
+ * 出力バッファを返す。
+ * @return 出力バッファ
+ */
+ protected CharBuffer getCharBuffer(){
+ return this.charBuffer;
+ }
+
+ /**
+ * デコードハンドラの設定。
+ * nullオブジェクトを指定しても構わないが、
+ * その場合パース時に例外を起こす。
+ * @param decodeHandler デコードハンドラ
+ */
+ public void setDecodeHandler(DecodeHandler decodeHandler){
+ this.decodeHandler = decodeHandler;
+ return;
+ }
+
+ /**
+ * デコードエラー格納配列の再アサイン。
+ * 配列の内容は保持される。
+ * 決して縮小することは無い。
+ * メモ:java.util.Arrays#copyOf()はJRE1.5にない。
+ * @param size 再アサイン量。バイト長。
+ */
+ protected void reassignErrorData(int size){
+ int oldLength = this.errorData.length;
+ if(oldLength >= size) return;
+ int newSize = size;
+ if(oldLength * 2 > newSize) newSize = oldLength * 2;
+ byte[] newData = new byte[newSize];
+ System.arraycopy(this.errorData, 0, newData, 0, oldLength);
+ this.errorData = newData;
+ return;
+ }
+
+ /**
+ * デコードハンドラに文字列を渡す。
+ * @throws DecodeException デコードエラー
+ */
+ protected void flushContent() throws DecodeException{
+ if(this.charBuffer.position() <= 0){
+ return;
+ }
+
+ this.charBuffer.flip();
+ this.decodeHandler.charContent(this.charBuffer);
+ this.charBuffer.clear();
+
+ return;
+ }
+
+ /**
+ * デコードハンドラにデコードエラーを渡す。
+ * @param result デコード結果
+ * @throws DecodeException デコードエラー
+ * @throws IOException 入力エラー
+ */
+ protected void putDecodeError(CoderResult result)
+ throws IOException,
+ DecodeException{
+ int length = chopErrorSequence(result);
+ this.decodeHandler.decodingError(this.errorData, 0, length);
+ return;
+ }
+
+ /**
+ * デコードエラーの原因バイト列を抽出する。
+ * {@link #errorData}の先頭にバイト列が格納され、バイト長が返される。
+ * @param result デコード結果
+ * @return 原因バイト列の長さ
+ * @throws IOException 入力エラー。
+ * ※このメソッドを継承する場合、必要に応じて先読みをしてもよいし、
+ * その結果生じたIO例外を投げてもよい。
+ */
+ protected int chopErrorSequence(CoderResult result) throws IOException{
+ int errorLength = result.length();
+ reassignErrorData(errorLength);
+ this.byteBuffer.get(this.errorData, 0, errorLength); // 相対get
+ return errorLength;
+ }
+
+ /**
+ * チャンネルからの入力を読み進める。
+ * 前回の読み残しはバッファ前方に詰め直される。
+ * @return 入力バイト数。
+ * @throws java.io.IOException 入出力エラー
+ */
+ protected int readByteBuffer() throws IOException{
+ this.byteBuffer.compact();
+
+ int length = this.channel.read(this.byteBuffer);
+ if(length <= 0){
+ this.isEndOfInput = true;
+ }
+
+ this.byteBuffer.flip();
+
+ return length;
+ }
+
+ /**
+ * バイトストリームのデコードを開始する。
+ * @param istream 入力ストリーム
+ * @throws IOException 入出力エラー
+ * @throws DecodeException デコードエラー
+ */
+ public void decode(InputStream istream)
+ throws IOException,
+ DecodeException {
+ this.channel = Channels.newChannel(istream);
+
+ try{
+ decodeChannel();
+ }finally{
+ this.channel.close();
+ this.channel = null;
+ istream.close();
+ }
+
+ return;
+ }
+
+ /**
+ * 内部チャネルのデコードを開始する。
+ * @throws IOException 入出力エラー
+ * @throws DecodeException デコードエラー
+ */
+ protected void decodeChannel()
+ throws IOException,
+ DecodeException {
+ initDecoder();
+
+ this.decodeHandler.startDecoding(this.decoder);
+
+ for(;;){
+ CoderResult result;
+ if(this.isFlushing){
+ result = this.decoder.flush(this.charBuffer);
+ }else{
+ result = this.decoder.decode(this.byteBuffer,
+ this.charBuffer,
+ this.isEndOfInput);
+ }
+
+ if(result.isError()){
+ flushContent();
+ putDecodeError(result);
+ }else if(result.isOverflow()){ // 出力バッファが一杯
+ flushContent();
+ }else if(result.isUnderflow()){ // 入力バッファが空
+ if( ! this.isEndOfInput ){
+ readByteBuffer();
+ continue;
+ }
+
+ if( ! this.isFlushing ){
+ this.isFlushing = true;
+ continue;
+ }
+
+ flushContent();
+ break;
+ }else{
+ assert false;
+ }
+ }
+
+ this.decodeHandler.endDecoding();
+
+ return;
+ }
+
+}