4 * License : The MIT License
5 * Copyright(c) 2011 MikuToga Partners
8 package jp.sourceforge.mikutoga.parser;
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;
20 * <p>あらかじめ長さが既知であるバイト列をMMD入力ソースから読み取り、
22 * <p>デコード対象のバイト列が全てメモリ上に展開されるので、
24 * <p>入力バイト値0x00以降をデコーディングの対象から外す
26 * デフォルトではゼロチョップモードはオフ。
27 * ゼロチョップモードはUTF16などのデコーディング時に使っても意味が無い。
29 public class TextDecoder {
31 /** デコード作業用入力バッファ長のデフォルト。バイト単位。 */
32 public static final int BYTEBUF_SZ = 512;
35 private static final double WIDEN_RATE = 1.5;
38 private final CharsetDecoder decoder;
40 private boolean chopZero = false;
42 private byte[] byteArray;
43 private ByteBuffer byteBuffer; // byteArrayの別ビュー
44 private CharBuffer charBuffer;
45 private CharBuffer roBuffer; // charBufferの閲覧用ビュー
52 public TextDecoder(Charset cs){
53 this(cs.newDecoder());
61 public TextDecoder(CharsetDecoder decoder){
63 this.decoder = decoder;
64 this.decoder.onMalformedInput(CodingErrorAction.REPORT);
65 this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
70 * 指定されたサイズで文字デコード用バッファを用意する。
71 * 既存バッファで足りなければ新たに確保し直す。
72 * @param newSize バッファ長さ。単位はバイト数。
74 protected void prepareBuffer(int newSize){
75 if(this.byteArray != null && this.byteArray.length >= newSize){
79 int rounded = (int)( newSize * WIDEN_RATE );
80 if(rounded < BYTEBUF_SZ) rounded = BYTEBUF_SZ;
82 this.byteArray = new byte[rounded];
83 this.byteBuffer = ByteBuffer.wrap(this.byteArray);
85 float maxCharsPerByte = this.decoder.maxCharsPerByte();
87 (int)( this.byteBuffer.capacity() * maxCharsPerByte ) + 1;
88 this.charBuffer = CharBuffer.allocate(maxChars);
90 this.roBuffer = this.charBuffer.asReadOnlyBuffer();
98 * 入力バイト値0x00以降はデコード対象外となる。
99 * @param chop trueならゼロチョップモードオン
101 public void setZeroChopMode(boolean chop){
102 this.chopZero = chop;
108 * @return ゼロチョップモードならtrue
110 public boolean isZeroChopMode(){
111 return this.chopZero;
115 * 入力バイト列のバイト値'0'出現以降をチョップする。
116 * ゼロチョップモードでなければ何もしない。
118 protected void chopZeroTermed(){
119 if( ! this.chopZero ) return;
121 int limit = this.byteBuffer.limit();
123 for(int idx = 0; idx < limit; idx++){
124 byte bVal = this.byteArray[idx];
126 this.byteBuffer.limit(idx);
135 * バイト列を読み込み文字列へデコーディングする。
136 * @param source 入力ソース
137 * @param byteSize 読み込みバイトサイズ
138 * @return 内部に保持されるデコード結果。
139 * 次回呼び出しまでに結果の適切なコピーがなされなければならない。
140 * @throws MmdEofException 意図しないファイル末端
141 * @throws MmdFormatException 矛盾したバイトシーケンス
143 * @throws IOException 入力エラー
145 public CharBuffer parseString(MmdSource source, int byteSize)
146 throws MmdEofException, MmdFormatException, IOException{
147 prepareBuffer(byteSize);
149 source.parseByteArray(this.byteArray, 0, byteSize);
150 this.byteBuffer.rewind().limit(byteSize);
153 this.charBuffer.clear();
155 this.decoder.reset();
156 CoderResult decResult =
157 this.decoder.decode(this.byteBuffer, this.charBuffer, true);
158 if(decResult.isError()){
159 if(decResult.isUnmappable()){
160 throw new MmdFormatException("unmapped character",
161 source.getPosition() );
163 throw new MmdFormatException("illegal character encoding",
164 source.getPosition() );
166 }else if(decResult.isOverflow()){
170 this.roBuffer.rewind().limit(this.charBuffer.position());
172 return this.roBuffer;