-/*\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.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+/**
+ * 各種パーサの共通実装。
+ */
+public class CommonParser {
+
+ /** 日本語デコード作業用入力バッファ長。バイト単位。 */
+ public static final int TEXTBUF_SZ = 512;
+
+ /**
+ * MMD各種ファイルで用いられる日本語エンコーディング。(windows-31j)
+ * ほぼShift_JISのスーパーセットと思ってよい。
+ * デコード結果はUCS-2集合に収まるはず。
+ */
+ protected static final Charset CS_WIN31J = Charset.forName("windows-31j");
+
+ private static final byte TERMINATOR = (byte) '\0'; // 0x00
+ private static final char UCSYEN = '\u00a5';
+ private static final char SJISYEN = (char) 0x005c; // '\u005c\u005c';
+
+ private final MmdSource source;
+ private final CharsetDecoder decoder;
+ private final byte[] textArray;
+ private final ByteBuffer textBuffer; // textArrayの別ビュー
+ private final CharBuffer charBuffer;
+
+ /**
+ * コンストラクタ。
+ * @param source 入力ソース
+ */
+ public CommonParser(MmdSource source){
+ super();
+
+ this.source = source;
+ this.decoder = CS_WIN31J.newDecoder();
+ this.textArray = new byte[TEXTBUF_SZ];
+ this.textBuffer = ByteBuffer.wrap(this.textArray);
+ int maxChars =
+ (int)(TEXTBUF_SZ * (this.decoder.maxCharsPerByte())) + 1;
+ this.charBuffer = CharBuffer.allocate(maxChars);
+
+ this.decoder.onMalformedInput(CodingErrorAction.REPORT);
+ this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
+ this.textBuffer.clear();
+ this.charBuffer.clear();
+
+ return;
+ }
+
+ /**
+ * 入力ソースにまだデータが残っているか判定する。
+ * @return まだ読み込んでいないデータが残っていればtrue
+ * @throws IOException IOエラー
+ * @see MmdSource#hasMore()
+ */
+ protected boolean hasMore() throws IOException{
+ boolean result = this.source.hasMore();
+ return result;
+ }
+
+ /**
+ * 入力ソースを読み飛ばす。
+ * @param skipLength 読み飛ばすバイト数。
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み飛ばす途中でストリーム終端に達した。
+ * @see MmdSource#skip(long)
+ */
+ protected void skip(long skipLength)
+ throws IOException, MmdEofException {
+ long result = this.source.skip(skipLength);
+ if(result != skipLength){
+ throw new MmdEofException(this.source.getPosition());
+ }
+
+ return;
+ }
+
+ /**
+ * 入力ソースを読み飛ばす。
+ * @param skipLength 読み飛ばすバイト数。
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み飛ばす途中でストリーム終端に達した。
+ * @see MmdSource#skip(long)
+ */
+ protected void skip(int skipLength)
+ throws IOException, MmdEofException {
+ skip((long) skipLength);
+ }
+
+ /**
+ * byte値を読み込む。
+ * @return 読み込んだbyte値
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdSource#parseByte()
+ */
+ protected byte parseByte()
+ throws IOException, MmdEofException{
+ return this.source.parseByte();
+ }
+
+ /**
+ * 符号無し値としてbyte値を読み込み、int型に変換して返す。
+ * 符号は拡張されない。(0xffは0x000000ffとなる)
+ * @return 読み込まれた値のint値
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdSource#parseUByteAsInteger()
+ */
+ protected int parseUByteAsInteger()
+ throws IOException, MmdEofException{
+ return this.source.parseUByteAsInteger();
+ }
+
+ /**
+ * byte値を読み込み、boolean型に変換して返す。
+ * 0x00は偽、それ以外は真と解釈される。
+ * @return 読み込まれた値のboolean値
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdSource#parseBoolean()
+ */
+ protected boolean parseBoolean()
+ throws IOException, MmdEofException{
+ return this.source.parseBoolean();
+ }
+
+ /**
+ * short値を読み込む。
+ * short値はリトルエンディアンで格納されていると仮定される。
+ * @return 読み込んだshort値
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdSource#parseShort()
+ */
+ protected short parseShort()
+ throws IOException, MmdEofException{
+ return this.source.parseShort();
+ }
+
+ /**
+ * 符号無し値としてshort値を読み込み、int型に変換して返す。
+ * 符号は拡張されない。(0xffffは0x0000ffffとなる)
+ * short値はリトルエンディアンで格納されていると仮定される。
+ * @return 読み込まれた値のint値
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdSource#parseUShortAsInteger()
+ */
+ protected int parseUShortAsInteger()
+ throws IOException, MmdEofException{
+ return this.source.parseUShortAsInteger();
+ }
+
+ /**
+ * int値を読み込む。
+ * int値はリトルエンディアンで格納されていると仮定される。
+ * @return 読み込んだint値
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdSource#parseInteger()
+ */
+ protected int parseInteger()
+ throws IOException, MmdEofException{
+ return this.source.parseInteger();
+ }
+
+ /**
+ * float値を読み込む。
+ * float値はリトルエンディアンで格納されていると仮定される。
+ * @return 読み込んだfloat値
+ * @throws IOException IOエラー
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdSource#parseFloat()
+ */
+ protected float parseFloat()
+ throws IOException, MmdEofException{
+ return this.source.parseFloat();
+ }
+
+ /**
+ * byte配列を読み込む。
+ * @param dst 格納先配列
+ * @param offset 読み込み開始オフセット
+ * @param length 読み込みバイト数
+ * @throws IOException IOエラー
+ * @throws NullPointerException 配列がnull
+ * @throws IndexOutOfBoundsException 引数が配列属性と矛盾
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdSource#parseByteArray(byte[], int, int)
+ */
+ protected void parseByteArray(byte[] dst, int offset, int length)
+ throws IOException,
+ NullPointerException,
+ IndexOutOfBoundsException,
+ MmdEofException {
+ this.source.parseByteArray(dst, offset, length);
+ return;
+ }
+
+ /**
+ * byte配列を読み込む。
+ * 配列要素全ての読み込みが試みられる。
+ * @param dst 格納先配列
+ * @throws IOException IOエラー
+ * @throws NullPointerException 配列がnull
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdSource#parseByteArray(byte[])
+ */
+ protected void parseByteArray(byte[] dst)
+ throws IOException, NullPointerException, MmdEofException{
+ this.source.parseByteArray(dst);
+ return;
+ }
+
+ /**
+ * float配列を読み込む。
+ * @param dst 格納先配列
+ * @param offset 読み込み開始オフセット
+ * @param length 読み込みfloat要素数
+ * @throws IOException IOエラー
+ * @throws NullPointerException 配列がnull
+ * @throws IndexOutOfBoundsException 引数が配列属性と矛盾
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdSource#parseFloatArray(float[], int, int)
+ */
+ protected void parseFloatArray(float[] dst, int offset, int length)
+ throws IOException,
+ NullPointerException,
+ IndexOutOfBoundsException,
+ MmdEofException {
+ this.source.parseFloatArray(dst, offset, length);
+ return;
+ }
+
+ /**
+ * float配列を読み込む。
+ * 配列要素全ての読み込みが試みられる。
+ * @param dst 格納先配列
+ * @throws IOException IOエラー
+ * @throws NullPointerException 配列がnull
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see MmdSource#parseFloatArray(float[])
+ */
+ protected void parseFloatArray(float[] dst)
+ throws IOException, NullPointerException, MmdEofException{
+ this.source.parseFloatArray(dst);
+ return;
+ }
+
+ /**
+ * 指定された最大バイト長に収まるゼロ終端(0x00)文字列を読み込む。
+ * 入力バイト列はwindows-31jエンコーディングとして解釈される。
+ * ゼロ終端以降のデータは無視されるが、
+ * IO入力は指定バイト数だけ読み進められる。
+ * ゼロ終端が見つからないまま指定バイト数が読み込み終わった場合、
+ * そこまでのデータから文字列を構成する。
+ * <p>
+ * 戻り結果にはU+00A5(UCS円通貨記号)が含まれないことが保証される。
+ * ※0x5c(Win31J円通貨)はU+005C(UCSバックスラッシュ)にデコードされる。
+ *
+ * @param maxlen 読み込みバイト数
+ * @return デコードされた文字列
+ * @throws IOException IOエラー
+ * @throws IllegalArgumentException 読み込みバイト数が負であるか、
+ * または内部バッファに対し大きすぎる。
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @throws MmdFormatException 不正な文字エンコーディングが検出された。
+ */
+ protected String parseZeroTermString(int maxlen)
+ throws IOException,
+ IllegalArgumentException,
+ MmdEofException,
+ MmdFormatException {
+ if(this.textArray.length < maxlen || maxlen < 0){
+ throw new IllegalArgumentException();
+ }
+
+ this.source.parseByteArray(this.textArray, 0, maxlen);
+
+ int length = -1;
+ for(int pos = 0; pos < maxlen; pos++){
+ byte ch = this.textArray[pos];
+ if(ch == TERMINATOR){
+ length = pos;
+ break;
+ }
+ }
+ if(length < 0) length = maxlen;
+
+ this.textBuffer.rewind();
+ this.textBuffer.limit(length);
+ this.charBuffer.clear();
+ CoderResult decResult =
+ this.decoder.decode(this.textBuffer, this.charBuffer, true);
+ if( ! decResult.isUnderflow() || decResult.isError()){
+ throw new MmdFormatException("illegal character encoding",
+ this.source.getPosition() );
+ }
+
+ this.charBuffer.flip();
+ String result = this.charBuffer.toString();
+
+ if(result.indexOf(UCSYEN) >= 0){
+ result = result.replace(UCSYEN, SJISYEN);
+ }
+
+ return result;
+ }
+
+}