OSDN Git Service

from subversion repository
[jindolf/JinParser.git] / src / main / java / jp / sourceforge / jindolf / parser / StreamDecoder.java
1 /*\r
2  * stream decoder\r
3  *\r
4  * Copyright(c) 2009 olyutorskii\r
5  * $Id: StreamDecoder.java 894 2009-11-04 07:26:59Z olyutorskii $\r
6  */\r
7 \r
8 package jp.sourceforge.jindolf.parser;\r
9 \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
20 \r
21 /**\r
22  * バイトストリームからの文字デコーダ。\r
23  * 入力バイトストリームをデコードし、デコード結果およびデコードエラーを\r
24  * 文字デコードハンドラ{@link DecodeHandler}に通知する。\r
25  * このクラスは、\r
26  * デコードエラー詳細を察知できない{@link java.io.InputStreamReader}の\r
27  * 代替品として設計された。\r
28  * マルチスレッド対応はしていない。\r
29  */\r
30 public class StreamDecoder{\r
31 \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
36 \r
37     private final CharsetDecoder decoder;\r
38 \r
39     private ReadableByteChannel channel;\r
40     private final ByteBuffer byteBuffer;\r
41     private final CharBuffer charBuffer;\r
42 \r
43     private boolean isEndOfInput = false;\r
44     private boolean isFlushing = false;\r
45 \r
46     private DecodeHandler decodeHandler;\r
47     private byte[] errorData = new byte[4];\r
48 \r
49     /**\r
50      * コンストラクタ。\r
51      * @param decoder デコーダ\r
52      */\r
53     public StreamDecoder(CharsetDecoder decoder){\r
54         this(decoder, BYTEBUF_DEFSZ, CHARBUF_DEFSZ);\r
55         return;\r
56     }\r
57 \r
58     /**\r
59      * コンストラクタ。\r
60      * @param decoder デコーダ\r
61      * @param inbuf_sz 入力バッファサイズ\r
62      * @param outbuf_sz 出力バッファサイズ\r
63      * @throws NullPointerException デコーダにnullを渡した。\r
64      * @throws IllegalArgumentException バッファサイズが負。\r
65      */\r
66     public StreamDecoder(CharsetDecoder decoder,\r
67                            int inbuf_sz,\r
68                            int outbuf_sz )\r
69             throws NullPointerException,\r
70                    IllegalArgumentException {\r
71         super();\r
72 \r
73         if(decoder == null) throw new NullPointerException();\r
74 \r
75         if(inbuf_sz <= 0 || outbuf_sz <= 0){\r
76             throw new IllegalArgumentException();\r
77         }\r
78 \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
83 \r
84         initDecoderImpl();\r
85 \r
86         return;\r
87     }\r
88 \r
89     /**\r
90      * デコーダの初期化下請。\r
91      */\r
92     private void initDecoderImpl(){\r
93         this.byteBuffer.clear().flip();\r
94         this.charBuffer.clear();\r
95 \r
96         this.decoder.onMalformedInput     (CodingErrorAction.REPORT);\r
97         this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);\r
98         this.decoder.reset();\r
99 \r
100         this.isEndOfInput = false;\r
101         this.isFlushing = false;\r
102 \r
103         Arrays.fill(this.errorData, (byte)0x00);\r
104 \r
105         return;\r
106     }\r
107 \r
108     /**\r
109      * デコーダの初期化。\r
110      */\r
111     protected void initDecoder(){\r
112         initDecoderImpl();\r
113         return;\r
114     }\r
115 \r
116     /**\r
117      * 入力バッファを返す。\r
118      * @return 入力バッファ\r
119      */\r
120     protected ByteBuffer getByteBuffer(){\r
121         return this.byteBuffer;\r
122     }\r
123 \r
124     /**\r
125      * 出力バッファを返す。\r
126      * @return 出力バッファ\r
127      */\r
128     protected CharBuffer getCharBuffer(){\r
129         return this.charBuffer;\r
130     }\r
131 \r
132     /**\r
133      * デコードハンドラの設定。\r
134      * nullオブジェクトを指定しても構わないが、\r
135      * その場合パース時に例外を起こす。\r
136      * @param decodeHandler デコードハンドラ\r
137      */\r
138     public void setDecodeHandler(DecodeHandler decodeHandler){\r
139         this.decodeHandler = decodeHandler;\r
140         return;\r
141     }\r
142 \r
143     /**\r
144      * デコードエラー格納配列の再アサイン。\r
145      * 配列の内容は保持される。\r
146      * 決して縮小することは無い。\r
147      * メモ:java.util.Arrays#copyOf()はJRE1.5にない。\r
148      * @param size 再アサイン量。バイト長。\r
149      */\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
158         return;\r
159     }\r
160 \r
161     /**\r
162      * デコードハンドラに文字列を渡す。\r
163      * @throws DecodeException デコードエラー\r
164      */\r
165     protected void flushContent() throws DecodeException{\r
166         if(this.charBuffer.position() <= 0){\r
167             return;\r
168         }\r
169 \r
170         this.charBuffer.flip();\r
171         this.decodeHandler.charContent(this.charBuffer);\r
172         this.charBuffer.clear();\r
173 \r
174         return;\r
175     }\r
176 \r
177     /**\r
178      * デコードハンドラにデコードエラーを渡す。\r
179      * @param result デコード結果\r
180      * @throws DecodeException デコードエラー\r
181      * @throws IOException 入力エラー\r
182      */\r
183     protected void putDecodeError(CoderResult result)\r
184             throws IOException,\r
185                    DecodeException{\r
186         int length = chopErrorSequence(result);\r
187         this.decodeHandler.decodingError(this.errorData, 0, length);\r
188         return;\r
189     }\r
190 \r
191     /**\r
192      * デコードエラーの原因バイト列を抽出する。\r
193      * {@link #errorData}の先頭にバイト列が格納され、バイト長が返される。\r
194      * @param result デコード結果\r
195      * @return 原因バイト列の長さ\r
196      * @throws IOException 入力エラー。\r
197      * ※このメソッドを継承する場合、必要に応じて先読みをしてもよいし、\r
198      * その結果生じたIO例外を投げてもよい。\r
199      */\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
205     }\r
206 \r
207     /**\r
208      * チャンネルからの入力を読み進める。\r
209      * 前回の読み残しはバッファ前方に詰め直される。\r
210      * @return 入力バイト数。\r
211      * @throws java.io.IOException 入出力エラー\r
212      */\r
213     protected int readByteBuffer() throws IOException{\r
214         this.byteBuffer.compact();\r
215 \r
216         int length = this.channel.read(this.byteBuffer);\r
217         if(length <= 0){\r
218             this.isEndOfInput = true;\r
219         }\r
220 \r
221         this.byteBuffer.flip();\r
222 \r
223         return length;\r
224     }\r
225 \r
226     /**\r
227      * バイトストリームのデコードを開始する。\r
228      * @param istream 入力ストリーム\r
229      * @throws IOException 入出力エラー\r
230      * @throws DecodeException デコードエラー\r
231      */\r
232     public void decode(InputStream istream)\r
233             throws IOException,\r
234                    DecodeException {\r
235         this.channel = Channels.newChannel(istream);\r
236 \r
237         try{\r
238             decodeChannel();\r
239         }finally{\r
240             this.channel.close();\r
241             this.channel = null;\r
242             istream.close();\r
243         }\r
244 \r
245         return;\r
246     }\r
247 \r
248     /**\r
249      * 内部チャネルのデコードを開始する。\r
250      * @throws IOException 入出力エラー\r
251      * @throws DecodeException デコードエラー\r
252      */\r
253     protected void decodeChannel()\r
254             throws IOException,\r
255                    DecodeException {\r
256         initDecoder();\r
257 \r
258         this.decodeHandler.startDecoding(this.decoder);\r
259 \r
260         for(;;){\r
261             CoderResult result;\r
262             if(this.isFlushing){\r
263                 result = this.decoder.flush(this.charBuffer);\r
264             }else{\r
265                 result = this.decoder.decode(this.byteBuffer,\r
266                                              this.charBuffer,\r
267                                              this.isEndOfInput);\r
268             }\r
269 \r
270             if(result.isError()){\r
271                 flushContent();\r
272                 putDecodeError(result);\r
273             }else if(result.isOverflow()){      // 出力バッファが一杯\r
274                 flushContent();\r
275             }else if(result.isUnderflow()){     // 入力バッファが空\r
276                 if( ! this.isEndOfInput ){\r
277                     readByteBuffer();\r
278                     continue;\r
279                 }\r
280 \r
281                 if( ! this.isFlushing ){\r
282                     this.isFlushing = true;\r
283                     continue;\r
284                 }\r
285 \r
286                 flushContent();\r
287                 break;\r
288             }else{\r
289                 assert false;\r
290             }\r
291         }\r
292 \r
293         this.decodeHandler.endDecoding();\r
294 \r
295         return;\r
296     }\r
297 \r
298 }\r