OSDN Git Service

c54245fecb135c2546d1d103795f55a1ca521a05
[mikutoga/TogaGem.git] / src / main / java / jp / sfjp / mikutoga / bin / export / BinaryExporter.java
1 /*
2  * binary data exporter
3  *
4  * License : The MIT License
5  * Copyright(c) 2011 MikuToga Partners
6  */
7
8 package jp.sfjp.mikutoga.bin.export;
9
10 import java.io.ByteArrayOutputStream;
11 import java.io.Closeable;
12 import java.io.Flushable;
13 import java.io.IOException;
14 import java.io.OutputStream;
15 import java.nio.charset.CharacterCodingException;
16 import java.nio.charset.Charset;
17 import java.text.MessageFormat;
18
19 /**
20  * バイナリデータの出力を行う汎用エクスポーター。
21  *
22  * <p>基本的にリトルエンディアン形式で出力される。
23  */
24 public class BinaryExporter implements Closeable, Flushable{
25
26     private static final Charset CS_UTF16LE = Charset.forName("UTF-16LE");
27     private static final Charset CS_WIN31J  = Charset.forName("windows-31j");
28
29     private static final String ERRMSG_ILLENC    = "illegal encoding";
30     private static final String ERRMSG_TOOLONGTX =
31               "too long text: "
32             + "text \"{0}\" needs {1}bytes encoded but limit={2}bytes";
33
34     private static final int BYTES_SHORT  = Short   .SIZE / Byte.SIZE;
35     private static final int BYTES_INT    = Integer .SIZE / Byte.SIZE;
36     private static final int BYTES_LONG   = Long    .SIZE / Byte.SIZE;
37     private static final int BYTES_FLOAT  = Float   .SIZE / Byte.SIZE;
38     private static final int BYTES_DOUBLE = Double  .SIZE / Byte.SIZE;
39
40     private static final int BUFSZ_PRIM = BYTES_DOUBLE;
41
42     private static final int IDX0 = 0;
43     private static final int IDX1 = 1;
44     private static final int IDX2 = 2;
45     private static final int IDX3 = 3;
46     private static final int IDX4 = 4;
47     private static final int IDX5 = 5;
48     private static final int IDX6 = 6;
49     private static final int IDX7 = 7;
50
51     private static final int SH00 =  0;
52     private static final int SH08 =  8;
53     private static final int SH16 = 16;
54     private static final int SH24 = 24;
55     private static final int SH32 = 32;
56     private static final int SH40 = 40;
57     private static final int SH48 = 48;
58     private static final int SH56 = 56;
59
60     static{
61         assert BYTES_DOUBLE <= BUFSZ_PRIM;
62         assert BYTES_FLOAT  <= BUFSZ_PRIM;
63         assert BYTES_LONG   <= BUFSZ_PRIM;
64         assert BYTES_INT    <= BUFSZ_PRIM;
65         assert BYTES_SHORT  <= BUFSZ_PRIM;
66     }
67
68
69     private final OutputStream ostream;
70
71     private final byte[] barray;
72
73     private final TextExporter texporter_w31j;
74     private final TextExporter texporter_u16le;
75     private final ByteArrayOutputStream xos;
76
77
78     /**
79      * コンストラクタ。
80      * @param ostream 出力ストリーム
81      * @throws NullPointerException 引数がnull
82      */
83     public BinaryExporter(OutputStream ostream) throws NullPointerException{
84         super();
85
86         if(ostream == null) throw new NullPointerException();
87         this.ostream = ostream;
88
89         this.barray = new byte[BUFSZ_PRIM];
90
91         this.texporter_w31j  = new TextExporter(CS_WIN31J);
92         this.texporter_u16le = new TextExporter(CS_UTF16LE);
93         this.xos = new ByteArrayOutputStream();
94
95         return;
96     }
97
98
99     /**
100      * 出力ストリームを閉じる。
101      * @throws IOException 出力エラー
102      */
103     @Override
104     public void close() throws IOException{
105         this.ostream.close();
106         return;
107     }
108
109     /**
110      * 出力をフラッシュする。
111      * I/O効率とデバッグ効率のバランスを考え、ご利用は計画的に。
112      * @throws IOException 出力エラー
113      */
114     @Override
115     public void flush() throws IOException{
116         this.ostream.flush();
117         return;
118     }
119
120     /**
121      * byte値を出力する。
122      * @param bVal byte値
123      * @return this自身
124      * @throws IOException 出力エラー
125      */
126     public BinaryExporter dumpByte(byte bVal) throws IOException{
127         this.ostream.write(bVal);
128         return this;
129     }
130
131     /**
132      * byte値を出力する。
133      * @param iVal int値。上位24bitは捨てられる。
134      * @return this自身
135      * @throws IOException 出力エラー
136      */
137     public BinaryExporter dumpByte(int iVal) throws IOException{
138         this.ostream.write(iVal);
139         return this;
140     }
141
142     /**
143      * byte型配列を出力する。
144      * @param array 配列
145      * @return this自身
146      * @throws IOException 出力エラー
147      */
148     public BinaryExporter dumpByteArray(byte[] array)
149             throws IOException{
150         this.ostream.write(array);
151         return this;
152     }
153
154     /**
155      * byte型配列の部分列を出力する。
156      * @param array 配列
157      * @param offset 出力開始位置
158      * @param length 出力バイト数
159      * @return this自身
160      * @throws IOException 出力エラー
161      */
162     public BinaryExporter dumpByteArray(byte[] array, int offset, int length)
163             throws IOException{
164         this.ostream.write(array, offset, length);
165         return this;
166     }
167
168     /**
169      * short値をリトルエンディアンで出力する。
170      * @param sVal short値
171      * @return this自身
172      * @throws IOException 出力エラー
173      */
174     @SuppressWarnings("PMD.AvoidUsingShortType")
175     public BinaryExporter dumpLeShort(short sVal) throws IOException{
176         this.barray[IDX0] = (byte)(sVal >>> SH00);
177         this.barray[IDX1] = (byte)(sVal >>> SH08);
178
179         this.ostream.write(this.barray, 0, BYTES_SHORT);
180
181         return this;
182     }
183
184     /**
185      * short値をリトルエンディアンで出力する。
186      * @param iVal int値。上位16bitは捨てられる。
187      * @return this自身
188      * @throws IOException 出力エラー
189      */
190     @SuppressWarnings("PMD.AvoidUsingShortType")
191     public BinaryExporter dumpLeShort(int iVal) throws IOException{
192         short sVal = (short)iVal;
193         dumpLeShort(sVal);
194         return this;
195     }
196
197     /**
198      * int値をリトルエンディアンで出力する。
199      * @param iVal int値
200      * @return this自身
201      * @throws IOException 出力エラー
202      */
203     public BinaryExporter dumpLeInt(int iVal) throws IOException{
204         this.barray[IDX0] = (byte)(iVal >>> SH00);
205         this.barray[IDX1] = (byte)(iVal >>> SH08);
206         this.barray[IDX2] = (byte)(iVal >>> SH16);
207         this.barray[IDX3] = (byte)(iVal >>> SH24);
208
209         this.ostream.write(this.barray, 0, BYTES_INT);
210
211         return this;
212     }
213
214     /**
215      * long値をリトルエンディアンで出力する。
216      * @param lVal long値
217      * @return this自身
218      * @throws IOException 出力エラー
219      */
220     public BinaryExporter dumpLeLong(long lVal) throws IOException{
221         this.barray[IDX0] = (byte)(lVal >>> SH00);
222         this.barray[IDX1] = (byte)(lVal >>> SH08);
223         this.barray[IDX2] = (byte)(lVal >>> SH16);
224         this.barray[IDX3] = (byte)(lVal >>> SH24);
225         this.barray[IDX4] = (byte)(lVal >>> SH32);
226         this.barray[IDX5] = (byte)(lVal >>> SH40);
227         this.barray[IDX6] = (byte)(lVal >>> SH48);
228         this.barray[IDX7] = (byte)(lVal >>> SH56);
229
230         this.ostream.write(this.barray, 0, BYTES_LONG);
231
232         return this;
233     }
234
235     /**
236      * float値をリトルエンディアンで出力する。
237      * @param fVal float値
238      * @return this自身
239      * @throws IOException 出力エラー
240      */
241     public BinaryExporter dumpLeFloat(float fVal) throws IOException{
242         int rawiVal = Float.floatToRawIntBits(fVal);
243         dumpLeInt(rawiVal);
244         return this;
245     }
246
247     /**
248      * double値をリトルエンディアンで出力する。
249      * @param dVal double値
250      * @return this自身
251      * @throws IOException 出力エラー
252      */
253     public BinaryExporter dumpLeDouble(double dVal) throws IOException{
254         long rawlVal = Double.doubleToRawLongBits(dVal);
255         dumpLeLong(rawlVal);
256         return this;
257     }
258
259     /**
260      * 詰め物パディングを出力する。
261      *
262      * @param filler byte型配列によるパディングデータの並び。
263      *
264      *     <p>指定パディング長より長い部分は出力されない。
265      *     指定パディング長に満たない場合は最後の要素が繰り返し出力される。
266      *
267      *     <p>配列長が0の場合は何も出力されない。
268      *
269      * @param fillerLength パディング長。
270      *
271      *     <p>パディング長が0以下の場合は何も出力されない。
272      *
273      * @return this
274      * @throws IOException 出力エラー
275      */
276     public BinaryExporter dumpFiller(byte[] filler, int fillerLength)
277             throws IOException {
278         if(filler.length <= 0 || fillerLength <= 0){
279             return this;
280         }
281
282         byte lastData = filler[filler.length - 1];
283
284         int fillerIdx = 0;
285         for(int remain = fillerLength; remain > 0; remain--){
286             byte bVal;
287             if(fillerIdx < filler.length) bVal = filler[fillerIdx++];
288             else                          bVal = lastData;
289             dumpByte(bVal);
290         }
291
292         return this;
293     }
294
295     /**
296      * Windows31J文字列をを固定バイト長で出力する。
297      * 固定バイト長に満たない箇所はパディングデータが詰められる。
298      * @param text テキスト
299      * @param fixedLength 固定バイト長。0以下の場合は無制限。
300      * @param filler 詰め物パディングデータ
301      * @return this
302      * @throws IOException 出力エラー
303      * @throws IllegalTextExportException テキスト出力エラー。
304      *     出力が固定長を超えようとした、
305      *     もしくは不正なエンコードが行われたかのいずれか。
306      */
307     public BinaryExporter dumpFixedW31j(CharSequence text,
308                                           int fixedLength,
309                                           byte[] filler )
310             throws IOException, IllegalTextExportException{
311         this.xos.reset();
312
313         int encodedSize;
314         try{
315             encodedSize =
316                     this.texporter_w31j.encodeToByteStream(text, this.xos);
317         }catch(CharacterCodingException e){
318             throw new IllegalTextExportException(ERRMSG_ILLENC, e);
319         }
320
321         if( 0 < fixedLength && fixedLength < encodedSize ){
322             String message =
323                     MessageFormat.format(ERRMSG_TOOLONGTX,
324                                          text, encodedSize, fixedLength);
325             throw new IllegalTextExportException(message);
326         }
327
328         this.xos.writeTo(this.ostream);
329         int xferred = this.xos.size();
330
331         int remain = fixedLength - xferred;
332         if(remain > 0){
333             dumpFiller(filler, remain);
334         }
335
336         return this;
337     }
338
339     /**
340      * UTF16-LE文字列をホレリス形式で出力する。
341      * UTF16-LEエンコード結果のバイト長を
342      * 4byte整数としてリトルエンディアンで出力した後に、
343      * エンコード結果のバイト列が出力される。
344      * @param text 文字列
345      * @return エンコードバイト列長
346      * @throws IOException 出力エラー
347      * @throws IllegalTextExportException テキスト出力エラー。
348      *     出力が固定長を超えようとした、
349      *     もしくは不正なエンコードが行われたかのいずれか。
350      */
351     public int dumpHollerithUtf16LE(CharSequence text)
352             throws IOException, IllegalTextExportException{
353         this.xos.reset();
354
355         int encodedSize;
356         try{
357             encodedSize =
358                     this.texporter_u16le.encodeToByteStream(text, this.xos);
359         }catch(CharacterCodingException e){
360             assert false;  // これはない
361             throw new IllegalTextExportException(ERRMSG_ILLENC, e);
362         }
363
364         dumpLeInt(encodedSize);
365
366         this.xos.writeTo(this.ostream);
367         int xferred = this.xos.size();
368
369         assert xferred == encodedSize;
370
371         return xferred;
372     }
373
374 }