OSDN Git Service

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