-/*\r
- * common MMD parser\r
- *\r
- * License : The MIT License\r
- * Copyright(c) 2010 MikuToga Partners\r
- */\r
-\r
-package jp.sourceforge.mikutoga.parser;\r
-\r
-import java.io.IOException;\r
-import java.nio.ByteBuffer;\r
-import java.nio.CharBuffer;\r
-import java.nio.charset.Charset;\r
-import java.nio.charset.CharsetDecoder;\r
-import java.nio.charset.CoderResult;\r
-import java.nio.charset.CodingErrorAction;\r
-\r
-/**\r
- * 各種パーサの共通実装。\r
- */\r
-public class CommonParser {\r
-\r
- /** 日本語デコード作業用入力バッファ長。バイト単位。 */\r
- public static final int TEXTBUF_SZ = 512;\r
-\r
- /**\r
- * MMD各種ファイルで用いられる日本語エンコーディング。(windows-31j)\r
- * ほぼShift_JISのスーパーセットと思ってよい。\r
- * デコード結果はUCS-2集合に収まるはず。\r
- */\r
- protected static final Charset CS_WIN31J = Charset.forName("windows-31j");\r
-\r
- private static final byte TERMINATOR = (byte) '\0'; // 0x00\r
- private static final char UCSYEN = '\u00a5';\r
- private static final char SJISYEN = (char) 0x005c; // '\u005c\u005c';\r
-\r
- private final MmdSource source;\r
- private final CharsetDecoder decoder;\r
- private final byte[] textArray;\r
- private final ByteBuffer textBuffer; // textArrayの別ビュー\r
- private final CharBuffer charBuffer;\r
-\r
- /**\r
- * コンストラクタ。\r
- * @param source 入力ソース\r
- */\r
- public CommonParser(MmdSource source){\r
- super();\r
-\r
- this.source = source;\r
- this.decoder = CS_WIN31J.newDecoder();\r
- this.textArray = new byte[TEXTBUF_SZ];\r
- this.textBuffer = ByteBuffer.wrap(this.textArray);\r
- int maxChars =\r
- (int)(TEXTBUF_SZ * (this.decoder.maxCharsPerByte())) + 1;\r
- this.charBuffer = CharBuffer.allocate(maxChars);\r
-\r
- this.decoder.onMalformedInput(CodingErrorAction.REPORT);\r
- this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);\r
- this.textBuffer.clear();\r
- this.charBuffer.clear();\r
-\r
- return;\r
- }\r
-\r
- /**\r
- * 入力ソースにまだデータが残っているか判定する。\r
- * @return まだ読み込んでいないデータが残っていればtrue\r
- * @throws IOException IOエラー\r
- * @see MmdSource#hasMore()\r
- */\r
- protected boolean hasMore() throws IOException{\r
- boolean result = this.source.hasMore();\r
- return result;\r
- }\r
-\r
- /**\r
- * 入力ソースを読み飛ばす。\r
- * @param skipLength 読み飛ばすバイト数。\r
- * @throws IOException IOエラー\r
- * @throws MmdEofException 読み飛ばす途中でストリーム終端に達した。\r
- * @see MmdSource#skip(long)\r
- */\r
- protected void skip(long skipLength)\r
- throws IOException, MmdEofException {\r
- long result = this.source.skip(skipLength);\r
- if(result != skipLength){\r
- throw new MmdEofException(this.source.getPosition());\r
- }\r
-\r
- return;\r
- }\r
-\r
- /**\r
- * 入力ソースを読み飛ばす。\r
- * @param skipLength 読み飛ばすバイト数。\r
- * @throws IOException IOエラー\r
- * @throws MmdEofException 読み飛ばす途中でストリーム終端に達した。\r
- * @see MmdSource#skip(long)\r
- */\r
- protected void skip(int skipLength)\r
- throws IOException, MmdEofException {\r
- skip((long) skipLength);\r
- }\r
-\r
- /**\r
- * byte値を読み込む。\r
- * @return 読み込んだbyte値\r
- * @throws IOException IOエラー\r
- * @throws MmdEofException 読み込む途中でストリーム終端に達した。\r
- * @see MmdSource#parseByte()\r
- */\r
- protected byte parseByte()\r
- throws IOException, MmdEofException{\r
- return this.source.parseByte();\r
- }\r
-\r
- /**\r
- * 符号無し値としてbyte値を読み込み、int型に変換して返す。\r
- * 符号は拡張されない。(0xffは0x000000ffとなる)\r
- * @return 読み込まれた値のint値\r
- * @throws IOException IOエラー\r
- * @throws MmdEofException 読み込む途中でストリーム終端に達した。\r
- * @see MmdSource#parseUByteAsInteger()\r
- */\r
- protected int parseUByteAsInteger()\r
- throws IOException, MmdEofException{\r
- return this.source.parseUByteAsInteger();\r
- }\r
-\r
- /**\r
- * byte値を読み込み、boolean型に変換して返す。\r
- * 0x00は偽、それ以外は真と解釈される。\r
- * @return 読み込まれた値のboolean値\r
- * @throws IOException IOエラー\r
- * @throws MmdEofException 読み込む途中でストリーム終端に達した。\r
- * @see MmdSource#parseBoolean()\r
- */\r
- protected boolean parseBoolean()\r
- throws IOException, MmdEofException{\r
- return this.source.parseBoolean();\r
- }\r
-\r
- /**\r
- * short値を読み込む。\r
- * short値はリトルエンディアンで格納されていると仮定される。\r
- * @return 読み込んだshort値\r
- * @throws IOException IOエラー\r
- * @throws MmdEofException 読み込む途中でストリーム終端に達した。\r
- * @see MmdSource#parseShort()\r
- */\r
- protected short parseShort()\r
- throws IOException, MmdEofException{\r
- return this.source.parseShort();\r
- }\r
-\r
- /**\r
- * 符号無し値としてshort値を読み込み、int型に変換して返す。\r
- * 符号は拡張されない。(0xffffは0x0000ffffとなる)\r
- * short値はリトルエンディアンで格納されていると仮定される。\r
- * @return 読み込まれた値のint値\r
- * @throws IOException IOエラー\r
- * @throws MmdEofException 読み込む途中でストリーム終端に達した。\r
- * @see MmdSource#parseUShortAsInteger()\r
- */\r
- protected int parseUShortAsInteger()\r
- throws IOException, MmdEofException{\r
- return this.source.parseUShortAsInteger();\r
- }\r
-\r
- /**\r
- * int値を読み込む。\r
- * int値はリトルエンディアンで格納されていると仮定される。\r
- * @return 読み込んだint値\r
- * @throws IOException IOエラー\r
- * @throws MmdEofException 読み込む途中でストリーム終端に達した。\r
- * @see MmdSource#parseInteger()\r
- */\r
- protected int parseInteger()\r
- throws IOException, MmdEofException{\r
- return this.source.parseInteger();\r
- }\r
-\r
- /**\r
- * float値を読み込む。\r
- * float値はリトルエンディアンで格納されていると仮定される。\r
- * @return 読み込んだfloat値\r
- * @throws IOException IOエラー\r
- * @throws MmdEofException 読み込む途中でストリーム終端に達した。\r
- * @see MmdSource#parseFloat()\r
- */\r
- protected float parseFloat()\r
- throws IOException, MmdEofException{\r
- return this.source.parseFloat();\r
- }\r
-\r
- /**\r
- * byte配列を読み込む。\r
- * @param dst 格納先配列\r
- * @param offset 読み込み開始オフセット\r
- * @param length 読み込みバイト数\r
- * @throws IOException IOエラー\r
- * @throws NullPointerException 配列がnull\r
- * @throws IndexOutOfBoundsException 引数が配列属性と矛盾\r
- * @throws MmdEofException 読み込む途中でストリーム終端に達した。\r
- * @see MmdSource#parseByteArray(byte[], int, int)\r
- */\r
- protected void parseByteArray(byte[] dst, int offset, int length)\r
- throws IOException,\r
- NullPointerException,\r
- IndexOutOfBoundsException,\r
- MmdEofException {\r
- this.source.parseByteArray(dst, offset, length);\r
- return;\r
- }\r
-\r
- /**\r
- * byte配列を読み込む。\r
- * 配列要素全ての読み込みが試みられる。\r
- * @param dst 格納先配列\r
- * @throws IOException IOエラー\r
- * @throws NullPointerException 配列がnull\r
- * @throws MmdEofException 読み込む途中でストリーム終端に達した。\r
- * @see MmdSource#parseByteArray(byte[])\r
- */\r
- protected void parseByteArray(byte[] dst)\r
- throws IOException, NullPointerException, MmdEofException{\r
- this.source.parseByteArray(dst);\r
- return;\r
- }\r
-\r
- /**\r
- * float配列を読み込む。\r
- * @param dst 格納先配列\r
- * @param offset 読み込み開始オフセット\r
- * @param length 読み込みfloat要素数\r
- * @throws IOException IOエラー\r
- * @throws NullPointerException 配列がnull\r
- * @throws IndexOutOfBoundsException 引数が配列属性と矛盾\r
- * @throws MmdEofException 読み込む途中でストリーム終端に達した。\r
- * @see MmdSource#parseFloatArray(float[], int, int)\r
- */\r
- protected void parseFloatArray(float[] dst, int offset, int length)\r
- throws IOException,\r
- NullPointerException,\r
- IndexOutOfBoundsException,\r
- MmdEofException {\r
- this.source.parseFloatArray(dst, offset, length);\r
- return;\r
- }\r
-\r
- /**\r
- * float配列を読み込む。\r
- * 配列要素全ての読み込みが試みられる。\r
- * @param dst 格納先配列\r
- * @throws IOException IOエラー\r
- * @throws NullPointerException 配列がnull\r
- * @throws MmdEofException 読み込む途中でストリーム終端に達した。\r
- * @see MmdSource#parseFloatArray(float[])\r
- */\r
- protected void parseFloatArray(float[] dst)\r
- throws IOException, NullPointerException, MmdEofException{\r
- this.source.parseFloatArray(dst);\r
- return;\r
- }\r
-\r
- /**\r
- * 指定された最大バイト長に収まるゼロ終端(0x00)文字列を読み込む。\r
- * 入力バイト列はwindows-31jエンコーディングとして解釈される。\r
- * ゼロ終端以降のデータは無視されるが、\r
- * IO入力は指定バイト数だけ読み進められる。\r
- * ゼロ終端が見つからないまま指定バイト数が読み込み終わった場合、\r
- * そこまでのデータから文字列を構成する。\r
- * <p>\r
- * 戻り結果にはU+00A5(UCS円通貨記号)が含まれないことが保証される。\r
- * ※0x5c(Win31J円通貨)はU+005C(UCSバックスラッシュ)にデコードされる。\r
- *\r
- * @param maxlen 読み込みバイト数\r
- * @return デコードされた文字列\r
- * @throws IOException IOエラー\r
- * @throws IllegalArgumentException 読み込みバイト数が負であるか、\r
- * または内部バッファに対し大きすぎる。\r
- * @throws MmdEofException 読み込む途中でストリーム終端に達した。\r
- * @throws MmdFormatException 不正な文字エンコーディングが検出された。\r
- */\r
- protected String parseZeroTermString(int maxlen)\r
- throws IOException,\r
- IllegalArgumentException,\r
- MmdEofException,\r
- MmdFormatException {\r
- if(this.textArray.length < maxlen || maxlen < 0){\r
- throw new IllegalArgumentException();\r
- }\r
-\r
- this.source.parseByteArray(this.textArray, 0, maxlen);\r
-\r
- int length = -1;\r
- for(int pos = 0; pos < maxlen; pos++){\r
- byte ch = this.textArray[pos];\r
- if(ch == TERMINATOR){\r
- length = pos;\r
- break;\r
- }\r
- }\r
- if(length < 0) length = maxlen;\r
-\r
- this.textBuffer.rewind();\r
- this.textBuffer.limit(length);\r
- this.charBuffer.clear();\r
- CoderResult decResult =\r
- this.decoder.decode(this.textBuffer, this.charBuffer, true);\r
- if( ! decResult.isUnderflow() || decResult.isError()){\r
- throw new MmdFormatException("illegal character encoding",\r
- this.source.getPosition() );\r
- }\r
-\r
- this.charBuffer.flip();\r
- String result = this.charBuffer.toString();\r
-\r
- if(result.indexOf(UCSYEN) >= 0){\r
- result = result.replace(UCSYEN, SJISYEN);\r
- }\r
-\r
- return result;\r
- }\r
-\r
-}\r
+/*
+ * common MMD parser
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.parser;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * 各種パーサの共通実装。
+ */
+public class CommonParser {
+
+ private static final int BYTES_SHORT = Short .SIZE / Byte.SIZE;
+ private static final int BYTES_INT = Integer.SIZE / Byte.SIZE;
+ private static final int BYTES_FLOAT = Float .SIZE / Byte.SIZE;
+ private static final int BYTES_PRIM = 4;
+
+ private static final int MASK_8BIT = 0xff;
+ private static final int MASK_16BIT = 0xffff;
+
+ static{
+ assert BYTES_PRIM >= BYTES_FLOAT;
+ assert BYTES_PRIM >= BYTES_INT;
+ assert BYTES_PRIM >= BYTES_SHORT;
+ }
+
+
+ private final PushbackInputStream is;
+
+ private final byte[] readBuffer;
+ private final ByteBuffer beBuf;
+ private final ByteBuffer leBuf;
+
+ private long position = 0L;
+
+
+ /**
+ * コンストラクタ。
+ * @param source 入力ソース
+ */
+ public CommonParser(InputStream source){
+ super();
+
+ this.is = new PushbackInputStream(source, 1);
+
+ this.readBuffer = new byte[BYTES_PRIM];
+
+ this.beBuf = ByteBuffer.wrap(this.readBuffer);
+ this.leBuf = ByteBuffer.wrap(this.readBuffer);
+
+ this.beBuf.order(ByteOrder.BIG_ENDIAN);
+ this.leBuf.order(ByteOrder.LITTLE_ENDIAN);
+
+ return;
+ }
+
+
+ /**
+ * 入力ソースの読み込み位置を返す。
+ * @return 入力ソースの読み込み位置。単位はbyte。
+ */
+ protected long getPosition(){
+ long result = this.position;
+ return result;
+ }
+
+ /**
+ * 入力ソースにまだデータが残っているか判定する。
+ * @return まだ読み込んでいないデータが残っていればtrue
+ * @throws IOException IOエラー
+ */
+ public boolean hasMore() throws IOException{
+ int bVal;
+
+ try{
+ bVal = this.is.read();
+ }catch(EOFException e){ // ありえない?
+ return false;
+ }
+
+ if(bVal < 0){
+ return false;
+ }
+
+ this.is.unread(bVal);
+
+ return true;
+ }
+
+ /**
+ * 入力ソースを読み飛ばす。
+ * @param skipLength 読み飛ばすバイト数。
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み飛ばす途中でストリーム終端に達した。
+ * @see InputStream#skip(long)
+ */
+ protected void skip(long skipLength)
+ throws IOException, MmdEofException {
+ long remain = skipLength;
+
+ while(remain > 0L){
+ long txSize = this.is.skip(remain);
+ if(txSize <= 0L){
+ throw new MmdEofException(this.position);
+ }
+ remain -= txSize;
+ this.position += txSize;
+ }
+
+ return;
+ }
+
+ /**
+ * byte配列を読み込む。
+ * @param dst 格納先配列
+ * @param off 読み込み開始オフセット
+ * @param length 読み込みバイト数
+ * @throws IOException IOエラー
+ * @throws NullPointerException 配列がnull
+ * @throws IndexOutOfBoundsException 引数が配列属性と矛盾
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see InputStream#read(byte[], int, int)
+ */
+ protected void parseByteArray(byte[] dst, int off, int length)
+ throws IOException,
+ NullPointerException,
+ IndexOutOfBoundsException,
+ MmdEofException {
+ int remain = length;
+ int offset = off;
+
+ while(remain > 0){
+ int txSize = this.is.read(dst, offset, remain);
+ if(txSize <= 0){
+ throw new MmdEofException(this.position);
+ }
+ remain -= txSize;
+ offset += txSize;
+ this.position += txSize;
+ }
+
+ return;
+ }
+
+ /**
+ * byte配列を読み込む。
+ * 配列要素全ての読み込みが試みられる。
+ * @param dst 格納先配列
+ * @throws IOException IOエラー
+ * @throws NullPointerException 配列がnull
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see InputStream#read(byte[])
+ */
+ protected void parseByteArray(byte[] dst)
+ throws IOException, NullPointerException, MmdEofException{
+ parseByteArray(dst, 0, dst.length);
+ return;
+ }
+
+ /**
+ * 内部バッファへ指定バイト数だけ読み込む。
+ * @param fillSize
+ * @throws IOException
+ * @throws MmdEofException
+ */
+ private void fillBuffer(int fillSize)
+ throws IOException, MmdEofException {
+ parseByteArray(this.readBuffer, 0, fillSize);
+ return;
+ }
+
+ /**
+ * byte値を読み込む。
+ * @return 読み込んだbyte値
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdInputStream#parseByte()
+ */
+ protected byte parseByte()
+ throws IOException, MmdEofException{
+ int bData = this.is.read();
+ if(bData < 0){
+ throw new MmdEofException(this.position);
+ }
+
+ byte result = (byte) bData;
+ this.position++;
+
+ return result;
+ }
+
+ /**
+ * 符号無し値としてbyte値を読み込み、int型に変換して返す。
+ * 符号は拡張されない。(0xffは0x000000ffとなる)
+ * @return 読み込まれた値のint値
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdInputStream#parseUByteAsInt()
+ */
+ protected int parseUByteAsInt()
+ throws IOException, MmdEofException{
+ return ((int) parseByte()) & MASK_8BIT;
+ }
+
+ /**
+ * byte値を読み込み、boolean型に変換して返す。
+ * 0x00は偽、それ以外は真と解釈される。
+ * @return 読み込まれた値のboolean値
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdInputStream#parseBoolean()
+ */
+ protected boolean parseBoolean()
+ throws IOException, MmdEofException{
+ byte result = parseByte();
+ if(result == 0x00) return false;
+ return true;
+ }
+
+ /**
+ * short値を読み込む。
+ * short値はリトルエンディアンで格納されていると仮定される。
+ * @return 読み込んだshort値
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdInputStream#parseShort()
+ */
+ protected short parseLeShort()
+ throws IOException, MmdEofException{
+ fillBuffer(BYTES_SHORT);
+ short result = this.leBuf.getShort(0);
+ return result;
+ }
+
+ /**
+ * 符号無し値としてshort値を読み込み、int型に変換して返す。
+ * 符号は拡張されない。(0xffffは0x0000ffffとなる)
+ * short値はリトルエンディアンで格納されていると仮定される。
+ * @return 読み込まれた値のint値
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdInputStream#parseUShortAsInteger()
+ */
+ protected int parseLeUShortAsInt()
+ throws IOException, MmdEofException{
+ return ((int) parseLeShort()) & MASK_16BIT;
+ }
+
+ /**
+ * int値を読み込む。
+ * int値はリトルエンディアンで格納されていると仮定される。
+ * @return 読み込んだint値
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdInputStream#parseInteger()
+ */
+ protected int parseLeInt()
+ throws IOException, MmdEofException{
+ fillBuffer(BYTES_INT);
+ int result = this.leBuf.getInt(0);
+ return result;
+ }
+
+ /**
+ * float値を読み込む。
+ * float値はリトルエンディアンで格納されていると仮定される。
+ * @return 読み込んだfloat値
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdInputStream#parseFloat()
+ */
+ protected float parseLeFloat()
+ throws IOException, MmdEofException{
+ fillBuffer(BYTES_FLOAT);
+ float result = this.leBuf.getFloat(0);
+ return result;
+ }
+
+ /**
+ * 固定バイト長の文字列を読み込む。
+ * @param decoder 文字デコーダ
+ * @param byteLen 読み込む固定バイト長
+ * @return 文字列
+ * @throws IOException 入力エラー
+ * @throws MmdEofException 固定長バイト列を読む前に末端に達した。
+ * @throws MmdFormatException 文字エンコーディングに関するエラー
+ */
+ protected String parseString(TextDecoder decoder, int byteLen)
+ throws IOException, MmdEofException, MmdFormatException {
+ byte[] buf = decoder.prepareBuffer(byteLen);
+ parseByteArray(buf, 0, byteLen);
+ long basePos = getPosition();
+ String result= decoder.decode(basePos, byteLen);
+ return result;
+ }
+
+}