OSDN Git Service

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