OSDN Git Service

入出力強化
[mikutoga/TogaGem.git] / src / main / java / jp / sourceforge / mikutoga / parser / TextDecoder.java
1 /*
2  * character decoder
3  *
4  * License : The MIT License
5  * Copyright(c) 2011 MikuToga Partners
6  */
7
8 package jp.sourceforge.mikutoga.parser;
9
10 import java.io.IOException;
11 import java.nio.ByteBuffer;
12 import java.nio.CharBuffer;
13 import java.nio.charset.Charset;
14 import java.nio.charset.CharsetDecoder;
15 import java.nio.charset.CoderResult;
16 import java.nio.charset.CodingErrorAction;
17
18 /**
19  * 文字デコーダー。
20  * <p>あらかじめ長さが既知であるバイト列をMMD入力ソースから読み取り、
21  * デコーディング結果を返す。
22  * <p>デコード対象のバイト列が全てメモリ上に展開されるので、
23  * 巨大なテキストのデコードには不適当。
24  * <p>入力バイト値0x00以降をデコーディングの対象から外す
25  * 「ゼロチョップモード」を備える。
26  * デフォルトではゼロチョップモードはオフ。
27  * ゼロチョップモードはUTF16などのデコーディング時に使っても意味が無い。
28  */
29 public class TextDecoder {
30
31     /** デコード作業用入力バッファ長のデフォルト。バイト単位。 */
32     public static final int BYTEBUF_SZ = 512;
33
34     /** バッファ成長率。 */
35     private static final double WIDEN_RATE = 1.5;
36
37
38     private final CharsetDecoder decoder;
39
40     private boolean chopZero = false;
41
42     private byte[] byteArray;
43     private ByteBuffer byteBuffer;  // byteArrayの別ビュー
44     private CharBuffer charBuffer;
45     private CharBuffer roBuffer;    // charBufferの閲覧用ビュー
46
47
48     /**
49      * コンストラクタ。
50      * @param cs キャラクタセット
51      */
52     public TextDecoder(Charset cs){
53         this(cs.newDecoder());
54         return;
55     }
56
57     /**
58      * コンストラクタ。
59      * @param decoder デコーダ
60      */
61     public TextDecoder(CharsetDecoder decoder){
62         super();
63         this.decoder = decoder;
64         this.decoder.onMalformedInput(CodingErrorAction.REPORT);
65         this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
66         return;
67     }
68
69     /**
70      * 指定されたサイズで文字デコード用バッファを用意する。
71      * 既存バッファで足りなければ新たに確保し直す。
72      * @param newSize バッファ長さ。単位はバイト数。
73      */
74     protected void prepareBuffer(int newSize){
75         if(this.byteArray != null && this.byteArray.length >= newSize){
76             return;
77         }
78
79         int rounded = (int)( newSize * WIDEN_RATE );
80         if(rounded < BYTEBUF_SZ) rounded = BYTEBUF_SZ;
81
82         this.byteArray = new byte[rounded];
83         this.byteBuffer = ByteBuffer.wrap(this.byteArray);
84
85         float maxCharsPerByte = this.decoder.maxCharsPerByte();
86         int maxChars =
87                 (int)( this.byteBuffer.capacity() * maxCharsPerByte ) + 1;
88         this.charBuffer = CharBuffer.allocate(maxChars);
89
90         this.roBuffer = this.charBuffer.asReadOnlyBuffer();
91
92         return;
93     }
94
95     /**
96      * ゼロチョップモードを設定する。
97      * ゼロチョップモードをオンにすると、
98      * 入力バイト値0x00以降はデコード対象外となる。
99      * @param chop trueならゼロチョップモードオン
100      */
101     public void setZeroChopMode(boolean chop){
102         this.chopZero = chop;
103         return;
104     }
105
106     /**
107      * ゼロチョップモードか否か判定する。
108      * @return ゼロチョップモードならtrue
109      */
110     public boolean isZeroChopMode(){
111         return this.chopZero;
112     }
113
114     /**
115      * 入力バイト列のバイト値'0'出現以降をチョップする。
116      * ゼロチョップモードでなければ何もしない。
117      */
118     protected void chopZeroTermed(){
119         if( ! this.chopZero ) return;
120
121         int limit = this.byteBuffer.limit();
122
123         for(int idx = 0; idx < limit; idx++){
124             byte bVal = this.byteArray[idx];
125             if(bVal == 0x00){
126                 this.byteBuffer.limit(idx);
127                 break;
128             }
129         }
130
131         return;
132     }
133
134     /**
135      * バイト列を読み込み文字列へデコーディングする。
136      * @param is 入力ストリーム
137      * @param byteSize 読み込みバイトサイズ
138      * @return 内部に保持されるデコード結果。
139      * 次回呼び出しまでに結果の適切なコピーがなされなければならない。
140      * @throws MmdEofException 意図しないファイル末端
141      * @throws MmdFormatException 矛盾したバイトシーケンス
142      * もしくは未定義文字
143      * @throws IOException 入力エラー
144      */
145     public CharBuffer parseString(MmdInputStream is, int byteSize)
146             throws MmdEofException, MmdFormatException, IOException{
147         prepareBuffer(byteSize);
148
149         int readSize = is.read(this.byteArray, 0, byteSize);
150         if(readSize != byteSize){
151             throw new MmdEofException(is.getPosition());
152         }
153
154         this.byteBuffer.rewind().limit(byteSize);
155         chopZeroTermed();
156
157         this.charBuffer.clear();
158
159         this.decoder.reset();
160         CoderResult decResult =
161                 this.decoder.decode(this.byteBuffer, this.charBuffer, true);
162         if(decResult.isError()){
163             if(decResult.isUnmappable()){
164                 throw new MmdFormatException("unmapped character",
165                                              is.getPosition() );
166             }else{
167                 throw new MmdFormatException("illegal character encoding",
168                                              is.getPosition() );
169             }
170         }else if(decResult.isOverflow()){
171             assert false;
172         }
173
174         this.roBuffer.rewind().limit(this.charBuffer.position());
175
176         return this.roBuffer;
177     }
178
179 }