OSDN Git Service

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