OSDN Git Service

XML出力改善
[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.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;
16
17 /**
18  * 文字デコーダー。
19  * <p>あらかじめ長さが既知であるバイト列を読み取り、
20  * 文字列へのデコード結果を返す。
21  * <p>デコード対象のバイト列が全てメモリ上に展開されるので、
22  * 巨大なテキストのデコードには不適当。
23  * <p>入力バイト値0x00以降をデコード処理の対象から外す
24  * 「ゼロチョップモード」を備える。
25  * デフォルトではゼロチョップモードはオフ。
26  * ゼロチョップモードはUTF16などのデコーディング時に使ってはならない。
27  */
28 public class TextDecoder {
29
30     /** デコード作業用入力バッファ長のデフォルト。バイト単位。 */
31     public static final int DEF_BUFSZ = 512;
32
33
34     private final CharsetDecoder decoder;
35
36     private byte[] byteArray;
37     private ByteBuffer byteBuffer;  // byteArrayの別ビュー
38     private CharBuffer charBuffer;
39
40     private boolean chopZero = false;
41
42
43     /**
44      * コンストラクタ。
45      * @param cs キャラクタセット
46      */
47     public TextDecoder(Charset cs){
48         this(cs.newDecoder());
49         return;
50     }
51
52     /**
53      * コンストラクタ。
54      * @param decoder デコーダ
55      */
56     public TextDecoder(CharsetDecoder decoder){
57         super();
58
59         if(decoder == null) throw new NullPointerException();
60
61         this.decoder = decoder;
62         this.decoder.onMalformedInput     (CodingErrorAction.REPORT);
63         this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
64
65         return;
66     }
67
68     /**
69      * 指定されたバイト長を満たす、デコード用入力バッファを用意する。
70      * 既存バッファで足りなければ新たに確保し直す。
71      * <p>内部用出力用バッファも同時に適切な長さで確保される。
72      * @param newSize 新たなバッファ長。単位はバイト数。
73      * @return 入力バッファ。指定バイト長より長いかもしれない。他用厳禁
74      */
75     public byte[] prepareBuffer(int newSize){
76         if(this.byteArray != null && this.byteArray.length >= newSize){
77             return this.byteArray;
78         }
79
80         int rounded = newSize;
81         if(rounded < DEF_BUFSZ) rounded = DEF_BUFSZ;
82
83         this.byteArray = new byte[rounded];
84         this.byteBuffer = ByteBuffer.wrap(this.byteArray);
85
86         float maxCharsPerByte = this.decoder.maxCharsPerByte();
87         int maxChars =
88                 (int)( this.byteArray.length * maxCharsPerByte ) + 1;
89         this.charBuffer = CharBuffer.allocate(maxChars);
90
91         return this.byteArray;
92     }
93
94     /**
95      * ゼロチョップモードを設定する。
96      * ゼロチョップモードをオンにすると、
97      * 入力バイト値0x00以降はデコード対象外となる。
98      * @param chop trueならゼロチョップモードオン
99      */
100     public void setZeroChopMode(boolean chop){
101         this.chopZero = chop;
102         return;
103     }
104
105     /**
106      * ゼロチョップモードか否か判定する。
107      * @return ゼロチョップモードならtrue
108      */
109     public boolean isZeroChopMode(){
110         return this.chopZero;
111     }
112
113     /**
114      * 入力バイト列のバイト値'0'出現以降をチョップする。
115      * ゼロチョップモードでなければ何もしない。
116      */
117     protected void chopZeroTermed(){
118         if( ! this.chopZero ) return;
119
120         int limit = this.byteBuffer.limit();
121
122         for(int idx = 0; idx < limit; idx++){
123             byte bVal = this.byteArray[idx];
124             if(bVal == 0x00){
125                 this.byteBuffer.limit(idx);
126                 break;
127             }
128         }
129
130         return;
131     }
132
133     /**
134      * 指定配列を内部にコピーした後、デコード処理を行う。
135      * @param basePos エラー情報に含まれるストリーム位置
136      * @param buf 入力バッファ
137      * @return デコードされた文字列
138      * @throws MmdFormatException デコード異常
139      */
140     public String decode(long basePos, byte[] buf)
141             throws MmdFormatException {
142         String result = decode(basePos, buf, 0, buf.length);
143         return result;
144     }
145
146     /**
147      * 指定配列の一部を内部にコピーした後、デコード処理を行う。
148      * @param basePos エラー情報に含まれるストリーム位置
149      * @param buf 入力バッファ
150      * @param off 位置オフセット
151      * @param byteLen バイト長
152      * @return デコードされた文字列
153      * @throws MmdFormatException デコード異常
154      * @throws IndexOutOfBoundsException 不正な位置指定。
155      */
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);
161         return result;
162     }
163
164     /**
165      * 内部バッファのデコード処理を行う。
166      * @param basePos エラー情報に含まれるストリーム位置
167      * @param byteLen バイト長
168      * @return デコードされた文字列
169      * @throws MmdFormatException デコード異常
170      * @throws IndexOutOfBoundsException 不正なバイト長。
171      */
172     public String decode(long basePos, int byteLen)
173             throws MmdFormatException, IndexOutOfBoundsException {
174         if(this.byteArray.length < byteLen){
175             throw new IndexOutOfBoundsException();
176         }
177
178         this.byteBuffer.rewind().limit(byteLen);
179         chopZeroTermed();
180
181         this.charBuffer.clear();
182
183         this.decoder.reset();
184         CoderResult decResult =
185                 this.decoder.decode(this.byteBuffer, this.charBuffer, true);
186
187         if(decResult.isError()){
188             String errMsg;
189             if(decResult.isUnmappable()){
190                 errMsg = "unmapped character";
191             }else{
192                 errMsg = "illegal character encoding";
193             }
194             long errPos = basePos + decResult.length();
195             throw new MmdFormatException(errMsg, errPos);
196         }
197
198         assert ! decResult.isOverflow();
199
200         String result = this.charBuffer.flip().toString();
201         return result;
202     }
203
204 }