OSDN Git Service

0faa85e137355e99952df8208a97c19d8ae90de7
[mikutoga/TogaGem.git] / src / main / java / jp / sourceforge / mikutoga / parser / CommonParser.java
1 /*
2  * common MMD parser
3  *
4  * License : The MIT License
5  * Copyright(c) 2010 MikuToga Partners
6  */
7
8 package jp.sourceforge.mikutoga.parser;
9
10 import java.io.IOException;
11 import java.nio.ByteBuffer;
12 import java.nio.CharBuffer;
13 import java.nio.charset.Charset;
14 import java.nio.charset.CharsetDecoder;
15 import java.nio.charset.CoderResult;
16 import java.nio.charset.CodingErrorAction;
17
18 /**
19  * 各種パーサの共通実装。
20  */
21 public class CommonParser {
22
23     /** 日本語デコード作業用入力バッファ長。バイト単位。 */
24     public static final int TEXTBUF_SZ = 512;
25
26     /**
27      * MMD各種ファイルで用いられる日本語エンコーディング。(windows-31j)
28      * ほぼShift_JISのスーパーセットと思ってよい。
29      * デコード結果はUCS-2集合に収まるはず。
30      */
31     protected static final Charset CS_WIN31J = Charset.forName("windows-31j");
32
33     private static final byte TERMINATOR = (byte) '\0';  // 0x00
34     private static final char UCSYEN = '\u00a5';
35     private static final char SJISYEN = (char) 0x005c;  // '\u005c\u005c';
36
37     private final MmdSource source;
38     private final CharsetDecoder decoder;
39     private final byte[] textArray;
40     private final ByteBuffer textBuffer;  // textArrayの別ビュー
41     private final CharBuffer charBuffer;
42
43     /**
44      * コンストラクタ。
45      * @param source 入力ソース
46      */
47     public CommonParser(MmdSource source){
48         super();
49
50         this.source = source;
51         this.decoder = CS_WIN31J.newDecoder();
52         this.textArray = new byte[TEXTBUF_SZ];
53         this.textBuffer = ByteBuffer.wrap(this.textArray);
54         int maxChars =
55                 (int)(TEXTBUF_SZ * (this.decoder.maxCharsPerByte())) + 1;
56         this.charBuffer = CharBuffer.allocate(maxChars);
57
58         this.decoder.onMalformedInput(CodingErrorAction.REPORT);
59         this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
60         this.textBuffer.clear();
61         this.charBuffer.clear();
62
63         return;
64     }
65
66     /**
67      * 入力ソースにまだデータが残っているか判定する。
68      * @return まだ読み込んでいないデータが残っていればtrue
69      * @throws IOException IOエラー
70      * @see MmdSource#hasMore()
71      */
72     protected boolean hasMore() throws IOException{
73         boolean result = this.source.hasMore();
74         return result;
75     }
76
77     /**
78      * 入力ソースを読み飛ばす。
79      * @param skipLength 読み飛ばすバイト数。
80      * @throws IOException IOエラー
81      * @throws MmdEofException 読み飛ばす途中でストリーム終端に達した。
82      * @see MmdSource#skip(long)
83      */
84     protected void skip(long skipLength)
85             throws IOException, MmdEofException {
86         long result = this.source.skip(skipLength);
87         if(result != skipLength){
88             throw new MmdEofException(this.source.getPosition());
89         }
90
91         return;
92     }
93
94     /**
95      * 入力ソースを読み飛ばす。
96      * @param skipLength 読み飛ばすバイト数。
97      * @throws IOException IOエラー
98      * @throws MmdEofException 読み飛ばす途中でストリーム終端に達した。
99      * @see MmdSource#skip(long)
100      */
101     protected void skip(int skipLength)
102             throws IOException, MmdEofException {
103         skip((long) skipLength);
104     }
105
106     /**
107      * byte値を読み込む。
108      * @return 読み込んだbyte値
109      * @throws IOException IOエラー
110      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
111      * @see MmdSource#parseByte()
112      */
113     protected byte parseByte()
114             throws IOException, MmdEofException{
115         return this.source.parseByte();
116     }
117
118     /**
119      * 符号無し値としてbyte値を読み込み、int型に変換して返す。
120      * 符号は拡張されない。(0xffは0x000000ffとなる)
121      * @return 読み込まれた値のint値
122      * @throws IOException IOエラー
123      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
124      * @see MmdSource#parseUByteAsInteger()
125      */
126     protected int parseUByteAsInteger()
127             throws IOException, MmdEofException{
128         return this.source.parseUByteAsInteger();
129     }
130
131     /**
132      * byte値を読み込み、boolean型に変換して返す。
133      * 0x00は偽、それ以外は真と解釈される。
134      * @return 読み込まれた値のboolean値
135      * @throws IOException IOエラー
136      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
137      * @see MmdSource#parseBoolean()
138      */
139     protected boolean parseBoolean()
140             throws IOException, MmdEofException{
141         return this.source.parseBoolean();
142     }
143
144     /**
145      * short値を読み込む。
146      * short値はリトルエンディアンで格納されていると仮定される。
147      * @return 読み込んだshort値
148      * @throws IOException IOエラー
149      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
150      * @see MmdSource#parseShort()
151      */
152     protected short parseShort()
153             throws IOException, MmdEofException{
154         return this.source.parseShort();
155     }
156
157     /**
158      * 符号無し値としてshort値を読み込み、int型に変換して返す。
159      * 符号は拡張されない。(0xffffは0x0000ffffとなる)
160      * short値はリトルエンディアンで格納されていると仮定される。
161      * @return 読み込まれた値のint値
162      * @throws IOException IOエラー
163      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
164      * @see MmdSource#parseUShortAsInteger()
165      */
166     protected int parseUShortAsInteger()
167             throws IOException, MmdEofException{
168         return this.source.parseUShortAsInteger();
169     }
170
171     /**
172      * int値を読み込む。
173      * int値はリトルエンディアンで格納されていると仮定される。
174      * @return 読み込んだint値
175      * @throws IOException IOエラー
176      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
177      * @see MmdSource#parseInteger()
178      */
179     protected int parseInteger()
180             throws IOException, MmdEofException{
181         return this.source.parseInteger();
182     }
183
184     /**
185      * float値を読み込む。
186      * float値はリトルエンディアンで格納されていると仮定される。
187      * @return 読み込んだfloat値
188      * @throws IOException IOエラー
189      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
190      * @see MmdSource#parseFloat()
191      */
192     protected float parseFloat()
193             throws IOException, MmdEofException{
194         return this.source.parseFloat();
195     }
196
197     /**
198      * byte配列を読み込む。
199      * @param dst 格納先配列
200      * @param offset 読み込み開始オフセット
201      * @param length 読み込みバイト数
202      * @throws IOException IOエラー
203      * @throws NullPointerException 配列がnull
204      * @throws IndexOutOfBoundsException 引数が配列属性と矛盾
205      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
206      * @see MmdSource#parseByteArray(byte[], int, int)
207      */
208     protected void parseByteArray(byte[] dst, int offset, int length)
209             throws IOException,
210                    NullPointerException,
211                    IndexOutOfBoundsException,
212                    MmdEofException {
213         this.source.parseByteArray(dst, offset, length);
214         return;
215     }
216
217     /**
218      * byte配列を読み込む。
219      * 配列要素全ての読み込みが試みられる。
220      * @param dst 格納先配列
221      * @throws IOException IOエラー
222      * @throws NullPointerException 配列がnull
223      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
224      * @see MmdSource#parseByteArray(byte[])
225      */
226     protected void parseByteArray(byte[] dst)
227             throws IOException, NullPointerException, MmdEofException{
228         this.source.parseByteArray(dst);
229         return;
230     }
231
232     /**
233      * float配列を読み込む。
234      * @param dst 格納先配列
235      * @param offset 読み込み開始オフセット
236      * @param length 読み込みfloat要素数
237      * @throws IOException IOエラー
238      * @throws NullPointerException 配列がnull
239      * @throws IndexOutOfBoundsException 引数が配列属性と矛盾
240      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
241      * @see MmdSource#parseFloatArray(float[], int, int)
242      */
243     protected void parseFloatArray(float[] dst, int offset, int length)
244             throws IOException,
245                    NullPointerException,
246                    IndexOutOfBoundsException,
247                    MmdEofException {
248         this.source.parseFloatArray(dst, offset, length);
249         return;
250     }
251
252     /**
253      * float配列を読み込む。
254      * 配列要素全ての読み込みが試みられる。
255      * @param dst 格納先配列
256      * @throws IOException IOエラー
257      * @throws NullPointerException 配列がnull
258      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
259      * @see MmdSource#parseFloatArray(float[])
260      */
261     protected void parseFloatArray(float[] dst)
262             throws IOException, NullPointerException, MmdEofException{
263         this.source.parseFloatArray(dst);
264         return;
265     }
266
267     /**
268      * 指定された最大バイト長に収まるゼロ終端(0x00)文字列を読み込む。
269      * 入力バイト列はwindows-31jエンコーディングとして解釈される。
270      * ゼロ終端以降のデータは無視されるが、
271      * IO入力は指定バイト数だけ読み進められる。
272      * ゼロ終端が見つからないまま指定バイト数が読み込み終わった場合、
273      * そこまでのデータから文字列を構成する。
274      * <p>
275      * 戻り結果にはU+00A5(UCS円通貨記号)が含まれないことが保証される。
276      * ※0x5c(Win31J円通貨)はU+005C(UCSバックスラッシュ)にデコードされる。
277      *
278      * @param maxlen 読み込みバイト数
279      * @return デコードされた文字列
280      * @throws IOException IOエラー
281      * @throws IllegalArgumentException 読み込みバイト数が負であるか、
282      * または内部バッファに対し大きすぎる。
283      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
284      * @throws MmdFormatException 不正な文字エンコーディングが検出された。
285      */
286     protected String parseZeroTermWin31J(int maxlen)
287             throws IOException,
288                    IllegalArgumentException,
289                    MmdEofException,
290                    MmdFormatException {
291         if(this.textArray.length < maxlen || maxlen < 0){
292             throw new IllegalArgumentException();
293         }
294
295         this.source.parseByteArray(this.textArray, 0, maxlen);
296
297         int length = -1;
298         for(int pos = 0; pos < maxlen; pos++){
299             byte ch = this.textArray[pos];
300             if(ch == TERMINATOR){
301                 length = pos;
302                 break;
303             }
304         }
305         if(length < 0) length = maxlen;
306
307         this.textBuffer.rewind();
308         this.textBuffer.limit(length);
309         this.charBuffer.clear();
310         CoderResult decResult =
311                 this.decoder.decode(this.textBuffer, this.charBuffer, true);
312         if( ! decResult.isUnderflow() || decResult.isError()){
313             throw new MmdFormatException("illegal character encoding",
314                                          this.source.getPosition() );
315         }
316
317         this.charBuffer.flip();
318         String result = this.charBuffer.toString();
319
320         if(result.indexOf(UCSYEN) >= 0){
321             result = result.replace(UCSYEN, SJISYEN);
322         }
323
324         return result;
325     }
326
327 }