OSDN Git Service

Merge commit '2234b50cfbe7c86237086a3bf4e62397814a390e'
[jindolf/JinParser.git] / src / main / java / jp / sourceforge / jindolf / parser / StreamDecoder.java
index 796478d..5482846 100644 (file)
-/*\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;
+    }
+
+}