OSDN Git Service

cc54c26e8da07a0dd6f74cff1f9d7fd96ec2f691
[mikutoga/TogaGem.git] / src / main / java / jp / sfjp / mikutoga / bin / export / TextExporter.java
1 /*
2  * text 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.IOException;
12 import java.io.OutputStream;
13 import java.nio.ByteBuffer;
14 import java.nio.CharBuffer;
15 import java.nio.charset.CharacterCodingException;
16 import java.nio.charset.Charset;
17 import java.nio.charset.CharsetEncoder;
18 import java.nio.charset.CoderResult;
19 import java.nio.charset.CodingErrorAction;
20
21 /**
22  * 任意のエンコーダによるテキストデータのバイナリ出力を行う。
23  */
24 public class TextExporter {
25
26     /** デフォルトの入力バッファサイズ(単位:char)。 */
27     public static final int DEFBUFSZ_CHAR = 128;
28     /** デフォルトの出力バッファサイズ(単位:byte)。 */
29     public static final int DEFBUFSZ_BYTE = 128;
30
31
32     private final CharsetEncoder encoder;
33     private CharBuffer cbuf = CharBuffer.allocate(DEFBUFSZ_CHAR);
34     private byte[] barray = new byte[DEFBUFSZ_BYTE];
35     private ByteBuffer bbuf = ByteBuffer.wrap(this.barray);
36
37     private CharSequence textData;
38     private int textLength;
39     private int inPos;
40
41
42     /**
43      * コンストラクタ。
44      * @param encoder エンコーダ
45      * @throws NullPointerException 引数がnull
46      */
47     public TextExporter(CharsetEncoder encoder) throws NullPointerException{
48         super();
49
50         if(encoder == null) throw new NullPointerException();
51         this.encoder = encoder;
52         this.encoder.reset();
53         this.encoder.onMalformedInput(CodingErrorAction.REPORT);
54         this.encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
55
56         this.cbuf.clear();
57         this.bbuf.clear();
58
59         return;
60     }
61
62     /**
63      * コンストラクタ。
64      * @param cs 文字セット
65      */
66     public TextExporter(Charset cs){
67         this(cs.newEncoder());
68         return;
69     }
70
71     /**
72      * エンコーダを返す。
73      * @return エンコーダ
74      */
75     public CharsetEncoder getEncoder(){
76         return this.encoder;
77     }
78
79     /**
80      * 入力内部バッファサイズを設定する。
81      * @param newSize バッファサイズ。(単位:char)
82      * @throws IllegalArgumentException サイズ指定が正で無かった。
83      */
84     public void setCharBufSize(int newSize)
85             throws IllegalArgumentException {
86         if(newSize <= 0) throw new IllegalArgumentException();
87
88         this.cbuf = CharBuffer.allocate(newSize);
89         this.cbuf.clear();
90
91         return;
92     }
93
94     /**
95      * 出力内部バッファサイズを設定する。
96      * 最低限必要な出力バッファサイズはエンコード設定により異なる。
97      * @param newSize バッファサイズ。(単位:byte)
98      * @throws IllegalArgumentException サイズ指定が小さすぎる。
99      */
100     public void setByteBufSize(int newSize)
101             throws IllegalArgumentException {
102         float ratio = this.encoder.maxBytesPerChar();
103         int minSz = (int)( StrictMath.floor(ratio) );
104         if(newSize < minSz) throw new IllegalArgumentException();
105
106         this.barray = new byte[newSize];
107         this.bbuf = ByteBuffer.wrap(this.barray);
108         this.bbuf.clear();
109
110         return;
111     }
112
113     /**
114      * 与えられた文字列をエンコードしてストリームに出力する。
115      * @param text 文字列
116      * @param os 出力ストリーム
117      * @return 出力バイト長
118      * @throws IOException 出力エラー
119      * @throws CharacterCodingException エンコードエラー
120      */
121     public int dumpText(CharSequence text, OutputStream os)
122             throws IOException, CharacterCodingException {
123         this.textData = text;
124
125         int total = 0;
126         try{
127             total = dumpTextImpl(os);
128         }finally{
129             this.textData = null;
130         }
131
132         return total;
133     }
134
135     /**
136      * 文字列をエンコードしてストリームに出力する。
137      * @param os 出力ストリーム
138      * @return 出力バイト長
139      * @throws IOException 出力エラー
140      * @throws CharacterCodingException エンコードエラー
141      */
142     private int dumpTextImpl(OutputStream os)
143             throws IOException, CharacterCodingException {
144         reset();
145
146         int total = 0;
147
148         for(;;){
149             loadCharBuffer();
150             CoderResult result = encode();
151             if(result.isUnderflow()){
152                 this.cbuf.clear();
153                 if( ! hasMoreInput() ){
154                     total += sweepByteBuffer(os);
155                     break;
156                 }
157             }else if(result.isOverflow()){
158                 total += sweepByteBuffer(os);
159                 this.cbuf.compact();
160             }else if(result.isError()){
161                 result.throwException();
162             }
163         }
164
165         total += flush(os);
166
167         return total;
168     }
169
170     /**
171      * 各種内部状態をリセットする。
172      */
173     private void reset(){
174         this.cbuf.clear();
175         this.bbuf.clear();
176
177         this.encoder.reset();
178
179         this.textLength = this.textData.length();
180         this.inPos = 0;
181
182         return;
183     }
184
185     /**
186      * 入力バッファにまだ入力していない文字があるか判定する。
187      * @return 入力バッファにまだ入力していない文字があればtrue
188      */
189     private boolean hasMoreInput(){
190         if(this.inPos < this.textLength) return true;
191         return false;
192     }
193
194     /**
195      * 入力バッファに文字を埋める。
196      *
197      * <p>入力バッファが一杯になるか
198      * 入力文字列がなくなるまでバッファが埋められる。
199      */
200     private void loadCharBuffer(){
201         while(this.cbuf.hasRemaining() && hasMoreInput()){
202             char ch = this.textData.charAt(this.inPos++);
203             this.cbuf.put(ch);
204         }
205         this.cbuf.flip();
206         return;
207     }
208
209     /**
210      * エンコードを行う。
211      * @return エンコード結果
212      */
213     private CoderResult encode(){
214         boolean endOfInput;
215         if(hasMoreInput()) endOfInput = false;
216         else               endOfInput = true;
217
218         CoderResult result;
219         result = this.encoder.encode(this.cbuf, this.bbuf, endOfInput);
220         return result;
221     }
222
223     /**
224      * 出力バッファを吐き出す。
225      * @param os 出力ストリーム
226      * @return 出力バイト長
227      * @throws IOException 出力エラー
228      */
229     private int sweepByteBuffer(OutputStream os) throws IOException{
230         this.bbuf.flip();
231
232         int total = this.bbuf.remaining();
233         os.write(this.barray, 0, total);
234
235         this.bbuf.clear();
236
237         return total;
238     }
239
240     /**
241      * エンコーダをフラッシュする。
242      * @param os 出力ストリーム
243      * @return 出力バイト長
244      * @throws IOException 出力エラー
245      * @throws CharacterCodingException エンコーディングエラー
246      */
247     private int flush(OutputStream os)
248             throws IOException, CharacterCodingException {
249         int total = 0;
250
251         CoderResult result;
252         do{
253             result = this.encoder.flush(this.bbuf);
254             if(result.isError()) result.throwException();
255
256             total += sweepByteBuffer(os);
257
258         }while( ! result.isUnderflow() );
259
260         return total;
261     }
262
263     /**
264      * 与えられた文字列のエンコード結果を格納先バイトストリームへ格納する。
265      *
266      * <p>エンコード結果は格納先ストリームに追記される。
267      *
268      * @param text 文字列
269      * @param bos 格納先ストリーム
270      * @return エンコードしたバイト数。
271      * @throws CharacterCodingException エンコードエラー
272      */
273     @SuppressWarnings("PMD.AvoidRethrowingException")
274     public int encodeToByteStream(CharSequence text,
275                                      ByteArrayOutputStream bos )
276             throws CharacterCodingException {
277         int result = 0;
278         try{
279             result = dumpText(text, bos);
280         }catch(CharacterCodingException e){
281             throw e;
282         }catch(IOException e){
283             // ありえない
284             assert false;
285             throw new AssertionError(e);
286         }
287
288         return result;
289     }
290
291 }