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.CharBuffer;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetDecoder;
-import java.nio.charset.CoderResult;
-import java.nio.charset.CodingErrorAction;
+import java.nio.ByteOrder;
/**
* 各種パーサの共通実装。
*/
public class CommonParser {
- /** 日本語デコード作業用入力バッファ長。バイト単位。 */
- public static final int TEXTBUF_SZ = 512;
+ 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;
+ }
- /**
- * 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 PushbackInputStream is;
+
+ private final byte[] readBuffer;
+ private final ByteBuffer beBuf;
+ private final ByteBuffer leBuf;
+
+ private long position = 0L;
- 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){
+ public CommonParser(InputStream 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.is = new PushbackInputStream(source, 1);
+
+ this.readBuffer = new byte[BYTES_PRIM];
+
+ this.beBuf = ByteBuffer.wrap(this.readBuffer);
+ this.leBuf = ByteBuffer.wrap(this.readBuffer);
- this.decoder.onMalformedInput(CodingErrorAction.REPORT);
- this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
- this.textBuffer.clear();
- this.charBuffer.clear();
+ 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エラー
- * @see MmdSource#hasMore()
*/
- protected boolean hasMore() throws IOException{
- boolean result = this.source.hasMore();
- return result;
+ 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 MmdSource#skip(long)
+ * @see InputStream#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());
+ 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;
}
/**
- * 入力ソースを読み飛ばす。
- * @param skipLength 読み飛ばすバイト数。
+ * byte配列を読み込む。
+ * @param dst 格納先配列
+ * @param off 読み込み開始オフセット
+ * @param length 読み込みバイト数
* @throws IOException IOエラー
- * @throws MmdEofException 読み飛ばす途中でストリーム終端に達した。
- * @see MmdSource#skip(long)
+ * @throws NullPointerException 配列がnull
+ * @throws IndexOutOfBoundsException 引数が配列属性と矛盾
+ * @throws MmdEofException 読み込む途中でストリーム終端に達した。
+ * @see InputStream#read(byte[], int, int)
*/
- protected void skip(int skipLength)
+ 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 {
- skip((long) skipLength);
+ parseByteArray(this.readBuffer, 0, fillSize);
+ return;
}
/**
* @return 読み込んだbyte値
* @throws IOException IOエラー
* @throws MmdEofException 読み込む途中でストリーム終端に達した。
- * @see MmdSource#parseByte()
+ * @see MmdInputStream#parseByte()
*/
protected byte parseByte()
throws IOException, MmdEofException{
- return this.source.parseByte();
+ int bData = this.is.read();
+ if(bData < 0){
+ throw new MmdEofException(this.position);
+ }
+
+ byte result = (byte) bData;
+ this.position++;
+
+ return result;
}
/**
* @return 読み込まれた値のint値
* @throws IOException IOエラー
* @throws MmdEofException 読み込む途中でストリーム終端に達した。
- * @see MmdSource#parseUByteAsInteger()
+ * @see MmdInputStream#parseUByteAsInt()
*/
- protected int parseUByteAsInteger()
+ protected int parseUByteAsInt()
throws IOException, MmdEofException{
- return this.source.parseUByteAsInteger();
+ return ((int) parseByte()) & MASK_8BIT;
}
/**
* @return 読み込まれた値のboolean値
* @throws IOException IOエラー
* @throws MmdEofException 読み込む途中でストリーム終端に達した。
- * @see MmdSource#parseBoolean()
+ * @see MmdInputStream#parseBoolean()
*/
protected boolean parseBoolean()
throws IOException, MmdEofException{
- return this.source.parseBoolean();
+ byte result = parseByte();
+ if(result == 0x00) return false;
+ return true;
}
/**
* @return 読み込んだshort値
* @throws IOException IOエラー
* @throws MmdEofException 読み込む途中でストリーム終端に達した。
- * @see MmdSource#parseShort()
+ * @see MmdInputStream#parseShort()
*/
- protected short parseShort()
+ protected short parseLeShort()
throws IOException, MmdEofException{
- return this.source.parseShort();
+ fillBuffer(BYTES_SHORT);
+ short result = this.leBuf.getShort(0);
+ return result;
}
/**
* @return 読み込まれた値のint値
* @throws IOException IOエラー
* @throws MmdEofException 読み込む途中でストリーム終端に達した。
- * @see MmdSource#parseUShortAsInteger()
+ * @see MmdInputStream#parseUShortAsInteger()
*/
- protected int parseUShortAsInteger()
+ protected int parseLeUShortAsInt()
throws IOException, MmdEofException{
- return this.source.parseUShortAsInteger();
+ return ((int) parseLeShort()) & MASK_16BIT;
}
/**
* @return 読み込んだint値
* @throws IOException IOエラー
* @throws MmdEofException 読み込む途中でストリーム終端に達した。
- * @see MmdSource#parseInteger()
+ * @see MmdInputStream#parseInteger()
*/
- protected int parseInteger()
+ protected int parseLeInt()
throws IOException, MmdEofException{
- return this.source.parseInteger();
+ fillBuffer(BYTES_INT);
+ int result = this.leBuf.getInt(0);
+ return result;
}
/**
* @return 読み込んだfloat値
* @throws IOException IOエラー
* @throws MmdEofException 読み込む途中でストリーム終端に達した。
- * @see MmdSource#parseFloat()
+ * @see MmdInputStream#parseFloat()
*/
- protected float parseFloat()
+ protected float parseLeFloat()
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;
+ fillBuffer(BYTES_FLOAT);
+ float result = this.leBuf.getFloat(0);
+ return result;
}
/**
- * 指定された最大バイト長に収まるゼロ終端(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 不正な文字エンコーディングが検出された。
+ * 固定バイト長の文字列を読み込む。
+ * @param decoder 文字デコーダ
+ * @param byteLen 読み込む固定バイト長
+ * @return 文字列
+ * @throws IOException 入力エラー
+ * @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);
- }
-
+ 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;
}