OSDN Git Service

split decoder to Jiocema
[jindolf/JinParser.git] / src / main / java / jp / sourceforge / jindolf / parser / SjisNotifier.java
1 /*
2  * Shift_JIS decode notifier
3  *
4  * License : The MIT License
5  * Copyright(c) 2018 olyutorskii
6  */
7
8 package jp.sourceforge.jindolf.parser;
9
10 import io.bitbucket.olyutorskii.jiocema.DecodeNotifier;
11 import java.io.IOException;
12 import java.nio.ByteBuffer;
13 import java.nio.charset.CoderResult;
14
15 /**
16  * Shift_JISバイト列のデコードエラーに特化した、
17  * {@link DecodeNotifier}の派生クラス。
18  *
19  * <p>Javaランタイムの細かな仕様差異による
20  * デコードエラー出現パターンゆらぎの正規化を行う。
21  *
22  * <ul>
23  *
24  * <li>バイト列 [0xff:0x32]や[0x81:0xfd]を
25  * 2バイト長Unmapエラーとして検出してしまう
26  * Javaランタイムへの対処。
27  *
28  * <li>バイト列 [0x85,0x40](未割り当て9区の文字) を、
29  * 1バイトのエラーと文字@に分離してしまうJavaランタイムへの対処。
30  *
31  * <li>バイト列 [0x80:0x41]先頭を1バイト長Unmapエラーとして検出してしまう
32  * Javaランタイムへの対処。
33  *
34  * </ul>
35  *
36  * <p>TODO: 1.7系ランタイムによっては
37  * [0x81, 0x7f]が「÷」にデコードされる場合がある問題が未解決。
38  *
39  * @see https://en.wikipedia.org/wiki/Shift_JIS
40  * @see sun.nio.cs.ext.SJIS
41  */
42 public class SjisNotifier extends DecodeNotifier{
43
44     private static final String MSGFORM_SJBUFLEN =
45             "input buffer length must be 2 or more for Shift_JIS";
46
47
48     /**
49      * コンストラクタ。
50      *
51      * <p>デコーダにはShift_JIS用ランタイムが用いられる。
52      *
53      * <p>バッファサイズはデフォルト値が用いられる。
54      *
55      * @see DecodeNotifier#DEFSZ_BYTEBUF
56      * @see DecodeNotifier#DEFSZ_CHARBUF
57      */
58     public SjisNotifier(){
59         this(DEFSZ_BYTEBUF, DEFSZ_CHARBUF);
60         return;
61     }
62
63     /**
64      * コンストラクタ。
65      *
66      * <p>デコーダにはShift_JIS用ランタイムが用いられる。
67      *
68      * @param inbufSz 入力バッファサイズ。
69      *     シフトJIS上位下位のため2以上を指定しなければならない。
70      * @param outbufSz 出力バッファサイズ。
71      *     サロゲートペア格納のため2以上を指定しなければならない。
72      * @throws IllegalArgumentException 不適切なバッファサイズ
73      */
74     public SjisNotifier(int inbufSz, int outbufSz)
75             throws IllegalArgumentException {
76         super(ShiftJis.CHARSET.newDecoder(), inbufSz, outbufSz);
77
78         if(inbufSz < 2){
79             throw new IllegalArgumentException(MSGFORM_SJBUFLEN);
80         }
81
82         return;
83     }
84
85
86     /**
87      * Javaランタイムの差異によるシフトJISデコードエラーの揺らぎを正規化する。
88      *
89      * <ul>
90      *
91      * <li>2バイト長のUnmapエラーがシフトJISの形式を満たさない場合、
92      * 1バイト長のMalformedエラーに修正する。
93      *
94      * <li>2バイト長でない、もしくはUnmapでないエラー時に、
95      * 入力バッファ未読部先頭がシフトJISの形式を満たす場合、
96      * 2バイト長のUnmapエラーに修正する。
97      *
98      * <li>1バイト長のUnmapエラーで
99      * 入力バッファ未読部先頭がシフトJISの形式を満たさない場合、
100      * 1バイト長のMalformedエラーに修正する。
101      *
102      * </ul>
103      *
104      * <p>必要に応じて1バイト以上の追加先読みを行う。
105      *
106      * <p>{@inheritDoc}
107      *
108      * @param errInfo {@inheritDoc}
109      * @return {@inheritDoc}
110      * @throws IOException {@inheritDoc}
111      */
112     @Override
113     protected CoderResult modifyErrorResult(CoderResult errInfo)
114             throws IOException {
115         boolean unmapSingle = false;
116         boolean unmapDouble = false;
117         if(errInfo.isUnmappable()){
118             int errorLength = errInfo.length();
119             switch(errorLength){
120             case 1: unmapSingle = true; break;
121             case 2: unmapDouble = true; break;
122             default:                    break;
123             }
124         }
125
126         if(unmapDouble){
127             return modifyUnmapDoubleError(errInfo);
128         }
129
130         boolean detectSjis = false;
131         if(fillDoubleBytes()){
132             if(isSjisHeadErr()){
133                 detectSjis = true;
134             }
135         }
136
137         CoderResult newResult;
138         if(detectSjis){
139             newResult = CoderResult.unmappableForLength(2);
140         }else if(unmapSingle){
141             newResult = CoderResult.malformedForLength(1);
142         }else{
143             newResult = errInfo;
144         }
145
146         return newResult;
147     }
148
149     /**
150      * modify 2-byte unmap decode error.
151      *
152      * <p>if 2-bytes sequence is invalid Shift_JIS,
153      * single-byte malformed error will be return.
154      *
155      * <p>Yes, [81:fd] must not be Unmap-error.
156      *
157      * @param errInfo original error information
158      * @return modified error information
159      */
160     private CoderResult modifyUnmapDoubleError(CoderResult errInfo){
161         assert errInfo.isUnmappable();
162         assert errInfo.length() == 2;
163
164         CoderResult newResult;
165         if(isSjisHeadErr()){
166             newResult = errInfo;
167         }else{
168             newResult = CoderResult.malformedForLength(1);
169         }
170
171         return newResult;
172     }
173
174     /**
175      * 入力バッファ未読部長が2バイト以上になるまで入力を進める。
176      *
177      * @return 未読部長が2バイト未満の段階で入力が終了したらfalse
178      * @throws IOException 入力エラー
179      */
180     private boolean fillDoubleBytes() throws IOException{
181         ByteBuffer inbuffer = getByteBuffer();
182         while(inbuffer.remaining() < 2){
183             if( ! hasMoreInput()) return false;
184             supplyInputBytes();
185         }
186         return true;
187     }
188
189     /**
190      * 入力バッファ未読部先頭がシフトJISの2バイト長文字形式か判定する。
191      *
192      * <p>入力バッファ未読部の長さが2未満の場合は常に偽となる。
193      *
194      * <p>文字集合がJIS X0208に収まるか否かのUnmap判定は行わない。
195      *
196      * @return シフトJISの2バイト長文字形式ならtrue
197      */
198     private boolean isSjisHeadErr(){
199         ByteBuffer inbuffer = getByteBuffer();
200
201         if(inbuffer.remaining() < 2) return false;
202         int currPos = inbuffer.position();
203         int nextPos = currPos + 1;
204
205         byte curr = inbuffer.get(currPos);
206         byte next = inbuffer.get(nextPos);
207
208         boolean result;
209         result = ShiftJis.isShiftJIS(curr, next);
210
211         return result;
212     }
213
214 }