OSDN Git Service

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