OSDN Git Service

Maven3対応。
[mikutoga/TogaGem.git] / src / main / java / jp / sourceforge / mikutoga / parser / MmdSource.java
1 /*
2  * MMD file input source
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.Closeable;
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.io.PushbackInputStream;
14 import java.nio.ByteBuffer;
15 import java.nio.ByteOrder;
16
17 /**
18  * MMD各種ファイルの入力ソース。
19  * 入力ソース終端の判定が可能。
20  * パースエラー発生位置(バイト単位)の取得が可能。
21  * リトルエンディアン形式で格納された各種プリミティブ型値の解決を行う。
22  */
23 public class MmdSource implements Closeable {
24
25     private static final int BYTES_SHORT = Short  .SIZE / Byte.SIZE;
26     private static final int BYTES_INT   = Integer.SIZE / Byte.SIZE;
27     private static final int BYTES_FLOAT = Float  .SIZE / Byte.SIZE;
28     private static final int BUF_SZ = 4;
29
30     private static final int MASK_8BIT = 0xff;
31     private static final int MASK_16BIT = 0xffff;
32
33     static{
34         assert BUF_SZ >= BYTES_SHORT;
35         assert BUF_SZ >= BYTES_INT;
36         assert BUF_SZ >= BYTES_FLOAT;
37     }
38
39     private final PushbackInputStream istream;
40     private final byte[] readArray;       // 読み込みバッファ
41     private final ByteBuffer readBuffer;  // 読み込みバッファの別ビュー
42     private long position;                // 読み込み位置
43
44     /**
45      * コンストラクタ。
46      * @param is 入力ストリーム。
47      * I/O効率が考慮されたバッファリングを行うストリームを渡すのが望ましい。
48      * @throws NullPointerException ストリーム引数がnull。
49      */
50     public MmdSource(InputStream is)
51             throws NullPointerException {
52         super();
53
54         if(is == null) throw new NullPointerException();
55
56         // 読み戻しバッファは1byte確保
57         this.istream = new PushbackInputStream(is);
58
59         this.readArray = new byte[BUF_SZ];
60         this.readBuffer = ByteBuffer.wrap(this.readArray);
61         this.readBuffer.order(ByteOrder.LITTLE_ENDIAN);
62         this.readBuffer.clear();
63
64         this.position = 0L;
65
66         return;
67     }
68
69     /**
70      * 今までに読み込みに成功したバイト数を返す。
71      * @return 読み込みに成功したバイト数。
72      */
73     public long getPosition(){
74         return this.position;
75     }
76
77     /**
78      * 入力ソースを読み飛ばす。
79      * 入力ソースがディスクファイルに由来する場合、
80      * 空読みするより早くなるかも。
81      * @param skipLength 読み飛ばすバイト数。
82      * @return 実際に読み飛ばしたバイト数。
83      * @throws IOException IOエラー
84      * @see java.io.InputStream#skip(long)
85      */
86     public long skip(long skipLength)
87             throws IOException{
88         if(skipLength <= 0L) return 0L;
89
90         long remain = skipLength;
91         while(remain > 0L){      // BufferedInputStream対策
92             long result = this.istream.skip(remain);
93             if(result <= 0L) break;
94             this.position += result;
95             remain -= result;
96         }
97
98         return skipLength - remain;
99     }
100
101     /**
102      * 入力ソースにまだデータが残っているか判定する。
103      * @return まだ読み込んでいないデータが残っていればtrue
104      * @throws IOException IOエラー
105      */
106     public boolean hasMore() throws IOException{
107         int bData = this.istream.read();
108         if(bData < 0){
109             return false;
110         }
111
112         this.istream.unread(bData);
113
114         return true;
115     }
116
117     /**
118      * 入力ソースを閉じる。
119      * 読み込み済みバイト数の情報は保持される。
120      * @throws IOException IOエラー
121      * @see java.io.InputStream#close()
122      */
123     @Override
124     public void close() throws IOException{
125         this.istream.close();
126         this.readBuffer.clear();
127         return;
128     }
129
130     /**
131      * 指定したバイト数だけ内部バッファに読み込む。
132      * @param fillSize 読み込むバイト数
133      * @throws IOException IOエラー
134      * @throws IndexOutOfBoundsException 引数がバッファサイズと矛盾。
135      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
136      */
137     protected void fillBuffer(int fillSize)
138             throws IOException, IndexOutOfBoundsException, MmdEofException{
139         int result = this.istream.read(this.readArray, 0, fillSize);
140         if(result >= 0){
141             this.position += result;
142         }
143
144         if(result != fillSize){
145             throw new MmdEofException(this.position);
146         }
147
148         this.readBuffer.rewind();
149
150         return;
151     }
152
153     /**
154      * byte値を読み込む。
155      * @return 読み込んだbyte値
156      * @throws IOException IOエラー
157      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
158      */
159     public byte parseByte() throws IOException, MmdEofException{
160         int bData = this.istream.read();
161         if(bData < 0){
162             throw new MmdEofException(this.position);
163         }else{
164             this.position++;
165         }
166
167         byte result = (byte) bData;
168         return result;
169     }
170
171     /**
172      * 符号無し値としてbyte値を読み込み、int型に変換して返す。
173      * 符号は拡張されない。(0xffは0x000000ffとなる)
174      * @return 読み込まれた値のint値
175      * @throws IOException IOエラー
176      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
177      */
178     public int parseUByteAsInteger()
179             throws IOException, MmdEofException{
180         return ((int) parseByte()) & MASK_8BIT;
181     }
182
183     /**
184      * byte値を読み込み、boolean型に変換して返す。
185      * 0x00は偽、それ以外は真と解釈される。
186      * @return 読み込まれた値のboolean値
187      * @throws IOException IOエラー
188      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
189      */
190     public boolean parseBoolean() throws IOException, MmdEofException{
191         byte result = parseByte();
192         if(result == 0x00) return false;
193         return true;
194     }
195
196     /**
197      * short値を読み込む。
198      * short値はリトルエンディアンで格納されていると仮定される。
199      * @return 読み込んだshort値
200      * @throws IOException IOエラー
201      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
202      */
203     public short parseShort() throws IOException, MmdEofException{
204         fillBuffer(BYTES_SHORT);
205         short result = this.readBuffer.getShort();
206         return result;
207     }
208
209     /**
210      * 符号無し値としてshort値を読み込み、int型に変換して返す。
211      * 符号は拡張されない。(0xffffは0x0000ffffとなる)
212      * short値はリトルエンディアンで格納されていると仮定される。
213      * @return 読み込まれた値のint値
214      * @throws IOException IOエラー
215      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
216      */
217     public int parseUShortAsInteger()
218             throws IOException, MmdEofException{
219         return ((int) parseShort()) & MASK_16BIT;
220     }
221
222     /**
223      * int値を読み込む。
224      * int値はリトルエンディアンで格納されていると仮定される。
225      * @return 読み込んだint値
226      * @throws IOException IOエラー
227      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
228      */
229     public int parseInteger() throws IOException, MmdEofException{
230         fillBuffer(BYTES_INT);
231         int result = this.readBuffer.getInt();
232         return result;
233     }
234
235     /**
236      * float値を読み込む。
237      * float値はリトルエンディアンで格納されていると仮定される。
238      * @return 読み込んだfloat値
239      * @throws IOException IOエラー
240      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
241      */
242     public float parseFloat() throws IOException, MmdEofException{
243         fillBuffer(BYTES_FLOAT);
244         float result = this.readBuffer.getFloat();
245         return result;
246     }
247
248     /**
249      * byte配列を読み込む。
250      * @param dst 格納先配列
251      * @param offset 読み込み開始オフセット
252      * @param length 読み込みバイト数
253      * @throws IOException IOエラー
254      * @throws NullPointerException 配列がnull
255      * @throws IndexOutOfBoundsException 引数が配列属性と矛盾
256      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
257      * @see java.io.InputStream#read(byte[], int, int)
258      */
259     public void parseByteArray(byte[] dst, int offset, int length)
260             throws IOException,
261                    NullPointerException,
262                    IndexOutOfBoundsException,
263                    MmdEofException {
264         int result = this.istream.read(dst, offset, length);
265         if(result >= 0){
266             this.position += result;
267         }
268
269         if(result != length){
270             throw new MmdEofException(this.position);
271         }
272
273         return;
274     }
275
276     /**
277      * byte配列を読み込む。
278      * @param dst 格納先配列
279      * @throws IOException IOエラー
280      * @throws NullPointerException 配列がnull
281      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
282      */
283     public void parseByteArray(byte[] dst)
284             throws IOException, NullPointerException, MmdEofException{
285         parseByteArray(dst, 0, dst.length);
286         return;
287     }
288
289     /**
290      * float配列を読み込む。
291      * @param dst 格納先配列
292      * @param offset 読み込み開始オフセット
293      * @param length 読み込みfloat要素数
294      * @throws IOException IOエラー
295      * @throws NullPointerException 配列がnull
296      * @throws IndexOutOfBoundsException 引数が配列属性と矛盾
297      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
298      */
299     public void parseFloatArray(float[] dst, int offset, int length)
300             throws IOException,
301                    NullPointerException,
302                    IndexOutOfBoundsException,
303                    MmdEofException {
304         if(offset < 0 || length < 0 || dst.length - offset < length){
305             throw new IndexOutOfBoundsException();
306         }
307
308         for(int idx = 0; idx < length; idx++){
309             dst[offset+idx] = parseFloat();
310         }
311
312         return;
313     }
314
315     /**
316      * float配列を読み込む。
317      * @param dst 格納先配列
318      * @throws IOException IOエラー
319      * @throws NullPointerException 配列がnull
320      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
321      */
322     public void parseFloatArray(float[] dst)
323             throws IOException, NullPointerException, MmdEofException{
324         parseFloatArray(dst, 0, dst.length);
325         return;
326     }
327
328     // TODO ビッグエンディアン対応が今後必要になる状況はありうるか?
329 }