OSDN Git Service

2.102.3-SNAPSHOT 開発開始
[mikutoga/TogaGem.git] / src / main / java / jp / sfjp / mikutoga / bin / parser / CommonParser.java
1 /*
2  * common MMD parser
3  *
4  * License : The MIT License
5  * Copyright(c) 2010 MikuToga Partners
6  */
7
8 package jp.sfjp.mikutoga.bin.parser;
9
10 import java.io.EOFException;
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 import java.nio.charset.CharacterCodingException;
17 import java.nio.charset.MalformedInputException;
18 import java.nio.charset.UnmappableCharacterException;
19
20 /**
21  * 各種バイナリファイルパーサの共通実装。
22  * <p>バイト列、各種プリミティブ型値およびエンコードされた文字列を読み込む。
23  * <p>long,double、およびビッグエンディアン形式のデータは未サポート。
24  */
25 public class CommonParser {
26
27     private static final String ERRMSG_ILLENC =
28             "illegal character encoding";
29     private static final String ERRMSG_UNMAP =
30             "unmapped character";
31
32     private static final int BYTES_SHORT = Short  .SIZE / Byte.SIZE;
33     private static final int BYTES_INT   = Integer.SIZE / Byte.SIZE;
34     private static final int BYTES_FLOAT = Float  .SIZE / Byte.SIZE;
35     private static final int BYTES_PRIM = 4;
36
37     private static final int MASK_8BIT  =   0xff;
38     private static final int MASK_16BIT = 0xffff;
39
40     static{
41         assert BYTES_PRIM >= BYTES_FLOAT;
42         assert BYTES_PRIM >= BYTES_INT;
43         assert BYTES_PRIM >= BYTES_SHORT;
44     }
45
46
47     private final PushbackInputStream is;
48
49     private final byte[] readBuffer;
50 //  private final ByteBuffer beBuf;
51     private final ByteBuffer leBuf;
52
53     private long position = 0L;
54
55     private ByteBuffer btextBuf;
56
57
58     /**
59      * コンストラクタ。
60      * @param source 入力ソース
61      */
62     public CommonParser(InputStream source){
63         super();
64
65         this.is = new PushbackInputStream(source, 1);
66
67         this.readBuffer = new byte[BYTES_PRIM];
68
69 //      this.beBuf = ByteBuffer.wrap(this.readBuffer);
70         this.leBuf = ByteBuffer.wrap(this.readBuffer);
71
72 //      this.beBuf.order(ByteOrder.BIG_ENDIAN);
73         this.leBuf.order(ByteOrder.LITTLE_ENDIAN);
74
75         return;
76     }
77
78
79     /**
80      * 入力ソースの読み込み位置を返す。
81      * @return 入力ソースの読み込み位置。単位はbyte。
82      */
83     protected long getPosition(){
84         long result = this.position;
85         return result;
86     }
87
88     /**
89      * 入力ソースにまだデータが残っているか判定する。
90      * @return まだ読み込んでいないデータが残っていればtrue
91      * @throws IOException IOエラー
92      */
93     public boolean hasMore() throws IOException{
94         int bVal;
95
96         try{
97             bVal = this.is.read();
98         }catch(EOFException e){ // ありえない?
99             return false;
100         }
101
102         if(bVal < 0){
103             return false;
104         }
105
106         this.is.unread(bVal);
107
108         return true;
109     }
110
111     /**
112      * 入力ソースを読み飛ばす。
113      * @param skipLength 読み飛ばすバイト数。
114      * @throws IOException IOエラー
115      * @throws MmdEofException 読み飛ばす途中でストリーム終端に達した。
116      * @see InputStream#skip(long)
117      */
118     protected void skip(long skipLength)
119             throws IOException, MmdEofException {
120         long remain = skipLength;
121
122         while(remain > 0L){
123             long txSize = this.is.skip(remain);
124             if(txSize <= 0L){
125                 throw new MmdEofException(this.position);
126             }
127             remain -= txSize;
128             this.position += txSize;
129         }
130
131         return;
132     }
133
134     /**
135      * byte配列を読み込む。
136      * @param dst 格納先配列
137      * @param off 読み込み開始オフセット
138      * @param length 読み込みバイト数
139      * @throws IOException IOエラー
140      * @throws NullPointerException 配列がnull
141      * @throws IndexOutOfBoundsException 引数が配列属性と矛盾
142      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
143      * @see InputStream#read(byte[], int, int)
144      */
145     protected void parseByteArray(byte[] dst, int off, int length)
146             throws IOException,
147                    NullPointerException,
148                    IndexOutOfBoundsException,
149                    MmdEofException {
150         int remain = length;
151         int offset = off;
152
153         while(remain > 0){
154             int txSize = this.is.read(dst, offset, remain);
155             if(txSize <= 0){
156                 throw new MmdEofException(this.position);
157             }
158             remain -= txSize;
159             offset += txSize;
160             this.position += txSize;
161         }
162
163         return;
164     }
165
166     /**
167      * byte配列を読み込む。
168      * <p>配列要素全ての読み込みが試みられる。
169      * @param dst 格納先配列
170      * @throws IOException IOエラー
171      * @throws NullPointerException 配列がnull
172      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
173      * @see InputStream#read(byte[])
174      */
175     protected void parseByteArray(byte[] dst)
176             throws IOException, NullPointerException, MmdEofException{
177         parseByteArray(dst, 0, dst.length);
178         return;
179     }
180
181     /**
182      * 内部バッファへ指定バイト数だけ読み込む。
183      * @param fillSize バイト長
184      * @throws IOException IOエラー
185      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
186      */
187     private void fillBuffer(int fillSize)
188             throws IOException, MmdEofException {
189         parseByteArray(this.readBuffer, 0, fillSize);
190         return;
191     }
192
193     /**
194      * byte値を読み込む。
195      * @return 読み込んだbyte値
196      * @throws IOException IOエラー
197      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
198      */
199     protected byte parseByte()
200             throws IOException, MmdEofException{
201         int bData = this.is.read();
202         if(bData < 0){
203             throw new MmdEofException(this.position);
204         }
205
206         byte result = (byte) bData;
207         this.position++;
208
209         return result;
210     }
211
212     /**
213      * 符号無し値としてbyte値を読み込み、int型に変換して返す。
214      * <p>符号は拡張されない。(0xffは0x000000ffとなる)
215      * @return 読み込まれた値のint値
216      * @throws IOException IOエラー
217      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
218      */
219     protected int parseUByteAsInt()
220             throws IOException, MmdEofException{
221         return parseByte() & MASK_8BIT;
222     }
223
224     /**
225      * byte値を読み込み、boolean型に変換して返す。
226      * <p>0x00は偽、それ以外は真と解釈される。
227      * @return 読み込まれた値のboolean値
228      * @throws IOException IOエラー
229      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
230      */
231     protected boolean parseBoolean()
232             throws IOException, MmdEofException{
233         byte result = parseByte();
234         if(result == 0x00) return false;
235         return true;
236     }
237
238     /**
239      * short値を読み込む。
240      * <p>short値はリトルエンディアンで格納されていると仮定される。
241      * @return 読み込んだshort値
242      * @throws IOException IOエラー
243      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
244      */
245     protected short parseLeShort()
246             throws IOException, MmdEofException{
247         fillBuffer(BYTES_SHORT);
248         short result = this.leBuf.getShort(0);
249         return result;
250     }
251
252     /**
253      * 符号無し値としてshort値を読み込み、int型に変換して返す。
254      * <p>符号は拡張されない。(0xffffは0x0000ffffとなる)
255      * <p>short値はリトルエンディアンで格納されていると仮定される。
256      * @return 読み込まれた値のint値
257      * @throws IOException IOエラー
258      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
259      */
260     protected int parseLeUShortAsInt()
261             throws IOException, MmdEofException{
262         return parseLeShort() & MASK_16BIT;
263     }
264
265     /**
266      * int値を読み込む。
267      * <p>int値はリトルエンディアンで格納されていると仮定される。
268      * @return 読み込んだint値
269      * @throws IOException IOエラー
270      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
271      */
272     protected int parseLeInt()
273             throws IOException, MmdEofException{
274         fillBuffer(BYTES_INT);
275         int result = this.leBuf.getInt(0);
276         return result;
277     }
278
279     /**
280      * float値を読み込む。
281      * <p>float値はリトルエンディアンで格納されていると仮定される。
282      * @return 読み込んだfloat値
283      * @throws IOException IOエラー
284      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
285      */
286     protected float parseLeFloat()
287             throws IOException, MmdEofException{
288         fillBuffer(BYTES_FLOAT);
289         float result = this.leBuf.getFloat(0);
290         return result;
291     }
292
293     /**
294      * 固定バイト長の文字列を読み込む。
295      * @param decoder 文字デコーダ
296      * @param byteLen 読み込む固定バイト長
297      * @return 文字列
298      * @throws IOException 入力エラー
299      * @throws MmdEofException 固定長バイト列を読む前に末端に達した。
300      * @throws MmdFormatException 文字エンコーディングに関するエラー
301      */
302     protected String parseString(TextDecoder decoder, int byteLen)
303             throws IOException, MmdEofException, MmdFormatException {
304         if(this.btextBuf == null || this.btextBuf.capacity() < byteLen){
305             this.btextBuf = ByteBuffer.allocate(byteLen);
306         }
307
308         byte[] buf = this.btextBuf.array();
309         this.btextBuf.clear();
310         parseByteArray(buf, 0, byteLen);
311         this.btextBuf.limit(byteLen);
312
313         String result;
314
315         try{
316             result = decoder.decode(this.btextBuf);
317         }catch(UnmappableCharacterException e){
318             String errmsg = ERRMSG_UNMAP;
319             long errpos = getPosition() - byteLen + e.getInputLength();
320             MmdFormatException ex = new MmdFormatException(errmsg, errpos);
321             ex.initCause(e);
322             throw ex;
323         }catch(MalformedInputException e){
324             String errmsg = ERRMSG_ILLENC;
325             long errpos = getPosition() - byteLen + e.getInputLength();
326             MmdFormatException ex = new MmdFormatException(errmsg, errpos);
327             ex.initCause(e);
328             throw ex;
329         }catch(CharacterCodingException e){  // 状況不明
330             String errmsg = ERRMSG_ILLENC;
331             long errpos = getPosition();
332             MmdFormatException ex = new MmdFormatException(errmsg, errpos);
333             ex.initCause(e);
334             throw ex;
335         }
336
337         return result;
338     }
339
340 }