4 * License : The MIT License
5 * Copyright(c) 2011 MikuToga Partners
8 package jp.sourceforge.mikutoga.parser;
10 import java.nio.ByteBuffer;
11 import java.nio.CharBuffer;
12 import java.nio.charset.Charset;
13 import java.nio.charset.CharsetDecoder;
14 import java.nio.charset.CoderResult;
15 import java.nio.charset.CodingErrorAction;
19 * <p>あらかじめ長さが既知であるバイト列を読み取り、
21 * <p>デコード対象のバイト列が全てメモリ上に展開されるので、
23 * <p>入力バイト値0x00以降をデコード処理の対象から外す
25 * デフォルトではゼロチョップモードはオフ。
26 * ゼロチョップモードはUTF16などのデコーディング時に使ってはならない。
28 public class TextDecoder {
30 /** デコード作業用入力バッファ長のデフォルト。バイト単位。 */
31 public static final int DEF_BUFSZ = 512;
34 private final CharsetDecoder decoder;
36 private byte[] byteArray;
37 private ByteBuffer byteBuffer; // byteArrayの別ビュー
38 private CharBuffer charBuffer;
40 private boolean chopZero = false;
47 public TextDecoder(Charset cs){
48 this(cs.newDecoder());
56 public TextDecoder(CharsetDecoder decoder){
59 if(decoder == null) throw new NullPointerException();
61 this.decoder = decoder;
62 this.decoder.onMalformedInput (CodingErrorAction.REPORT);
63 this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
69 * 指定されたバイト長を満たす、デコード用入力バッファを用意する。
70 * 既存バッファで足りなければ新たに確保し直す。
71 * <p>内部用出力用バッファも同時に適切な長さで確保される。
72 * @param newSize 新たなバッファ長。単位はバイト数。
73 * @return 入力バッファ。指定バイト長より長いかもしれない。他用厳禁
75 public byte[] prepareBuffer(int newSize){
76 if(this.byteArray != null && this.byteArray.length >= newSize){
77 return this.byteArray;
80 int rounded = newSize;
81 if(rounded < DEF_BUFSZ) rounded = DEF_BUFSZ;
83 this.byteArray = new byte[rounded];
84 this.byteBuffer = ByteBuffer.wrap(this.byteArray);
86 float maxCharsPerByte = this.decoder.maxCharsPerByte();
88 (int)( this.byteArray.length * maxCharsPerByte ) + 1;
89 this.charBuffer = CharBuffer.allocate(maxChars);
91 return this.byteArray;
97 * 入力バイト値0x00以降はデコード対象外となる。
98 * @param chop trueならゼロチョップモードオン
100 public void setZeroChopMode(boolean chop){
101 this.chopZero = chop;
107 * @return ゼロチョップモードならtrue
109 public boolean isZeroChopMode(){
110 return this.chopZero;
114 * 入力バイト列のバイト値'0'出現以降をチョップする。
115 * ゼロチョップモードでなければ何もしない。
117 protected void chopZeroTermed(){
118 if( ! this.chopZero ) return;
120 int limit = this.byteBuffer.limit();
122 for(int idx = 0; idx < limit; idx++){
123 byte bVal = this.byteArray[idx];
125 this.byteBuffer.limit(idx);
134 * 指定配列を内部にコピーした後、デコード処理を行う。
135 * @param basePos エラー情報に含まれるストリーム位置
138 * @throws MmdFormatException デコード異常
140 public String decode(long basePos, byte[] buf)
141 throws MmdFormatException {
142 String result = decode(basePos, buf, 0, buf.length);
147 * 指定配列の一部を内部にコピーした後、デコード処理を行う。
148 * @param basePos エラー情報に含まれるストリーム位置
151 * @param byteLen バイト長
153 * @throws MmdFormatException デコード異常
154 * @throws IndexOutOfBoundsException 不正な位置指定。
156 public String decode(long basePos, byte[] buf, int off, int byteLen)
157 throws MmdFormatException, IndexOutOfBoundsException {
158 prepareBuffer(byteLen);
159 System.arraycopy(buf, off, this.byteArray, 0, byteLen);
160 String result = decode(basePos, byteLen);
166 * @param basePos エラー情報に含まれるストリーム位置
167 * @param byteLen バイト長
169 * @throws MmdFormatException デコード異常
170 * @throws IndexOutOfBoundsException 不正なバイト長。
172 public String decode(long basePos, int byteLen)
173 throws MmdFormatException, IndexOutOfBoundsException {
174 if(this.byteArray.length < byteLen){
175 throw new IndexOutOfBoundsException();
178 this.byteBuffer.rewind().limit(byteLen);
181 this.charBuffer.clear();
183 this.decoder.reset();
184 CoderResult decResult =
185 this.decoder.decode(this.byteBuffer, this.charBuffer, true);
187 if(decResult.isError()){
189 if(decResult.isUnmappable()){
190 errMsg = "unmapped character";
192 errMsg = "illegal character encoding";
194 long errPos = basePos + decResult.length();
195 throw new MmdFormatException(errMsg, errPos);
198 assert ! decResult.isOverflow();
200 String result = this.charBuffer.flip().toString();