4 * License : The MIT License
5 * Copyright(c) 2010 MikuToga Partners
8 package jp.sourceforge.mikutoga.parser;
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;
21 public class CommonParser {
23 /** 日本語デコード作業用入力バッファ長。バイト単位。 */
24 public static final int TEXTBUF_SZ = 512;
27 * MMD各種ファイルで用いられる日本語エンコーディング。(windows-31j)
28 * ほぼShift_JISのスーパーセットと思ってよい。
29 * デコード結果はUCS-2集合に収まるはず。
31 protected static final Charset CS_WIN31J = Charset.forName("windows-31j");
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';
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;
47 public CommonParser(MmdSource source){
51 this.decoder = CS_WIN31J.newDecoder();
52 this.textArray = new byte[TEXTBUF_SZ];
53 this.textBuffer = ByteBuffer.wrap(this.textArray);
55 (int)(TEXTBUF_SZ * (this.decoder.maxCharsPerByte())) + 1;
56 this.charBuffer = CharBuffer.allocate(maxChars);
58 this.decoder.onMalformedInput(CodingErrorAction.REPORT);
59 this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
60 this.textBuffer.clear();
61 this.charBuffer.clear();
67 * 入力ソースにまだデータが残っているか判定する。
68 * @return まだ読み込んでいないデータが残っていればtrue
69 * @throws IOException IOエラー
70 * @see MmdSource#hasMore()
72 protected boolean hasMore() throws IOException{
73 boolean result = this.source.hasMore();
79 * @param skipLength 読み飛ばすバイト数。
80 * @throws IOException IOエラー
81 * @throws MmdEofException 読み飛ばす途中でストリーム終端に達した。
82 * @see MmdSource#skip(long)
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());
96 * @param skipLength 読み飛ばすバイト数。
97 * @throws IOException IOエラー
98 * @throws MmdEofException 読み飛ばす途中でストリーム終端に達した。
99 * @see MmdSource#skip(long)
101 protected void skip(int skipLength)
102 throws IOException, MmdEofException {
103 skip((long) skipLength);
109 * @throws IOException IOエラー
110 * @throws MmdEofException 読み込む途中でストリーム終端に達した。
111 * @see MmdSource#parseByte()
113 protected byte parseByte()
114 throws IOException, MmdEofException{
115 return this.source.parseByte();
119 * 符号無し値としてbyte値を読み込み、int型に変換して返す。
120 * 符号は拡張されない。(0xffは0x000000ffとなる)
121 * @return 読み込まれた値のint値
122 * @throws IOException IOエラー
123 * @throws MmdEofException 読み込む途中でストリーム終端に達した。
124 * @see MmdSource#parseUByteAsInteger()
126 protected int parseUByteAsInteger()
127 throws IOException, MmdEofException{
128 return this.source.parseUByteAsInteger();
132 * byte値を読み込み、boolean型に変換して返す。
133 * 0x00は偽、それ以外は真と解釈される。
134 * @return 読み込まれた値のboolean値
135 * @throws IOException IOエラー
136 * @throws MmdEofException 読み込む途中でストリーム終端に達した。
137 * @see MmdSource#parseBoolean()
139 protected boolean parseBoolean()
140 throws IOException, MmdEofException{
141 return this.source.parseBoolean();
146 * short値はリトルエンディアンで格納されていると仮定される。
147 * @return 読み込んだshort値
148 * @throws IOException IOエラー
149 * @throws MmdEofException 読み込む途中でストリーム終端に達した。
150 * @see MmdSource#parseShort()
152 protected short parseShort()
153 throws IOException, MmdEofException{
154 return this.source.parseShort();
158 * 符号無し値としてshort値を読み込み、int型に変換して返す。
159 * 符号は拡張されない。(0xffffは0x0000ffffとなる)
160 * short値はリトルエンディアンで格納されていると仮定される。
161 * @return 読み込まれた値のint値
162 * @throws IOException IOエラー
163 * @throws MmdEofException 読み込む途中でストリーム終端に達した。
164 * @see MmdSource#parseUShortAsInteger()
166 protected int parseUShortAsInteger()
167 throws IOException, MmdEofException{
168 return this.source.parseUShortAsInteger();
173 * int値はリトルエンディアンで格納されていると仮定される。
175 * @throws IOException IOエラー
176 * @throws MmdEofException 読み込む途中でストリーム終端に達した。
177 * @see MmdSource#parseInteger()
179 protected int parseInteger()
180 throws IOException, MmdEofException{
181 return this.source.parseInteger();
186 * float値はリトルエンディアンで格納されていると仮定される。
187 * @return 読み込んだfloat値
188 * @throws IOException IOエラー
189 * @throws MmdEofException 読み込む途中でストリーム終端に達した。
190 * @see MmdSource#parseFloat()
192 protected float parseFloat()
193 throws IOException, MmdEofException{
194 return this.source.parseFloat();
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)
208 protected void parseByteArray(byte[] dst, int offset, int length)
210 NullPointerException,
211 IndexOutOfBoundsException,
213 this.source.parseByteArray(dst, offset, length);
221 * @throws IOException IOエラー
222 * @throws NullPointerException 配列がnull
223 * @throws MmdEofException 読み込む途中でストリーム終端に達した。
224 * @see MmdSource#parseByteArray(byte[])
226 protected void parseByteArray(byte[] dst)
227 throws IOException, NullPointerException, MmdEofException{
228 this.source.parseByteArray(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)
243 protected void parseFloatArray(float[] dst, int offset, int length)
245 NullPointerException,
246 IndexOutOfBoundsException,
248 this.source.parseFloatArray(dst, offset, length);
256 * @throws IOException IOエラー
257 * @throws NullPointerException 配列がnull
258 * @throws MmdEofException 読み込む途中でストリーム終端に達した。
259 * @see MmdSource#parseFloatArray(float[])
261 protected void parseFloatArray(float[] dst)
262 throws IOException, NullPointerException, MmdEofException{
263 this.source.parseFloatArray(dst);
268 * 指定された最大バイト長に収まるゼロ終端(0x00)文字列を読み込む。
269 * 入力バイト列はwindows-31jエンコーディングとして解釈される。
271 * IO入力は指定バイト数だけ読み進められる。
272 * ゼロ終端が見つからないまま指定バイト数が読み込み終わった場合、
273 * そこまでのデータから文字列を構成する。
275 * 戻り結果にはU+00A5(UCS円通貨記号)が含まれないことが保証される。
276 * ※0x5c(Win31J円通貨)はU+005C(UCSバックスラッシュ)にデコードされる。
278 * @param maxlen 読み込みバイト数
280 * @throws IOException IOエラー
281 * @throws IllegalArgumentException 読み込みバイト数が負であるか、
283 * @throws MmdEofException 読み込む途中でストリーム終端に達した。
284 * @throws MmdFormatException 不正な文字エンコーディングが検出された。
286 protected String parseZeroTermString(int maxlen)
288 IllegalArgumentException,
291 if(this.textArray.length < maxlen || maxlen < 0){
292 throw new IllegalArgumentException();
295 this.source.parseByteArray(this.textArray, 0, maxlen);
298 for(int pos = 0; pos < maxlen; pos++){
299 byte ch = this.textArray[pos];
300 if(ch == TERMINATOR){
305 if(length < 0) length = maxlen;
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() );
317 this.charBuffer.flip();
318 String result = this.charBuffer.toString();
320 if(result.indexOf(UCSYEN) >= 0){
321 result = result.replace(UCSYEN, SJISYEN);